Merge branch 'salsa-rs:master' into singleton

This commit is contained in:
Onigbinde Oluwamuyiwa Elijah 2022-09-11 09:12:17 +01:00 committed by GitHub
commit fac05b3c9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1376 additions and 137 deletions

View file

@ -41,6 +41,7 @@ To learn more about Salsa, try one of the following:
- read the [heavily commented `hello_world` example](https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs);
- check out the [Salsa book](https://salsa-rs.github.io/salsa);
- [中文版](https://zjp-cn.github.io/salsa)
- watch one of our [videos](https://salsa-rs.github.io/salsa/videos.html).
## Getting in touch

View file

@ -10,5 +10,5 @@ proc-macro = true
heck = "0.4"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }
syn = { version = "1.0", features = ["full", "extra-traits", "visit-mut"] }
eyre = "0.6.5"

View file

@ -51,8 +51,6 @@ impl InputStruct {
fn generate_input(&self) -> syn::Result<TokenStream> {
self.validate_input()?;
let (config_structs, config_impls) = self.field_config_structs_and_impls(self.all_fields());
let id_struct = self.id_struct();
let inherent_impl = self.input_inherent_impl();
let ingredients_for_impl = self.input_ingredients();
@ -61,13 +59,11 @@ impl InputStruct {
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#(#config_structs)*
#id_struct
#inherent_impl
#ingredients_for_impl
#as_id_impl
#as_debug_with_db_impl
#(#config_impls)*
#salsa_struct_in_db_impl
})
}

View file

@ -5,14 +5,17 @@ pub(crate) fn tracked(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = syn::parse_macro_input!(input as Item);
match item {
let res = match item {
syn::Item::Struct(item) => crate::tracked_struct::tracked(args, item),
syn::Item::Fn(item) => crate::tracked_fn::tracked(args, item),
_ => syn::Error::new(
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 be applied to structs and functions only".to_string(),
)
.into_compile_error()
.into(),
"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

@ -1,31 +1,32 @@
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(
pub(crate) fn tracked_fn(
args: proc_macro::TokenStream,
item_fn: syn::ItemFn,
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as Args);
match tracked_fn(args, item_fn) {
Ok(p) => p.into(),
Err(e) => e.into_compile_error().into(),
}
}
fn tracked_fn(args: Args, item_fn: syn::ItemFn) -> syn::Result<TokenStream> {
if item_fn.sig.inputs.len() <= 1 {
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 and salsa struct argument",
"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 requires_interning(&item_fn) {
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`",
@ -40,28 +41,20 @@ fn tracked_fn(args: Args, item_fn: syn::ItemFn) -> syn::Result<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 (getter, item_impl) = wrapper_fns(&args, &item_fn, &config_ty)?;
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! {
#struct_item
#configuration_impl
#ingredients_for_impl
#fn_struct
// we generate a `'db` lifetime that clippy
// sometimes doesn't like
#[allow(clippy::needless_lifetimes)]
#getter
#item_impl
#item_fn
})
}
type Args = Options<TrackedFn>;
type FnArgs = Options<TrackedFn>;
struct TrackedFn;
@ -87,6 +80,219 @@ impl crate::options::AllowedOptions for TrackedFn {
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;
}
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)?;
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 {
@ -105,11 +311,17 @@ fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
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> }
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! {
@ -121,27 +333,43 @@ fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
}
}
/// True if this fn takes more arguments.
fn requires_interning(item_fn: &syn::ItemFn) -> bool {
item_fn.sig.inputs.len() > 2
#[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 {
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,
syn::FnArg::Typed(pat_ty) => (*pat_ty.ty).clone(),
}
}
fn fn_configuration(args: &Args, item_fn: &syn::ItemFn) -> Configuration {
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).clone();
let key_ty = if requires_interning(item_fn) {
parse_quote!(salsa::id::Id)
} else {
salsa_struct_ty.clone()
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);
@ -209,36 +437,39 @@ fn fn_configuration(args: &Args, item_fn: &syn::ItemFn) -> Configuration {
}
fn ingredients_for_impl(
args: &Args,
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 = if requires_interning(item_fn) {
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)
let intern_map: syn::Expr = match function_type(item_fn) {
FunctionType::Constant | FunctionType::SalsaStruct => {
parse_quote! {
salsa::interned::IdentityInterner::new()
}
}
} else {
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)
}
}
}
};
@ -284,14 +515,11 @@ fn ingredients_for_impl(
}
}
fn wrapper_fns(
args: &Args,
fn setter_impl(
args: &FnArgs,
item_fn: &syn::ItemFn,
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, config_ty)?;
) -> 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)?;
@ -315,45 +543,53 @@ fn wrapper_fns(
}
};
Ok((getter_fn, setter_impl))
Ok(setter_impl)
}
/// Creates the `get` associated function.
/// 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: &Args,
item_fn: &syn::ItemFn,
args: &FnArgs,
fn_sig: &mut syn::Signature,
block_span: proc_macro2::Span,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let mut getter_fn = item_fn.clone();
let arg_idents: Vec<_> = item_fn
.sig
) -> 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(_) => Err(syn::Error::new(arg.span(), "unexpected receiver")),
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(), "unexpected receiver")),
_ => return Err(syn::Error::new(arg.span(), "unsupported argument kind")),
}),
}
})
.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() => {
// 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 {
getter_fn.block = Box::new(parse_quote_spanned! {
item_fn.block.span() => {
parse_quote_spanned! {
block_span => {
Clone::clone(#config_ty::get(#(#arg_idents,)*))
}
});
}
Ok(getter_fn)
}
})
}
/// Creates a `get` associated function that returns `&Value`
@ -361,14 +597,14 @@ fn getter_fn(
///
/// (Helper for `getter_fn`)
fn ref_getter_fn(
args: &Args,
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());
ref_getter_fn = make_fn_return_ref(ref_getter_fn)?;
make_fn_return_ref(&mut ref_getter_fn.sig)?;
let (db_var, arg_names) = fn_args(item_fn)?;
ref_getter_fn.block = parse_quote! {
@ -386,7 +622,7 @@ fn 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: &Args,
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ImplItemMethod> {
@ -439,7 +675,7 @@ fn setter_fn(
/// my_tracked_fn::set_lru_capacity(16)
/// ```
fn set_lru_capacity_fn(
args: &Args,
args: &FnArgs,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemMethod>> {
if args.lru.is_none() {
@ -460,7 +696,7 @@ fn set_lru_capacity_fn(
}
fn specify_fn(
args: &Args,
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemMethod>> {
@ -493,19 +729,16 @@ fn specify_fn(
},
}))
}
/// 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
/// 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(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)?;
fn make_fn_return_ref(mut 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 ref_getter_fn.sig.output {
ReturnType::Default => (
syn::Token![->](ref_getter_fn.sig.paren_token.span),
parse_quote!(()),
),
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),
};
@ -516,24 +749,31 @@ fn make_fn_return_ref(mut ref_getter_fn: syn::ItemFn) -> syn::Result<syn::ItemFn
elem,
};
ref_getter_fn.sig.output = syn::ReturnType::Type(right_arrow, Box::new(ref_output.into()));
fn_sig.output = syn::ReturnType::Type(right_arrow, Box::new(ref_output.into()));
Ok(ref_getter_fn)
Ok(())
}
/// Given an item function, 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::ItemFn) -> syn::Result<(syn::Lifetime, &syn::Type)> {
match &mut func.sig.inputs[0] {
syn::FnArg::Receiver(r) => Err(syn::Error::new(r.span(), "expected database, not self")),
/// 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.sig.generics.params.insert(
func.generics.params.insert(
0,
syn::LifetimeDef {
attrs: vec![],
@ -566,7 +806,7 @@ fn db_lifetime_and_ty(func: &mut syn::ItemFn) -> syn::Result<(syn::Lifetime, &sy
/// on the function ingredient to extract the values pushed (transitively)
/// into an accumulator.
fn accumulated_fn(
args: &Args,
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
@ -581,7 +821,7 @@ fn accumulated_fn(
-> Vec<<__A as salsa::accumulator::Accumulator>::Data>
};
let (db_lifetime, _) = db_lifetime_and_ty(&mut accumulated_fn)?;
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 {

View file

@ -10,13 +10,8 @@ use crate::salsa_struct::{SalsaField, SalsaStruct};
pub(crate) fn tracked(
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
) -> proc_macro::TokenStream {
match SalsaStruct::with_struct(args, struct_item)
.and_then(|el| TrackedStruct(el).generate_tracked())
{
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
}
) -> syn::Result<TokenStream> {
SalsaStruct::with_struct(args, struct_item).and_then(|el| TrackedStruct(el).generate_tracked())
}
struct TrackedStruct(SalsaStruct<Self>);

View file

@ -3,3 +3,13 @@ use crate::{Database, IngredientIndex};
pub trait SalsaStructInDb<DB: ?Sized + Database> {
fn register_dependent_fn(db: &DB, index: IngredientIndex);
}
/// A ZST that implements [`SalsaStructInDb`]
///
/// It is used for implementing "constant" tracked function
/// (ones that only take a database as an argument).
pub struct Singleton;
impl<DB: ?Sized + Database> SalsaStructInDb<DB> for Singleton {
fn register_dependent_fn(_db: &DB, _index: IngredientIndex) {}
}

View file

@ -0,0 +1,17 @@
#[salsa::jar(db = Db)]
struct Jar(AccTwoUnnamedFields, AccNamedField);
trait Db: salsa::DbWithJar<Jar> {}
// accumulator with more than one unnamed fields
#[salsa::accumulator(jar = Jar)]
struct AccTwoUnnamedFields (u32, u32);
// accumulator with named fields
#[salsa::accumulator(jar = Jar)]
struct AccNamedField {
field: u32,
}
fn main() {}

View file

@ -0,0 +1,23 @@
error: accumulator structs should have only one anonymous field
--> tests/compile-fail/accumulator_fields_incompatibles.rs:8:8
|
8 | struct AccTwoUnnamedFields (u32, u32);
| ^^^^^^^^^^^^^^^^^^^
error: accumulator structs should have only one anonymous field
--> tests/compile-fail/accumulator_fields_incompatibles.rs:13:8
|
13 | struct AccNamedField {
| ^^^^^^^^^^^^^
error[E0412]: cannot find type `AccTwoUnnamedFields` in this scope
--> tests/compile-fail/accumulator_fields_incompatibles.rs:2:12
|
2 | struct Jar(AccTwoUnnamedFields, AccNamedField);
| ^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccNamedField` in this scope
--> tests/compile-fail/accumulator_fields_incompatibles.rs:2:33
|
2 | struct Jar(AccTwoUnnamedFields, AccNamedField);
| ^^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,30 @@
#[salsa::jar(db = Db)]
struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::accumulator(jar = Jar, return_ref)]
struct AccWithRetRef (u32);
#[salsa::accumulator(jar = Jar, specify)]
struct AccWithSpecify (u32);
#[salsa::accumulator(jar = Jar, no_eq)]
struct AccWithNoEq (u32);
#[salsa::accumulator(jar = Jar, data = MyAcc)]
struct AccWithData (u32);
#[salsa::accumulator(jar = Jar, db = Db)]
struct AcWithcDb (u32);
#[salsa::accumulator(jar = Jar, recover_fn = recover)]
struct AccWithRecover (u32);
#[salsa::accumulator(jar = Jar, lru =12)]
struct AccWithLru (u32);
#[salsa::accumulator(jar = Jar, constructor = Constructor)]
struct AccWithConstructor (u32);
fn main() {}

View file

@ -0,0 +1,95 @@
error: `return_ref` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:6:33
|
6 | #[salsa::accumulator(jar = Jar, return_ref)]
| ^^^^^^^^^^
error: `specify` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:9:33
|
9 | #[salsa::accumulator(jar = Jar, specify)]
| ^^^^^^^
error: `no_eq` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:12:33
|
12 | #[salsa::accumulator(jar = Jar, no_eq)]
| ^^^^^
error: `data` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:15:33
|
15 | #[salsa::accumulator(jar = Jar, data = MyAcc)]
| ^^^^
error: `db` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:18:33
|
18 | #[salsa::accumulator(jar = Jar, db = Db)]
| ^^
error: unrecognized option `recover_fn`
--> tests/compile-fail/accumulator_incompatibles.rs:21:33
|
21 | #[salsa::accumulator(jar = Jar, recover_fn = recover)]
| ^^^^^^^^^^
error: `lru` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:24:33
|
24 | #[salsa::accumulator(jar = Jar, lru =12)]
| ^^^
error: `constructor` option not allowed here
--> tests/compile-fail/accumulator_incompatibles.rs:27:33
|
27 | #[salsa::accumulator(jar = Jar, constructor = Constructor)]
| ^^^^^^^^^^^
error[E0412]: cannot find type `AccWithRetRef` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:12
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccWithSpecify` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:27
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccWithNoEq` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:43
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccWithData` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:56
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AcWithcDb` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:69
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccWithRecover` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:80
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccWithLru` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:96
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `AccWithConstructor` in this scope
--> tests/compile-fail/accumulator_incompatibles.rs:2:108
|
2 | struct Jar(AccWithRetRef, AccWithSpecify, AccWithNoEq, AccWithData, AcWithcDb, AccWithRecover, AccWithLru, AccWithConstructor);
| ^^^^^^^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,24 @@
#[salsa::jar(db = Db)]
struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input(jar = Jar, return_ref)]
struct InputWithRetRef (u32);
#[salsa::input(jar = Jar, specify)]
struct InputWithSpecify (u32);
#[salsa::input(jar = Jar, no_eq)]
struct InputNoWithEq (u32);
#[salsa::input(jar = Jar, db = Db)]
struct InputWithDb (u32);
#[salsa::input(jar = Jar, recover_fn = recover)]
struct InputWithRecover (u32);
#[salsa::input(jar = Jar, lru =12)]
struct InputWithLru (u32);
fn main() {}

View file

@ -0,0 +1,71 @@
error: `return_ref` option not allowed here
--> tests/compile-fail/input_struct_incompatibles.rs:6:27
|
6 | #[salsa::input(jar = Jar, return_ref)]
| ^^^^^^^^^^
error: `specify` option not allowed here
--> tests/compile-fail/input_struct_incompatibles.rs:9:27
|
9 | #[salsa::input(jar = Jar, specify)]
| ^^^^^^^
error: `no_eq` option not allowed here
--> tests/compile-fail/input_struct_incompatibles.rs:12:27
|
12 | #[salsa::input(jar = Jar, no_eq)]
| ^^^^^
error: `db` option not allowed here
--> tests/compile-fail/input_struct_incompatibles.rs:15:27
|
15 | #[salsa::input(jar = Jar, db = Db)]
| ^^
error: unrecognized option `recover_fn`
--> tests/compile-fail/input_struct_incompatibles.rs:18:27
|
18 | #[salsa::input(jar = Jar, recover_fn = recover)]
| ^^^^^^^^^^
error: `lru` option not allowed here
--> tests/compile-fail/input_struct_incompatibles.rs:21:27
|
21 | #[salsa::input(jar = Jar, lru =12)]
| ^^^
error[E0412]: cannot find type `InputWithRetRef` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:2:12
|
2 | struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
| ^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InputWithSpecify` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:2:29
|
2 | struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
| ^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InputNoWithEq` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:2:47
|
2 | struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
| ^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InputWithDb` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:2:62
|
2 | struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
| ^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InputWithRecover` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:2:75
|
2 | struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
| ^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InputWithLru` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:2:93
|
2 | struct Jar(InputWithRetRef, InputWithSpecify, InputNoWithEq, InputWithDb, InputWithRecover, InputWithLru);
| ^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,36 @@
#[salsa::jar(db = Db)]
struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::interned(jar = Jar, return_ref)]
struct InternedWithRetRef {
field: u32,
}
#[salsa::interned(jar = Jar, specify)]
struct InternedWithSpecify {
field: u32,
}
#[salsa::interned(jar = Jar, no_eq)]
struct InternedWithNoEq {
field: u32,
}
#[salsa::interned(jar = Jar, db = Db)]
struct InternedWithDb {
field: u32,
}
#[salsa::interned(jar = Jar, recover_fn = recover)]
struct InternedWithRecover {
field: u32,
}
#[salsa::interned(jar = Jar, lru =12)]
struct InternedWithLru {
field: u32,
}
fn main() {}

View file

@ -0,0 +1,71 @@
error: `return_ref` option not allowed here
--> tests/compile-fail/interned_struct_incompatibles.rs:7:30
|
7 | #[salsa::interned(jar = Jar, return_ref)]
| ^^^^^^^^^^
error: `specify` option not allowed here
--> tests/compile-fail/interned_struct_incompatibles.rs:12:30
|
12 | #[salsa::interned(jar = Jar, specify)]
| ^^^^^^^
error: `no_eq` option not allowed here
--> tests/compile-fail/interned_struct_incompatibles.rs:17:30
|
17 | #[salsa::interned(jar = Jar, no_eq)]
| ^^^^^
error: `db` option not allowed here
--> tests/compile-fail/interned_struct_incompatibles.rs:22:30
|
22 | #[salsa::interned(jar = Jar, db = Db)]
| ^^
error: unrecognized option `recover_fn`
--> tests/compile-fail/interned_struct_incompatibles.rs:27:30
|
27 | #[salsa::interned(jar = Jar, recover_fn = recover)]
| ^^^^^^^^^^
error: `lru` option not allowed here
--> tests/compile-fail/interned_struct_incompatibles.rs:32:30
|
32 | #[salsa::interned(jar = Jar, lru =12)]
| ^^^
error[E0412]: cannot find type `InternedWithRetRef` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:2:12
|
2 | struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
| ^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InternedWithSpecify` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:2:32
|
2 | struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
| ^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InternedWithNoEq` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:2:53
|
2 | struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
| ^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InternedWithDb` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:2:71
|
2 | struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
| ^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InternedWithRecover` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:2:87
|
2 | struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
| ^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InternedWithLru` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:2:108
|
2 | struct Jar(InternedWithRetRef, InternedWithSpecify, InternedWithNoEq, InternedWithDb, InternedWithRecover, InternedWithLru);
| ^^^^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,33 @@
#[salsa::jar(db = Db, return_ref)]
struct JarWithRetRef(MyInput);
#[salsa::jar(db = Db, specify)]
struct JarWithDb(MyInput);
#[salsa::jar(db = Db, no_eq)]
struct JarWithNoEq(MyInput);
#[salsa::jar(db = Db, jar = Jar)]
struct JarWithJar(MyInput);
#[salsa::jar(db = Db, data = Data)]
struct JarWithData(MyInput);
#[salsa::jar(db = Db, recovery_fn = recover)]
struct JarWithRecover(MyInput);
#[salsa::jar(db = Db, lru = 32)]
struct JarWithLru(MyInput);
#[salsa::jar(db = Db, constructor = JarConstructor)]
struct JarWithConstructor(MyInput);
#[salsa::input(jar = Jar1)]
struct MyInput {
field: u32,
}
fn main() {
}

View file

@ -0,0 +1,61 @@
error: `return_ref` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:1:23
|
1 | #[salsa::jar(db = Db, return_ref)]
| ^^^^^^^^^^
error: `specify` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:4:23
|
4 | #[salsa::jar(db = Db, specify)]
| ^^^^^^^
error: `no_eq` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:8:23
|
8 | #[salsa::jar(db = Db, no_eq)]
| ^^^^^
error: `jar` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:11:23
|
11 | #[salsa::jar(db = Db, jar = Jar)]
| ^^^
error: `data` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:14:23
|
14 | #[salsa::jar(db = Db, data = Data)]
| ^^^^
error: `recovery_fn` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:17:23
|
17 | #[salsa::jar(db = Db, recovery_fn = recover)]
| ^^^^^^^^^^^
error: `lru` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:20:23
|
20 | #[salsa::jar(db = Db, lru = 32)]
| ^^^
error: `constructor` option not allowed here
--> tests/compile-fail/jars_incompatibles.rs:23:23
|
23 | #[salsa::jar(db = Db, constructor = JarConstructor)]
| ^^^^^^^^^^^
error[E0412]: cannot find type `Jar1` in this scope
--> tests/compile-fail/jars_incompatibles.rs:26:22
|
26 | #[salsa::input(jar = Jar1)]
| ^^^^ not found in this scope
error[E0412]: cannot find type `Jar1` in this scope
--> tests/compile-fail/jars_incompatibles.rs:26:22
|
26 | #[salsa::input(jar = Jar1)]
| ^^^^ not found in this scope
27 | struct MyInput {
| - help: you might be missing a type parameter: `<Jar1>`

View file

@ -0,0 +1,19 @@
#[salsa::jar(db = Db)]
struct Jar(InputWithBannedName1, InputWithBannedName2);
// Banned field name: `from`
#[salsa::input]
struct InputWithBannedName1 {
from: u32,
}
// Banned field name: `new`
#[salsa::input]
struct InputWithBannedName2 {
new: u32,
}
trait Db: salsa::DbWithJar<Jar> {}
fn main() {}

View file

@ -0,0 +1,23 @@
error: the field name `from` is disallowed in salsa structs
--> tests/compile-fail/salsa_fields_incompatibles.rs:7:5
|
7 | from: u32,
| ^^^^
error: the field name `new` is disallowed in salsa structs
--> tests/compile-fail/salsa_fields_incompatibles.rs:13:5
|
13 | new: u32,
| ^^^
error[E0412]: cannot find type `InputWithBannedName1` in this scope
--> tests/compile-fail/salsa_fields_incompatibles.rs:2:12
|
2 | struct Jar(InputWithBannedName1, InputWithBannedName2);
| ^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `InputWithBannedName2` in this scope
--> tests/compile-fail/salsa_fields_incompatibles.rs:2:34
|
2 | struct Jar(InputWithBannedName1, InputWithBannedName2);
| ^^^^^^^^^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,47 @@
#[salsa::jar(db = Db)]
struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_receiver_not_applied_to_impl_block);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input(jar = Jar)]
struct MyInput {
field: u32,
}
#[salsa::tracked(jar = Jar, data = Data)]
fn tracked_fn_with_data(db: &dyn Db, input: MyInput) -> u32 {
input.field(db) * 2
}
#[salsa::tracked(jar = Jar, db = Db)]
fn tracked_fn_with_db(db: &dyn Db, input: MyInput) -> u32 {
input.field(db) * 2
}
#[salsa::tracked(jar = Jar, constructor = TrackedFn3)]
fn tracked_fn_with_constructor(db: &dyn Db, input: MyInput) -> u32 {
input.field(db) * 2
}
#[salsa::tracked(jar = Jar)]
fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {
}
#[salsa::tracked(jar = Jar)]
fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {
}
#[salsa::tracked(jar = Jar, specify)]
fn tracked_fn_with_receiver_not_applied_to_impl_block(db: &dyn Db, input: MyInput, input: MyInput) -> u32 {
}
fn main() {}

View file

@ -0,0 +1,64 @@
error: `data` option not allowed here
--> tests/compile-fail/tracked_fn_incompatibles.rs:12:29
|
12 | #[salsa::tracked(jar = Jar, data = Data)]
| ^^^^
error: `db` option not allowed here
--> tests/compile-fail/tracked_fn_incompatibles.rs:17:29
|
17 | #[salsa::tracked(jar = Jar, db = Db)]
| ^^
error: `constructor` option not allowed here
--> tests/compile-fail/tracked_fn_incompatibles.rs:22:29
|
22 | #[salsa::tracked(jar = Jar, constructor = TrackedFn3)]
| ^^^^^^^^^^^
error: #[salsa::tracked] must also be applied to the impl block for tracked methods
--> tests/compile-fail/tracked_fn_incompatibles.rs:34:55
|
34 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {
| ^
error: tracked function takes too many arguments to have its value set with `specify`
--> tests/compile-fail/tracked_fn_incompatibles.rs:37:29
|
37 | #[salsa::tracked(jar = Jar, specify)]
| ^^^^^^^
error[E0412]: cannot find type `tracked_fn_with_data` in this scope
--> tests/compile-fail/tracked_fn_incompatibles.rs:2:21
|
2 | struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_rec...
| ^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `tracked_fn_with_db` in this scope
--> tests/compile-fail/tracked_fn_incompatibles.rs:2:43
|
2 | struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_rec...
| ^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `tracked_fn_with_constructor` in this scope
--> tests/compile-fail/tracked_fn_incompatibles.rs:2:63
|
2 | struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_rec...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: a struct with a similar name exists: `tracked_fn_with_one_input`
...
28 | #[salsa::tracked(jar = Jar)]
| ---------------------------- similarly named struct `tracked_fn_with_one_input` defined here
error[E0412]: cannot find type `tracked_fn_with_receiver_not_applied_to_impl_block` in this scope
--> tests/compile-fail/tracked_fn_incompatibles.rs:2:119
|
2 | ...r, tracked_fn_with_one_input, tracked_fn_with_receiver_not_applied_to_impl_block);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0308]: mismatched types
--> tests/compile-fail/tracked_fn_incompatibles.rs:29:46
|
29 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {
| ------------------------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

View file

@ -0,0 +1,74 @@
#[salsa::jar(db = Db)]
struct Jar(MyTracked);
#[salsa::tracked]
struct MyTracked {
field: u32,
}
#[salsa::tracked(return_ref)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(specify)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(no_eq)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(data = Data)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(db = Db)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(recover_fn = recover)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(lru = 32)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked(constructor = Constructor)]
impl std::default::Default for MyTracked {
fn default() -> Self {
}
}
#[salsa::tracked]
impl std::default::Default for [MyTracked; 12] {
fn default() -> Self {
}
}
trait Db: salsa::DbWithJar<Jar> {}
fn main() {}

View file

@ -0,0 +1,53 @@
error: `return_ref` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:10:18
|
10 | #[salsa::tracked(return_ref)]
| ^^^^^^^^^^
error: `specify` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:16:18
|
16 | #[salsa::tracked(specify)]
| ^^^^^^^
error: `no_eq` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:23:18
|
23 | #[salsa::tracked(no_eq)]
| ^^^^^
error: `data` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:30:18
|
30 | #[salsa::tracked(data = Data)]
| ^^^^
error: `db` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:37:18
|
37 | #[salsa::tracked(db = Db)]
| ^^
error: unrecognized option `recover_fn`
--> tests/compile-fail/tracked_impl_incompatibles.rs:44:18
|
44 | #[salsa::tracked(recover_fn = recover)]
| ^^^^^^^^^^
error: `lru` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:51:18
|
51 | #[salsa::tracked(lru = 32)]
| ^^^
error: `constructor` option not allowed here
--> tests/compile-fail/tracked_impl_incompatibles.rs:58:18
|
58 | #[salsa::tracked(constructor = Constructor)]
| ^^^^^^^^^^^
error: #[salsa::tracked] can only be applied to salsa structs
--> tests/compile-fail/tracked_impl_incompatibles.rs:65:32
|
65 | impl std::default::Default for [MyTracked; 12] {
| ^^^^^^^^^^^^^^^

View file

@ -0,0 +1,21 @@
#[salsa::jar(db = Db)]
struct Jar(Tracked);
#[salsa::tracked(jar = Jar)]
struct Tracked {
field: u32,
}
impl Tracked {
#[salsa::tracked]
fn use_tracked(&self) {
}
}
trait Db: salsa::DbWithJar<Jar> {}
fn main() {}

View file

@ -0,0 +1,5 @@
error: #[salsa::tracked] must also be applied to the impl block for tracked methods
--> tests/compile-fail/tracked_method_incompatibles.rs:13:20
|
13 | fn use_tracked(&self) {
| ^

View file

@ -0,0 +1,18 @@
#[salsa::jar(db = Db)]
struct Jar(MyInput, tracked_method_on_untracked_impl);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: u32,
}
impl MyInput {
#[salsa::tracked]
fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 {
input.field(db)
}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: #[salsa::tracked] must also be applied to the impl block for tracked methods
--> tests/compile-fail/tracked_method_on_untracked_impl.rs:13:41
|
13 | fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 {
| ^^^^
error[E0412]: cannot find type `tracked_method_on_untracked_impl` in this scope
--> tests/compile-fail/tracked_method_on_untracked_impl.rs:2:21
|
2 | struct Jar(MyInput, tracked_method_on_untracked_impl);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,36 @@
#[salsa::jar(db = Db)]
struct Jar(TrackedWithRetRef, TrackedSructWithSpecify, TrackedStructWithNoEq, TrackedStructWithDb, TrackedStructWithRecover, TrackedStructWithLru);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::tracked(jar = Jar, return_ref)]
struct TrackedWithRetRef {
field: u32,
}
#[salsa::tracked(jar = Jar, specify)]
struct TrackedSructWithSpecify {
field: u32,
}
#[salsa::tracked(jar = Jar, no_eq)]
struct TrackedStructWithNoEq {
field: u32,
}
#[salsa::tracked(jar = Jar, db = Db)]
struct TrackedStructWithDb {
field: u32,
}
#[salsa::tracked(jar = Jar, recover_fn = recover)]
struct TrackedStructWithRecover {
field: u32,
}
#[salsa::tracked(jar = Jar, lru =12)]
struct TrackedStructWithLru {
field: u32,
}
fn main() {}

View file

@ -0,0 +1,71 @@
error: `return_ref` option not allowed here
--> tests/compile-fail/tracked_struct_incompatibles.rs:7:29
|
7 | #[salsa::tracked(jar = Jar, return_ref)]
| ^^^^^^^^^^
error: `specify` option not allowed here
--> tests/compile-fail/tracked_struct_incompatibles.rs:12:29
|
12 | #[salsa::tracked(jar = Jar, specify)]
| ^^^^^^^
error: `no_eq` option not allowed here
--> tests/compile-fail/tracked_struct_incompatibles.rs:17:29
|
17 | #[salsa::tracked(jar = Jar, no_eq)]
| ^^^^^
error: `db` option not allowed here
--> tests/compile-fail/tracked_struct_incompatibles.rs:22:29
|
22 | #[salsa::tracked(jar = Jar, db = Db)]
| ^^
error: unrecognized option `recover_fn`
--> tests/compile-fail/tracked_struct_incompatibles.rs:27:29
|
27 | #[salsa::tracked(jar = Jar, recover_fn = recover)]
| ^^^^^^^^^^
error: `lru` option not allowed here
--> tests/compile-fail/tracked_struct_incompatibles.rs:32:29
|
32 | #[salsa::tracked(jar = Jar, lru =12)]
| ^^^
error[E0412]: cannot find type `TrackedWithRetRef` in this scope
--> tests/compile-fail/tracked_struct_incompatibles.rs:2:12
|
2 | struct Jar(TrackedWithRetRef, TrackedSructWithSpecify, TrackedStructWithNoEq, TrackedStructWithDb, TrackedStructWithRecover, TrackedStruc...
| ^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `TrackedSructWithSpecify` in this scope
--> tests/compile-fail/tracked_struct_incompatibles.rs:2:31
|
2 | struct Jar(TrackedWithRetRef, TrackedSructWithSpecify, TrackedStructWithNoEq, TrackedStructWithDb, TrackedStructWithRecover, TrackedStruc...
| ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `TrackedStructWithNoEq` in this scope
--> tests/compile-fail/tracked_struct_incompatibles.rs:2:56
|
2 | struct Jar(TrackedWithRetRef, TrackedSructWithSpecify, TrackedStructWithNoEq, TrackedStructWithDb, TrackedStructWithRecover, TrackedStruc...
| ^^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `TrackedStructWithDb` in this scope
--> tests/compile-fail/tracked_struct_incompatibles.rs:2:79
|
2 | struct Jar(TrackedWithRetRef, TrackedSructWithSpecify, TrackedStructWithNoEq, TrackedStructWithDb, TrackedStructWithRecover, TrackedStruc...
| ^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `TrackedStructWithRecover` in this scope
--> tests/compile-fail/tracked_struct_incompatibles.rs:2:100
|
2 | ...rackedStructWithNoEq, TrackedStructWithDb, TrackedStructWithRecover, TrackedStructWithLru);
| ^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0412]: cannot find type `TrackedStructWithLru` in this scope
--> tests/compile-fail/tracked_struct_incompatibles.rs:2:126
|
2 | ...ackedStructWithDb, TrackedStructWithRecover, TrackedStructWithLru);
| ^^^^^^^^^^^^^^^^^^^^ not found in this scope

View file

@ -0,0 +1,29 @@
//! Test that a constant `tracked` fn (has no inputs)
//! compiles and executes successfully.
#![allow(warnings)]
#[salsa::jar(db = Db)]
struct Jar(tracked_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::tracked]
fn tracked_fn(db: &dyn Db) -> u32 {
44
}
#[test]
fn execute() {
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
let mut db = Database::default();
assert_eq!(tracked_fn(&db), 44);
}

View file

@ -0,0 +1,62 @@
//! Test that a `tracked` fn on a `salsa::input`
//! compiles and executes successfully.
#![allow(warnings)]
#[salsa::jar(db = Db)]
struct Jar(
MyInput,
MyInput_tracked_fn,
MyInput_tracked_fn_ref,
MyInput_TrackedTrait_tracked_trait_fn,
);
trait Db: salsa::DbWithJar<Jar> {}
trait TrackedTrait {
fn tracked_trait_fn(self, db: &dyn Db) -> u32;
}
#[salsa::input]
struct MyInput {
field: u32,
}
#[salsa::tracked]
impl MyInput {
#[salsa::tracked]
fn tracked_fn(self, db: &dyn Db) -> u32 {
self.field(db) * 2
}
#[salsa::tracked(return_ref)]
fn tracked_fn_ref(self, db: &dyn Db) -> u32 {
self.field(db) * 3
}
}
#[salsa::tracked]
impl TrackedTrait for MyInput {
#[salsa::tracked]
fn tracked_trait_fn(self, db: &dyn Db) -> u32 {
self.field(db) * 4
}
}
#[test]
fn execute() {
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
let mut db = Database::default();
let object = MyInput::new(&mut db, 22);
assert_eq!(object.tracked_fn(&db), 44);
assert_eq!(*object.tracked_fn_ref(&db), 66);
assert_eq!(object.tracked_trait_fn(&db), 88);
}