diff --git a/components/salsa-macro-rules/src/setup_tracked_struct.rs b/components/salsa-macro-rules/src/setup_tracked_struct.rs index a783e3762..9ba326aec 100644 --- a/components/salsa-macro-rules/src/setup_tracked_struct.rs +++ b/components/salsa-macro-rules/src/setup_tracked_struct.rs @@ -2,37 +2,54 @@ #[macro_export] macro_rules! setup_tracked_struct { ( - // Attributes on the function + // Attributes on the function. attrs: [$(#[$attr:meta]),*], - // Visibility of the struct + // Visibility of the struct. vis: $vis:vis, - // Name of the struct + // Name of the struct. Struct: $Struct:ident, - // Name of the `'db` lifetime that the user gave + // Name of the `'db` lifetime that the user gave. db_lt: $db_lt:lifetime, - // Name user gave for `new` + // Name user gave for `new`. new_fn: $new_fn:ident, - // Field names + // Field names. field_ids: [$($field_id:ident),*], - // Field names - field_getters: [$($field_getter_vis:vis $field_getter_id:ident),*], + // Tracked field names. + tracked_ids: [$($tracked_id:ident),*], - // Field types, may reference `db_lt` + // Visibility and names of tracked fields. + tracked_getters: [$($tracked_getter_vis:vis $tracked_getter_id:ident),*], + + // Visibility and names of untracked fields. + untracked_getters: [$($untracked_getter_vis:vis $untracked_getter_id:ident),*], + + // Field types, may reference `db_lt`. field_tys: [$($field_ty:ty),*], + // Tracked field types. + tracked_tys: [$($tracked_ty:ty),*], + + // Untracked field types. + untracked_tys: [$($untracked_ty:ty),*], + // Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`). field_indices: [$($field_index:tt),*], - // Indices of fields to be used for id computations - id_field_indices: [$($id_field_index:tt),*], + // Absolute indices of any tracked fields, relative to all other fields of this struct. + tracked_indices: [$($tracked_index:tt),*], + + // Absolute indices of any untracked fields. + untracked_indices: [$($untracked_index:tt),*], - // A set of "field options". Each field option is a tuple `(maybe_clone, maybe_backdate)` where: + // A set of "field options" for each field. + // + // Each field option is a tuple `(maybe_clone, maybe_backdate)` where: // // * `maybe_clone` is either the identifier `clone` or `no_clone` // * `maybe_backdate` is either the identifier `backdate` or `no_backdate` @@ -41,7 +58,13 @@ macro_rules! setup_tracked_struct { // (see e.g. @maybe_clone below). field_options: [$($field_option:tt),*], - // Number of fields + // A set of "field options" for each tracked field. + tracked_options: [$($tracked_option:tt),*], + + // A set of "field options" for each untracked field. + untracked_options: [$($untracked_option:tt),*], + + // Number of fields. num_fields: $N:literal, // If true, generate a debug impl. @@ -98,8 +121,8 @@ macro_rules! setup_tracked_struct { s.0 } - fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { - ( $( &fields.$id_field_index ),* ) + fn untracked_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { + ( $( &fields.$untracked_index ),* ) } fn new_revisions(current_revision: $Revision) -> Self::Revisions { @@ -133,6 +156,7 @@ macro_rules! setup_tracked_struct { pub fn ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<$Configuration> { static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> = $zalsa::IngredientCache::new(); + CACHE.get_or_create(db, || { db.zalsa().add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl::<$Configuration>>::default()) }) @@ -199,17 +223,33 @@ macro_rules! setup_tracked_struct { } $( - $field_getter_vis fn $field_getter_id<$Db>(self, db: &$db_lt $Db) -> $crate::maybe_cloned_ty!($field_option, $db_lt, $field_ty) + $tracked_getter_vis fn $tracked_getter_id<$Db>(self, db: &$db_lt $Db) -> $crate::maybe_cloned_ty!($tracked_option, $db_lt, $tracked_ty) + where + // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` + $Db: ?Sized + $zalsa::Database, + { + let db = db.as_dyn_database(); + let fields = $Configuration::ingredient(db).tracked_field(db, self, $tracked_index); + $crate::maybe_clone!( + $tracked_option, + $tracked_ty, + &fields.$tracked_index, + ) + } + )* + + $( + $untracked_getter_vis fn $untracked_getter_id<$Db>(self, db: &$db_lt $Db) -> $crate::maybe_cloned_ty!($untracked_option, $db_lt, $untracked_ty) where // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` $Db: ?Sized + $zalsa::Database, { let db = db.as_dyn_database(); - let fields = $Configuration::ingredient(db).field(db, self, $field_index); + let fields = $Configuration::ingredient(db).untracked_field(db, self, $untracked_index); $crate::maybe_clone!( - $field_option, - $field_ty, - &fields.$field_index, + $untracked_option, + $untracked_ty, + &fields.$untracked_index, ) } )* diff --git a/components/salsa-macros/src/input.rs b/components/salsa-macros/src/input.rs index 9ad444913..78e10d202 100644 --- a/components/salsa-macros/src/input.rs +++ b/components/salsa-macros/src/input.rs @@ -60,7 +60,7 @@ impl crate::options::AllowedOptions for InputStruct { impl SalsaStructAllowedOptions for InputStruct { const KIND: &'static str = "input"; - const ALLOW_ID: bool = false; + const ALLOW_TRACKED: bool = false; const HAS_LIFETIME: bool = false; diff --git a/components/salsa-macros/src/interned.rs b/components/salsa-macros/src/interned.rs index 8caba77e4..3f78ba74d 100644 --- a/components/salsa-macros/src/interned.rs +++ b/components/salsa-macros/src/interned.rs @@ -61,7 +61,7 @@ impl crate::options::AllowedOptions for InternedStruct { impl SalsaStructAllowedOptions for InternedStruct { const KIND: &'static str = "interned"; - const ALLOW_ID: bool = false; + const ALLOW_TRACKED: bool = false; const HAS_LIFETIME: bool = true; diff --git a/components/salsa-macros/src/salsa_struct.rs b/components/salsa-macros/src/salsa_struct.rs index 1a24fdc42..0a0d0ac9b 100644 --- a/components/salsa-macros/src/salsa_struct.rs +++ b/components/salsa-macros/src/salsa_struct.rs @@ -41,8 +41,8 @@ pub(crate) trait SalsaStructAllowedOptions: AllowedOptions { /// The kind of struct (e.g., interned, input, tracked). const KIND: &'static str; - /// Are `#[id]` fields allowed? - const ALLOW_ID: bool; + /// Are `#[tracked]` fields allowed? + const ALLOW_TRACKED: bool; /// Does this kind of struct have a `'db` lifetime? const HAS_LIFETIME: bool; @@ -54,7 +54,7 @@ pub(crate) trait SalsaStructAllowedOptions: AllowedOptions { pub(crate) struct SalsaField<'s> { field: &'s syn::Field, - pub(crate) has_id_attr: bool, + pub(crate) has_tracked_attr: bool, pub(crate) has_default_attr: bool, pub(crate) has_ref_attr: bool, pub(crate) has_no_eq_attr: bool, @@ -66,7 +66,7 @@ const BANNED_FIELD_NAMES: &[&str] = &["from", "new"]; #[allow(clippy::type_complexity)] pub(crate) const FIELD_OPTION_ATTRIBUTES: &[(&str, fn(&syn::Attribute, &mut SalsaField))] = &[ - ("id", |_, ef| ef.has_id_attr = true), + ("tracked", |_, ef| ef.has_tracked_attr = true), ("default", |_, ef| ef.has_default_attr = true), ("return_ref", |_, ef| ef.has_ref_attr = true), ("no_eq", |_, ef| ef.has_no_eq_attr = true), @@ -102,7 +102,7 @@ where fields, }; - this.maybe_disallow_id_fields()?; + this.maybe_disallow_tracked_fields()?; this.maybe_disallow_default_fields()?; this.check_generics()?; @@ -118,24 +118,24 @@ where } } - /// Disallow `#[id]` attributes on the fields of this struct. + /// Disallow `#[tracked]` attributes on the fields of this struct. /// - /// If an `#[id]` field is found, return an error. + /// If an `#[tracked]` field is found, return an error. /// /// # Parameters /// /// * `kind`, the attribute name (e.g., `input` or `interned`) - fn maybe_disallow_id_fields(&self) -> syn::Result<()> { - if A::ALLOW_ID { + fn maybe_disallow_tracked_fields(&self) -> syn::Result<()> { + if A::ALLOW_TRACKED { return Ok(()); } - // Check if any field has the `#[id]` attribute. + // Check if any field has the `#[tracked]` attribute. for ef in &self.fields { - if ef.has_id_attr { + if ef.has_tracked_attr { return Err(syn::Error::new_spanned( ef.field, - format!("`#[id]` cannot be used with `#[salsa::{}]`", A::KIND), + format!("`#[tracked]` cannot be used with `#[salsa::{}]`", A::KIND), )); } } @@ -184,25 +184,34 @@ where .collect() } + pub(crate) fn tracked_ids(&self) -> Vec<&syn::Ident> { + self.tracked_fields_iter() + .map(|(_, f)| f.field.ident.as_ref().unwrap()) + .collect() + } + pub(crate) fn field_indices(&self) -> Vec { (0..self.fields.len()) .map(Literal::usize_unsuffixed) .collect() } - pub(crate) fn num_fields(&self) -> Literal { - Literal::usize_unsuffixed(self.fields.len()) + pub(crate) fn tracked_indices(&self) -> Vec { + self.tracked_fields_iter() + .map(|(index, _)| Literal::usize_unsuffixed(index)) + .collect() } - pub(crate) fn id_field_indices(&self) -> Vec { - self.fields - .iter() - .zip(0..) - .filter_map(|(f, index)| if f.has_id_attr { Some(index) } else { None }) - .map(Literal::usize_unsuffixed) + pub(crate) fn untracked_indices(&self) -> Vec { + self.untracked_fields_iter() + .map(|(index, _)| Literal::usize_unsuffixed(index)) .collect() } + pub(crate) fn num_fields(&self) -> Literal { + Literal::usize_unsuffixed(self.fields.len()) + } + pub(crate) fn required_fields(&self) -> Vec { self.fields .iter() @@ -222,10 +231,34 @@ where self.fields.iter().map(|f| &f.field.vis).collect() } + pub(crate) fn tracked_vis(&self) -> Vec<&syn::Visibility> { + self.tracked_fields_iter() + .map(|(_, f)| &f.field.vis) + .collect() + } + + pub(crate) fn untracked_vis(&self) -> Vec<&syn::Visibility> { + self.untracked_fields_iter() + .map(|(_, f)| &f.field.vis) + .collect() + } + pub(crate) fn field_getter_ids(&self) -> Vec<&syn::Ident> { self.fields.iter().map(|f| &f.get_name).collect() } + pub(crate) fn tracked_getter_ids(&self) -> Vec<&syn::Ident> { + self.tracked_fields_iter() + .map(|(_, f)| &f.get_name) + .collect() + } + + pub(crate) fn untracked_getter_ids(&self) -> Vec<&syn::Ident> { + self.untracked_fields_iter() + .map(|(_, f)| &f.get_name) + .collect() + } + pub(crate) fn field_setter_ids(&self) -> Vec<&syn::Ident> { self.fields.iter().map(|f| &f.set_name).collect() } @@ -241,6 +274,18 @@ where self.fields.iter().map(|f| &f.field.ty).collect() } + pub(crate) fn tracked_tys(&self) -> Vec<&syn::Type> { + self.tracked_fields_iter() + .map(|(_, f)| &f.field.ty) + .collect() + } + + pub(crate) fn untracked_tys(&self) -> Vec<&syn::Type> { + self.untracked_fields_iter() + .map(|(_, f)| &f.field.ty) + .collect() + } + pub(crate) fn field_indexed_tys(&self) -> Vec { self.fields .iter() @@ -250,35 +295,38 @@ where } pub(crate) fn field_options(&self) -> Vec { - self.fields - .iter() - .map(|f| { - let clone_ident = if f.has_ref_attr { - syn::Ident::new("no_clone", Span::call_site()) - } else { - syn::Ident::new("clone", Span::call_site()) - }; - - let backdate_ident = if f.has_no_eq_attr { - syn::Ident::new("no_backdate", Span::call_site()) - } else { - syn::Ident::new("backdate", Span::call_site()) - }; + self.fields.iter().map(SalsaField::options).collect() + } - let default_ident = if f.has_default_attr { - syn::Ident::new("default", Span::call_site()) - } else { - syn::Ident::new("required", Span::call_site()) - }; + pub(crate) fn tracked_options(&self) -> Vec { + self.tracked_fields_iter() + .map(|(_, f)| f.options()) + .collect() + } - quote!((#clone_ident, #backdate_ident, #default_ident)) - }) + pub(crate) fn untracked_options(&self) -> Vec { + self.untracked_fields_iter() + .map(|(_, f)| f.options()) .collect() } pub fn generate_debug_impl(&self) -> bool { self.args.no_debug.is_none() } + + fn tracked_fields_iter(&self) -> impl Iterator)> { + self.fields + .iter() + .enumerate() + .filter(|(_, f)| f.has_tracked_attr) + } + + fn untracked_fields_iter(&self) -> impl Iterator)> { + self.fields + .iter() + .enumerate() + .filter(|(_, f)| !f.has_tracked_attr) + } } impl<'s> SalsaField<'s> { @@ -299,7 +347,7 @@ impl<'s> SalsaField<'s> { let set_name = Ident::new(&format!("set_{}", field_name_str), field_name.span()); let mut result = SalsaField { field, - has_id_attr: false, + has_tracked_attr: false, has_ref_attr: false, has_default_attr: false, has_no_eq_attr: false, @@ -318,4 +366,26 @@ impl<'s> SalsaField<'s> { Ok(result) } + + fn options(&self) -> TokenStream { + let clone_ident = if self.has_ref_attr { + syn::Ident::new("no_clone", Span::call_site()) + } else { + syn::Ident::new("clone", Span::call_site()) + }; + + let backdate_ident = if self.has_no_eq_attr { + syn::Ident::new("no_backdate", Span::call_site()) + } else { + syn::Ident::new("backdate", Span::call_site()) + }; + + let default_ident = if self.has_default_attr { + syn::Ident::new("default", Span::call_site()) + } else { + syn::Ident::new("required", Span::call_site()) + }; + + quote!((#clone_ident, #backdate_ident, #default_ident)) + } } diff --git a/components/salsa-macros/src/tracked_struct.rs b/components/salsa-macros/src/tracked_struct.rs index 1730b3404..7367a3b28 100644 --- a/components/salsa-macros/src/tracked_struct.rs +++ b/components/salsa-macros/src/tracked_struct.rs @@ -55,7 +55,7 @@ impl crate::options::AllowedOptions for TrackedStruct { impl SalsaStructAllowedOptions for TrackedStruct { const KIND: &'static str = "tracked"; - const ALLOW_ID: bool = true; + const ALLOW_TRACKED: bool = true; const HAS_LIFETIME: bool = true; @@ -78,14 +78,29 @@ impl Macro { let struct_ident = &self.struct_item.ident; let db_lt = db_lifetime::db_lifetime(&self.struct_item.generics); let new_fn = salsa_struct.constructor_name(); + let field_ids = salsa_struct.field_ids(); - let field_vis = salsa_struct.field_vis(); - let field_getter_ids = salsa_struct.field_getter_ids(); + let tracked_ids = salsa_struct.tracked_ids(); + + let tracked_vis = salsa_struct.tracked_vis(); + let untracked_vis = salsa_struct.untracked_vis(); + + let tracked_getter_ids = salsa_struct.tracked_getter_ids(); + let untracked_getter_ids = salsa_struct.untracked_getter_ids(); + let field_indices = salsa_struct.field_indices(); - let id_field_indices = salsa_struct.id_field_indices(); - let num_fields = salsa_struct.num_fields(); + let tracked_indices = salsa_struct.tracked_indices(); + let untracked_indices = salsa_struct.untracked_indices(); + let field_options = salsa_struct.field_options(); + let tracked_options = salsa_struct.tracked_options(); + let untracked_options = salsa_struct.untracked_options(); + let field_tys = salsa_struct.field_tys(); + let tracked_tys = salsa_struct.tracked_tys(); + let untracked_tys = salsa_struct.untracked_tys(); + + let num_fields = salsa_struct.num_fields(); let generate_debug_impl = salsa_struct.generate_debug_impl(); let zalsa = self.hygiene.ident("zalsa"); @@ -106,11 +121,18 @@ impl Macro { db_lt: #db_lt, new_fn: #new_fn, field_ids: [#(#field_ids),*], - field_getters: [#(#field_vis #field_getter_ids),*], + tracked_ids: [#(#tracked_ids),*], + tracked_getters: [#(#tracked_vis #tracked_getter_ids),*], + untracked_getters: [#(#untracked_vis #untracked_getter_ids),*], field_tys: [#(#field_tys),*], + tracked_tys: [#(#tracked_tys),*], + untracked_tys: [#(#untracked_tys),*], field_indices: [#(#field_indices),*], - id_field_indices: [#(#id_field_indices),*], + tracked_indices: [#(#tracked_indices),*], + untracked_indices: [#(#untracked_indices),*], field_options: [#(#field_options),*], + tracked_options: [#(#tracked_options),*], + untracked_options: [#(#untracked_options),*], num_fields: #num_fields, generate_debug_impl: #generate_debug_impl, unused_names: [ diff --git a/examples/calc/ir.rs b/examples/calc/ir.rs index 9a5017ff4..c439b4a61 100644 --- a/examples/calc/ir.rs +++ b/examples/calc/ir.rs @@ -28,6 +28,7 @@ pub struct FunctionId<'db> { // ANCHOR: program #[salsa::tracked] pub struct Program<'db> { + #[tracked] #[return_ref] pub statements: Vec>, } @@ -76,14 +77,15 @@ pub enum Op { // ANCHOR: functions #[salsa::tracked] pub struct Function<'db> { - #[id] pub name: FunctionId<'db>, name_span: Span<'db>, + #[tracked] #[return_ref] pub args: Vec>, + #[tracked] #[return_ref] pub body: Expression<'db>, } @@ -91,7 +93,9 @@ pub struct Function<'db> { #[salsa::tracked] pub struct Span<'db> { + #[tracked] pub start: usize, + #[tracked] pub end: usize, } diff --git a/src/tracked_struct.rs b/src/tracked_struct.rs index 1a3a720d6..706ca3b5f 100644 --- a/src/tracked_struct.rs +++ b/src/tracked_struct.rs @@ -49,7 +49,7 @@ pub trait Configuration: Sized + 'static { /// Deref the struct to yield the underlying id. fn deref_struct(s: Self::Struct<'_>) -> Id; - fn id_fields(fields: &Self::Fields<'_>) -> impl Hash; + fn untracked_fields(fields: &Self::Fields<'_>) -> impl Hash; /// Create a new value revision array where each element is set to `current_revision`. fn new_revisions(current_revision: Revision) -> Self::Revisions; @@ -108,6 +108,8 @@ impl Jar for JarImpl { let struct_ingredient = >::new(struct_index); std::iter::once(Box::new(struct_ingredient) as _) + // Note that we create ingredients for untracked fields as well, in order to + // keep field indices relative to the entire struct. .chain((0..C::FIELD_DEBUG_NAMES.len()).map(|field_index| { Box::new(>::new(struct_index, field_index)) as _ })) @@ -281,7 +283,7 @@ where let identity_hash = IdentityHash { ingredient_index: self.ingredient_index, - hash: crate::hash::hash(&C::id_fields(&fields)), + hash: crate::hash::hash(&C::untracked_fields(&fields)), }; let (current_deps, disambiguator) = zalsa_local.disambiguate(identity_hash); @@ -536,10 +538,11 @@ where unsafe { self.to_self_ref(&value.fields) } } - /// Access to this value field. + /// Access to this tracked field. + /// /// Note that this function returns the entire tuple of value fields. /// The caller is responible for selecting the appropriate element. - pub fn field<'db>( + pub fn tracked_field<'db>( &'db self, db: &'db dyn crate::Database, s: C::Struct<'db>, @@ -563,6 +566,25 @@ where unsafe { self.to_self_ref(&data.fields) } } + + /// Access to this untracked field. + /// + /// Note that this function returns the entire tuple of value fields. + /// The caller is responible for selecting the appropriate element. + pub fn untracked_field<'db>( + &'db self, + db: &'db dyn crate::Database, + s: C::Struct<'db>, + _field_index: usize, + ) -> &'db C::Fields<'db> { + let (zalsa, _) = db.zalsas(); + let id = C::deref_struct(s); + let data = Self::data(zalsa.table(), id); + + data.read_lock(zalsa.current_revision()); + + unsafe { self.to_self_ref(&data.fields) } + } } impl Ingredient for IngredientImpl diff --git a/tests/compile-fail/input_struct_incompatibles.rs b/tests/compile-fail/input_struct_incompatibles.rs index 6786f558e..854a4ad5a 100644 --- a/tests/compile-fail/input_struct_incompatibles.rs +++ b/tests/compile-fail/input_struct_incompatibles.rs @@ -17,8 +17,8 @@ struct InputWithRecover(u32); struct InputWithLru(u32); #[salsa::input] -struct InputWithIdField { - #[id] +struct InputWithTrackedField { + #[tracked] field: u32, } diff --git a/tests/compile-fail/input_struct_incompatibles.stderr b/tests/compile-fail/input_struct_incompatibles.stderr index 1a68fb558..0dbb8aa33 100644 --- a/tests/compile-fail/input_struct_incompatibles.stderr +++ b/tests/compile-fail/input_struct_incompatibles.stderr @@ -34,15 +34,22 @@ error: `lru` option not allowed here 16 | #[salsa::input(lru =12)] | ^^^ -error: `#[id]` cannot be used with `#[salsa::input]` +error: `#[tracked]` cannot be used with `#[salsa::input]` --> tests/compile-fail/input_struct_incompatibles.rs:21:5 | -21 | / #[id] +21 | / #[tracked] 22 | | field: u32, | |______________^ -error: cannot find attribute `id` in this scope +error: cannot find attribute `tracked` in this scope --> tests/compile-fail/input_struct_incompatibles.rs:21:7 | -21 | #[id] - | ^^ +21 | #[tracked] + | ^^^^^^^ + | +help: consider importing one of these attribute macros + | +1 + use salsa::tracked; + | +1 + use salsa_macros::tracked; + | diff --git a/tests/compile-fail/interned_struct_incompatibles.rs b/tests/compile-fail/interned_struct_incompatibles.rs index 426fa9c5a..2ab050d6d 100644 --- a/tests/compile-fail/interned_struct_incompatibles.rs +++ b/tests/compile-fail/interned_struct_incompatibles.rs @@ -29,8 +29,8 @@ struct InternedWithLru { } #[salsa::interned] -struct InternedWithIdField { - #[id] +struct InternedWithTrackedField { + #[tracked] field: u32, } diff --git a/tests/compile-fail/interned_struct_incompatibles.stderr b/tests/compile-fail/interned_struct_incompatibles.stderr index 335a15360..d60890ede 100644 --- a/tests/compile-fail/interned_struct_incompatibles.stderr +++ b/tests/compile-fail/interned_struct_incompatibles.stderr @@ -34,15 +34,22 @@ error: `lru` option not allowed here 26 | #[salsa::interned(lru = 12)] | ^^^ -error: `#[id]` cannot be used with `#[salsa::interned]` +error: `#[tracked]` cannot be used with `#[salsa::interned]` --> tests/compile-fail/interned_struct_incompatibles.rs:33:5 | -33 | / #[id] +33 | / #[tracked] 34 | | field: u32, | |______________^ -error: cannot find attribute `id` in this scope +error: cannot find attribute `tracked` in this scope --> tests/compile-fail/interned_struct_incompatibles.rs:33:7 | -33 | #[id] - | ^^ +33 | #[tracked] + | ^^^^^^^ + | +help: consider importing one of these attribute macros + | +1 + use salsa::tracked; + | +1 + use salsa_macros::tracked; + | diff --git a/tests/deletion-drops.rs b/tests/deletion-drops.rs index 57811569c..2410aa346 100644 --- a/tests/deletion-drops.rs +++ b/tests/deletion-drops.rs @@ -14,9 +14,9 @@ struct MyInput { #[salsa::tracked] struct MyTracked<'db> { - #[id] identifier: u32, + #[tracked] #[return_ref] field: Bomb, } diff --git a/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs b/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs index fb62e1c5d..a03675e8b 100644 --- a/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs +++ b/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs @@ -28,7 +28,9 @@ fn final_result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] x: u32, + #[tracked] y: u32, } diff --git a/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs b/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs index a18447958..b5411e44c 100644 --- a/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs +++ b/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs @@ -26,6 +26,7 @@ fn result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("result_depends_on_y({:?})", input)); input.y(db) - 1 } + #[test] fn execute() { // result_depends_on_x = x + 1 diff --git a/tests/preverify-struct-with-leaked-data-2.rs b/tests/preverify-struct-with-leaked-data-2.rs index 5632e990f..66530343d 100644 --- a/tests/preverify-struct-with-leaked-data-2.rs +++ b/tests/preverify-struct-with-leaked-data-2.rs @@ -21,6 +21,7 @@ struct MyInput { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] counter: usize, } diff --git a/tests/preverify-struct-with-leaked-data.rs b/tests/preverify-struct-with-leaked-data.rs index f709890a0..90c7c51cc 100644 --- a/tests/preverify-struct-with-leaked-data.rs +++ b/tests/preverify-struct-with-leaked-data.rs @@ -21,6 +21,7 @@ struct MyInput { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] counter: usize, } diff --git a/tests/tracked-struct-id-field-bad-eq.rs b/tests/tracked-struct-id-field-bad-eq.rs index b003a3053..44deec6cb 100644 --- a/tests/tracked-struct-id-field-bad-eq.rs +++ b/tests/tracked-struct-id-field-bad-eq.rs @@ -28,7 +28,6 @@ impl From for BadEq { #[salsa::tracked] struct MyTracked<'db> { - #[id] field: BadEq, } diff --git a/tests/tracked-struct-id-field-bad-hash.rs b/tests/tracked-struct-id-field-bad-hash.rs index 8a391b3b6..78045299e 100644 --- a/tests/tracked-struct-id-field-bad-hash.rs +++ b/tests/tracked-struct-id-field-bad-hash.rs @@ -1,9 +1,9 @@ -//! Test for a tracked struct where the id field has a +//! Test for a tracked struct where an untracked field has a //! very poorly chosen hash impl (always returns 0). -//! This demonstrates that the `#[id]` fields on a struct +//! This demonstrates that the `untracked fields on a struct //! can change values and yet the struct can have the same //! id (because struct ids are based on the *hash* of the -//! `#[id]` fields). +//! untracked fields). use salsa::{Database as Db, Setter}; use test_log::test; @@ -32,7 +32,6 @@ impl std::hash::Hash for BadHash { #[salsa::tracked] struct MyTracked<'db> { - #[id] field: BadHash, } diff --git a/tests/tracked-struct-value-field-bad-eq.rs b/tests/tracked-struct-value-field-bad-eq.rs index f7444faea..3a02d63c5 100644 --- a/tests/tracked-struct-value-field-bad-eq.rs +++ b/tests/tracked-struct-value-field-bad-eq.rs @@ -33,6 +33,7 @@ impl From for BadEq { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] field: BadEq, } diff --git a/tests/tracked-struct-value-field-not-eq.rs b/tests/tracked-struct-value-field-not-eq.rs index eaf4a30c1..e37d4af9e 100644 --- a/tests/tracked-struct-value-field-not-eq.rs +++ b/tests/tracked-struct-value-field-not-eq.rs @@ -23,6 +23,7 @@ impl From for NotEq { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] #[no_eq] field: NotEq, } diff --git a/tests/tracked_struct_durability.rs b/tests/tracked_struct_durability.rs index f4ffaa8d3..a8c1706fc 100644 --- a/tests/tracked_struct_durability.rs +++ b/tests/tracked_struct_durability.rs @@ -32,21 +32,25 @@ struct File { #[salsa::tracked] struct Definition<'db> { + #[tracked] file: File, } #[salsa::tracked] struct Index<'db> { + #[tracked] definitions: Definitions<'db>, } #[salsa::tracked] struct Definitions<'db> { + #[tracked] definition: Definition<'db>, } #[salsa::tracked] struct Inference<'db> { + #[tracked] definition: Definition<'db>, } diff --git a/tests/tracked_struct_mixed_tracked_fields.rs b/tests/tracked_struct_mixed_tracked_fields.rs new file mode 100644 index 000000000..a5630c631 --- /dev/null +++ b/tests/tracked_struct_mixed_tracked_fields.rs @@ -0,0 +1,68 @@ +mod common; + +use salsa::{Database, Setter}; + +// A tracked struct with mixed tracked and untracked fields to ensure +// the correct field indices are used when tracking dependencies. +#[salsa::tracked] +struct Tracked<'db> { + untracked_1: usize, + + #[tracked] + tracked_1: usize, + + untracked_2: usize, + + untracked_3: usize, + + #[tracked] + tracked_2: usize, + + untracked_4: usize, +} + +#[salsa::input] +struct MyInput { + field1: usize, + field2: usize, +} + +#[salsa::tracked] +fn intermediate(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { + Tracked::new(db, 0, input.field1(db), 0, 0, input.field2(db), 0) +} + +#[salsa::tracked] +fn accumulate(db: &dyn salsa::Database, input: MyInput) -> (usize, usize) { + let tracked = intermediate(db, input); + let one = read_tracked_1(db, tracked); + let two = read_tracked_2(db, tracked); + + (one, two) +} + +#[salsa::tracked] +fn read_tracked_1<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { + tracked.tracked_1(db) +} + +#[salsa::tracked] +fn read_tracked_2<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { + tracked.tracked_2(db) +} + +#[test] +fn execute() { + let mut db = salsa::DatabaseImpl::default(); + let input = MyInput::new(&db, 1, 1); + + assert_eq!(accumulate(&db, input), (1, 1)); + + // Should only re-execute `read_tracked_1`. + input.set_field1(&mut db).to(2); + assert_eq!(accumulate(&db, input), (2, 1)); + + // Should only re-execute `read_tracked_2`. + input.set_field2(&mut db).to(2); + assert_eq!(accumulate(&db, input), (2, 2)); +} diff --git a/tests/tracked_with_intern.rs b/tests/tracked_with_intern.rs index a8a72e8c0..1b3a381d3 100644 --- a/tests/tracked_with_intern.rs +++ b/tests/tracked_with_intern.rs @@ -10,6 +10,7 @@ struct MyInput { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] field: MyInterned<'db>, } diff --git a/tests/tracked_with_struct_db.rs b/tests/tracked_with_struct_db.rs index 6c6d2ab84..670673a17 100644 --- a/tests/tracked_with_struct_db.rs +++ b/tests/tracked_with_struct_db.rs @@ -11,7 +11,9 @@ struct MyInput { #[salsa::tracked] struct MyTracked<'db> { + #[tracked] data: MyInput, + #[tracked] next: MyList<'db>, }