remove component and replace with specify option

You can now do `#[salsa::tracked(specify)]` and you will
get a method `some_fn::specify(...)` that can be used to
specify the value.
This commit is contained in:
Niko Matsakis 2022-08-05 02:46:16 -04:00
parent 1f1950c145
commit 4f234cfbb9
12 changed files with 248 additions and 461 deletions

View file

@ -21,6 +21,8 @@ struct Accumulator;
impl crate::options::AllowedOptions for Accumulator {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const JAR: bool = true;

View file

@ -1,411 +0,0 @@
use syn::spanned::Spanned;
use syn::{ItemFn, ReturnType};
use crate::configuration::{self, Configuration, CycleRecoveryStrategy};
use crate::options::Options;
// #[salsa::component(in Jar0)]
// fn my_func(db: &dyn Jar0Db, input1: u32, input2: u32) -> String {
// format!("Hello, world")
// }
pub(crate) fn component(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as Args);
let item_fn = syn::parse_macro_input!(input as ItemFn);
match component_helper(args, item_fn) {
Ok(v) => v,
Err(e) => return e.into_compile_error().into(),
}
}
fn component_helper(args: Args, item_fn: ItemFn) -> syn::Result<proc_macro::TokenStream> {
let struct_item = configuration_struct(&item_fn);
let configuration = fn_configuration(&args, &item_fn)?;
let struct_item_ident = &struct_item.ident;
let struct_ty: syn::Type = parse_quote!(#struct_item_ident);
let configuration_impl = configuration.to_impl(&struct_ty);
let ingredients_for_impl = ingredients_for_impl(&args, &struct_ty);
let (getter, item_impl) = wrapper_fns(&args, &item_fn, &struct_ty)?;
Ok(proc_macro::TokenStream::from(quote! {
#struct_item
#configuration_impl
#ingredients_for_impl
#getter
#item_impl
}))
}
struct Component;
type Args = Options<Component>;
impl crate::options::AllowedOptions for Component {
const RETURN_REF: bool = true;
const NO_EQ: bool = true;
const JAR: bool = true;
const DATA: bool = false;
const DB: bool = false;
}
fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
let fn_name = item_fn.sig.ident.clone();
let vis = &item_fn.vis;
parse_quote! {
#[allow(non_camel_case_types)]
#vis struct #fn_name {
function: salsa::function::FunctionIngredient<Self>,
}
}
}
fn fn_configuration(args: &Args, item_fn: &syn::ItemFn) -> syn::Result<Configuration> {
let jar_ty = args.jar_ty();
let key_ty = arg_ty(item_fn)?.clone();
let value_ty = configuration::value_ty(&item_fn.sig);
// FIXME: these are hardcoded for now
let cycle_strategy = CycleRecoveryStrategy::Panic;
let backdate_fn = configuration::should_backdate_value_fn(args.should_backdate());
let recover_fn = configuration::panic_cycle_recovery_fn();
// The type of the configuration struct; this has the same name as the fn itself.
let fn_ty = item_fn.sig.ident.clone();
// 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 invokes the function itself (which we embed within).
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);
#inner_fn_name(__db, __id)
}
};
Ok(Configuration {
jar_ty,
key_ty,
value_ty,
cycle_strategy,
backdate_fn,
execute_fn,
recover_fn,
})
}
fn ingredients_for_impl(args: &Args, struct_ty: &syn::Type) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
parse_quote! {
impl salsa::storage::IngredientsFor for #struct_ty {
type Ingredients = Self;
type Jar = #jar_ty;
fn create_ingredients<DB>(ingredients: &mut salsa::routes::Ingredients<DB>) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
Self {
function: {
let index = ingredients.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
});
salsa::function::FunctionIngredient::new(index)
},
}
}
}
}
}
fn wrapper_fns(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
) -> syn::Result<(syn::ItemFn, syn::ItemImpl)> {
// The "getter" has same signature as the original:
let getter_fn = getter_fn(args, item_fn, struct_ty)?;
let ref_getter_fn = ref_getter_fn(args, item_fn, struct_ty)?;
let accumulated_fn = accumulated_fn(args, item_fn, struct_ty)?;
let setter_fn = setter_fn(args, item_fn, struct_ty)?;
let item_impl: syn::ItemImpl = parse_quote! {
impl #struct_ty {
#ref_getter_fn
#setter_fn
#accumulated_fn
}
};
Ok((getter_fn, item_impl))
}
fn getter_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let mut getter_fn = item_fn.clone();
let arg_idents: Vec<_> = item_fn
.sig
.inputs
.iter()
.map(|arg| -> syn::Result<syn::Ident> {
match arg {
syn::FnArg::Receiver(_) => Err(syn::Error::new(arg.span(), "unexpected receiver")),
syn::FnArg::Typed(pat_ty) => Ok(match &*pat_ty.pat {
syn::Pat::Ident(ident) => ident.ident.clone(),
_ => return Err(syn::Error::new(arg.span(), "unexpected receiver")),
}),
}
})
.collect::<Result<_, _>>()?;
if args.return_ref.is_some() {
getter_fn = make_fn_return_ref(getter_fn)?;
getter_fn.block = Box::new(parse_quote_spanned! {
item_fn.block.span() => {
#struct_ty::get(#(#arg_idents,)*)
}
});
} else {
getter_fn.block = Box::new(parse_quote_spanned! {
item_fn.block.span() => {
Clone::clone(#struct_ty::get(#(#arg_idents,)*))
}
});
}
Ok(getter_fn)
}
fn ref_getter_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_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());
ref_getter_fn = make_fn_return_ref(ref_getter_fn)?;
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<#struct_ty>>::ingredient(__jar);
__ingredients.function.fetch(#db_var, #(#arg_names),*)
}
};
Ok(ref_getter_fn)
}
fn setter_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_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());
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(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#struct_ty>>::ingredient(__jar);
__ingredients.function.set(#db_var, #(#arg_names),*, #value_arg)
}
},
})
}
fn make_fn_return_ref(mut ref_getter_fn: syn::ItemFn) -> syn::Result<syn::ItemFn> {
// The 0th input should be a `&dyn Foo`. We need to ensure
// it has a named lifetime parameter.
let (db_lifetime, _) = db_lifetime_and_ty(&mut ref_getter_fn)?;
let (right_arrow, elem) = match ref_getter_fn.sig.output {
ReturnType::Default => (
syn::Token![->](ref_getter_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,
};
ref_getter_fn.sig.output = syn::ReturnType::Type(right_arrow, Box::new(ref_output.into()));
Ok(ref_getter_fn)
}
fn db_lifetime_and_ty(func: &mut syn::ItemFn) -> syn::Result<(syn::Lifetime, &syn::Type)> {
match &mut func.sig.inputs[0] {
syn::FnArg::Receiver(r) => {
return Err(syn::Error::new(r.span(), "expected database, not self"))
}
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.sig.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))
}
},
_ => {
return Err(syn::Error::new(
pat_ty.span(),
"expected database to be a `&` type",
))
}
},
}
}
fn accumulated_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_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)?;
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<#struct_ty>>::ingredient(__jar);
__ingredients.function.accumulated::<__A>(#db_var, #(#arg_names),*)
}
};
Ok(accumulated_fn)
}
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 argments have names
if item_fn.sig.inputs.len() == 0 {
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))
}
fn arg_ty(item_fn: &syn::ItemFn) -> syn::Result<&syn::Type> {
// Check that we have no receiver and that all argments have names
if item_fn.sig.inputs.len() != 2 {
return Err(syn::Error::new(
item_fn.sig.span(),
"component method needs a database argument and an entity",
));
}
match &item_fn.sig.inputs[1] {
syn::FnArg::Typed(pat_ty) => Ok(&pat_ty.ty),
_ => {
return Err(syn::Error::new(
item_fn.sig.inputs[1].span(),
"expected a fn parameter with a type",
));
}
}
}

View file

@ -30,6 +30,8 @@ struct Jar;
impl crate::options::AllowedOptions for Jar {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const JAR: bool = false;

View file

@ -26,7 +26,6 @@ macro_rules! parse_quote_spanned {
}
mod accumulator;
mod component;
mod configuration;
mod db;
mod input;
@ -63,11 +62,6 @@ pub fn input(args: TokenStream, input: TokenStream) -> TokenStream {
input::input(args, input)
}
#[proc_macro_attribute]
pub fn component(args: TokenStream, input: TokenStream) -> TokenStream {
component::component(args, input)
}
#[proc_macro_attribute]
pub fn tracked(args: TokenStream, input: TokenStream) -> TokenStream {
tracked::tracked(args, input)

View file

@ -19,6 +19,12 @@ pub(crate) struct Options<A: AllowedOptions> {
/// If this is `Some`, the value is the `no_eq` identifier.
pub no_eq: 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 `<path>`.
@ -43,6 +49,7 @@ 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(),
@ -55,6 +62,7 @@ impl<A: AllowedOptions> Default for Options<A> {
/// 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 JAR: bool;
const DATA: bool;
@ -111,6 +119,20 @@ impl<A: AllowedOptions> syn::parse::Parse for Options<A> {
"`no_eq` 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)?;

View file

@ -38,6 +38,8 @@ pub(crate) struct SalsaStruct {
impl crate::options::AllowedOptions for SalsaStruct {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const JAR: bool = true;

View file

@ -1,4 +1,4 @@
use proc_macro2::Literal;
use proc_macro2::{Literal, TokenStream};
use syn::spanned::Spanned;
use syn::{ReturnType, Token};
@ -24,13 +24,22 @@ fn tracked_fn(args: Args, item_fn: syn::ItemFn) -> syn::Result<TokenStream> {
));
}
if let Some(s) = &args.specify {
if requires_interning(&item_fn) {
return Err(syn::Error::new(
s.span(),
"tracked functon takes too many argments to have its value set with `specify`",
));
}
}
let struct_item = configuration_struct(&item_fn);
let configuration = fn_configuration(&args, &item_fn);
let struct_item_ident = &struct_item.ident;
let struct_ty: syn::Type = parse_quote!(#struct_item_ident);
let configuration_impl = configuration.to_impl(&struct_ty);
let ingredients_for_impl = ingredients_for_impl(&args, &item_fn, &struct_ty);
let (getter, setter) = wrapper_fns(&args, &item_fn, &struct_ty)?;
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 (getter, item_impl) = wrapper_fns(&args, &item_fn, &config_ty)?;
Ok(quote! {
#struct_item
@ -41,7 +50,7 @@ fn tracked_fn(args: Args, item_fn: syn::ItemFn) -> syn::Result<TokenStream> {
// sometimes doesn't like
#[allow(clippy::needless_lifetimes)]
#getter
#setter
#item_impl
})
}
@ -52,6 +61,8 @@ struct TrackedFn;
impl crate::options::AllowedOptions for TrackedFn {
const RETURN_REF: bool = true;
const SPECIFY: bool = true;
const NO_EQ: bool = true;
const JAR: bool = true;
@ -61,9 +72,9 @@ impl crate::options::AllowedOptions for TrackedFn {
const DB: bool = false;
}
/// Returns the key type for this tracked function. The result is
/// a tuple of the fn argments, ignoring the database.
fn key_ty(item_fn: &syn::ItemFn) -> syn::Type {
/// 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(),
@ -76,20 +87,49 @@ fn key_ty(item_fn: &syn::ItemFn) -> syn::Type {
fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
let fn_name = item_fn.sig.ident.clone();
let key_tuple_ty = key_ty(item_fn);
let visibility = &item_fn.vis;
let salsa_struct_ty = salsa_struct_ty(item_fn);
let intern_map: syn::Type = if requires_interning(item_fn) {
let key_ty = key_tuple_ty(item_fn);
parse_quote! { salsa::interned::InternedIngredient<salsa::Id, #key_ty> }
} else {
parse_quote! { salsa::interned::IdentityInterner<#salsa_struct_ty> }
};
parse_quote! {
#[allow(non_camel_case_types)]
#visibility struct #fn_name {
intern_map: salsa::interned::InternedIngredient<salsa::Id, #key_tuple_ty>,
#visibility struct #fn_name
where
#salsa_struct_ty: salsa::AsId, // require that the salsa struct is, well, a salsa struct!
{
intern_map: #intern_map,
function: salsa::function::FunctionIngredient<Self>,
}
}
}
/// True if this fn takes more arguments.
fn requires_interning(item_fn: &syn::ItemFn) -> bool {
item_fn.sig.inputs.len() > 2
}
/// 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 {
match &item_fn.sig.inputs[1] {
syn::FnArg::Receiver(_) => panic!("receiver not expected"),
syn::FnArg::Typed(pat_ty) => &pat_ty.ty,
}
}
fn fn_configuration(args: &Args, item_fn: &syn::ItemFn) -> Configuration {
let jar_ty = args.jar_ty();
let key_ty = parse_quote!(salsa::id::Id);
let key_ty = if requires_interning(item_fn) {
parse_quote!(salsa::id::Id)
} else {
salsa_struct_ty(item_fn).clone()
};
let value_ty = configuration::value_ty(&item_fn.sig);
// FIXME: these are hardcoded for now
@ -134,10 +174,33 @@ fn fn_configuration(args: &Args, item_fn: &syn::ItemFn) -> Configuration {
}
}
fn ingredients_for_impl(args: &Args, struct_ty: &syn::Type) -> syn::ItemImpl {
fn ingredients_for_impl(
args: &Args,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
let intern_map: syn::Expr = if requires_interning(item_fn) {
parse_quote! {
{
let index = ingredients.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
});
salsa::interned::InternedIngredient::new(index)
}
}
} else {
parse_quote! {
salsa::interned::IdentityInterner::new()
}
};
parse_quote! {
impl salsa::storage::IngredientsFor for #struct_ty {
impl salsa::storage::IngredientsFor for #config_ty {
type Ingredients = Self;
type Jar = #jar_ty;
@ -146,15 +209,7 @@ fn ingredients_for_impl(args: &Args, struct_ty: &syn::Type) -> syn::ItemImpl {
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
Self {
intern_map: {
let index = ingredients.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
});
salsa::interned::InternedIngredient::new(index)
},
intern_map: #intern_map,
function: {
let index = ingredients.push(|jars| {
@ -174,17 +229,18 @@ fn ingredients_for_impl(args: &Args, struct_ty: &syn::Type) -> syn::ItemImpl {
fn wrapper_fns(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
config_ty: &syn::Type,
) -> syn::Result<(syn::ItemFn, syn::ItemImpl)> {
// The "getter" has same signature as the original:
let getter_fn = getter_fn(args, item_fn, struct_ty)?;
let getter_fn = getter_fn(args, item_fn, config_ty)?;
let ref_getter_fn = ref_getter_fn(args, item_fn, struct_ty)?;
let accumulated_fn = accumulated_fn(args, item_fn, struct_ty)?;
let setter_fn = setter_fn(args, item_fn, struct_ty)?;
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 setter_impl: syn::ItemImpl = parse_quote! {
impl #struct_ty {
impl #config_ty {
#[allow(dead_code, clippy::needless_lifetimes)]
#ref_getter_fn
@ -193,6 +249,8 @@ fn wrapper_fns(
#[allow(dead_code, clippy::needless_lifetimes)]
#accumulated_fn
#specify_fn
}
};
@ -203,7 +261,7 @@ fn wrapper_fns(
fn getter_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let mut getter_fn = item_fn.clone();
let arg_idents: Vec<_> = item_fn
@ -224,13 +282,13 @@ fn getter_fn(
getter_fn = make_fn_return_ref(getter_fn)?;
getter_fn.block = Box::new(parse_quote_spanned! {
item_fn.block.span() => {
#struct_ty::get(#(#arg_idents,)*)
#config_ty::get(#(#arg_idents,)*)
}
});
} else {
getter_fn.block = Box::new(parse_quote_spanned! {
item_fn.block.span() => {
Clone::clone(#struct_ty::get(#(#arg_idents,)*))
Clone::clone(#config_ty::get(#(#arg_idents,)*))
}
});
}
@ -244,7 +302,7 @@ fn getter_fn(
fn ref_getter_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let jar_ty = args.jar_ty();
let mut ref_getter_fn = item_fn.clone();
@ -255,8 +313,8 @@ fn ref_getter_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<#struct_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names,)*));
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)
}
};
@ -269,7 +327,7 @@ fn ref_getter_fn(
fn setter_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
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.
@ -299,14 +357,49 @@ fn setter_fn(
block: parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#struct_ty>>::ingredient_mut(__jar);
let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names,)*));
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)
}
},
})
}
fn specify_fn(
args: &Args,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemMethod>> {
let specify = match &args.specify {
Some(s) => s,
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.set(#db_var, #(#arg_names,)* #value_arg)
}
},
}))
}
/// Given a function def tagged with `#[return_ref]`, modifies `ref_getter_fn`
/// so that it returns an `&Value` instead of `Value`. May introduce a name for the
/// database lifetime if required.
@ -386,7 +479,7 @@ fn db_lifetime_and_ty(func: &mut syn::ItemFn) -> syn::Result<(syn::Lifetime, &sy
fn accumulated_fn(
args: &Args,
item_fn: &syn::ItemFn,
struct_ty: &syn::Type,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let jar_ty = args.jar_ty();
@ -412,8 +505,8 @@ fn accumulated_fn(
accumulated_fn.block = parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#struct_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names,)*));
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)
}
};

View file

@ -1,6 +1,7 @@
use crossbeam::atomic::AtomicCell;
use crossbeam::queue::SegQueue;
use std::hash::Hash;
use std::marker::PhantomData;
use crate::durability::Durability;
use crate::id::AsId;
@ -198,3 +199,21 @@ where
None
}
}
pub struct IdentityInterner<Id: AsId> {
data: PhantomData<Id>,
}
impl<Id: AsId> IdentityInterner<Id> {
pub fn new() -> Self {
IdentityInterner { data: PhantomData }
}
pub fn intern(&self, _runtime: &Runtime, id: Id) -> Id {
id
}
pub fn data(&self, _runtime: &Runtime, id: Id) -> (Id,) {
(id,)
}
}

View file

@ -42,7 +42,6 @@ pub use self::storage::Storage;
pub use self::tracked_struct::TrackedStructData;
pub use self::tracked_struct::TrackedStructId;
pub use salsa_entity_macros::accumulator;
pub use salsa_entity_macros::component;
pub use salsa_entity_macros::db;
pub use salsa_entity_macros::input;
pub use salsa_entity_macros::interned;

View file

@ -3,5 +3,6 @@
mod tracked_fn_on_input;
mod tracked_fn_on_tracked;
mod tracked_fn_on_tracked_specify;
fn main() {}

View file

@ -2,7 +2,7 @@
//! compiles and executes successfully.
#[salsa::jar(db = Db)]
struct Jar(MyInput, tracked_fn);
struct Jar(MyInput, MyTracked, tracked_fn);
trait Db: salsa::DbWithJar<Jar> {}

View file

@ -0,0 +1,64 @@
//! Test that a `tracked` fn on a `salsa::input`
//! compiles and executes successfully.
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, tracked_fn, tracked_fn_extra);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input(jar = Jar)]
struct MyInput {
field: u32,
}
#[salsa::tracked(jar = Jar)]
struct MyTracked {
field: u32,
}
#[salsa::tracked(jar = Jar)]
fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked {
let t = MyTracked::new(db, input.field(db) * 2);
if input.field(db) != 0 {
tracked_fn_extra::specify(db, t, 2222);
}
t
}
#[salsa::tracked(jar = Jar, specify)]
fn tracked_fn_extra(_db: &dyn Db, _input: MyTracked) -> u32 {
0
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {
fn salsa_runtime(&self) -> &salsa::Runtime {
self.storage.runtime()
}
}
impl Db for Database {}
#[test]
fn execute_when_specified() {
let mut db = Database::default();
let input = MyInput::new(&mut db, 22);
let tracked = tracked_fn(&db, input);
assert_eq!(tracked.field(&db), 44);
assert_eq!(tracked_fn_extra(&db, tracked), 2222);
}
#[test]
fn execute_when_not_specified() {
let mut db = Database::default();
let input = MyInput::new(&mut db, 0);
let tracked = tracked_fn(&db, input);
assert_eq!(tracked.field(&db), 0);
assert_eq!(tracked_fn_extra(&db, tracked), 0);
}