mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-02-02 09:46:06 +00:00
create tracked field-ingredients
This commit is contained in:
parent
796dc004f2
commit
54b33c335a
18 changed files with 664 additions and 271 deletions
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
153
components/salsa-2022/src/tracked_struct/tracked_field.rs
Normal file
153
components/salsa-2022/src/tracked_struct/tracked_field.rs
Normal 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;
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }))",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -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 }))",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 }",
|
||||
|
|
64
salsa-2022-tests/tests/tracked-struct-field-bad-eq.rs
Normal file
64
salsa-2022-tests/tests/tracked-struct-field-bad-eq.rs
Normal 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);
|
||||
}
|
58
salsa-2022-tests/tests/tracked-struct-field-not-eq.rs
Normal file
58
salsa-2022-tests/tests/tracked-struct-field-not-eq.rs
Normal 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);
|
||||
}
|
67
salsa-2022-tests/tests/tracked-struct-id-field-bad-hash.rs
Normal file
67
salsa-2022-tests/tests/tracked-struct-id-field-bad-hash.rs
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue