create tracked field-ingredients

This commit is contained in:
Niko Matsakis 2024-03-16 07:18:52 -04:00
parent 796dc004f2
commit 54b33c335a
18 changed files with 664 additions and 271 deletions

View file

@ -25,12 +25,8 @@
//! * data method `impl Foo { fn data(&self, db: &dyn crate::Db) -> FooData { FooData { f: self.f(db), ... } } }`
//! * this could be optimized, particularly for interned fields
use crate::{
configuration,
options::{AllowedOptions, Options},
};
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use crate::options::{AllowedOptions, Options};
use proc_macro2::{Ident, Span, TokenStream};
use syn::spanned::Spanned;
pub(crate) enum SalsaStructKind {
@ -217,69 +213,6 @@ impl<A: AllowedOptions> SalsaStruct<A> {
}
}
/// For each of the fields passed as an argument,
/// generate a struct named `Ident_Field` and an impl
/// of `salsa::function::Configuration` for that struct.
pub(crate) fn field_config_structs_and_impls<'a>(
&self,
fields: impl Iterator<Item = &'a SalsaField>,
) -> (Vec<syn::ItemStruct>, Vec<syn::ItemImpl>) {
let ident = &self.id_ident();
let jar_ty = self.jar_ty();
let visibility = self.visibility();
fields
.map(|ef| {
let value_field_name = ef.name();
let value_field_ty = ef.ty();
let value_field_backdate = ef.is_backdate_field();
let config_name = syn::Ident::new(
&format!(
"__{}",
format!("{}_{}", ident, value_field_name).to_upper_camel_case()
),
value_field_name.span(),
);
let item_struct: syn::ItemStruct = parse_quote! {
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
#visibility struct #config_name(std::convert::Infallible);
};
let execute_string = Literal::string(&format!("`execute` method for field `{}::{}` invoked",
ident,
ef.name(),
));
let recover_from_cycle_string = Literal::string(&format!("`execute` method for field `{}::{}` invoked",
ident,
ef.name(),
));
let should_backdate_value_fn = configuration::should_backdate_value_fn(value_field_backdate);
let item_impl: syn::ItemImpl = parse_quote! {
impl salsa::function::Configuration for #config_name {
type Jar = #jar_ty;
type SalsaStruct = #ident;
type Key = #ident;
type Value = #value_field_ty;
const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = salsa::cycle::CycleRecoveryStrategy::Panic;
#should_backdate_value_fn
fn execute(db: &salsa::function::DynDb<Self>, key: Self::Key) -> Self::Value {
panic!(#execute_string)
}
fn recover_from_cycle(db: &salsa::function::DynDb<Self>, cycle: &salsa::Cycle, key: Self::Key) -> Self::Value {
panic!(#recover_from_cycle_string)
}
}
};
(item_struct, item_impl)
})
.unzip()
}
/// Generate `impl salsa::AsId for Foo`
pub(crate) fn as_id_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
@ -435,6 +368,10 @@ impl SalsaField {
Ok(result)
}
pub(crate) fn span(&self) -> Span {
self.field.span()
}
/// The name of this field (all `SalsaField` instances are named).
pub(crate) fn name(&self) -> &syn::Ident {
self.field.ident.as_ref().unwrap()

View file

@ -1,4 +1,4 @@
use proc_macro2::{Literal, TokenStream};
use proc_macro2::{Literal, Span, TokenStream};
use crate::salsa_struct::{SalsaField, SalsaStruct, SalsaStructKind};
@ -51,18 +51,18 @@ impl TrackedStruct {
fn generate_tracked(&self) -> syn::Result<TokenStream> {
self.validate_tracked()?;
let (config_structs, config_impls) =
self.field_config_structs_and_impls(self.value_fields());
let id_struct = self.id_struct();
let config_struct = self.config_struct();
let config_impl = self.config_impl(&config_struct);
let inherent_impl = self.tracked_inherent_impl();
let ingredients_for_impl = self.tracked_struct_ingredients(&config_structs);
let ingredients_for_impl = self.tracked_struct_ingredients(&config_struct);
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl();
let as_id_impl = self.as_id_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#(#config_structs)*
#config_struct
#config_impl
#id_struct
#inherent_impl
#ingredients_for_impl
@ -70,7 +70,6 @@ impl TrackedStruct {
#tracked_struct_in_db_impl
#as_id_impl
#as_debug_with_db_impl
#(#config_impls)*
})
}
@ -78,27 +77,103 @@ impl TrackedStruct {
Ok(())
}
fn config_struct(&self) -> syn::ItemStruct {
let config_ident = syn::Ident::new(
&format!("__{}Config", self.id_ident()),
self.id_ident().span(),
);
let visibility = self.visibility();
parse_quote! {
#visibility struct #config_ident {
_uninhabited: std::convert::Infallible,
}
}
}
fn config_impl(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl {
let id_ident = self.id_ident();
let config_ident = &config_struct.ident;
let field_tys: Vec<_> = self.all_fields().map(SalsaField::ty).collect();
let id_field_indices = self.id_field_indices();
let arity = self.all_field_count();
// Create the function body that will update the revisions for each field.
// If a field is a "backdate field" (the default), then we first check if
// the new value is `==` to the old value. If so, we leave the revision unchanged.
let old_value = syn::Ident::new("old_value_", Span::call_site());
let new_value = syn::Ident::new("new_value_", Span::call_site());
let revisions = syn::Ident::new("revisions_", Span::call_site());
let current_revision = syn::Ident::new("current_revision_", Span::call_site());
let update_revisions: TokenStream = self
.all_fields()
.zip(0..)
.map(|(field, i)| {
let field_index = Literal::u32_unsuffixed(i);
if field.is_backdate_field() {
quote_spanned! { field.span() =>
if #old_value.#field_index != #new_value.#field_index {
#revisions[#field_index] = #current_revision;
}
}
} else {
quote_spanned! { field.span() =>
#revisions[#field_index] = #current_revision;
}
}
})
.collect();
parse_quote! {
impl salsa::tracked_struct::Configuration for #config_ident {
type Id = #id_ident;
type Fields = ( #(#field_tys,)* );
type Revisions = [salsa::Revision; #arity];
fn id_fields(fields: &Self::Fields) -> impl std::hash::Hash {
( #( &fields.#id_field_indices ),* )
}
fn revision(revisions: &Self::Revisions, field_index: u32) -> salsa::Revision {
revisions[field_index as usize]
}
fn new_revisions(current_revision: salsa::Revision) -> Self::Revisions {
[current_revision; #arity]
}
fn update_revisions(
#current_revision: salsa::Revision,
#old_value: &Self::Fields,
#new_value: &Self::Fields,
#revisions: &mut Self::Revisions,
) {
#update_revisions
}
}
}
}
/// Generate an inherent impl with methods on the tracked type.
fn tracked_inherent_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let db_dyn_ty = self.db_dyn_ty();
let struct_index = self.tracked_struct_index();
let tracked_field_ingredients: Literal = self.tracked_field_ingredients_index();
let id_field_indices: Vec<_> = self.id_field_indices();
let id_field_names: Vec<_> = self.id_fields().map(SalsaField::name).collect();
let id_field_get_names: Vec<_> = self.id_fields().map(SalsaField::get_name).collect();
let id_field_tys: Vec<_> = self.id_fields().map(SalsaField::ty).collect();
let id_field_vises: Vec<_> = self.id_fields().map(SalsaField::vis).collect();
let id_field_clones: Vec<_> = self.id_fields().map(SalsaField::is_clone_field).collect();
let id_field_getters: Vec<syn::ImplItemMethod> = id_field_indices.iter().zip(&id_field_get_names).zip(&id_field_tys).zip(&id_field_vises).zip(&id_field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
let field_indices = self.all_field_indices();
let field_vises: Vec<_> = self.all_fields().map(SalsaField::vis).collect();
let field_tys: Vec<_> = self.all_fields().map(SalsaField::ty).collect();
let field_get_names: Vec<_> = self.all_fields().map(SalsaField::get_name).collect();
let field_clones: Vec<_> = self.all_fields().map(SalsaField::is_clone_field).collect();
let field_getters: Vec<syn::ImplItemMethod> = field_indices.iter().zip(&field_get_names).zip(&field_tys).zip(&field_vises).zip(&field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
&__ingredients.#struct_index.tracked_struct_data(__runtime, self).#field_index
&__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index
}
}
} else {
@ -107,65 +182,31 @@ impl TrackedStruct {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#struct_index.tracked_struct_data(__runtime, self).#field_index.clone()
__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone()
}
}
}
)
.collect();
let value_field_indices = self.value_field_indices();
let value_field_names: Vec<_> = self.value_fields().map(SalsaField::name).collect();
let value_field_vises: Vec<_> = self.value_fields().map(SalsaField::vis).collect();
let value_field_tys: Vec<_> = self.value_fields().map(SalsaField::ty).collect();
let value_field_get_names: Vec<_> = self.value_fields().map(SalsaField::get_name).collect();
let value_field_clones: Vec<_> = self
.value_fields()
.map(SalsaField::is_clone_field)
.collect();
let value_field_getters: Vec<syn::ImplItemMethod> = value_field_indices.iter().zip(&value_field_get_names).zip(&value_field_tys).zip(&value_field_vises).zip(&value_field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__db, self)
}
}
} else {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__db, self).clone()
}
}
}
)
.collect();
let all_field_names = self.all_field_names();
let all_field_tys = self.all_field_tys();
let field_names = self.all_field_names();
let field_tys = self.all_field_tys();
let constructor_name = self.constructor_name();
parse_quote! {
impl #ident {
pub fn #constructor_name(__db: &#db_dyn_ty, #(#all_field_names: #all_field_tys,)*) -> Self
pub fn #constructor_name(__db: &#db_dyn_ty, #(#field_names: #field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
let __id = __ingredients.#struct_index.new_struct(__runtime, (#(#id_field_names,)*));
#(
__ingredients.#value_field_indices.specify_and_record(__db, __id, #value_field_names);
)*
let __id = __ingredients.0.new_struct(
__runtime,
(#(#field_names,)*),
);
__id
}
#(#id_field_getters)*
#(#value_field_getters)*
#(#field_getters)*
}
}
}
@ -174,14 +215,15 @@ impl TrackedStruct {
///
/// The tracked struct's ingredients include both the main tracked struct ingredient along with a
/// function ingredient for each of the value fields.
fn tracked_struct_ingredients(&self, config_structs: &[syn::ItemStruct]) -> syn::ItemImpl {
fn tracked_struct_ingredients(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl {
use crate::literal;
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let id_field_tys: Vec<&syn::Type> = self.id_fields().map(SalsaField::ty).collect();
let value_field_indices: Vec<Literal> = self.value_field_indices();
let tracked_struct_index: Literal = self.tracked_struct_index();
let config_struct_names = config_structs.iter().map(|s| &s.ident);
let config_struct_name = &config_struct.ident;
let field_indices: Vec<Literal> = self.all_field_indices();
let arity = self.all_field_count();
let tracked_struct_ingredient: Literal = self.tracked_struct_ingredient_index();
let tracked_fields_ingredients: Literal = self.tracked_field_ingredients_index();
let debug_name_struct = literal(self.id_ident());
let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect();
@ -189,10 +231,8 @@ impl TrackedStruct {
impl salsa::storage::IngredientsFor for #ident {
type Jar = #jar_ty;
type Ingredients = (
#(
salsa::function::FunctionIngredient<#config_struct_names>,
)*
salsa::tracked_struct::TrackedStructIngredient<#ident, (#(#id_field_tys,)*)>,
salsa::tracked_struct::TrackedStructIngredient<#config_struct_name>,
[salsa::tracked_struct::TrackedFieldIngredient<#config_struct_name>; #arity],
);
fn create_ingredients<DB>(
@ -201,40 +241,43 @@ impl TrackedStruct {
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
(
let struct_ingredient = {
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#tracked_struct_ingredient
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#tracked_struct_ingredient
},
);
salsa::tracked_struct::TrackedStructIngredient::new(index, #debug_name_struct)
};
let field_ingredients = [
#(
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#value_field_indices
&ingredients.#tracked_fields_ingredients[#field_indices]
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#value_field_indices
&mut ingredients.#tracked_fields_ingredients[#field_indices]
},
);
salsa::function::FunctionIngredient::new(index, #debug_name_fields)
struct_ingredient.new_field_ingredient(index, #field_indices, #debug_name_fields)
},
)*
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#tracked_struct_index
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#tracked_struct_index
},
);
salsa::tracked_struct::TrackedStructIngredient::new(index, #debug_name_struct)
},
)
];
(struct_ingredient, field_ingredients)
}
}
}
@ -244,7 +287,7 @@ impl TrackedStruct {
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let tracked_struct_index: Literal = self.tracked_struct_index();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote! {
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
where
@ -253,7 +296,7 @@ impl TrackedStruct {
fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
ingredients.#tracked_struct_index.register_dependent_fn(index)
ingredients.#tracked_struct_ingredient.register_dependent_fn(index)
}
}
}
@ -263,7 +306,7 @@ impl TrackedStruct {
fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let tracked_struct_index = self.tracked_struct_index();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote! {
impl<DB> salsa::tracked_struct::TrackedStructInDb<DB> for #ident
where
@ -272,46 +315,44 @@ impl TrackedStruct {
fn database_key_index(self, db: &DB) -> salsa::DatabaseKeyIndex {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
ingredients.#tracked_struct_index.database_key_index(self)
ingredients.#tracked_struct_ingredient.database_key_index(self)
}
}
}
}
/// List of id fields (fields that are part of the tracked struct's identity across revisions).
///
/// If this is an enum, empty iterator.
fn id_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.all_fields().filter(|ef| ef.is_id_field())
/// The index of the tracked struct ingredient in the ingredient tuple.
fn tracked_struct_ingredient_index(&self) -> Literal {
Literal::usize_unsuffixed(0)
}
/// List of value fields (fields that are not part of the tracked struct's identity across revisions).
///
/// If this is an enum, empty iterator.
fn value_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.all_fields().filter(|ef| !ef.is_id_field())
/// The index of the tracked field ingredients array in the ingredient tuple.
fn tracked_field_ingredients_index(&self) -> Literal {
Literal::usize_unsuffixed(1)
}
/// For this struct, we create a tuple that contains the function ingredients
/// for each "other" field and the tracked-struct ingredient. This is the index of
/// the entity ingredient within that tuple.
fn tracked_struct_index(&self) -> Literal {
Literal::usize_unsuffixed(self.value_fields().count())
/// for each field and the tracked-struct ingredient. These are the indices
/// of the function ingredients within that tuple.
fn all_field_indices(&self) -> Vec<Literal> {
(0..self.all_fields().count())
.map(Literal::usize_unsuffixed)
.collect()
}
/// For this struct, we create a tuple that contains the function ingredients
/// for each "other" field and the tracked-struct ingredient. These are the indices
/// of the function ingredients within that tuple.
fn value_field_indices(&self) -> Vec<Literal> {
(0..self.value_fields().count())
.map(Literal::usize_unsuffixed)
.collect()
fn all_field_count(&self) -> Literal {
Literal::usize_unsuffixed(self.all_fields().count())
}
/// Indices of each of the id fields
fn id_field_indices(&self) -> Vec<Literal> {
(0..self.id_fields().count())
.map(Literal::usize_unsuffixed)
self.all_fields()
.zip(0..)
.filter(|(field, _)| field.is_id_field())
.map(|(_, index)| Literal::usize_unsuffixed(index))
.collect()
}
}

View file

@ -22,7 +22,6 @@ pub mod runtime;
pub mod salsa_struct;
pub mod setter;
pub mod storage;
#[doc(hidden)]
pub mod tracked_struct;
pub use self::cancelled::Cancelled;
@ -43,8 +42,6 @@ pub use self::routes::IngredientIndex;
pub use self::runtime::Runtime;
pub use self::storage::DbWithJar;
pub use self::storage::Storage;
pub use self::tracked_struct::TrackedStructData;
pub use self::tracked_struct::TrackedStructId;
pub use salsa_2022_macros::accumulator;
pub use salsa_2022_macros::db;
pub use salsa_2022_macros::input;

View file

@ -166,13 +166,16 @@ impl Runtime {
/// * Add a query read on `DatabaseKeyIndex::for_table(entity_index)`
/// * Identify a unique disambiguator for the hash within the current query,
/// adding the hash to the current query's disambiguator table.
/// * Return that hash + id of the current query.
/// * Returns a tuple of:
/// * the id of the current query
/// * the current dependencies (durability, changed_at) of current query
/// * the disambiguator index
pub(crate) fn disambiguate_entity(
&self,
entity_index: IngredientIndex,
reset_at: Revision,
data_hash: u64,
) -> (DatabaseKeyIndex, Disambiguator) {
) -> (DatabaseKeyIndex, StampedValue<()>, Disambiguator) {
self.report_tracked_read(
DependencyIndex::for_table(entity_index),
Durability::MAX,

View file

@ -290,7 +290,10 @@ impl LocalState {
}
#[track_caller]
pub(crate) fn disambiguate(&self, data_hash: u64) -> (DatabaseKeyIndex, Disambiguator) {
pub(crate) fn disambiguate(
&self,
data_hash: u64,
) -> (DatabaseKeyIndex, StampedValue<()>, Disambiguator) {
assert!(
self.query_in_progress(),
"cannot create a tracked struct disambiguator outside of a tracked function"
@ -298,7 +301,15 @@ impl LocalState {
self.with_query_stack(|stack| {
let top_query = stack.last_mut().unwrap();
let disambiguator = top_query.disambiguate(data_hash);
(top_query.database_key_index, disambiguator)
(
top_query.database_key_index,
StampedValue {
value: (),
durability: top_query.durability,
changed_at: top_query.changed_at,
},
disambiguator,
)
})
}
}

View file

@ -1,13 +1,14 @@
use std::fmt;
use std::{fmt, hash::Hash, sync::Arc};
use crossbeam::queue::SegQueue;
use crate::{
cycle::CycleRecoveryStrategy,
hash::FxDashMap,
id::AsId,
ingredient::{fmt_index, Ingredient, IngredientRequiresReset},
ingredient_list::IngredientList,
interned::{InternedData, InternedId, InternedIngredient},
interned::{InternedId, InternedIngredient},
key::{DatabaseKeyIndex, DependencyIndex},
plumbing::transmute_lifetime,
runtime::{local_state::QueryOrigin, Runtime},
@ -15,11 +16,49 @@ use crate::{
Database, Durability, Event, IngredientIndex, Revision,
};
pub trait TrackedStructId: InternedId {}
impl<T: InternedId> TrackedStructId for T {}
pub use self::tracked_field::TrackedFieldIngredient;
pub trait TrackedStructData: InternedData {}
impl<T: InternedData> TrackedStructData for T {}
mod tracked_field;
// ANCHOR: Configuration
/// Trait that defines the key properties of a tracked struct.
/// Implemented by the `#[salsa::tracked]` macro when applied
/// to a struct.
pub trait Configuration {
/// The id type used to define instances of this struct.
/// The [`TrackedStructIngredient`][] contains the interner
/// that will create the id values.
type Id: InternedId;
/// A (possibly empty) tuple of the fields for this struct.
type Fields;
/// A array of [`Revision`][] values, one per each of the value fields.
/// When a struct is re-recreated in a new revision, the
type Revisions;
fn id_fields(fields: &Self::Fields) -> impl Hash;
/// Access the revision of a given value field.
/// `field_index` will be between 0 and the number of value fields.
fn revision(revisions: &Self::Revisions, field_index: u32) -> Revision;
/// Create a new value revision array where each element is set to `current_revision`.
fn new_revisions(current_revision: Revision) -> Self::Revisions;
/// Update an existing value revision array `revisions`,
/// given the tuple of the old values (`old_value`)
/// and the tuple of the values (`new_value`).
/// If a value has changed, then its element is
/// updated to `current_revision`.
fn update_revisions(
current_revision: Revision,
old_value: &Self::Fields,
new_value: &Self::Fields,
revisions: &mut Self::Revisions,
);
}
// ANCHOR_END: Configuration
pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
/// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`.
@ -34,14 +73,13 @@ pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
/// Unlike normal interners, tracked struct indices can be deleted and reused aggressively:
/// when a tracked function re-executes,
/// any tracked structs that it created before but did not create this time can be deleted.
pub struct TrackedStructIngredient<Id, Data>
pub struct TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
C: Configuration,
{
interned: InternedIngredient<Id, TrackedStructKey>,
interned: InternedIngredient<C::Id, TrackedStructKey>,
entity_data: FxDashMap<Id, Box<TrackedStructValue<Data>>>,
entity_data: Arc<FxDashMap<C::Id, Box<TrackedStructValue<C>>>>,
/// A list of each tracked function `f` whose key is this
/// tracked struct.
@ -56,7 +94,7 @@ where
/// references to that data floating about that are tied to the lifetime of some
/// `&db` reference. This queue itself is not freed until we have an `&mut db` reference,
/// guaranteeing that there are no more references to it.
deleted_entries: SegQueue<Box<TrackedStructValue<Data>>>,
deleted_entries: SegQueue<Box<TrackedStructValue<C>>>,
debug_name: &'static str,
}
@ -68,40 +106,81 @@ struct TrackedStructKey {
data_hash: u64,
}
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
struct TrackedStructValue<Data> {
// ANCHOR: TrackedStructValue
#[derive(Debug)]
struct TrackedStructValue<C>
where
C: Configuration,
{
/// The durability minimum durability of all inputs consumed
/// by the creator query prior to creating this tracked struct.
/// If any of those inputs changes, then the creator query may
/// create this struct with different values.
durability: Durability,
/// The revision when this entity was most recently created.
/// Typically the current revision.
/// Used to detect "leaks" outside of the salsa system -- i.e.,
/// access to tracked structs that have not (yet?) been created in the
/// current revision. This should be impossible within salsa queries
/// but it can happen through "leaks" like thread-local data or storing
/// values outside of the root salsa query.
created_at: Revision,
data: Data,
/// Fields of this tracked struct. They can change across revisions,
/// but they do not change within a particular revision.
fields: C::Fields,
/// The revision information for each field: when did this field last change.
/// When tracked structs are re-created, this revision may be updated to the
/// current revision if the value is different.
revisions: C::Revisions,
}
// ANCHOR_END: TrackedStructValue
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
pub struct Disambiguator(pub u32);
impl<Id, Data> TrackedStructIngredient<Id, Data>
impl<C> TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
C: Configuration,
{
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
Self {
interned: InternedIngredient::new(index, debug_name),
entity_data: FxDashMap::default(),
entity_data: Default::default(),
dependent_fns: IngredientList::new(),
deleted_entries: SegQueue::default(),
debug_name,
}
}
pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex {
pub fn new_field_ingredient(
&self,
field_ingredient_index: IngredientIndex,
field_index: u32,
field_debug_name: &'static str,
) -> TrackedFieldIngredient<C> {
TrackedFieldIngredient {
ingredient_index: field_ingredient_index,
field_index,
entity_data: self.entity_data.clone(),
struct_debug_name: self.debug_name,
field_debug_name,
}
}
pub fn database_key_index(&self, id: C::Id) -> DatabaseKeyIndex {
DatabaseKeyIndex {
ingredient_index: self.interned.ingredient_index(),
key_index: id.as_id(),
}
}
pub fn new_struct(&self, runtime: &Runtime, data: Data) -> Id {
let data_hash = crate::hash::hash(&data);
let (query_key, disambiguator) = runtime.disambiguate_entity(
pub fn new_struct(&self, runtime: &Runtime, fields: C::Fields) -> C::Id {
let data_hash = crate::hash::hash(&C::id_fields(&fields));
let (query_key, current_deps, disambiguator) = runtime.disambiguate_entity(
self.interned.ingredient_index(),
self.interned.reset_at(),
data_hash,
@ -112,33 +191,40 @@ where
disambiguator,
data_hash,
};
let (result, new_id) = self.interned.intern_full(runtime, entity_key);
runtime.add_output(self.database_key_index(result).into());
let (id, new_id) = self.interned.intern_full(runtime, entity_key);
runtime.add_output(self.database_key_index(id).into());
let current_revision = runtime.current_revision();
if new_id {
let created_at = runtime.current_revision();
self.entity_data
.insert(result, Box::new(TrackedStructValue { created_at, data }));
let old_value = self.entity_data.insert(
id,
Box::new(TrackedStructValue {
created_at: current_revision,
durability: current_deps.durability,
fields,
revisions: C::new_revisions(current_deps.changed_at),
}),
);
assert!(old_value.is_none());
} else {
let mut data = self.entity_data.get_mut(&id).unwrap();
let data = &mut *data;
if current_deps.durability < data.durability {
data.revisions = C::new_revisions(current_revision);
} else {
C::update_revisions(current_revision, &data.fields, &fields, &mut data.revisions);
}
data.created_at = current_revision;
data.durability = current_deps.durability;
// Subtle but important: we *always* update the values of the fields,
// even if they are `==` to the old values. This is because the `==`
// operation might not mean tha tthe fields are bitwise equal, and we
// want to take the new value.
data.fields = fields;
}
result
}
pub fn tracked_struct_data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db Data {
let Some(data) = self.entity_data.get(&id) else {
panic!("no data found for entity id {id:?}");
};
runtime.report_tracked_read(
DependencyIndex::for_table(self.interned.ingredient_index()),
Durability::MAX,
data.created_at,
);
// Unsafety clause:
//
// * Values are only removed or altered when we have `&mut self`
unsafe { transmute_lifetime(self, &data.data) }
id
}
/// Deletes the given entities. This is used after a query `Q` executes and we can compare
@ -151,7 +237,7 @@ where
/// Using this method on an entity id that MAY be used in the current revision will lead to
/// unspecified results (but not UB). See [`InternedIngredient::delete_index`] for more
/// discussion and important considerations.
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: Id) {
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: C::Id) {
db.salsa_event(Event {
runtime_id: db.runtime().id(),
kind: crate::EventKind::DidDiscard {
@ -177,11 +263,10 @@ where
}
}
impl<DB: ?Sized, Id, Data> Ingredient<DB> for TrackedStructIngredient<Id, Data>
impl<DB: ?Sized, C> Ingredient<DB> for TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
DB: crate::Database,
DB: Database,
C: Configuration,
{
fn ingredient_index(&self) -> IngredientIndex {
self.interned.ingredient_index()
@ -218,7 +303,7 @@ where
// `executor` creates a tracked struct `salsa_output_key`,
// but it did not in the current revision.
// In that case, we can delete `stale_output_key` and any data associated with it.
let stale_output_key: Id = Id::from_id(stale_output_key.unwrap());
let stale_output_key: C::Id = <C::Id>::from_id(stale_output_key.unwrap());
self.delete_entity(db.as_salsa_database(), stale_output_key);
}
@ -236,10 +321,9 @@ where
}
}
impl<Id, Data> IngredientRequiresReset for TrackedStructIngredient<Id, Data>
impl<C> IngredientRequiresReset for TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
C: Configuration,
{
const RESET_ON_NEW_REVISION: bool = true;
}

View file

@ -0,0 +1,153 @@
use std::sync::Arc;
use crate::{
hash::FxDashMap,
id::AsId,
ingredient::{Ingredient, IngredientRequiresReset},
key::DependencyIndex,
plumbing::transmute_lifetime,
tracked_struct::TrackedStructValue,
IngredientIndex, Runtime,
};
use super::Configuration;
/// Created for each tracked struct.
/// This ingredient only stores the "id" fields.
/// It is a kind of "dressed up" interner;
/// the active query + values of id fields are hashed to create the tracked struct id.
/// The value fields are stored in [`crate::function::FunctionIngredient`] instances keyed by the tracked struct id.
/// Unlike normal interners, tracked struct indices can be deleted and reused aggressively:
/// when a tracked function re-executes,
/// any tracked structs that it created before but did not create this time can be deleted.
pub struct TrackedFieldIngredient<C>
where
C: Configuration,
{
/// Index of this ingredient in the database (used to construct database-ids, etc).
pub(super) ingredient_index: IngredientIndex,
pub(super) field_index: u32,
pub(super) entity_data: Arc<FxDashMap<C::Id, Box<TrackedStructValue<C>>>>,
pub(super) struct_debug_name: &'static str,
pub(super) field_debug_name: &'static str,
}
impl<C> TrackedFieldIngredient<C>
where
C: Configuration,
{
/// Access to this value 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>(&'db self, runtime: &'db Runtime, id: C::Id) -> &'db C::Fields {
let Some(data) = self.entity_data.get(&id) else {
panic!("no data found for entity id {id:?}");
};
let current_revision = runtime.current_revision();
let created_at = data.created_at;
assert!(
created_at == current_revision,
"access to tracked struct from previous revision"
);
let changed_at = C::revision(&data.revisions, self.field_index);
runtime.report_tracked_read(
DependencyIndex {
ingredient_index: self.ingredient_index,
key_index: Some(id.as_id()),
},
data.durability,
changed_at,
);
// Unsafety clause:
//
// * Values are only removed or altered when we have `&mut self`
unsafe { transmute_lifetime(self, &data.fields) }
}
}
impl<DB: ?Sized, C> Ingredient<DB> for TrackedFieldIngredient<C>
where
C: Configuration,
{
fn ingredient_index(&self) -> IngredientIndex {
self.ingredient_index
}
fn cycle_recovery_strategy(&self) -> crate::cycle::CycleRecoveryStrategy {
crate::cycle::CycleRecoveryStrategy::Panic
}
fn maybe_changed_after(
&self,
_db: &DB,
input: crate::key::DependencyIndex,
revision: crate::Revision,
) -> bool {
let id = <C::Id>::from_id(input.key_index.unwrap());
eprintln!("maybe_changed_after({id:?}, {revision:?})");
match self.entity_data.get(&id) {
Some(data) => {
let field_changed_at = C::revision(&data.revisions, self.field_index);
field_changed_at > revision
}
None => {
panic!("no data found for field `{id:?}`");
}
}
}
fn origin(&self, _key_index: crate::Id) -> Option<crate::runtime::local_state::QueryOrigin> {
None
}
fn mark_validated_output(
&self,
_db: &DB,
_executor: crate::DatabaseKeyIndex,
_output_key: Option<crate::Id>,
) {
panic!("tracked field ingredients have no outputs")
}
fn remove_stale_output(
&self,
_db: &DB,
_executor: crate::DatabaseKeyIndex,
_stale_output_key: Option<crate::Id>,
) {
panic!("tracked field ingredients have no outputs")
}
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
panic!("tracked field ingredients are not registered as dependent")
}
fn reset_for_new_revision(&mut self) {
panic!("tracked field ingredients do not require reset")
}
fn fmt_index(
&self,
index: Option<crate::Id>,
fmt: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(
fmt,
"{}.{}({:?})",
self.struct_debug_name,
self.field_debug_name,
index.unwrap()
)
}
}
impl<C> IngredientRequiresReset for TrackedFieldIngredient<C>
where
C: Configuration,
{
const RESET_ON_NEW_REVISION: bool = false;
}

View file

@ -9,6 +9,7 @@ edition = "2021"
derive-new = "0.5.9"
salsa = { path = "../../components/salsa-2022", package = "salsa-2022" }
ordered-float = "3.0"
test-log = { version = "0.2.15", features = ["trace"] }
[dev-dependencies]
expect-test = "1.4.0"
expect-test = "1.4.0"

View file

@ -38,6 +38,7 @@ impl Database {
// ANCHOR: db_impl
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
eprintln!("Event: {event:?}");
// Log interesting events, if logging is enabled
if let Some(logs) = &self.logs {
// don't log boring events

View file

@ -5,6 +5,8 @@ use crate::ir::{
use derive_new::new;
#[cfg(test)]
use expect_test::expect;
#[cfg(test)]
use test_log::test;
// ANCHOR: parse_statements
#[salsa::tracked]

View file

@ -54,11 +54,3 @@ error[E0412]: cannot find type `tracked_fn_with_receiver_not_applied_to_impl_blo
|
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

@ -120,12 +120,9 @@ fn basic() {
"intermediate_result(MyInput(Id { value: 1 }))",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })",
"salsa_event(DidDiscard { key: MyTracked(2) })",
"salsa_event(DidDiscard { key: field(2) })",
"salsa_event(DidDiscard { key: contribution_from_struct(2) })",
"salsa_event(DidDiscard { key: MyTracked(5) })",
"salsa_event(DidDiscard { key: field(5) })",
"salsa_event(DidDiscard { key: copy_field(5) })",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: field(2) })",
"final_result(MyInput(Id { value: 1 }))",
]"#]]);
}

View file

@ -106,9 +106,7 @@ fn basic() {
"intermediate_result(MyInput(Id { value: 1 }))",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })",
"salsa_event(DidDiscard { key: MyTracked(2) })",
"salsa_event(DidDiscard { key: field(2) })",
"salsa_event(DidDiscard { key: contribution_from_struct(2) })",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: field(2) })",
"final_result(MyInput(Id { value: 1 }))",
]"#]]);
}

View file

@ -31,7 +31,7 @@ impl salsa::Database for Database {}
impl Db for Database {}
#[test]
#[should_panic(expected = "`execute` method for field")]
#[should_panic(expected = "access to tracked struct from previous revision")]
fn execute() {
let mut db = Database::default();

View file

@ -147,7 +147,6 @@ fn test_run_10() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
@ -171,7 +170,6 @@ fn test_run_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
@ -209,7 +207,6 @@ fn test_run_0_then_5_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -229,7 +226,6 @@ fn test_run_0_then_5_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
@ -237,7 +233,6 @@ fn test_run_0_then_5_then_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -282,7 +277,6 @@ fn test_run_0_then_5_then_10_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -302,7 +296,6 @@ fn test_run_0_then_5_then_10_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
@ -310,7 +303,6 @@ fn test_run_0_then_5_then_10_then_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: read_maybe_specified(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: final_result(0) } }",
]"#]]);
@ -324,15 +316,12 @@ fn test_run_0_then_5_then_10_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -369,7 +358,6 @@ fn test_run_5_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
@ -377,7 +365,6 @@ fn test_run_5_then_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",

View file

@ -0,0 +1,64 @@
//! Test a field whose `PartialEq` impl is always true.
//! This can our "last changed" data to be wrong
//! but we *should* always reflect the final values.
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, the_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: bool,
}
#[derive(Eq, Hash, Debug, Clone)]
struct BadEq {
field: bool,
}
impl PartialEq for BadEq {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl From<bool> for BadEq {
fn from(value: bool) -> Self {
Self { field: value }
}
}
#[salsa::tracked]
struct MyTracked {
#[id]
field: BadEq,
}
#[salsa::tracked]
fn the_fn(db: &dyn Db, input: MyInput) {
let tracked0 = MyTracked::new(db, BadEq::from(input.field(db)));
assert_eq!(tracked0.field(db).field, input.field(db));
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn execute() {
let mut db = Database::default();
let input = MyInput::new(&db, true);
the_fn(&db, input);
input.set_field(&mut db).to(false);
the_fn(&db, input);
}

View file

@ -0,0 +1,58 @@
//! Test a field whose `PartialEq` impl is always true.
//! This can our "last changed" data to be wrong
//! but we *should* always reflect the final values.
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, the_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: bool,
}
#[derive(Hash, Debug, Clone)]
struct NotEq {
field: bool,
}
impl From<bool> for NotEq {
fn from(value: bool) -> Self {
Self { field: value }
}
}
#[salsa::tracked]
struct MyTracked {
#[no_eq]
field: NotEq,
}
#[salsa::tracked]
fn the_fn(db: &dyn Db, input: MyInput) {
let tracked0 = MyTracked::new(db, NotEq::from(input.field(db)));
assert_eq!(tracked0.field(db).field, input.field(db));
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn execute() {
let mut db = Database::default();
let input = MyInput::new(&db, true);
the_fn(&db, input);
input.set_field(&mut db).to(false);
the_fn(&db, input);
}

View file

@ -0,0 +1,67 @@
//! Test for a tracked struct where the id field has a
//! very poorly chosen hash impl (always returns 0).
//! This demonstrates that the `#[id]` 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).
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, the_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: bool,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)]
struct BadHash {
field: bool,
}
impl From<bool> for BadHash {
fn from(value: bool) -> Self {
Self { field: value }
}
}
impl std::hash::Hash for BadHash {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_i16(0);
}
}
#[salsa::tracked]
struct MyTracked {
#[id]
field: BadHash,
}
#[salsa::tracked]
fn the_fn(db: &dyn Db, input: MyInput) {
let tracked0 = MyTracked::new(db, BadHash::from(input.field(db)));
assert_eq!(tracked0.field(db).field, input.field(db));
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn execute() {
let mut db = Database::default();
let input = MyInput::new(&db, true);
the_fn(&db, input);
input.set_field(&mut db).to(false);
the_fn(&db, input);
}