mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-15 01:39:25 +00:00
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:
parent
1f1950c145
commit
4f234cfbb9
12 changed files with 248 additions and 461 deletions
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
|
||||
mod tracked_fn_on_input;
|
||||
mod tracked_fn_on_tracked;
|
||||
mod tracked_fn_on_tracked_specify;
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -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> {}
|
||||
|
||||
|
|
64
salsa-2022-tests/tracked_fn_on_tracked_specify.rs
Normal file
64
salsa-2022-tests/tracked_fn_on_tracked_specify.rs
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue