Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1 @@
{"files":{"Cargo.toml":"f52122185e9c1ed013834e800b45aa8481559ed2f01ed518ff0a7ae9ad8886bd","src/accumulator.rs":"4da510729f72652abdd9a09a5a962fa0cad25339b04838638d4a20133c0298e2","src/configuration.rs":"4d47f6ad909966ac201d3346925bf177997d875b1f858896e9fb4b25ac84b4fe","src/db.rs":"202d18bc346585376b5df00e5a42aace0ed6f0b06c0ab888e69f31ae0f02eca9","src/input.rs":"f5f5628b4b65f7f6be336bfd052fd052f23c9a517cc15052843a046b7c5ae7c9","src/interned.rs":"06b10fa5afd46f5e093ce5c242e9975be54355fb19560f429d1e8f494e68173b","src/jar.rs":"472f9a72af791b236e233136031ec3df642e39fd42ea6d554cdac7c9c844f6b1","src/lib.rs":"f3d2284a4c9f62365466a314e5072cd9eedb43fa005c4b688516bb6fa482358a","src/options.rs":"ee18e422f9589e07691895b7cc6f0baa8129ffd7293e0a59db6c03a6b1a6279d","src/salsa_struct.rs":"9a132d5fab49060e6c9cfb634e6cd62550736b826b67c8e23b4827e90926e052","src/tracked.rs":"e7e6af1e0ec5e73578419e0a36e8f7ff4f38c6e4d25ccffba045cb5c8193c942","src/tracked_fn.rs":"2ee1897ccd6880a56198911748ebaeabb9b534d6bc0c42fc3d7a03d2777c4a3a","src/tracked_struct.rs":"24a511419588071c5f26a96ba5980ccb031aeecc510eddb0e52238945d287f91"},"package":null}

View file

@ -0,0 +1,32 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "salsa-2022-macros"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
eyre = "0.6.5"
heck = "0.4"
proc-macro2 = "1.0"
quote = "1.0"
[dependencies.syn]
version = "1.0"
features = [
"full",
"extra-traits",
"visit-mut",
]

View file

@ -0,0 +1,162 @@
use syn::ItemStruct;
// #[salsa::accumulator(jar = Jar0)]
// struct Accumulator(DataType);
pub(crate) fn accumulator(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as Args);
let struct_impl = syn::parse_macro_input!(input as ItemStruct);
accumulator_contents(&args, &struct_impl)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
type Args = crate::options::Options<Accumulator>;
struct Accumulator;
impl crate::options::AllowedOptions for Accumulator {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = false;
const DB: bool = false;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = false;
}
fn accumulator_contents(
args: &Args,
struct_item: &syn::ItemStruct,
) -> syn::Result<proc_macro2::TokenStream> {
// We expect a single anonymous field.
let data_ty = data_ty(struct_item)?;
let struct_name = &struct_item.ident;
let struct_ty = &parse_quote! {#struct_name};
let inherent_impl = inherent_impl(args, struct_ty, data_ty);
let ingredients_for_impl = ingredients_for_impl(args, struct_name, data_ty);
let struct_item_out = struct_item_out(args, struct_item, data_ty);
let accumulator_impl = accumulator_impl(args, struct_ty, data_ty);
Ok(quote! {
#inherent_impl
#ingredients_for_impl
#struct_item_out
#accumulator_impl
})
}
fn data_ty(struct_item: &syn::ItemStruct) -> syn::Result<&syn::Type> {
if let syn::Fields::Unnamed(fields) = &struct_item.fields {
if fields.unnamed.len() != 1 {
Err(syn::Error::new(
struct_item.ident.span(),
"accumulator structs should have only one anonymous field",
))
} else {
Ok(&fields.unnamed[0].ty)
}
} else {
Err(syn::Error::new(
struct_item.ident.span(),
"accumulator structs should have only one anonymous field",
))
}
}
fn struct_item_out(
_args: &Args,
struct_item: &syn::ItemStruct,
data_ty: &syn::Type,
) -> syn::ItemStruct {
let mut struct_item_out = struct_item.clone();
struct_item_out.fields = syn::Fields::Unnamed(parse_quote! {
(std::marker::PhantomData<#data_ty>)
});
struct_item_out
}
fn inherent_impl(args: &Args, struct_ty: &syn::Type, data_ty: &syn::Type) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
parse_quote! {
impl #struct_ty {
pub fn push<DB: ?Sized>(db: &DB, data: #data_ty)
where
DB: salsa::storage::HasJar<#jar_ty>,
{
let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #struct_ty >>::ingredient(jar);
ingredients.push(runtime, data)
}
}
}
}
fn ingredients_for_impl(
args: &Args,
struct_name: &syn::Ident,
data_ty: &syn::Type,
) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
let debug_name = crate::literal(struct_name);
parse_quote! {
impl salsa::storage::IngredientsFor for #struct_name {
type Ingredients = salsa::accumulator::AccumulatorIngredient<#data_ty>;
type Jar = #jar_ty;
fn create_ingredients<DB>(routes: &mut salsa::routes::Routes<DB>) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar)
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar)
},
);
salsa::accumulator::AccumulatorIngredient::new(index, #debug_name)
}
}
}
}
fn accumulator_impl(args: &Args, struct_ty: &syn::Type, data_ty: &syn::Type) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
parse_quote! {
impl salsa::accumulator::Accumulator for #struct_ty {
type Data = #data_ty;
type Jar = #jar_ty;
fn accumulator_ingredient<'db, Db>(
db: &'db Db,
) -> &'db salsa::accumulator::AccumulatorIngredient<Self::Data>
where
Db: ?Sized + salsa::storage::HasJar<Self::Jar>
{
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#struct_ty>>::ingredient(jar);
ingredients
}
}
}
}

View file

@ -0,0 +1,94 @@
pub(crate) struct Configuration {
pub(crate) jar_ty: syn::Type,
pub(crate) salsa_struct_ty: syn::Type,
pub(crate) key_ty: syn::Type,
pub(crate) value_ty: syn::Type,
pub(crate) cycle_strategy: CycleRecoveryStrategy,
pub(crate) backdate_fn: syn::ImplItemMethod,
pub(crate) execute_fn: syn::ImplItemMethod,
pub(crate) recover_fn: syn::ImplItemMethod,
}
impl Configuration {
pub(crate) fn to_impl(&self, self_ty: &syn::Type) -> syn::ItemImpl {
let Configuration {
jar_ty,
salsa_struct_ty,
key_ty,
value_ty,
cycle_strategy,
backdate_fn,
execute_fn,
recover_fn,
} = self;
parse_quote! {
impl salsa::function::Configuration for #self_ty {
type Jar = #jar_ty;
type SalsaStruct = #salsa_struct_ty;
type Key = #key_ty;
type Value = #value_ty;
const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = #cycle_strategy;
#backdate_fn
#execute_fn
#recover_fn
}
}
}
}
pub(crate) enum CycleRecoveryStrategy {
Panic,
Fallback,
}
impl quote::ToTokens for CycleRecoveryStrategy {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
CycleRecoveryStrategy::Panic => {
tokens.extend(quote! {salsa::cycle::CycleRecoveryStrategy::Panic})
}
CycleRecoveryStrategy::Fallback => {
tokens.extend(quote! {salsa::cycle::CycleRecoveryStrategy::Fallback})
}
}
}
}
/// Returns an appropriate definition for `should_backdate_value` depending on
/// whether this value is memoized or not.
pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemMethod {
if should_backdate {
parse_quote! {
fn should_backdate_value(v1: &Self::Value, v2: &Self::Value) -> bool {
salsa::function::should_backdate_value(v1, v2)
}
}
} else {
parse_quote! {
fn should_backdate_value(_v1: &Self::Value, _v2: &Self::Value) -> bool {
false
}
}
}
}
/// Returns an appropriate definition for `recover_from_cycle` for cases where
/// the cycle recovery is panic.
pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemMethod {
parse_quote! {
fn recover_from_cycle(
_db: &salsa::function::DynDb<Self>,
_cycle: &salsa::Cycle,
_key: Self::Key,
) -> Self::Value {
panic!()
}
}
}
pub(crate) fn value_ty(sig: &syn::Signature) -> syn::Type {
match &sig.output {
syn::ReturnType::Default => parse_quote!(()),
syn::ReturnType::Type(_, ty) => syn::Type::clone(ty),
}
}

View file

@ -0,0 +1,233 @@
use proc_macro2::Literal;
use syn::{spanned::Spanned, Token};
// Source:
//
// #[salsa::db(Jar0, Jar1, Jar2)]
// pub struct Database {
// storage: salsa::Storage<Self>,
// }
pub(crate) fn db(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as Args);
let input = syn::parse_macro_input!(input as syn::ItemStruct);
let storage = match find_storage_field(&input) {
Ok(v) => v,
Err(err) => {
let err = Literal::string(err);
let error = quote_spanned!(input.ident.span() => compile_error!(#err));
return quote! {
#input
#error
}
.into();
}
};
let as_salsa_database_impl = as_salsa_database_impl(&input);
let has_jars_impl = has_jars_impl(&args, &input, &storage);
let has_jars_dyn_impl = has_jars_dyn_impl(&input, &storage);
let per_jar_impls = per_jar_impls(&args, &input, &storage);
quote! {
#input
#as_salsa_database_impl
#has_jars_impl
#has_jars_dyn_impl
#(#per_jar_impls)*
}
.into()
}
pub struct Args {
jar_paths: syn::punctuated::Punctuated<syn::Path, Token![,]>,
}
impl syn::parse::Parse for Args {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
Ok(Self {
jar_paths: syn::punctuated::Punctuated::parse_terminated(input)?,
})
}
}
fn find_storage_field(input: &syn::ItemStruct) -> Result<syn::Ident, &'static str> {
let storage = "storage";
for field in input.fields.iter() {
if let Some(i) = &field.ident {
if i == storage {
return Ok(i.clone());
}
} else {
return Err(
"database struct must be a braced struct (`{}`) with a field named storage",
);
}
}
Err("database has no field named `storage`")
}
fn as_salsa_database_impl(input: &syn::ItemStruct) -> syn::ItemImpl {
let db = &input.ident;
parse_quote! {
impl salsa::database::AsSalsaDatabase for #db {
fn as_salsa_database(&self) -> &dyn salsa::Database {
self
}
}
}
}
fn has_jars_impl(args: &Args, input: &syn::ItemStruct, storage: &syn::Ident) -> syn::ItemImpl {
let jar_paths: Vec<&syn::Path> = args.jar_paths.iter().collect();
let jar_field_names: Vec<_> = args
.jar_paths
.iter()
.zip(0..)
.map(|(p, i)| syn::LitInt::new(&format!("{}", i), p.span()))
.collect();
let db = &input.ident;
parse_quote! {
// ANCHOR: HasJars
impl salsa::storage::HasJars for #db {
type Jars = (#(#jar_paths,)*);
// ANCHOR_END: HasJars
fn jars(&self) -> (&Self::Jars, &salsa::Runtime) {
self.#storage.jars()
}
fn jars_mut(&mut self) -> (&mut Self::Jars, &mut salsa::Runtime) {
self.#storage.jars_mut()
}
// ANCHOR: create_jars
fn create_jars(routes: &mut salsa::routes::Routes<Self>) -> Box<Self::Jars> {
unsafe {
salsa::plumbing::create_jars_inplace::<#db>(|jars| {
#(
unsafe {
let place = std::ptr::addr_of_mut!((*jars).#jar_field_names);
<#jar_paths as salsa::jar::Jar>::init_jar(place, routes);
}
)*
})
}
}
// ANCHOR_END: create_jars
}
}
}
fn has_jars_dyn_impl(input: &syn::ItemStruct, storage: &syn::Ident) -> syn::ItemImpl {
let db = &input.ident;
parse_quote! {
impl salsa::storage::HasJarsDyn for #db {
fn runtime(&self) -> &salsa::Runtime {
self.#storage.runtime()
}
fn runtime_mut(&mut self) ->&mut salsa::Runtime {
self.#storage.runtime_mut()
}
fn maybe_changed_after(
&self,
input: salsa::key::DependencyIndex,
revision: salsa::Revision,
) -> bool {
let ingredient = self.#storage.ingredient(input.ingredient_index());
ingredient.maybe_changed_after(self, input, revision)
}
fn cycle_recovery_strategy(
&self,
ingredient_index: salsa::IngredientIndex,
) -> salsa::cycle::CycleRecoveryStrategy {
let ingredient = self.#storage.ingredient(ingredient_index);
ingredient.cycle_recovery_strategy()
}
fn origin(
&self,
index: salsa::DatabaseKeyIndex,
) -> Option<salsa::runtime::local_state::QueryOrigin> {
let ingredient = self.#storage.ingredient(index.ingredient_index());
ingredient.origin(index.key_index())
}
fn mark_validated_output(&self, executor: salsa::DatabaseKeyIndex, output: salsa::key::DependencyIndex) {
let ingredient = self.#storage.ingredient(output.ingredient_index());
ingredient.mark_validated_output(self, executor, output.key_index());
}
fn remove_stale_output(&self, executor: salsa::DatabaseKeyIndex, stale_output: salsa::key::DependencyIndex) {
let ingredient = self.#storage.ingredient(stale_output.ingredient_index());
ingredient.remove_stale_output(self, executor, stale_output.key_index());
}
fn salsa_struct_deleted(&self, ingredient: salsa::IngredientIndex, id: salsa::Id) {
let ingredient = self.#storage.ingredient(ingredient);
ingredient.salsa_struct_deleted(self, id);
}
fn fmt_index(&self, index: salsa::key::DependencyIndex, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ingredient = self.#storage.ingredient(index.ingredient_index());
ingredient.fmt_index(index.key_index(), fmt)
}
}
}
}
fn per_jar_impls(args: &Args, input: &syn::ItemStruct, storage: &syn::Ident) -> Vec<syn::ItemImpl> {
let db = &input.ident;
args.jar_paths
.iter()
.zip(0..)
.flat_map(|(jar_path, jar_index)| {
let jar_index = Literal::u32_unsuffixed(jar_index);
vec![
parse_quote! {
impl salsa::storage::DbWithJar<#jar_path> for #db {
fn as_jar_db<'db>(&'db self) -> &'db <#jar_path as salsa::jar::Jar<'db>>::DynDb
where
'db: 'db,
{
self as &'db <#jar_path as salsa::jar::Jar<'db>>::DynDb
}
}
},
parse_quote! {
impl salsa::storage::HasJar<#jar_path> for #db {
fn jar(&self) -> (&#jar_path, &salsa::Runtime) {
let (__jars, __runtime) = self.#storage.jars();
(&__jars.#jar_index, __runtime)
}
fn jar_mut(&mut self) -> (&mut #jar_path, &mut salsa::Runtime) {
let (__jars, __runtime) = self.#storage.jars_mut();
(&mut __jars.#jar_index, __runtime)
}
}
},
parse_quote! {
impl salsa::storage::JarFromJars<#jar_path> for #db {
fn jar_from_jars<'db>(jars: &Self::Jars) -> &#jar_path {
&jars.#jar_index
}
fn jar_from_jars_mut<'db>(jars: &mut Self::Jars) -> &mut #jar_path {
&mut jars.#jar_index
}
}
}
]
})
.collect()
}

View file

@ -0,0 +1,321 @@
use crate::salsa_struct::{SalsaField, SalsaStruct, SalsaStructKind};
use proc_macro2::{Literal, TokenStream};
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
/// * the "id struct" `struct Foo(salsa::Id)`
/// * the entity ingredient, which maps the id fields to the `Id`
/// * for each value field, a function ingredient
pub(crate) fn input(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match SalsaStruct::new(SalsaStructKind::Input, args, input)
.and_then(|el| InputStruct(el).generate_input())
{
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
}
}
struct InputStruct(SalsaStruct<Self>);
impl std::ops::Deref for InputStruct {
type Target = SalsaStruct<Self>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl crate::options::AllowedOptions for InputStruct {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const SINGLETON: bool = true;
const JAR: bool = true;
const DATA: bool = true;
const DB: bool = false;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = true;
}
impl InputStruct {
fn generate_input(&self) -> syn::Result<TokenStream> {
let id_struct = self.id_struct();
let inherent_impl = self.input_inherent_impl();
let ingredients_for_impl = self.input_ingredients();
let as_id_impl = self.as_id_impl();
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#id_struct
#inherent_impl
#ingredients_for_impl
#as_id_impl
#as_debug_with_db_impl
#salsa_struct_in_db_impl
})
}
/// Generate an inherent impl with methods on the entity type.
fn input_inherent_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let db_dyn_ty = self.db_dyn_ty();
let input_index = self.input_index();
let field_indices = self.all_field_indices();
let field_names = self.all_field_names();
let field_vises = self.all_field_vises();
let field_tys: Vec<_> = self.all_field_tys();
let field_clones: Vec<_> = self.all_fields().map(SalsaField::is_clone_field).collect();
let get_field_names: Vec<_> = self.all_get_field_names();
let field_getters: Vec<syn::ImplItemMethod> = field_indices.iter().zip(&get_field_names).zip(&field_vises).zip(&field_tys).zip(&field_clones).map(|((((field_index, get_field_name), field_vis), field_ty), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #get_field_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__runtime, self)
}
}
} else {
parse_quote! {
#field_vis fn #get_field_name<'db>(self, __db: &'db #db_dyn_ty) -> #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__runtime, self).clone()
}
}
}
)
.collect();
// setters
let set_field_names = self.all_set_field_names();
let field_setters: Vec<syn::ImplItemMethod> = field_indices.iter()
.zip(&set_field_names)
.zip(&field_vises)
.zip(&field_tys)
.filter_map(|(((field_index, &set_field_name), field_vis), field_ty)| {
let set_field_name = set_field_name?;
Some(parse_quote! {
#field_vis fn #set_field_name<'db>(self, __db: &'db mut #db_dyn_ty) -> salsa::setter::Setter<'db, #ident, #field_ty>
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient_mut(__jar);
salsa::setter::Setter::new(__runtime, self, &mut __ingredients.#field_index)
}
})
})
.collect();
let constructor_name = self.constructor_name();
let singleton = self.0.is_isingleton();
let constructor: syn::ImplItemMethod = if singleton {
parse_quote! {
/// Creates a new singleton input
///
/// # Panics
///
/// If called when an instance already exists
pub fn #constructor_name(__db: &#db_dyn_ty, #(#field_names: #field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
let __id = __ingredients.#input_index.new_singleton_input(__runtime);
#(
__ingredients.#field_indices.store_new(__runtime, __id, #field_names, salsa::Durability::LOW);
)*
__id
}
}
} else {
parse_quote! {
pub fn #constructor_name(__db: &#db_dyn_ty, #(#field_names: #field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
let __id = __ingredients.#input_index.new_input(__runtime);
#(
__ingredients.#field_indices.store_new(__runtime, __id, #field_names, salsa::Durability::LOW);
)*
__id
}
}
};
if singleton {
let get: syn::ImplItemMethod = parse_quote! {
#[track_caller]
pub fn get(__db: &#db_dyn_ty) -> Self {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#input_index.get_singleton_input(__runtime).expect("singleton input struct not yet initialized")
}
};
let try_get: syn::ImplItemMethod = parse_quote! {
#[track_caller]
pub fn try_get(__db: &#db_dyn_ty) -> Option<Self> {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#input_index.get_singleton_input(__runtime)
}
};
parse_quote! {
impl #ident {
#constructor
#get
#try_get
#(#field_getters)*
#(#field_setters)*
}
}
} else {
parse_quote! {
impl #ident {
#constructor
#(#field_getters)*
#(#field_setters)*
}
}
}
// }
}
/// Generate the `IngredientsFor` impl for this entity.
///
/// The entity's ingredients include both the main entity ingredient along with a
/// function ingredient for each of the value fields.
fn input_ingredients(&self) -> syn::ItemImpl {
use crate::literal;
let ident = self.id_ident();
let field_ty = self.all_field_tys();
let jar_ty = self.jar_ty();
let all_field_indices: Vec<Literal> = self.all_field_indices();
let input_index: Literal = self.input_index();
let debug_name_struct = literal(self.id_ident());
let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect();
parse_quote! {
impl salsa::storage::IngredientsFor for #ident {
type Jar = #jar_ty;
type Ingredients = (
#(
salsa::input_field::InputFieldIngredient<#ident, #field_ty>,
)*
salsa::input::InputIngredient<#ident>,
);
fn create_ingredients<DB>(
routes: &mut salsa::routes::Routes<DB>,
) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
(
#(
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#all_field_indices
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#all_field_indices
},
);
salsa::input_field::InputFieldIngredient::new(index, #debug_name_fields)
},
)*
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#input_index
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#input_index
},
);
salsa::input::InputIngredient::new(index, #debug_name_struct)
},
)
}
}
}
}
/// For the entity, we create a tuple that contains the function ingredients
/// for each "other" field and the entity ingredient. This is the index of
/// the entity ingredient within that tuple.
fn input_index(&self) -> Literal {
Literal::usize_unsuffixed(self.all_fields().count())
}
/// For the entity, we create a tuple that contains the function ingredients
/// for each field and an entity ingredient. These are the indices
/// of the function ingredients within that tuple.
fn all_field_indices(&self) -> Vec<Literal> {
self.all_fields()
.zip(0..)
.map(|(_, i)| Literal::usize_unsuffixed(i))
.collect()
}
/// Names of setters of all fields that should be generated. Returns an optional Ident for the field name
/// that is None when the field should not generate a setter.
///
/// Setters are not created for fields with #[id] tag so they'll be safe to include in debug formatting
pub(crate) fn all_set_field_names(&self) -> Vec<Option<&syn::Ident>> {
self.all_fields()
.map(|ef| (!ef.has_id_attr).then(|| ef.set_name()))
.collect()
}
/// Implementation of `SalsaStructInDb`.
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
parse_quote! {
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
where
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
{
fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {
// Do nothing here, at least for now.
// If/when we add ability to delete inputs, this would become relevant.
}
}
}
}
}

View file

@ -0,0 +1,195 @@
use crate::salsa_struct::{SalsaStruct, SalsaStructKind};
use proc_macro2::TokenStream;
// #[salsa::interned(jar = Jar0, data = TyData0)]
// #[derive(Eq, PartialEq, Hash, Debug, Clone)]
// struct Ty0 {
// field1: Type1,
// #[id(ref)] field2: Type2,
// ...
// }
pub(crate) fn interned(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match SalsaStruct::new(SalsaStructKind::Interned, args, input)
.and_then(|el| InternedStruct(el).generate_interned())
{
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
}
}
struct InternedStruct(SalsaStruct<Self>);
impl std::ops::Deref for InternedStruct {
type Target = SalsaStruct<Self>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl crate::options::AllowedOptions for InternedStruct {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = true;
const DB: bool = false;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = true;
}
impl InternedStruct {
fn generate_interned(&self) -> syn::Result<TokenStream> {
self.validate_interned()?;
let id_struct = self.id_struct();
let data_struct = self.data_struct();
let ingredients_for_impl = self.ingredients_for_impl();
let as_id_impl = self.as_id_impl();
let named_fields_impl = self.inherent_impl_for_named_fields();
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#id_struct
#data_struct
#ingredients_for_impl
#as_id_impl
#named_fields_impl
#salsa_struct_in_db_impl
#as_debug_with_db_impl
})
}
fn validate_interned(&self) -> syn::Result<()> {
self.disallow_id_fields("interned")?;
Ok(())
}
/// If this is an interned struct, then generate methods to access each field,
/// as well as a `new` method.
fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl {
let vis = self.visibility();
let id_ident = self.id_ident();
let db_dyn_ty = self.db_dyn_ty();
let jar_ty = self.jar_ty();
let field_getters: Vec<syn::ImplItemMethod> = self
.all_fields()
.map(|field| {
let field_name = field.name();
let field_ty = field.ty();
let field_vis = field.vis();
let field_get_name = field.get_name();
if field.is_clone_field() {
parse_quote! {
#field_vis fn #field_get_name(self, db: &#db_dyn_ty) -> #field_ty {
let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #id_ident >>::ingredient(jar);
std::clone::Clone::clone(&ingredients.data(runtime, self).#field_name)
}
}
} else {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, db: &'db #db_dyn_ty) -> &'db #field_ty {
let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #id_ident >>::ingredient(jar);
&ingredients.data(runtime, self).#field_name
}
}
}
})
.collect();
let field_names = self.all_field_names();
let field_tys = self.all_field_tys();
let data_ident = self.data_ident();
let constructor_name = self.constructor_name();
let new_method: syn::ImplItemMethod = parse_quote! {
#vis fn #constructor_name(
db: &#db_dyn_ty,
#(#field_names: #field_tys,)*
) -> Self {
let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #id_ident >>::ingredient(jar);
ingredients.intern(runtime, #data_ident {
#(#field_names,)*
})
}
};
parse_quote! {
impl #id_ident {
#(#field_getters)*
#new_method
}
}
}
/// Generates an impl of `salsa::storage::IngredientsFor`.
///
/// For a memoized type, the only ingredient is an `InternedIngredient`.
fn ingredients_for_impl(&self) -> syn::ItemImpl {
let id_ident = self.id_ident();
let debug_name = crate::literal(id_ident);
let jar_ty = self.jar_ty();
let data_ident = self.data_ident();
parse_quote! {
impl salsa::storage::IngredientsFor for #id_ident {
type Jar = #jar_ty;
type Ingredients = salsa::interned::InternedIngredient<#id_ident, #data_ident>;
fn create_ingredients<DB>(
routes: &mut salsa::routes::Routes<DB>,
) -> Self::Ingredients
where
DB: salsa::storage::JarFromJars<Self::Jar>,
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar)
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar)
},
);
salsa::interned::InternedIngredient::new(index, #debug_name)
}
}
}
}
/// Implementation of `SalsaStructInDb`.
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
parse_quote! {
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
where
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
{
fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {
// Do nothing here, at least for now.
// If/when we add ability to delete inputs, this would become relevant.
}
}
}
}
}

View file

@ -0,0 +1,172 @@
use proc_macro2::Literal;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Field, FieldsUnnamed, Ident, ItemStruct, Path, Token};
use crate::options::Options;
// Source:
//
// #[salsa::jar(db = Jar0Db)]
// pub struct Jar0(Entity0, Ty0, EntityComponent0, my_func);
pub(crate) fn jar(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let options = syn::parse_macro_input!(args as Args);
let db_path = match options.db_path {
Some(v) => v,
None => panic!("no `db` specified"),
};
let input = syn::parse_macro_input!(input as ItemStruct);
jar_struct_and_friends(&db_path, &input).into()
}
type Args = Options<Jar>;
struct Jar;
impl crate::options::AllowedOptions for Jar {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = false;
const DATA: bool = false;
const DB: bool = true;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = false;
}
pub(crate) fn jar_struct_and_friends(
jar_trait: &Path,
input: &ItemStruct,
) -> proc_macro2::TokenStream {
let output_struct = jar_struct(input);
let jar_struct = &input.ident;
// for each field, we need to generate an impl of `HasIngredientsFor`
let has_ingredients_for_impls: Vec<_> = input
.fields
.iter()
.zip(0..)
.map(|(field, index)| has_ingredients_for_impl(jar_struct, field, index))
.collect();
let jar_impl = jar_impl(jar_struct, jar_trait, input);
quote! {
#output_struct
#(#has_ingredients_for_impls)*
#jar_impl
}
}
pub(crate) fn has_ingredients_for_impl(
jar_struct: &Ident,
field: &Field,
index: u32,
) -> proc_macro2::TokenStream {
let field_ty = &field.ty;
let index = Literal::u32_unsuffixed(index);
quote! {
impl salsa::storage::HasIngredientsFor<#field_ty> for #jar_struct {
fn ingredient(&self) -> &<#field_ty as salsa::storage::IngredientsFor>::Ingredients {
&self.#index
}
fn ingredient_mut(&mut self) -> &mut <#field_ty as salsa::storage::IngredientsFor>::Ingredients {
&mut self.#index
}
}
}
}
pub(crate) fn jar_impl(
jar_struct: &Ident,
jar_trait: &Path,
input: &ItemStruct,
) -> proc_macro2::TokenStream {
let field_tys: Vec<_> = input.fields.iter().map(|f| &f.ty).collect();
let field_var_names: &Vec<_> = &input
.fields
.iter()
.zip(0..)
.map(|(f, i)| syn::LitInt::new(&format!("{}", i), f.ty.span()))
.collect();
// ANCHOR: init_jar
quote! {
unsafe impl<'salsa_db> salsa::jar::Jar<'salsa_db> for #jar_struct {
type DynDb = dyn #jar_trait + 'salsa_db;
unsafe fn init_jar<DB>(place: *mut Self, routes: &mut salsa::routes::Routes<DB>)
where
DB: salsa::storage::JarFromJars<Self> + salsa::storage::DbWithJar<Self>,
{
#(
unsafe {
std::ptr::addr_of_mut!((*place).#field_var_names)
.write(<#field_tys as salsa::storage::IngredientsFor>::create_ingredients(routes));
}
)*
}
}
}
// ANCHOR_END: init_jar
}
pub(crate) fn jar_struct(input: &ItemStruct) -> ItemStruct {
let mut output_struct = input.clone();
output_struct.fields = generate_fields(input).into();
if output_struct.semi_token.is_none() {
output_struct.semi_token = Some(Token![;](input.struct_token.span));
}
output_struct
}
fn generate_fields(input: &ItemStruct) -> FieldsUnnamed {
// Generate the
let mut output_fields = Punctuated::new();
for field in input.fields.iter() {
let mut field = field.clone();
// Convert to anonymous fields
field.ident = None;
let field_ty = &field.ty;
field.ty =
syn::parse2(quote!(< #field_ty as salsa::storage::IngredientsFor >::Ingredients))
.unwrap();
output_fields.push(field);
}
let paren_token = match &input.fields {
syn::Fields::Named(f) => syn::token::Paren {
span: f.brace_token.span,
},
syn::Fields::Unnamed(f) => f.paren_token,
syn::Fields::Unit => syn::token::Paren {
span: input.ident.span(),
},
};
FieldsUnnamed {
paren_token,
unnamed: output_fields,
}
}

View file

@ -0,0 +1,73 @@
//! This crate provides salsa's macros and attributes.
#![recursion_limit = "256"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
macro_rules! parse_quote {
($($inp:tt)*) => {
syn::parse2(quote!{$($inp)*}).unwrap_or_else(|err| {
panic!("failed to parse at {}:{}:{}: {}", file!(), line!(), column!(), err)
})
}
}
macro_rules! parse_quote_spanned {
($($inp:tt)*) => {
syn::parse2(quote_spanned!{$($inp)*}).unwrap_or_else(|err| {
panic!("failed to parse at {}:{}:{}: {}", file!(), line!(), column!(), err)
})
}
}
/// Convert a single Ident to Literal: useful when &'static str is needed.
pub(crate) fn literal(ident: &proc_macro2::Ident) -> proc_macro2::Literal {
proc_macro2::Literal::string(&ident.to_string())
}
mod accumulator;
mod configuration;
mod db;
mod input;
mod interned;
mod jar;
mod options;
mod salsa_struct;
mod tracked;
mod tracked_fn;
mod tracked_struct;
#[proc_macro_attribute]
pub fn accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
accumulator::accumulator(args, input)
}
#[proc_macro_attribute]
pub fn jar(args: TokenStream, input: TokenStream) -> TokenStream {
jar::jar(args, input)
}
#[proc_macro_attribute]
pub fn db(args: TokenStream, input: TokenStream) -> TokenStream {
db::db(args, input)
}
#[proc_macro_attribute]
pub fn interned(args: TokenStream, input: TokenStream) -> TokenStream {
interned::interned(args, input)
}
#[proc_macro_attribute]
pub fn input(args: TokenStream, input: TokenStream) -> TokenStream {
input::input(args, input)
}
#[proc_macro_attribute]
pub fn tracked(args: TokenStream, input: TokenStream) -> TokenStream {
tracked::tracked(args, input)
}

View file

@ -0,0 +1,280 @@
use std::marker::PhantomData;
use syn::{ext::IdentExt, spanned::Spanned};
/// "Options" are flags that can be supplied to the various salsa related
/// macros. They are listed like `(ref, no_eq, foo=bar)` etc. The commas
/// are required and trailing commas are permitted. The options accepted
/// for any particular location are configured via the `AllowedOptions`
/// trait.
pub(crate) struct Options<A: AllowedOptions> {
/// The `return_ref` option is used to signal that field/return type is "by ref"
///
/// If this is `Some`, the value is the `ref` identifier.
pub return_ref: Option<syn::Ident>,
/// The `no_eq` option is used to signal that a given field does not implement
/// the `Eq` trait and cannot be compared for equality.
///
/// If this is `Some`, the value is the `no_eq` identifier.
pub no_eq: Option<syn::Ident>,
/// The `singleton` option is used on input with only one field
/// It allows the creation of convenient methods
pub singleton: Option<syn::Ident>,
/// The `specify` option is used to signal that a tracked function can
/// have its value externally specified (at least some of the time).
///
/// If this is `Some`, the value is the `specify` identifier.
pub specify: Option<syn::Ident>,
/// The `jar = <type>` option is used to indicate the jar; it defaults to `crate::jar`.
///
/// If this is `Some`, the value is the `<type>`.
pub jar_ty: Option<syn::Type>,
/// The `db = <path>` option is used to indicate the db.
///
/// If this is `Some`, the value is the `<path>`.
pub db_path: Option<syn::Path>,
/// The `recovery_fn = <path>` option is used to indicate the recovery function.
///
/// If this is `Some`, the value is the `<path>`.
pub recovery_fn: Option<syn::Path>,
/// The `data = <ident>` option is used to define the name of the data type for an interned
/// struct.
///
/// If this is `Some`, the value is the `<ident>`.
pub data: Option<syn::Ident>,
/// The `lru = <usize>` option is used to set the lru capacity for a tracked function.
///
/// If this is `Some`, the value is the `<usize>`.
pub lru: Option<usize>,
/// The `constructor = <ident>` option lets the user specify the name of
/// the constructor of a salsa struct.
///
/// If this is `Some`, the value is the `<ident>`.
pub constructor_name: Option<syn::Ident>,
/// Remember the `A` parameter, which plays no role after parsing.
phantom: PhantomData<A>,
}
impl<A: AllowedOptions> Default for Options<A> {
fn default() -> Self {
Self {
return_ref: Default::default(),
specify: Default::default(),
no_eq: Default::default(),
jar_ty: Default::default(),
db_path: Default::default(),
recovery_fn: Default::default(),
data: Default::default(),
constructor_name: Default::default(),
phantom: Default::default(),
lru: Default::default(),
singleton: Default::default(),
}
}
}
/// These flags determine which options are allowed in a given context
pub(crate) trait AllowedOptions {
const RETURN_REF: bool;
const SPECIFY: bool;
const NO_EQ: bool;
const SINGLETON: bool;
const JAR: bool;
const DATA: bool;
const DB: bool;
const RECOVERY_FN: bool;
const LRU: bool;
const CONSTRUCTOR_NAME: bool;
}
type Equals = syn::Token![=];
type Comma = syn::Token![,];
impl<A: AllowedOptions> Options<A> {
/// Returns the `jar type` given by the user; if none is given,
/// returns the default `crate::Jar`.
pub(crate) fn jar_ty(&self) -> syn::Type {
if let Some(jar_ty) = &self.jar_ty {
return jar_ty.clone();
}
parse_quote! {crate::Jar}
}
pub(crate) fn should_backdate(&self) -> bool {
self.no_eq.is_none()
}
}
impl<A: AllowedOptions> syn::parse::Parse for Options<A> {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut options = Options::default();
while !input.is_empty() {
let ident: syn::Ident = syn::Ident::parse_any(input)?;
if ident == "return_ref" {
if A::RETURN_REF {
if let Some(old) = std::mem::replace(&mut options.return_ref, Some(ident)) {
return Err(syn::Error::new(
old.span(),
"option `return_ref` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`return_ref` option not allowed here",
));
}
} else if ident == "no_eq" {
if A::NO_EQ {
if let Some(old) = std::mem::replace(&mut options.no_eq, Some(ident)) {
return Err(syn::Error::new(old.span(), "option `no_eq` provided twice"));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`no_eq` option not allowed here",
));
}
} else if ident == "singleton" {
if A::SINGLETON {
if let Some(old) = std::mem::replace(&mut options.singleton, Some(ident)) {
return Err(syn::Error::new(
old.span(),
"option `singleton` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`singleton` option not allowed here",
));
}
} else if ident == "specify" {
if A::SPECIFY {
if let Some(old) = std::mem::replace(&mut options.specify, Some(ident)) {
return Err(syn::Error::new(
old.span(),
"option `specify` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`specify` option not allowed here",
));
}
} else if ident == "jar" {
if A::JAR {
let _eq = Equals::parse(input)?;
let ty = syn::Type::parse(input)?;
if let Some(old) = std::mem::replace(&mut options.jar_ty, Some(ty)) {
return Err(syn::Error::new(old.span(), "option `jar` provided twice"));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`jar` option not allowed here",
));
}
} else if ident == "db" {
if A::DB {
let _eq = Equals::parse(input)?;
let path = syn::Path::parse(input)?;
if let Some(old) = std::mem::replace(&mut options.db_path, Some(path)) {
return Err(syn::Error::new(old.span(), "option `db` provided twice"));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`db` option not allowed here",
));
}
} else if ident == "recovery_fn" {
if A::RECOVERY_FN {
let _eq = Equals::parse(input)?;
let path = syn::Path::parse(input)?;
if let Some(old) = std::mem::replace(&mut options.recovery_fn, Some(path)) {
return Err(syn::Error::new(
old.span(),
"option `recovery_fn` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`recovery_fn` option not allowed here",
));
}
} else if ident == "data" {
if A::DATA {
let _eq = Equals::parse(input)?;
let ident = syn::Ident::parse(input)?;
if let Some(old) = std::mem::replace(&mut options.data, Some(ident)) {
return Err(syn::Error::new(old.span(), "option `data` provided twice"));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`data` option not allowed here",
));
}
} else if ident == "lru" {
if A::LRU {
let _eq = Equals::parse(input)?;
let lit = syn::LitInt::parse(input)?;
let value = lit.base10_parse::<usize>()?;
if let Some(old) = std::mem::replace(&mut options.lru, Some(value)) {
return Err(syn::Error::new(old.span(), "option `lru` provided twice"));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`lru` option not allowed here",
));
}
} else if ident == "constructor" {
if A::CONSTRUCTOR_NAME {
let _eq = Equals::parse(input)?;
let ident = syn::Ident::parse(input)?;
if let Some(old) = std::mem::replace(&mut options.constructor_name, Some(ident))
{
return Err(syn::Error::new(
old.span(),
"option `constructor` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`constructor` option not allowed here",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
format!("unrecognized option `{}`", ident),
));
}
if input.is_empty() {
break;
}
let _comma = Comma::parse(input)?;
}
Ok(options)
}
}

View file

@ -0,0 +1,472 @@
//! Common code for `#[salsa::interned]`, `#[salsa::input]`, and
//! `#[salsa::tracked]` decorators.
//!
//! Example of usage:
//!
//! ```rust,ignore
//! #[salsa::interned(jar = Jar0, data = TyData0)]
//! #[derive(Eq, PartialEq, Hash, Debug, Clone)]
//! struct Ty0 {
//! field1: Type1,
//! #[ref] field2: Type2,
//! ...
//! }
//! ```
//! For an interned or entity struct `Foo`, we generate:
//!
//! * the actual struct: `struct Foo(Id);`
//! * constructor function: `impl Foo { fn new(db: &crate::Db, field1: Type1, ..., fieldN: TypeN) -> Self { ... } }
//! * field accessors: `impl Foo { fn field1(&self) -> Type1 { self.field1.clone() } }`
//! * if the field is `ref`, we generate `fn field1(&self) -> &Type1`
//!
//! Only if there are no `ref` fields:
//!
//! * the data type: `struct FooData { field1: Type1, ... }` or `enum FooData { ... }`
//! * data method `impl Foo { fn data(&self, db: &dyn crate::Db) -> FooData { FooData { f: self.f(db), ... } } }`
//! * this could be optimized, particularly for interned fields
use crate::{
configuration,
options::{AllowedOptions, Options},
};
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use syn::spanned::Spanned;
pub(crate) enum SalsaStructKind {
Input,
Tracked,
Interned,
}
pub(crate) struct SalsaStruct<A: AllowedOptions> {
kind: SalsaStructKind,
args: Options<A>,
struct_item: syn::ItemStruct,
fields: Vec<SalsaField>,
}
const BANNED_FIELD_NAMES: &[&str] = &["from", "new"];
impl<A: AllowedOptions> SalsaStruct<A> {
pub(crate) fn new(
kind: SalsaStructKind,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> syn::Result<Self> {
let struct_item = syn::parse(input)?;
Self::with_struct(kind, args, struct_item)
}
pub(crate) fn with_struct(
kind: SalsaStructKind,
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
) -> syn::Result<Self> {
let args: Options<A> = syn::parse(args)?;
let fields = Self::extract_options(&struct_item)?;
Ok(Self {
kind,
args,
struct_item,
fields,
})
}
/// Extract out the fields and their options:
/// If this is a struct, it must use named fields, so we can define field accessors.
/// If it is an enum, then this is not necessary.
pub(crate) fn extract_options(struct_item: &syn::ItemStruct) -> syn::Result<Vec<SalsaField>> {
match &struct_item.fields {
syn::Fields::Named(n) => Ok(n
.named
.iter()
.map(SalsaField::new)
.collect::<syn::Result<Vec<_>>>()?),
f => Err(syn::Error::new_spanned(
f,
"must have named fields for a struct",
)),
}
}
/// Iterator over all named fields.
///
/// If this is an enum, empty iterator.
pub(crate) fn all_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.fields.iter()
}
pub(crate) fn is_identity_field(&self, field: &SalsaField) -> bool {
match self.kind {
SalsaStructKind::Input | SalsaStructKind::Tracked => field.has_id_attr,
SalsaStructKind::Interned => true,
}
}
/// Names of all fields (id and value).
///
/// If this is an enum, empty vec.
pub(crate) fn all_field_names(&self) -> Vec<&syn::Ident> {
self.all_fields().map(|ef| ef.name()).collect()
}
/// Visibilities of all fields
pub(crate) fn all_field_vises(&self) -> Vec<&syn::Visibility> {
self.all_fields().map(|ef| ef.vis()).collect()
}
/// Names of getters of all fields
pub(crate) fn all_get_field_names(&self) -> Vec<&syn::Ident> {
self.all_fields().map(|ef| ef.get_name()).collect()
}
/// Types of all fields (id and value).
///
/// If this is an enum, empty vec.
pub(crate) fn all_field_tys(&self) -> Vec<&syn::Type> {
self.all_fields().map(|ef| ef.ty()).collect()
}
/// The name of the "identity" struct (this is the name the user gave, e.g., `Foo`).
pub(crate) fn id_ident(&self) -> &syn::Ident {
&self.struct_item.ident
}
/// Type of the jar for this struct
pub(crate) fn jar_ty(&self) -> syn::Type {
self.args.jar_ty()
}
/// checks if the "singleton" flag was set
pub(crate) fn is_isingleton(&self) -> bool {
self.args.singleton.is_some()
}
pub(crate) fn db_dyn_ty(&self) -> syn::Type {
let jar_ty = self.jar_ty();
parse_quote! {
<#jar_ty as salsa::jar::Jar<'_>>::DynDb
}
}
/// The name of the "data" struct (this comes from the `data = Foo` option or,
/// if that is not provided, by concatenating `Data` to the name of the struct).
pub(crate) fn data_ident(&self) -> syn::Ident {
match &self.args.data {
Some(d) => d.clone(),
None => syn::Ident::new(
&format!("__{}Data", self.id_ident()),
self.id_ident().span(),
),
}
}
/// Generate `struct Foo(Id)`
pub(crate) fn id_struct(&self) -> syn::ItemStruct {
let ident = self.id_ident();
let visibility = &self.struct_item.vis;
// Extract the attributes the user gave, but screen out derive, since we are adding our own.
let attrs: Vec<_> = self
.struct_item
.attrs
.iter()
.filter(|attr| !attr.path.is_ident("derive"))
.collect();
parse_quote! {
#(#attrs)*
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
#visibility struct #ident(salsa::Id);
}
}
/// Generates the `struct FooData` struct (or enum).
/// This type inherits all the attributes written by the user.
///
/// When using named fields, we synthesize the struct and field names.
///
/// When no named fields are available, copy the existing type.
pub(crate) fn data_struct(&self) -> syn::ItemStruct {
let ident = self.data_ident();
let visibility = self.visibility();
let all_field_names = self.all_field_names();
let all_field_tys = self.all_field_tys();
parse_quote! {
/// Internal struct used for interned item
#[derive(Eq, PartialEq, Hash, Clone)]
#visibility struct #ident {
#(
#all_field_names: #all_field_tys,
)*
}
}
}
/// Returns the visibility of this item
pub(crate) fn visibility(&self) -> &syn::Visibility {
&self.struct_item.vis
}
/// Returns the `constructor_name` in `Options` if it is `Some`, else `new`
pub(crate) fn constructor_name(&self) -> syn::Ident {
match self.args.constructor_name.clone() {
Some(name) => name,
None => Ident::new("new", Span::call_site()),
}
}
/// For each of the fields passed as an argument,
/// generate a struct named `Ident_Field` and an impl
/// of `salsa::function::Configuration` for that struct.
pub(crate) fn field_config_structs_and_impls<'a>(
&self,
fields: impl Iterator<Item = &'a SalsaField>,
) -> (Vec<syn::ItemStruct>, Vec<syn::ItemImpl>) {
let ident = &self.id_ident();
let jar_ty = self.jar_ty();
let visibility = self.visibility();
fields
.map(|ef| {
let value_field_name = ef.name();
let value_field_ty = ef.ty();
let value_field_backdate = ef.is_backdate_field();
let config_name = syn::Ident::new(
&format!(
"__{}",
format!("{}_{}", ident, value_field_name).to_upper_camel_case()
),
value_field_name.span(),
);
let item_struct: syn::ItemStruct = parse_quote! {
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
#visibility struct #config_name(std::convert::Infallible);
};
let execute_string = Literal::string(&format!("`execute` method for field `{}::{}` invoked",
ident,
ef.name(),
));
let recover_from_cycle_string = Literal::string(&format!("`execute` method for field `{}::{}` invoked",
ident,
ef.name(),
));
let should_backdate_value_fn = configuration::should_backdate_value_fn(value_field_backdate);
let item_impl: syn::ItemImpl = parse_quote! {
impl salsa::function::Configuration for #config_name {
type Jar = #jar_ty;
type SalsaStruct = #ident;
type Key = #ident;
type Value = #value_field_ty;
const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = salsa::cycle::CycleRecoveryStrategy::Panic;
#should_backdate_value_fn
fn execute(db: &salsa::function::DynDb<Self>, key: Self::Key) -> Self::Value {
panic!(#execute_string)
}
fn recover_from_cycle(db: &salsa::function::DynDb<Self>, cycle: &salsa::Cycle, key: Self::Key) -> Self::Value {
panic!(#recover_from_cycle_string)
}
}
};
(item_struct, item_impl)
})
.unzip()
}
/// Generate `impl salsa::AsId for Foo`
pub(crate) fn as_id_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
parse_quote! {
impl salsa::AsId for #ident {
fn as_id(self) -> salsa::Id {
self.0
}
fn from_id(id: salsa::Id) -> Self {
#ident(id)
}
}
}
}
/// Generate `impl salsa::DebugWithDb for Foo`
pub(crate) fn as_debug_with_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let db_type = self.db_dyn_ty();
let ident_string = ident.to_string();
// `::salsa::debug::helper::SalsaDebug` will use `DebugWithDb` or fallbak to `Debug`
let fields = self
.all_fields()
.into_iter()
.map(|field| -> TokenStream {
let field_name_string = field.name().to_string();
let field_getter = field.get_name();
let field_ty = field.ty();
let field_debug = quote_spanned! { field.field.span() =>
debug_struct = debug_struct.field(
#field_name_string,
&::salsa::debug::helper::SalsaDebug::<#field_ty, #db_type>::salsa_debug(
#[allow(clippy::needless_borrow)]
&self.#field_getter(_db),
_db,
_include_all_fields
)
);
};
if self.is_identity_field(field) {
quote_spanned! { field.field.span() =>
#field_debug
}
} else {
quote_spanned! { field.field.span() =>
if _include_all_fields {
#field_debug
}
}
}
})
.collect::<TokenStream>();
// `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl
parse_quote_spanned! {ident.span()=>
impl ::salsa::DebugWithDb<#db_type> for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: &#db_type, _include_all_fields: bool) -> ::std::fmt::Result {
#[allow(unused_imports)]
use ::salsa::debug::helper::Fallback;
let mut debug_struct = &mut f.debug_struct(#ident_string);
debug_struct = debug_struct.field("[salsa id]", &self.0.as_u32());
#fields
debug_struct.finish()
}
}
}
}
/// Disallow `#[id]` attributes on the fields of this struct.
///
/// If an `#[id]` field is found, return an error.
///
/// # Parameters
///
/// * `kind`, the attribute name (e.g., `input` or `interned`)
pub(crate) fn disallow_id_fields(&self, kind: &str) -> syn::Result<()> {
for ef in self.all_fields() {
if ef.has_id_attr {
return Err(syn::Error::new(
ef.name().span(),
format!("`#[id]` cannot be used with `#[salsa::{kind}]`"),
));
}
}
Ok(())
}
}
#[allow(clippy::type_complexity)]
pub(crate) const FIELD_OPTION_ATTRIBUTES: &[(&str, fn(&syn::Attribute, &mut SalsaField))] = &[
("id", |_, ef| ef.has_id_attr = true),
("return_ref", |_, ef| ef.has_ref_attr = true),
("no_eq", |_, ef| ef.has_no_eq_attr = true),
("get", |attr, ef| {
ef.get_name = attr.parse_args().unwrap();
}),
("set", |attr, ef| {
ef.set_name = attr.parse_args().unwrap();
}),
];
pub(crate) struct SalsaField {
field: syn::Field,
pub(crate) has_id_attr: bool,
pub(crate) has_ref_attr: bool,
pub(crate) has_no_eq_attr: bool,
get_name: syn::Ident,
set_name: syn::Ident,
}
impl SalsaField {
pub(crate) fn new(field: &syn::Field) -> syn::Result<Self> {
let field_name = field.ident.as_ref().unwrap();
let field_name_str = field_name.to_string();
if BANNED_FIELD_NAMES.iter().any(|n| *n == field_name_str) {
return Err(syn::Error::new(
field_name.span(),
&format!(
"the field name `{}` is disallowed in salsa structs",
field_name_str
),
));
}
let get_name = Ident::new(&field_name_str, Span::call_site());
let set_name = Ident::new(&format!("set_{}", field_name_str), Span::call_site());
let mut result = SalsaField {
field: field.clone(),
has_id_attr: false,
has_ref_attr: false,
has_no_eq_attr: false,
get_name,
set_name,
};
// Scan the attributes and look for the salsa attributes:
for attr in &field.attrs {
for (fa, func) in FIELD_OPTION_ATTRIBUTES {
if attr.path.is_ident(fa) {
func(attr, &mut result);
}
}
}
Ok(result)
}
/// The name of this field (all `SalsaField` instances are named).
pub(crate) fn name(&self) -> &syn::Ident {
self.field.ident.as_ref().unwrap()
}
/// The visibility of this field.
pub(crate) fn vis(&self) -> &syn::Visibility {
&self.field.vis
}
/// The type of this field (all `SalsaField` instances are named).
pub(crate) fn ty(&self) -> &syn::Type {
&self.field.ty
}
/// The name of this field's get method
pub(crate) fn get_name(&self) -> &syn::Ident {
&self.get_name
}
/// The name of this field's get method
pub(crate) fn set_name(&self) -> &syn::Ident {
&self.set_name
}
/// Do you clone the value of this field? (True if it is not a ref field)
pub(crate) fn is_clone_field(&self) -> bool {
!self.has_ref_attr
}
/// Do you potentially backdate the value of this field? (True if it is not a no-eq field)
pub(crate) fn is_backdate_field(&self) -> bool {
!self.has_no_eq_attr
}
}

View file

@ -0,0 +1,21 @@
use syn::{spanned::Spanned, Item};
pub(crate) fn tracked(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = syn::parse_macro_input!(input as Item);
let res = match item {
syn::Item::Struct(item) => crate::tracked_struct::tracked(args, item),
syn::Item::Fn(item) => crate::tracked_fn::tracked_fn(args, item),
syn::Item::Impl(item) => crate::tracked_fn::tracked_impl(args, item),
_ => Err(syn::Error::new(
item.span(),
"tracked can only be applied to structs, functions, and impls",
)),
};
match res {
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
}
}

View file

@ -0,0 +1,893 @@
use proc_macro2::{Literal, TokenStream};
use syn::spanned::Spanned;
use syn::visit_mut::VisitMut;
use syn::{ReturnType, Token};
use crate::configuration::{self, Configuration, CycleRecoveryStrategy};
use crate::options::Options;
pub(crate) fn tracked_fn(
args: proc_macro::TokenStream,
mut item_fn: syn::ItemFn,
) -> syn::Result<TokenStream> {
let args: FnArgs = syn::parse(args)?;
if item_fn.sig.inputs.is_empty() {
return Err(syn::Error::new(
item_fn.sig.ident.span(),
"tracked functions must have at least a database argument",
));
}
if let syn::FnArg::Receiver(receiver) = &item_fn.sig.inputs[0] {
return Err(syn::Error::new(
receiver.span(),
"#[salsa::tracked] must also be applied to the impl block for tracked methods",
));
}
if let Some(s) = &args.specify {
if function_type(&item_fn) == FunctionType::RequiresInterning {
return Err(syn::Error::new(
s.span(),
"tracked function takes too many arguments to have its value set with `specify`",
));
}
if args.lru.is_some() {
return Err(syn::Error::new(
s.span(),
"`specify` and `lru` cannot be used together",
));
}
}
let (config_ty, fn_struct) = fn_struct(&args, &item_fn)?;
*item_fn.block = getter_fn(&args, &mut item_fn.sig, item_fn.block.span(), &config_ty)?;
Ok(quote! {
#fn_struct
// we generate a `'db` lifetime that clippy
// sometimes doesn't like
#[allow(clippy::needless_lifetimes)]
#item_fn
})
}
type FnArgs = Options<TrackedFn>;
struct TrackedFn;
impl crate::options::AllowedOptions for TrackedFn {
const RETURN_REF: bool = true;
const SPECIFY: bool = true;
const NO_EQ: bool = true;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = false;
const DB: bool = false;
const RECOVERY_FN: bool = true;
const LRU: bool = true;
const CONSTRUCTOR_NAME: bool = false;
}
type ImplArgs = Options<TrackedImpl>;
pub(crate) fn tracked_impl(
args: proc_macro::TokenStream,
mut item_impl: syn::ItemImpl,
) -> syn::Result<TokenStream> {
let args: ImplArgs = syn::parse(args)?;
let self_type = match &*item_impl.self_ty {
syn::Type::Path(path) => path,
_ => {
return Err(syn::Error::new(
item_impl.self_ty.span(),
"#[salsa::tracked] can only be applied to salsa structs",
))
}
};
let self_type_name = &self_type.path.segments.last().unwrap().ident;
let name_prefix = match &item_impl.trait_ {
Some((_, trait_name, _)) => format!(
"{}_{}",
self_type_name,
trait_name.segments.last().unwrap().ident
),
None => format!("{}", self_type_name),
};
let extra_impls = item_impl
.items
.iter_mut()
.filter_map(|item| {
let item_method = match item {
syn::ImplItem::Method(item_method) => item_method,
_ => return None,
};
let salsa_tracked_attr = item_method.attrs.iter().position(|attr| {
let path = &attr.path.segments;
path.len() == 2
&& path[0].arguments == syn::PathArguments::None
&& path[0].ident == "salsa"
&& path[1].arguments == syn::PathArguments::None
&& path[1].ident == "tracked"
})?;
let salsa_tracked_attr = item_method.attrs.remove(salsa_tracked_attr);
let inner_args = if !salsa_tracked_attr.tokens.is_empty() {
salsa_tracked_attr.parse_args()
} else {
Ok(FnArgs::default())
};
let inner_args = match inner_args {
Ok(inner_args) => inner_args,
Err(err) => return Some(Err(err)),
};
let name = format!("{}_{}", name_prefix, item_method.sig.ident);
Some(tracked_method(
&args,
inner_args,
item_method,
self_type,
&name,
))
})
// Collate all the errors so we can display them all at once
.fold(Ok(Vec::new()), |mut acc, res| {
match (&mut acc, res) {
(Ok(extra_impls), Ok(impls)) => extra_impls.push(impls),
(Ok(_), Err(err)) => acc = Err(err),
(Err(_), Ok(_)) => {}
(Err(errors), Err(err)) => errors.combine(err),
}
acc
})?;
Ok(quote! {
#item_impl
#(#extra_impls)*
})
}
struct TrackedImpl;
impl crate::options::AllowedOptions for TrackedImpl {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const JAR: bool = true;
const DATA: bool = false;
const DB: bool = false;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = false;
const SINGLETON: bool = false;
}
fn tracked_method(
outer_args: &ImplArgs,
mut args: FnArgs,
item_method: &mut syn::ImplItemMethod,
self_type: &syn::TypePath,
name: &str,
) -> syn::Result<TokenStream> {
args.jar_ty = args.jar_ty.or_else(|| outer_args.jar_ty.clone());
if item_method.sig.inputs.len() <= 1 {
return Err(syn::Error::new(
item_method.sig.ident.span(),
"tracked methods must have at least self and a database argument",
));
}
let mut item_fn = syn::ItemFn {
attrs: item_method.attrs.clone(),
vis: item_method.vis.clone(),
sig: item_method.sig.clone(),
block: Box::new(rename_self_in_block(item_method.block.clone())?),
};
item_fn.sig.ident = syn::Ident::new(name, item_fn.sig.ident.span());
// Flip the first and second arguments as the rest of the code expects the
// database to come first and the struct to come second. We also need to
// change the self argument to a normal typed argument called __salsa_self.
let mut original_inputs = item_fn.sig.inputs.into_pairs();
let self_param = match original_inputs.next().unwrap().into_value() {
syn::FnArg::Receiver(r) if r.reference.is_none() => r,
arg => return Err(syn::Error::new(arg.span(), "first argument must be self")),
};
let db_param = original_inputs.next().unwrap().into_value();
let mut inputs = syn::punctuated::Punctuated::new();
inputs.push(db_param);
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: self_param.attrs,
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: Vec::new(),
by_ref: None,
mutability: self_param.mutability,
ident: syn::Ident::new("__salsa_self", self_param.self_token.span),
subpat: None,
})),
colon_token: Default::default(),
ty: Box::new(syn::Type::Path(self_type.clone())),
}));
inputs.push_punct(Default::default());
inputs.extend(original_inputs);
item_fn.sig.inputs = inputs;
let (config_ty, fn_struct) = crate::tracked_fn::fn_struct(&args, &item_fn)?;
// we generate a `'db` lifetime that clippy
// sometimes doesn't like
item_method
.attrs
.push(syn::parse_quote! {#[allow(clippy::needless_lifetimes)]});
item_method.block = getter_fn(
&args,
&mut item_method.sig,
item_method.block.span(),
&config_ty,
)?;
Ok(fn_struct)
}
/// Rename all occurrences of `self` to `__salsa_self` in a block
/// so that it can be used in a free function.
fn rename_self_in_block(mut block: syn::Block) -> syn::Result<syn::Block> {
struct RenameIdent(syn::Result<()>);
impl syn::visit_mut::VisitMut for RenameIdent {
fn visit_ident_mut(&mut self, i: &mut syn::Ident) {
if i == "__salsa_self" {
let err = syn::Error::new(
i.span(),
"Existing variable name clashes with 'self' -> '__salsa_self' renaming",
);
match &mut self.0 {
Ok(()) => self.0 = Err(err),
Err(errors) => errors.combine(err),
}
}
if i == "self" {
*i = syn::Ident::new("__salsa_self", i.span());
}
}
}
let mut rename = RenameIdent(Ok(()));
rename.visit_block_mut(&mut block);
rename.0.map(move |()| block)
}
/// Create the struct representing the function and all of its impls.
///
/// This returns the name of the constructed type and the code defining everything.
fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, TokenStream)> {
let struct_item = configuration_struct(item_fn);
let configuration = fn_configuration(args, item_fn);
let struct_item_ident = &struct_item.ident;
let config_ty: syn::Type = parse_quote!(#struct_item_ident);
let configuration_impl = configuration.to_impl(&config_ty);
let ingredients_for_impl = ingredients_for_impl(args, item_fn, &config_ty);
let item_impl = setter_impl(args, item_fn, &config_ty)?;
Ok((
config_ty,
quote! {
#struct_item
#configuration_impl
#ingredients_for_impl
#item_impl
},
))
}
/// Returns the key type for this tracked function.
/// This is a tuple of all the argument types (apart from the database).
fn key_tuple_ty(item_fn: &syn::ItemFn) -> syn::Type {
let arg_tys = item_fn.sig.inputs.iter().skip(1).map(|arg| match arg {
syn::FnArg::Receiver(_) => unreachable!(),
syn::FnArg::Typed(pat_ty) => pat_ty.ty.clone(),
});
parse_quote!(
(#(#arg_tys,)*)
)
}
fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
let fn_name = item_fn.sig.ident.clone();
let visibility = &item_fn.vis;
let salsa_struct_ty = salsa_struct_ty(item_fn);
let intern_map: syn::Type = match function_type(item_fn) {
FunctionType::Constant => {
parse_quote! { salsa::interned::IdentityInterner<()> }
}
FunctionType::SalsaStruct => {
parse_quote! { salsa::interned::IdentityInterner<#salsa_struct_ty> }
}
FunctionType::RequiresInterning => {
let key_ty = key_tuple_ty(item_fn);
parse_quote! { salsa::interned::InternedIngredient<salsa::Id, #key_ty> }
}
};
parse_quote! {
#[allow(non_camel_case_types)]
#visibility struct #fn_name {
intern_map: #intern_map,
function: salsa::function::FunctionIngredient<Self>,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum FunctionType {
Constant,
SalsaStruct,
RequiresInterning,
}
fn function_type(item_fn: &syn::ItemFn) -> FunctionType {
match item_fn.sig.inputs.len() {
0 => unreachable!(
"functions have been checked to have at least a database argument by this point"
),
1 => FunctionType::Constant,
2 => FunctionType::SalsaStruct,
_ => FunctionType::RequiresInterning,
}
}
/// Every tracked fn takes a salsa struct as its second argument.
/// This fn returns the type of that second argument.
fn salsa_struct_ty(item_fn: &syn::ItemFn) -> syn::Type {
if item_fn.sig.inputs.len() == 1 {
return parse_quote! { salsa::salsa_struct::Singleton };
}
match &item_fn.sig.inputs[1] {
syn::FnArg::Receiver(_) => panic!("receiver not expected"),
syn::FnArg::Typed(pat_ty) => (*pat_ty.ty).clone(),
}
}
fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration {
let jar_ty = args.jar_ty();
let salsa_struct_ty = salsa_struct_ty(item_fn);
let key_ty = match function_type(item_fn) {
FunctionType::Constant => parse_quote!(()),
FunctionType::SalsaStruct => salsa_struct_ty.clone(),
FunctionType::RequiresInterning => parse_quote!(salsa::id::Id),
};
let value_ty = configuration::value_ty(&item_fn.sig);
let fn_ty = item_fn.sig.ident.clone();
let indices = (0..item_fn.sig.inputs.len() - 1).map(Literal::usize_unsuffixed);
let (cycle_strategy, recover_fn) = if let Some(recovery_fn) = &args.recovery_fn {
// Create the `recover_from_cycle` function, which (a) maps from the interned id to the actual
// keys and then (b) invokes the recover function itself.
let cycle_strategy = CycleRecoveryStrategy::Fallback;
let cycle_fullback = parse_quote! {
fn recover_from_cycle(__db: &salsa::function::DynDb<Self>, __cycle: &salsa::Cycle, __id: Self::Key) -> Self::Value {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients =
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.data(__runtime, __id).clone();
#recovery_fn(__db, __cycle, #(__key.#indices),*)
}
};
(cycle_strategy, cycle_fullback)
} else {
// When the `recovery_fn` attribute is not set, set `cycle_strategy` to `Panic`
let cycle_strategy = CycleRecoveryStrategy::Panic;
let cycle_panic = configuration::panic_cycle_recovery_fn();
(cycle_strategy, cycle_panic)
};
let backdate_fn = configuration::should_backdate_value_fn(args.should_backdate());
// The type of the configuration struct; this has the same name as the fn itself.
// Make a copy of the fn with a different name; we will invoke this from `execute`.
// We need to change the name because, otherwise, if the function invoked itself
// recursively it would not go through the query system.
let inner_fn_name = &syn::Ident::new("__fn", item_fn.sig.ident.span());
let mut inner_fn = item_fn.clone();
inner_fn.sig.ident = inner_fn_name.clone();
// Create the `execute` function, which (a) maps from the interned id to the actual
// keys and then (b) invokes the function itself (which we embed within).
let indices = (0..item_fn.sig.inputs.len() - 1).map(Literal::usize_unsuffixed);
let execute_fn = parse_quote! {
fn execute(__db: &salsa::function::DynDb<Self>, __id: Self::Key) -> Self::Value {
#inner_fn
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients =
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.data(__runtime, __id).clone();
#inner_fn_name(__db, #(__key.#indices),*)
}
};
Configuration {
jar_ty,
salsa_struct_ty,
key_ty,
value_ty,
cycle_strategy,
backdate_fn,
execute_fn,
recover_fn,
}
}
fn ingredients_for_impl(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
let debug_name = crate::literal(&item_fn.sig.ident);
let intern_map: syn::Expr = match function_type(item_fn) {
FunctionType::Constant | FunctionType::SalsaStruct => {
parse_quote! {
salsa::interned::IdentityInterner::new()
}
}
FunctionType::RequiresInterning => {
parse_quote! {
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
&ingredients.intern_map
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient_mut(jar);
&mut ingredients.intern_map
}
);
salsa::interned::InternedIngredient::new(index, #debug_name)
}
}
}
};
// set 0 as default to disable LRU
let lru = args.lru.unwrap_or(0);
// get the name of the function as a string literal
let debug_name = crate::literal(&item_fn.sig.ident);
parse_quote! {
impl salsa::storage::IngredientsFor for #config_ty {
type Ingredients = Self;
type Jar = #jar_ty;
fn create_ingredients<DB>(routes: &mut salsa::routes::Routes<DB>) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
Self {
intern_map: #intern_map,
function: {
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
&ingredients.function
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient_mut(jar);
&mut ingredients.function
});
let ingredient = salsa::function::FunctionIngredient::new(index, #debug_name);
ingredient.set_capacity(#lru);
ingredient
}
}
}
}
}
}
fn setter_impl(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemImpl> {
let ref_getter_fn = ref_getter_fn(args, item_fn, config_ty)?;
let accumulated_fn = accumulated_fn(args, item_fn, config_ty)?;
let setter_fn = setter_fn(args, item_fn, config_ty)?;
let specify_fn = specify_fn(args, item_fn, config_ty)?.map(|f| quote! { #f });
let set_lru_fn = set_lru_capacity_fn(args, config_ty)?.map(|f| quote! { #f });
let setter_impl: syn::ItemImpl = parse_quote! {
impl #config_ty {
#[allow(dead_code, clippy::needless_lifetimes)]
#ref_getter_fn
#[allow(dead_code, clippy::needless_lifetimes)]
#setter_fn
#[allow(dead_code, clippy::needless_lifetimes)]
#accumulated_fn
#set_lru_fn
#specify_fn
}
};
Ok(setter_impl)
}
/// Creates the shim function that looks like the original function but calls
/// into the machinery we've just generated rather than executing the code.
fn getter_fn(
args: &FnArgs,
fn_sig: &mut syn::Signature,
block_span: proc_macro2::Span,
config_ty: &syn::Type,
) -> syn::Result<syn::Block> {
let mut is_method = false;
let mut arg_idents: Vec<_> = fn_sig
.inputs
.iter()
.map(|arg| -> syn::Result<syn::Ident> {
match arg {
syn::FnArg::Receiver(receiver) => {
is_method = true;
Ok(syn::Ident::new("self", receiver.self_token.span()))
}
syn::FnArg::Typed(pat_ty) => Ok(match &*pat_ty.pat {
syn::Pat::Ident(ident) => ident.ident.clone(),
_ => return Err(syn::Error::new(arg.span(), "unsupported argument kind")),
}),
}
})
.collect::<Result<_, _>>()?;
// If this is a method then the order of the database and the salsa struct are reversed
// because the self argument must always come first.
if is_method {
arg_idents.swap(0, 1);
}
Ok(if args.return_ref.is_some() {
make_fn_return_ref(fn_sig)?;
parse_quote_spanned! {
block_span => {
#config_ty::get(#(#arg_idents,)*)
}
}
} else {
parse_quote_spanned! {
block_span => {
Clone::clone(#config_ty::get(#(#arg_idents,)*))
}
}
})
}
/// Creates a `get` associated function that returns `&Value`
/// (to be used when `return_ref` is specified).
///
/// (Helper for `getter_fn`)
fn ref_getter_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let jar_ty = args.jar_ty();
let mut ref_getter_fn = item_fn.clone();
ref_getter_fn.sig.ident = syn::Ident::new("get", item_fn.sig.ident.span());
make_fn_return_ref(&mut ref_getter_fn.sig)?;
let (db_var, arg_names) = fn_args(item_fn)?;
ref_getter_fn.block = parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*));
__ingredients.function.fetch(#db_var, __key)
}
};
Ok(ref_getter_fn)
}
/// Creates a `set` associated function that can be used to set (given an `&mut db`)
/// the value for this function for some inputs.
fn setter_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ImplItemMethod> {
// The setter has *always* the same signature as the original:
// but it takes a value arg and has no return type.
let jar_ty = args.jar_ty();
let (db_var, arg_names) = fn_args(item_fn)?;
let mut setter_sig = item_fn.sig.clone();
let value_ty = configuration::value_ty(&item_fn.sig);
setter_sig.ident = syn::Ident::new("set", item_fn.sig.ident.span());
match &mut setter_sig.inputs[0] {
// change from `&dyn ...` to `&mut dyn...`
syn::FnArg::Receiver(_) => unreachable!(), // early fns should have detected
syn::FnArg::Typed(pat_ty) => match &mut *pat_ty.ty {
syn::Type::Reference(ty) => {
ty.mutability = Some(Token![mut](ty.and_token.span()));
}
_ => unreachable!(), // early fns should have detected
},
}
let value_arg = syn::Ident::new("__value", item_fn.sig.output.span());
setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty));
setter_sig.output = ReturnType::Default;
Ok(syn::ImplItemMethod {
attrs: vec![],
vis: item_fn.vis.clone(),
defaultness: None,
sig: setter_sig,
block: parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient_mut(__jar);
let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*));
__ingredients.function.store(__runtime, __key, #value_arg, salsa::Durability::LOW)
}
},
})
}
/// Create a `set_lru_capacity` associated function that can be used to change LRU
/// capacity at runtime.
/// Note that this function is only generated if the tracked function has the lru option set.
///
/// # Examples
///
/// ```rust,ignore
/// #[salsa::tracked(lru=32)]
/// fn my_tracked_fn(db: &dyn crate::Db, ...) { }
///
/// my_tracked_fn::set_lru_capacity(16)
/// ```
fn set_lru_capacity_fn(
args: &FnArgs,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemMethod>> {
if args.lru.is_none() {
return Ok(None);
}
let jar_ty = args.jar_ty();
let lru_fn = parse_quote! {
#[allow(dead_code, clippy::needless_lifetimes)]
fn set_lru_capacity(__db: &salsa::function::DynDb<Self>, __value: usize) {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients =
<_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
__ingredients.function.set_capacity(__value);
}
};
Ok(Some(lru_fn))
}
fn specify_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemMethod>> {
if args.specify.is_none() {
return Ok(None);
}
// `specify` has the same signature as the original,
// but it takes a value arg and has no return type.
let jar_ty = args.jar_ty();
let (db_var, arg_names) = fn_args(item_fn)?;
let mut setter_sig = item_fn.sig.clone();
let value_ty = configuration::value_ty(&item_fn.sig);
setter_sig.ident = syn::Ident::new("specify", item_fn.sig.ident.span());
let value_arg = syn::Ident::new("__value", item_fn.sig.output.span());
setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty));
setter_sig.output = ReturnType::Default;
Ok(Some(syn::ImplItemMethod {
attrs: vec![],
vis: item_fn.vis.clone(),
defaultness: None,
sig: setter_sig,
block: parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
__ingredients.function.specify_and_record(#db_var, #(#arg_names,)* #value_arg)
}
},
}))
}
/// Given a function def tagged with `#[return_ref]`, modifies `fn_sig` so that
/// it returns an `&Value` instead of `Value`. May introduce a name for the
/// database lifetime if required.
fn make_fn_return_ref(fn_sig: &mut syn::Signature) -> syn::Result<()> {
// An input should be a `&dyn Db`.
// We need to ensure it has a named lifetime parameter.
let (db_lifetime, _) = db_lifetime_and_ty(fn_sig)?;
let (right_arrow, elem) = match fn_sig.output.clone() {
ReturnType::Default => (syn::Token![->](fn_sig.paren_token.span), parse_quote!(())),
ReturnType::Type(rarrow, ty) => (rarrow, ty),
};
let ref_output = syn::TypeReference {
and_token: syn::Token![&](right_arrow.span()),
lifetime: Some(db_lifetime),
mutability: None,
elem,
};
fn_sig.output = syn::ReturnType::Type(right_arrow, Box::new(ref_output.into()));
Ok(())
}
/// Given a function signature, identifies the name given to the `&dyn Db` reference
/// and returns it, along with the type of the database.
/// If the database lifetime did not have a name, then modifies the item function
/// so that it is called `'__db` and returns that.
fn db_lifetime_and_ty(func: &mut syn::Signature) -> syn::Result<(syn::Lifetime, &syn::Type)> {
// If this is a method, then the database should be the second argument.
let db_loc = if matches!(func.inputs[0], syn::FnArg::Receiver(_)) {
1
} else {
0
};
match &mut func.inputs[db_loc] {
syn::FnArg::Receiver(r) => Err(syn::Error::new(r.span(), "two self arguments")),
syn::FnArg::Typed(pat_ty) => match &mut *pat_ty.ty {
syn::Type::Reference(ty) => match &ty.lifetime {
Some(lt) => Ok((lt.clone(), &pat_ty.ty)),
None => {
let and_token_span = ty.and_token.span();
let ident = syn::Ident::new("__db", and_token_span);
func.generics.params.insert(
0,
syn::LifetimeDef {
attrs: vec![],
lifetime: syn::Lifetime {
apostrophe: and_token_span,
ident: ident.clone(),
},
colon_token: None,
bounds: Default::default(),
}
.into(),
);
let db_lifetime = syn::Lifetime {
apostrophe: and_token_span,
ident,
};
ty.lifetime = Some(db_lifetime.clone());
Ok((db_lifetime, &pat_ty.ty))
}
},
_ => Err(syn::Error::new(
pat_ty.span(),
"expected database to be a `&` type",
)),
},
}
}
/// Generates the `accumulated` function, which invokes `accumulated`
/// on the function ingredient to extract the values pushed (transitively)
/// into an accumulator.
fn accumulated_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let jar_ty = args.jar_ty();
let mut accumulated_fn = item_fn.clone();
accumulated_fn.sig.ident = syn::Ident::new("accumulated", item_fn.sig.ident.span());
accumulated_fn.sig.generics.params.push(parse_quote! {
__A: salsa::accumulator::Accumulator
});
accumulated_fn.sig.output = parse_quote! {
-> Vec<<__A as salsa::accumulator::Accumulator>::Data>
};
let (db_lifetime, _) = db_lifetime_and_ty(&mut accumulated_fn.sig)?;
let predicate: syn::WherePredicate = parse_quote!(<#jar_ty as salsa::jar::Jar<#db_lifetime>>::DynDb: salsa::storage::HasJar<<__A as salsa::accumulator::Accumulator>::Jar>);
if let Some(where_clause) = &mut accumulated_fn.sig.generics.where_clause {
where_clause.predicates.push(predicate);
} else {
accumulated_fn.sig.generics.where_clause = parse_quote!(where #predicate);
}
let (db_var, arg_names) = fn_args(item_fn)?;
accumulated_fn.block = parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*));
__ingredients.function.accumulated::<__A>(#db_var, __key)
}
};
Ok(accumulated_fn)
}
/// Examines the function arguments and returns a tuple of:
///
/// * the name of the database argument
/// * the name(s) of the key arguments
fn fn_args(item_fn: &syn::ItemFn) -> syn::Result<(proc_macro2::Ident, Vec<proc_macro2::Ident>)> {
// Check that we have no receiver and that all arguments have names
if item_fn.sig.inputs.is_empty() {
return Err(syn::Error::new(
item_fn.sig.span(),
"method needs a database argument",
));
}
let mut input_names = vec![];
for input in &item_fn.sig.inputs {
match input {
syn::FnArg::Receiver(r) => {
return Err(syn::Error::new(r.span(), "no self argument expected"));
}
syn::FnArg::Typed(pat_ty) => match &*pat_ty.pat {
syn::Pat::Ident(ident) => {
input_names.push(ident.ident.clone());
}
_ => {
return Err(syn::Error::new(
pat_ty.pat.span(),
"all arguments must be given names",
));
}
},
}
}
// Database is the first argument
let db_var = input_names[0].clone();
let arg_names = input_names[1..].to_owned();
Ok((db_var, arg_names))
}

View file

@ -0,0 +1,324 @@
use proc_macro2::{Literal, TokenStream};
use crate::salsa_struct::{SalsaField, SalsaStruct, SalsaStructKind};
/// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
/// * the "id struct" `struct Foo(salsa::Id)`
/// * the tracked ingredient, which maps the id fields to the `Id`
/// * for each value field, a function ingredient
pub(crate) fn tracked(
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
) -> syn::Result<TokenStream> {
SalsaStruct::with_struct(SalsaStructKind::Tracked, args, struct_item)
.and_then(|el| TrackedStruct(el).generate_tracked())
}
struct TrackedStruct(SalsaStruct<Self>);
impl std::ops::Deref for TrackedStruct {
type Target = SalsaStruct<Self>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl crate::options::AllowedOptions for TrackedStruct {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = true;
const DB: bool = false;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = true;
}
impl TrackedStruct {
fn generate_tracked(&self) -> syn::Result<TokenStream> {
self.validate_tracked()?;
let (config_structs, config_impls) =
self.field_config_structs_and_impls(self.value_fields());
let id_struct = self.id_struct();
let inherent_impl = self.tracked_inherent_impl();
let ingredients_for_impl = self.tracked_struct_ingredients(&config_structs);
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl();
let as_id_impl = self.as_id_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#(#config_structs)*
#id_struct
#inherent_impl
#ingredients_for_impl
#salsa_struct_in_db_impl
#tracked_struct_in_db_impl
#as_id_impl
#as_debug_with_db_impl
#(#config_impls)*
})
}
fn validate_tracked(&self) -> syn::Result<()> {
Ok(())
}
/// Generate an inherent impl with methods on the tracked type.
fn tracked_inherent_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let db_dyn_ty = self.db_dyn_ty();
let struct_index = self.tracked_struct_index();
let id_field_indices: Vec<_> = self.id_field_indices();
let id_field_names: Vec<_> = self.id_fields().map(SalsaField::name).collect();
let id_field_get_names: Vec<_> = self.id_fields().map(SalsaField::get_name).collect();
let id_field_tys: Vec<_> = self.id_fields().map(SalsaField::ty).collect();
let id_field_vises: Vec<_> = self.id_fields().map(SalsaField::vis).collect();
let id_field_clones: Vec<_> = self.id_fields().map(SalsaField::is_clone_field).collect();
let id_field_getters: Vec<syn::ImplItemMethod> = id_field_indices.iter().zip(&id_field_get_names).zip(&id_field_tys).zip(&id_field_vises).zip(&id_field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
&__ingredients.#struct_index.tracked_struct_data(__runtime, self).#field_index
}
}
} else {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#struct_index.tracked_struct_data(__runtime, self).#field_index.clone()
}
}
}
)
.collect();
let value_field_indices = self.value_field_indices();
let value_field_names: Vec<_> = self.value_fields().map(SalsaField::name).collect();
let value_field_vises: Vec<_> = self.value_fields().map(SalsaField::vis).collect();
let value_field_tys: Vec<_> = self.value_fields().map(SalsaField::ty).collect();
let value_field_get_names: Vec<_> = self.value_fields().map(SalsaField::get_name).collect();
let value_field_clones: Vec<_> = self
.value_fields()
.map(SalsaField::is_clone_field)
.collect();
let value_field_getters: Vec<syn::ImplItemMethod> = value_field_indices.iter().zip(&value_field_get_names).zip(&value_field_tys).zip(&value_field_vises).zip(&value_field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__db, self)
}
}
} else {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__db, self).clone()
}
}
}
)
.collect();
let all_field_names = self.all_field_names();
let all_field_tys = self.all_field_tys();
let constructor_name = self.constructor_name();
parse_quote! {
impl #ident {
pub fn #constructor_name(__db: &#db_dyn_ty, #(#all_field_names: #all_field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
let __id = __ingredients.#struct_index.new_struct(__runtime, (#(#id_field_names,)*));
#(
__ingredients.#value_field_indices.specify_and_record(__db, __id, #value_field_names);
)*
__id
}
#(#id_field_getters)*
#(#value_field_getters)*
}
}
}
/// Generate the `IngredientsFor` impl for this tracked struct.
///
/// The tracked struct's ingredients include both the main tracked struct ingredient along with a
/// function ingredient for each of the value fields.
fn tracked_struct_ingredients(&self, config_structs: &[syn::ItemStruct]) -> syn::ItemImpl {
use crate::literal;
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let id_field_tys: Vec<&syn::Type> = self.id_fields().map(SalsaField::ty).collect();
let value_field_indices: Vec<Literal> = self.value_field_indices();
let tracked_struct_index: Literal = self.tracked_struct_index();
let config_struct_names = config_structs.iter().map(|s| &s.ident);
let debug_name_struct = literal(self.id_ident());
let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect();
parse_quote! {
impl salsa::storage::IngredientsFor for #ident {
type Jar = #jar_ty;
type Ingredients = (
#(
salsa::function::FunctionIngredient<#config_struct_names>,
)*
salsa::tracked_struct::TrackedStructIngredient<#ident, (#(#id_field_tys,)*)>,
);
fn create_ingredients<DB>(
routes: &mut salsa::routes::Routes<DB>,
) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
(
#(
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#value_field_indices
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#value_field_indices
},
);
salsa::function::FunctionIngredient::new(index, #debug_name_fields)
},
)*
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#tracked_struct_index
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#tracked_struct_index
},
);
salsa::tracked_struct::TrackedStructIngredient::new(index, #debug_name_struct)
},
)
}
}
}
}
/// Implementation of `SalsaStructInDb`.
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let tracked_struct_index: Literal = self.tracked_struct_index();
parse_quote! {
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
where
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
{
fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
ingredients.#tracked_struct_index.register_dependent_fn(index)
}
}
}
}
/// Implementation of `TrackedStructInDb`.
fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let tracked_struct_index = self.tracked_struct_index();
parse_quote! {
impl<DB> salsa::tracked_struct::TrackedStructInDb<DB> for #ident
where
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
{
fn database_key_index(self, db: &DB) -> salsa::DatabaseKeyIndex {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
ingredients.#tracked_struct_index.database_key_index(self)
}
}
}
}
/// List of id fields (fields that are part of the tracked struct's identity across revisions).
///
/// If this is an enum, empty iterator.
fn id_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.all_fields().filter(|ef| ef.is_id_field())
}
/// List of value fields (fields that are not part of the tracked struct's identity across revisions).
///
/// If this is an enum, empty iterator.
fn value_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.all_fields().filter(|ef| !ef.is_id_field())
}
/// For this struct, we create a tuple that contains the function ingredients
/// for each "other" field and the tracked-struct ingredient. This is the index of
/// the entity ingredient within that tuple.
fn tracked_struct_index(&self) -> Literal {
Literal::usize_unsuffixed(self.value_fields().count())
}
/// For this struct, we create a tuple that contains the function ingredients
/// for each "other" field and the tracked-struct ingredient. These are the indices
/// of the function ingredients within that tuple.
fn value_field_indices(&self) -> Vec<Literal> {
(0..self.value_fields().count())
.map(Literal::usize_unsuffixed)
.collect()
}
/// Indices of each of the id fields
fn id_field_indices(&self) -> Vec<Literal> {
(0..self.id_fields().count())
.map(Literal::usize_unsuffixed)
.collect()
}
}
impl SalsaField {
/// true if this is an id field
fn is_id_field(&self) -> bool {
self.has_id_attr
}
}