This commit is contained in:
Niko Matsakis 2024-07-15 07:46:23 -04:00
parent 57eb0c45b4
commit fdc363b65f
43 changed files with 939 additions and 3213 deletions

View file

@ -14,8 +14,9 @@
mod maybe_backdate;
mod maybe_clone;
mod setup_input;
mod setup_input_struct;
mod setup_interned_fn;
mod setup_interned_struct;
mod setup_struct_fn;
mod setup_tracked_struct;
mod unexpected_cycle_recovery;

View file

@ -2,7 +2,7 @@
#[macro_export]
macro_rules! maybe_backdate {
(
($maybe_clone:ident, no_backdate)
($maybe_clone:ident, no_backdate),
$field_ty:ty,
$old_field_place:expr,
$new_field_place:expr,
@ -11,7 +11,7 @@ macro_rules! maybe_backdate {
$zalsa:ident,
) => {
$zalsa::update::always_update(
$zalsa::always_update(
&mut $revision_place,
$current_revision,
&mut $old_field_place,
@ -28,12 +28,11 @@ macro_rules! maybe_backdate {
$current_revision:expr,
$zalsa:ident,
) => {
if $zalsa::update::helper::Dispatch::<$field_ty>::maybe_update(
$old_field_ptr_expr,
if $zalsa::UpdateDispatch::<$field_ty>::maybe_update(
std::ptr::addr_of_mut!($old_field_place),
$new_field_place,
) {
$revision_place = #current_revision;
$revision_place = $current_revision;
}
};
};
}

View file

@ -1,6 +1,6 @@
/// Macro for setting up a function that must intern its arguments.
#[macro_export]
macro_rules! setup_input {
macro_rules! setup_input_struct {
(
// Attributes on the struct
attrs: [$(#[$attr:meta]),*],
@ -41,9 +41,6 @@ macro_rules! setup_input {
$Configuration:ident,
$CACHE:ident,
$Db:ident,
$NonNull:ident,
$Revision:ident,
$ValueStruct:ident,
]
) => {
$(#[$attr])*
@ -53,8 +50,6 @@ macro_rules! setup_input {
const _: () = {
use salsa::plumbing as $zalsa;
use $zalsa::input as $zalsa_struct;
use $zalsa::Revision as $Revision;
use std::ptr::NonNull as $NonNull;
struct $Configuration;
@ -73,10 +68,7 @@ macro_rules! setup_input {
}
impl $Configuration {
pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::IngredientImpl<Self>
where
Db: ?Sized + $zalsa::Database,
{
pub fn ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<Self> {
static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new();
CACHE.get_or_create(db, || {
@ -84,10 +76,7 @@ macro_rules! setup_input {
})
}
pub fn ingredient_mut<Db>(db: &Db) -> (&mut $zalsa_struct::IngredientImpl<Self>, &mut $zalsa::Runtime)
where
Db: ?Sized + $zalsa::Database,
{
pub fn ingredient_mut(db: &mut dyn $zalsa::Database) -> (&mut $zalsa_struct::IngredientImpl<Self>, &mut $zalsa::Runtime) {
let index = db.add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Configuration>>::default());
let (ingredient, runtime) = db.lookup_ingredient_mut(index);
let ingredient = ingredient.assert_type_mut::<$zalsa_struct::IngredientImpl<Self>>();
@ -110,29 +99,36 @@ macro_rules! setup_input {
impl std::fmt::Debug for $Struct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).fields(self.0);
let f = f.debug_struct(stringify!($Struct));
let fields = $Configuration::ingredient(db).leak_fields(*self);
let mut f = f.debug_struct(stringify!($Struct));
let f = f.field("[salsa id]", &self.0.as_u32());
$(
let f = f.field(stringify!($field_id), &fields.$field_index);
)*
f.finish()
}).unwrap_or_else(|| {
f.debug_tuple(stringify!($Struct))
.field(&self.0)
f.debug_struct(stringify!($Struct))
.field("[salsa id]", &self.0.as_u32())
.finish()
})
}
}
impl $zalsa::SalsaStructInDb for $Struct {
fn register_dependent_fn(_db: &dyn $zalsa::Database, _index: $zalsa::IngredientIndex) {
// Inputs don't bother with dependent functions
}
}
impl $Struct {
pub fn new<Db>(db: &Db, $($field_id: $field_ty),*) -> Self
pub fn new<$Db>(db: &$Db, $($field_id: $field_ty),*) -> Self
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
Db: ?Sized + salsa::Database,
$Db: ?Sized + salsa::Database,
{
let current_revision = $zalsa::current_revision(db);
let stamps = $zalsa::Array::new([$zalsa::stamp(current_revision, Default::default()); $N]);
$Configuration::ingredient(db).new_input(($($field_id,)*), stamps)
$Configuration::ingredient(db.as_salsa_database()).new_input(($($field_id,)*), stamps)
}
$(
@ -142,7 +138,7 @@ macro_rules! setup_input {
$Db: ?Sized + $zalsa::Database,
{
let runtime = db.runtime();
let fields = $Configuration::ingredient(db).field(runtime, self, $field_index);
let fields = $Configuration::ingredient(db.as_salsa_database()).field(runtime, self, $field_index);
$zalsa::maybe_clone!(
$field_option,
$field_ty,
@ -158,7 +154,7 @@ macro_rules! setup_input {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database,
{
let (ingredient, runtime) = $Configuration::ingredient_mut(db);
let (ingredient, runtime) = $Configuration::ingredient_mut(db.as_salsa_database_mut());
$zalsa::input::SetterImpl::new(
runtime,
self,

View file

@ -63,8 +63,8 @@ macro_rules! setup_interned_fn {
#[derive(Copy, Clone)]
struct $InternedData<'db>(
std::ptr::NonNull<$zalsa::interned::ValueStruct<$Configuration>>,
std::marker::PhantomData<&'db $zalsa::interned::ValueStruct<$Configuration>>,
std::ptr::NonNull<$zalsa::interned::Value<$Configuration>>,
std::marker::PhantomData<&'db $zalsa::interned::Value<$Configuration>>,
);
static $FN_CACHE: $zalsa::IngredientCache<$zalsa::function::IngredientImpl<$Configuration>> =
@ -73,8 +73,8 @@ macro_rules! setup_interned_fn {
static $INTERN_CACHE: $zalsa::IngredientCache<$zalsa::interned::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new();
impl $zalsa::SalsaStructInDb<dyn $Db> for $InternedData<'_> {
fn register_dependent_fn(_db: &dyn $Db, _index: $zalsa::IngredientIndex) {}
impl $zalsa::SalsaStructInDb for $InternedData<'_> {
fn register_dependent_fn(_db: &dyn $zalsa::Database, _index: $zalsa::IngredientIndex) {}
}
impl $zalsa::function::Configuration for $Configuration {
@ -94,7 +94,7 @@ macro_rules! setup_interned_fn {
old_value: &Self::Output<'_>,
new_value: &Self::Output<'_>,
) -> bool {
old_value == new_value
$zalsa::should_backdate_value(old_value, new_value)
}
fn execute<'db>($db: &'db Self::DbView, ($($input_id),*): ($($input_ty),*)) -> Self::Output<'db> {
@ -113,7 +113,7 @@ macro_rules! setup_interned_fn {
fn id_to_input<'db>(db: &'db Self::DbView, key: salsa::Id) -> Self::Input<'db> {
let ingredient = $INTERN_CACHE.get_or_create(db.as_salsa_database(), || {
db.add_or_lookup_jar_by_type(&$Configuration) + 1
db.add_or_lookup_jar_by_type(&$Configuration).successor(0)
});
ingredient.data(key).clone()
}
@ -127,12 +127,12 @@ macro_rules! setup_interned_fn {
type Struct<$db_lt> = $InternedData<$db_lt>;
unsafe fn struct_from_raw<'db>(
ptr: std::ptr::NonNull<$zalsa::interned::ValueStruct<Self>>,
ptr: std::ptr::NonNull<$zalsa::interned::Value<Self>>,
) -> Self::Struct<'db> {
$InternedData(ptr, std::marker::PhantomData)
}
fn deref_struct(s: Self::Struct<'_>) -> &$zalsa::interned::ValueStruct<Self> {
fn deref_struct(s: Self::Struct<'_>) -> &$zalsa::interned::Value<Self> {
unsafe { s.0.as_ref() }
}
}
@ -147,21 +147,24 @@ macro_rules! setup_interned_fn {
first_index,
)),
Box::new(<$zalsa::interned::IngredientImpl<$Configuration>>::new(
first_index + 1,
first_index.successor(0)
)),
]
}
}
let intern_ingredient = $INTERN_CACHE.get_or_create($db.as_salsa_database(), || {
$db.add_or_lookup_jar_by_type(&$Configuration) + 1
});
let key = intern_ingredient.intern_id($db.runtime(), ($($input_id),*));
$zalsa::attach_database($db, || {
let intern_ingredient = $INTERN_CACHE.get_or_create($db.as_salsa_database(), || {
$db.add_or_lookup_jar_by_type(&$Configuration).successor(0)
});
let key = intern_ingredient.intern_id($db.runtime(), ($($input_id),*));
let fn_ingredient = $FN_CACHE.get_or_create($db.as_salsa_database(), || {
$db.add_or_lookup_jar_by_type(&$Configuration)
});
fn_ingredient.fetch($db, key).clone()
let fn_ingredient = $FN_CACHE.get_or_create($db.as_salsa_database(), || {
<dyn $Db as $Db>::zalsa_db($db);
$db.add_or_lookup_jar_by_type(&$Configuration)
});
fn_ingredient.fetch($db, key).clone()
})
}
};
}

View file

@ -0,0 +1,170 @@
/// Macro for setting up a function that must intern its arguments.
#[macro_export]
macro_rules! setup_interned_struct {
(
// Attributes on the struct
attrs: [$(#[$attr:meta]),*],
// Visibility of the struct
vis: $vis:vis,
// Name of the struct
Struct: $Struct:ident,
// Name of the `'db` lifetime that the user gave
db_lt: $db_lt:lifetime,
// Name user gave for `new`
new_fn: $new_fn:ident,
// A series of option tuples; see `setup_tracked_struct` macro
field_options: [$($field_option:tt),*],
// Field names
field_ids: [$($field_id:ident),*],
// Names for field setter methods (typically `set_foo`)
field_setter_ids: [$($field_setter_id:ident),*],
// Field types
field_tys: [$($field_ty:ty),*],
// Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
field_indices: [$($field_index:tt),*],
// Number of fields
num_fields: $N:literal,
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
// We have the procedural macro generate names for those items that are
// not used elsewhere in the user's code.
unused_names: [
$zalsa:ident,
$zalsa_struct:ident,
$Configuration:ident,
$CACHE:ident,
$Db:ident,
]
) => {
$(#[$attr])*
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
$vis struct $Struct<$db_lt>(
std::ptr::NonNull<salsa::plumbing::interned::Value < $Struct<'static> >>,
std::marker::PhantomData < & $db_lt salsa::plumbing::interned::Value < $Struct<'static> > >
);
const _: () = {
use salsa::plumbing as $zalsa;
use $zalsa::interned as $zalsa_struct;
type $Configuration = $Struct<'static>;
impl $zalsa_struct::Configuration for $Configuration {
const DEBUG_NAME: &'static str = stringify!($Struct);
type Data<$db_lt> = ($($field_ty,)*);
type Struct<$db_lt> = $Struct<$db_lt>;
unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull<$zalsa_struct::Value<Self>>) -> Self::Struct<'db> {
$Struct(ptr, std::marker::PhantomData)
}
fn deref_struct(s: Self::Struct<'_>) -> &$zalsa_struct::Value<Self> {
unsafe { s.0.as_ref() }
}
}
impl $Configuration {
pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::IngredientImpl<Self>
where
Db: ?Sized + $zalsa::Database,
{
static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new();
CACHE.get_or_create(db, || {
db.add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Configuration>>::default())
})
}
}
impl $zalsa::FromId for $Struct<'_> {
fn from_id(id: salsa::Id) -> Self {
Self(id)
}
}
impl $zalsa::AsId for $Struct<'_> {
fn as_id(&self) -> salsa::Id {
self.0
}
}
impl std::fmt::Debug for $Struct<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).fields(self.0);
let f = f.debug_struct(stringify!($Struct));
$(
let f = f.field(stringify!($field_id), &fields.$field_index);
)*
f.finish()
}).unwrap_or_else(|| {
f.debug_tuple(stringify!($Struct))
.field(&self.0)
.finish()
})
}
}
impl $zalsa::SalsaStructInDb for $Struct<'_> {
fn register_dependent_fn(_db: &dyn $zalsa::Database, _index: $zalsa::IngredientIndex) {
// Inputs don't bother with dependent functions
}
}
impl<$db_lt> $Struct<$db_lt> {
pub fn new<$Db>(db: &$db_lt $Db, $($field_id: $field_ty),*) -> Self
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + salsa::Database,
{
let runtime = db.runtime();
let current_revision = $zalsa::current_revision(db);
let stamps = $zalsa::Array::new([$zalsa::stamp(current_revision, Default::default()); $N]);
$Configuration::ingredient(db).intern(runtime, ($($field_id,)*), stamps)
}
$(
pub fn $field_id<'db, $Db>(self, db: &'db $Db) -> $zalsa::maybe_cloned_ty!($field_option, 'db, $field_ty)
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database,
{
let runtime = db.runtime();
let fields = $Configuration::ingredient(db).field(runtime, self, $field_index);
$zalsa::maybe_clone!(
$field_option,
$field_ty,
&fields.$field_index,
)
}
)*
$(
#[must_use]
pub fn $field_setter_id<'db, $Db>(self, db: &'db mut $Db) -> impl salsa::Setter<FieldTy = $field_ty> + 'db
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database,
{
let (ingredient, runtime) = $Configuration::ingredient_mut(db);
$zalsa::input::SetterImpl::new(
runtime,
self,
$field_index,
ingredient,
|fields, f| std::mem::replace(&mut fields.$field_index, f),
)
}
)*
}
};
};
}

View file

@ -79,7 +79,7 @@ macro_rules! setup_struct_fn {
old_value: &Self::Output<'_>,
new_value: &Self::Output<'_>,
) -> bool {
old_value == new_value
$zalsa::should_backdate_value(old_value, new_value)
}
fn execute<'db>($db: &'db Self::DbView, $input_id: $input_ty) -> Self::Output<'db> {
@ -97,7 +97,7 @@ macro_rules! setup_struct_fn {
}
fn id_to_input<'db>(db: &'db Self::DbView, key: salsa::Id) -> Self::Input<'db> {
$zalsa::LookupId::lookup_id(key, db)
$zalsa::LookupId::lookup_id(key, db.as_salsa_database())
}
}
@ -114,10 +114,14 @@ macro_rules! setup_struct_fn {
}
}
let fn_ingredient = $FN_CACHE.get_or_create($db.as_salsa_database(), || {
$db.add_or_lookup_jar_by_type(&$Configuration)
});
fn_ingredient.fetch($db, $input_id).clone()
$zalsa::attach_database($db, || {
let fn_ingredient = $FN_CACHE.get_or_create($db.as_salsa_database(), || {
<dyn $Db as $Db>::zalsa_db($db);
$db.add_or_lookup_jar_by_type(&$Configuration)
});
fn_ingredient.fetch($db, $zalsa::AsId::as_id(&$input_id)).clone()
})
}
};
}

View file

@ -2,6 +2,9 @@
#[macro_export]
macro_rules! setup_tracked_struct {
(
// Attributes on the function
attrs: [$(#[$attr:meta]),*],
// Visibility of the struct
vis: $vis:vis,
@ -51,17 +54,20 @@ macro_rules! setup_tracked_struct {
$Revision:ident,
]
) => {
$vis struct $Struct<$db_lt> {
}
$(#[$attr])*
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
$vis struct $Struct<$db_lt>(
std::ptr::NonNull<salsa::plumbing::tracked_struct::Value < $Struct<'static> >>,
std::marker::PhantomData < & $db_lt salsa::plumbing::tracked_struct::Value < $Struct<'static> > >
);
const _: () = {
use salsa::plumbing as $zalsa;
use $zalsa_struct as $zalsa_struct;
use $zalsa::tracked_struct as $zalsa_struct;
use $zalsa::Revision as $Revision;
use std::ptr::NonNull as $NonNull;
struct $Configuration;
type $Configuration = $Struct<'static>;
impl $zalsa_struct::Configuration for $Configuration {
const DEBUG_NAME: &'static str = stringify!($Struct);
@ -72,7 +78,7 @@ macro_rules! setup_tracked_struct {
type Fields<$db_lt> = ($($field_ty,)*);
type Revisions = [$Revision; $N];
type Revisions = $zalsa::Array<$Revision, $N>;
type Struct<$db_lt> = $Struct<$db_lt>;
@ -84,25 +90,21 @@ macro_rules! setup_tracked_struct {
unsafe { s.0.as_ref() }
}
fn id_fields(fields: &Self::Fields<'_>) -> impl Hash {
fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash {
( $( &fields.$id_field_index ),* )
}
fn revision(revisions: &Self::Revisions, field_index: u32) -> $Revision {
revisions[field_index as usize]
}
fn new_revisions(current_revision: $Revision) -> Self::Revisions {
[current_revision; $N]
$zalsa::Array::new([current_revision; $N])
}
unsafe fn update_fields<'db>(
current_revision: Revision,
current_revision: $Revision,
revisions: &mut Self::Revisions,
old_fields: *mut Self::Fields<'db>,
new_fields: Self::Fields<'db>,
) {
use salsa::update::helper::Fallback as _;
use $zalsa::UpdateFallback as _;
unsafe {
$(
$crate::maybe_backdate!(
@ -120,46 +122,67 @@ macro_rules! setup_tracked_struct {
}
impl $Configuration {
pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::Ingredient<Self> {
static CACHE: $zalsa::IngredientCache<$zalsa_struct::Ingredient<Self>> =
pub fn ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<$Configuration> {
static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new();
CACHE.get_or_create(|| {
db.add_or_lookup_jar_by_type(&$zalsa_struct::JarImpl::<$Configuration>)
CACHE.get_or_create(db, || {
db.add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl::<$Configuration>>::default())
})
}
}
impl<'db> $zalsa::LookupId<'db> for $Struct<$db_lt> {
fn lookup_id(id: salsa::Id, db: &'db dyn Database) -> Self {
fn lookup_id(id: salsa::Id, db: &'db dyn $zalsa::Database) -> Self {
$Configuration::ingredient(db).lookup_struct(db.runtime(), id)
}
}
impl<$db_lt, $Db> $zalsa::SalsaStructInDb<$Db> for $Struct<$db_lt>
where
$Db: ?Sized + $zalsa::Database,
{
fn register_dependent_fn(db: & $Db, index: $zalsa::IngredientIndex) {
impl $zalsa::AsId for $Struct<'_> {
fn as_id(&self) -> $zalsa::Id {
unsafe { self.0.as_ref() }.as_id()
}
}
impl $zalsa::SalsaStructInDb for $Struct<'_> {
fn register_dependent_fn(db: &dyn $zalsa::Database, index: $zalsa::IngredientIndex) {
$Configuration::ingredient(db).register_dependent_fn(index)
}
}
impl<$db_lt, $Db> $zalsa_struct::TrackedStructInDb<#db> for $Struct<$db_lt>
where
$Db: ?Sized + $zalsa::Database,
{
fn database_key_index(db: &$Db, id: $zalsa::Id) -> $zalsa::DatabaseKeyIndex {
impl $zalsa::TrackedStructInDb for $Struct<'_> {
fn database_key_index(db: &dyn $zalsa::Database, id: $zalsa::Id) -> $zalsa::DatabaseKeyIndex {
$Configuration::ingredient(db).database_key_index(id)
}
}
unsafe impl Send for $Struct<'_> {}
unsafe impl Sync for $Struct<'_> {}
impl std::fmt::Debug for $Struct<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).leak_fields(*self);
let mut f = f.debug_struct(stringify!($Struct));
$(
let f = f.field(stringify!($field_id), &fields.$field_index);
)*
f.finish()
}).unwrap_or_else(|| {
f.debug_tuple(stringify!($Struct))
.field(&self.0)
.finish()
})
}
}
impl<$db_lt> $Struct<$db_lt> {
pub fn $new_fn<$Db>(db: &$Db, $($field_id: $field_ty),*) -> Self
pub fn $new_fn<$Db>(db: &$db_lt $Db, $($field_id: $field_ty),*) -> Self
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database,
{
$Configuration::ingredient(db).new_struct(
$Configuration::ingredient(db.as_salsa_database()).new_struct(
db.runtime(),
($($field_id,)*)
)

View file

@ -1,103 +0,0 @@
use proc_macro2::Literal;
use crate::xform::ChangeLt;
pub(crate) struct Configuration {
pub(crate) debug_name: Literal,
pub(crate) db_lt: syn::Lifetime,
pub(crate) jar_ty: syn::Type,
pub(crate) salsa_struct_ty: syn::Type,
pub(crate) input_ty: syn::Type,
pub(crate) value_ty: syn::Type,
pub(crate) cycle_strategy: CycleRecoveryStrategy,
pub(crate) backdate_fn: syn::ImplItemFn,
pub(crate) execute_fn: syn::ImplItemFn,
pub(crate) recover_fn: syn::ImplItemFn,
}
impl Configuration {
pub(crate) fn to_impl(&self, self_ty: &syn::Type) -> syn::ItemImpl {
let Configuration {
debug_name,
db_lt,
jar_ty,
salsa_struct_ty,
input_ty,
value_ty,
cycle_strategy,
backdate_fn,
execute_fn,
recover_fn,
} = self;
parse_quote! {
impl salsa::function::Configuration for #self_ty {
const DEBUG_NAME: &'static str = #debug_name;
type Jar = #jar_ty;
type SalsaStruct<#db_lt> = #salsa_struct_ty;
type Input<#db_lt> = #input_ty;
type Value<#db_lt> = #value_ty;
const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = #cycle_strategy;
#backdate_fn
#execute_fn
#recover_fn
}
}
}
}
pub(crate) enum CycleRecoveryStrategy {
Panic,
Fallback,
}
impl quote::ToTokens for CycleRecoveryStrategy {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
CycleRecoveryStrategy::Panic => {
tokens.extend(quote! {salsa::cycle::CycleRecoveryStrategy::Panic})
}
CycleRecoveryStrategy::Fallback => {
tokens.extend(quote! {salsa::cycle::CycleRecoveryStrategy::Fallback})
}
}
}
}
/// Returns an appropriate definition for `should_backdate_value` depending on
/// whether this value is memoized or not.
pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemFn {
if should_backdate {
parse_quote! {
fn should_backdate_value(v1: &Self::Value<'_>, v2: &Self::Value<'_>) -> bool {
salsa::function::should_backdate_value(v1, v2)
}
}
} else {
parse_quote! {
fn should_backdate_value(_v1: &Self::Value<'_>, _v2: &Self::Value<'_>) -> bool {
false
}
}
}
}
/// Returns an appropriate definition for `recover_from_cycle` for cases where
/// the cycle recovery is panic.
pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemFn {
parse_quote! {
fn recover_from_cycle<'db>(
_db: &'db salsa::function::DynDb<Self>,
_cycle: &salsa::Cycle,
_key: salsa::Id,
) -> Self::Value<'db> {
panic!()
}
}
}
pub(crate) fn value_ty(db_lt: &syn::Lifetime, sig: &syn::Signature) -> syn::Type {
match &sig.output {
syn::ReturnType::Default => parse_quote!(()),
syn::ReturnType::Type(_, ty) => ChangeLt::elided_to(db_lt).in_type(ty),
}
}

View file

@ -19,7 +19,7 @@ pub(crate) fn db(
let input = syn::parse_macro_input!(input as syn::Item);
let db_macro = DbMacro { hygiene };
match db_macro.try_db(input) {
Ok(v) => v.into(),
Ok(v) => crate::debug::dump_tokens("db", v).into(),
Err(e) => e.to_compile_error().into(),
}
}
@ -106,7 +106,7 @@ impl DbMacro {
fn add_salsa_view_method(&self, input: &mut syn::ItemTrait) -> syn::Result<()> {
input.items.push(parse_quote! {
#[doc(hidden)]
fn zalsa_add_view(&self);
fn zalsa_db(&self);
});
Ok(())
}
@ -124,7 +124,7 @@ impl DbMacro {
input.items.push(parse_quote! {
#[doc(hidden)]
#[allow(uncommon_codepoins)]
fn zalsa_add_view(&self) {
fn zalsa_db(&self) {
use salsa::plumbing as #zalsa;
#zalsa::views(self).add::<Self, dyn #TraitPath>(|t| t, |t| t);
}

View file

@ -1,90 +0,0 @@
use proc_macro2::{Literal, Span, TokenStream};
pub(crate) fn debug_with_db(input: syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
// Figure out the lifetime to use for the `dyn Db` that we will expect.
// We allow structs to have at most one lifetime -- if a lifetime parameter is present,
// it should be `'db`. We may want to generalize this later.
let num_lifetimes = input.generics.lifetimes().count();
if num_lifetimes > 1 {
return syn::Result::Err(syn::Error::new(
input.generics.lifetimes().nth(1).unwrap().lifetime.span(),
"only one lifetime is supported",
));
}
// Generate the type of database we expect. This hardcodes the convention of using `jar::Jar`.
// That's not great and should be fixed but we'd have to add a custom attribute and I am too lazy.
#[allow(non_snake_case)]
let DB: syn::Type = parse_quote! {
<crate::Jar as salsa::jar::Jar>::DynDb
};
let structure: synstructure::Structure = synstructure::Structure::new(&input);
let fmt = syn::Ident::new("fmt", Span::call_site());
let db = syn::Ident::new("db", Span::call_site());
// Generic the match arm for each variant.
let fields: TokenStream = structure
.variants()
.iter()
.map(|variant| {
let variant_name = &variant.ast().ident;
let variant_name = Literal::string(&variant_name.to_string());
// Closure: given a binding, generate a call to the `salsa_debug` helper to either
// print its "debug with db" value or just use `std::fmt::Debug`. This is a nice hack that
// lets us use `debug_with_db` when available; it won't work great for generic types unless we add
// `DebugWithDb` bounds though.
let binding_tokens = |binding: &synstructure::BindingInfo| {
let field_ty = &binding.ast().ty;
quote!(
&::salsa::debug::helper::SalsaDebug::<#field_ty, #DB>::salsa_debug(
#binding,
#db,
)
)
};
// Create something like `fmt.debug_struct(...).field().field().finish()`
// for each struct field; the values to be debugged are created by
// the `binding_tokens` closure above.
let fields = match variant.ast().fields {
syn::Fields::Named(_) => variant.fold(
quote!(#fmt.debug_struct(#variant_name)),
|tokens, binding| {
let binding_name =
Literal::string(&binding.ast().ident.as_ref().unwrap().to_string());
let binding_data = binding_tokens(binding);
quote!(#tokens . field(#binding_name, #binding_data))
},
),
syn::Fields::Unnamed(_) | syn::Fields::Unit => variant.fold(
quote!(#fmt.debug_tuple(#variant_name)),
|tokens, binding| {
let binding_data = binding_tokens(binding);
quote!(#tokens . field(#binding_data))
},
),
};
quote!(#fields . finish(),)
})
.collect();
let tokens = structure.gen_impl(quote! {
gen impl ::salsa::debug::DebugWithDb<#DB> for @Self {
fn fmt(&self, #fmt: &mut std::fmt::Formatter<'_>, #db: & #DB) -> std::fmt::Result {
use ::salsa::debug::helper::Fallback as _;
match self {
#fields
}
}
}
});
Ok(crate::debug::dump_tokens(&input.ident, tokens))
}

View file

@ -1,5 +1,9 @@
use crate::salsa_struct::{SalsaField, SalsaStruct};
use proc_macro2::{Literal, TokenStream};
use crate::{
hygiene::Hygiene,
options::Options,
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions},
};
use proc_macro2::TokenStream;
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
@ -10,21 +14,23 @@ pub(crate) fn input(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match SalsaStruct::new(args, input, "input").and_then(|el| InputStruct(el).generate_input()) {
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
let args = syn::parse_macro_input!(args as InputArgs);
let hygiene = Hygiene::from1(&input);
let struct_item = syn::parse_macro_input!(input as syn::ItemStruct);
let m = Macro {
hygiene,
args,
struct_item,
};
match m.try_macro() {
Ok(v) => v.into(),
Err(e) => e.to_compile_error().into(),
}
}
struct InputStruct(SalsaStruct<Self>);
type InputArgs = Options<InputStruct>;
impl std::ops::Deref for InputStruct {
type Target = SalsaStruct<Self>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct InputStruct;
impl crate::options::AllowedOptions for InputStruct {
const RETURN_REF: bool = false;
@ -47,291 +53,87 @@ impl crate::options::AllowedOptions for InputStruct {
const CONSTRUCTOR_NAME: bool = true;
}
impl InputStruct {
fn generate_input(&self) -> syn::Result<TokenStream> {
self.require_no_generics()?;
impl SalsaStructAllowedOptions for InputStruct {
const KIND: &'static str = "input";
let id_struct = self.the_struct_id();
let inherent_impl = self.input_inherent_impl();
let ingredients_for_impl = self.input_ingredients();
let as_id_impl = self.as_id_impl();
let from_id_impl = self.impl_of_from_id();
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
let debug_impl = self.debug_impl();
const ALLOW_ID: bool = false;
Ok(quote! {
#id_struct
#inherent_impl
#ingredients_for_impl
#as_id_impl
#from_id_impl
#as_debug_with_db_impl
#salsa_struct_in_db_impl
#debug_impl
})
}
const HAS_LIFETIME: bool = false;
}
/// Generate an inherent impl with methods on the entity type.
fn input_inherent_impl(&self) -> syn::ItemImpl {
let ident = self.the_ident();
let jar_ty = self.jar_ty();
let db_dyn_ty = self.db_dyn_ty();
let input_index = self.input_index();
struct Macro {
hygiene: Hygiene,
args: InputArgs,
struct_item: syn::ItemStruct,
}
let field_indices = self.all_field_indices();
let field_names = self.all_field_names();
let field_vises = self.all_field_vises();
let field_tys: Vec<_> = self.all_field_tys();
let field_clones: Vec<_> = self.all_fields().map(SalsaField::is_clone_field).collect();
let get_field_names: Vec<_> = self.all_get_field_names();
let field_getters: Vec<syn::ImplItemFn> = field_indices.iter().zip(&get_field_names).zip(&field_vises).zip(&field_tys).zip(&field_clones).map(|((((field_index, get_field_name), field_vis), field_ty), is_clone_field)|
if !*is_clone_field {
parse_quote_spanned! { get_field_name.span() =>
#field_vis fn #get_field_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(__runtime, self)
}
}
} else {
parse_quote_spanned! { get_field_name.span() =>
#field_vis fn #get_field_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(__runtime, self).clone()
}
}
}
)
.collect();
impl Macro {
#[allow(non_snake_case)]
fn try_macro(&self) -> syn::Result<TokenStream> {
let salsa_struct = SalsaStruct::new(&self.struct_item, &self.args)?;
// setters
let set_field_names = self.all_set_field_names();
let field_setters: Vec<syn::ImplItemFn> = field_indices.iter()
.zip(&set_field_names)
.zip(&field_vises)
.zip(&field_tys)
.filter_map(|(((field_index, &set_field_name), field_vis), field_ty)| {
let set_field_name = set_field_name?;
Some(parse_quote_spanned! { set_field_name.span() =>
#field_vis fn #set_field_name<'db>(self, __db: &'db mut #db_dyn_ty) -> salsa::setter::Setter<'db, #ident, #field_ty>
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient_mut(__jar);
salsa::setter::Setter::new(__runtime, self, &mut __ingredients.#field_index)
}
})
})
.collect();
let attrs = &self.struct_item.attrs;
let vis = &self.struct_item.vis;
let struct_ident = &self.struct_item.ident;
let field_ids = salsa_struct.field_ids();
let field_indices = salsa_struct.field_indices();
let num_fields = salsa_struct.num_fields();
let field_setter_ids = salsa_struct.field_setter_ids();
let field_options = salsa_struct.field_options();
let field_tys = salsa_struct.field_tys();
let constructor_name = self.constructor_name();
let singleton = self.0.is_isingleton();
let zalsa = self.hygiene.ident("zalsa");
let zalsa_struct = self.hygiene.ident("zalsa_struct");
let Configuration = self.hygiene.ident("Configuration");
let CACHE = self.hygiene.ident("CACHE");
let Db = self.hygiene.ident("Db");
let constructor: syn::ImplItemFn = if singleton {
parse_quote_spanned! { constructor_name.span() =>
/// Creates a new singleton input
///
/// # Panics
///
/// If called when an instance already exists
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.#input_index.new_singleton_input(__runtime);
#(
__ingredients.#field_indices.store_new(__runtime, __id, #field_names, salsa::Durability::LOW);
)*
__id
}
}
} else {
parse_quote_spanned! { constructor_name.span() =>
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.#input_index.new_input(__runtime);
#(
__ingredients.#field_indices.store_new(__runtime, __id, #field_names, salsa::Durability::LOW);
)*
__id
}
}
};
Ok(crate::debug::dump_tokens(
struct_ident,
quote! {
salsa::plumbing::setup_input_struct!(
// Attributes on the struct
attrs: [#(#attrs),*],
let salsa_id = quote!(
pub fn salsa_id(&self) -> salsa::Id {
self.0
}
);
// Visibility of the struct
vis: #vis,
if singleton {
let get: syn::ImplItemFn = parse_quote! {
#[track_caller]
pub fn get(__db: &#db_dyn_ty) -> Self {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#input_index.get_singleton_input(__runtime).expect("singleton input struct not yet initialized")
}
};
// Name of the struct
Struct: #struct_ident,
let try_get: syn::ImplItemFn = parse_quote! {
#[track_caller]
pub fn try_get(__db: &#db_dyn_ty) -> Option<Self> {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#input_index.get_singleton_input(__runtime)
}
};
// Name user gave for `new`
new_fn: new, // FIXME
parse_quote! {
#[allow(dead_code)]
impl #ident {
#constructor
// A series of option tuples; see `setup_tracked_struct` macro
field_options: [#(#field_options),*],
#get
// Field names
field_ids: [#(#field_ids),*],
#try_get
// Names for field setter methods (typically `set_foo`)
field_setter_ids: [#(#field_setter_ids),*],
#(#field_getters)*
// Field types
field_tys: [#(#field_tys),*],
#(#field_setters)*
// Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
field_indices: [#(#field_indices),*],
#salsa_id
}
}
} else {
parse_quote! {
#[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)]
impl #ident {
#constructor
// Number of fields
num_fields: #num_fields,
#(#field_getters)*
#(#field_setters)*
#salsa_id
}
}
}
// }
}
/// Generate the `IngredientsFor` impl for this entity.
///
/// The entity's ingredients include both the main entity ingredient along with a
/// function ingredient for each of the value fields.
fn input_ingredients(&self) -> syn::ItemImpl {
use crate::literal;
let ident = self.the_ident();
let field_ty = self.all_field_tys();
let jar_ty = self.jar_ty();
let all_field_indices: Vec<Literal> = self.all_field_indices();
let input_index: Literal = self.input_index();
let debug_name_struct = literal(self.the_ident());
let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect();
parse_quote! {
impl salsa::storage::IngredientsFor for #ident {
type Jar = #jar_ty;
type Ingredients = (
#(
salsa::input_field::InputFieldIngredient<#ident, #field_ty>,
)*
salsa::input::InputIngredient<#ident>,
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
// We have the procedural macro generate names for those items that are
// not used elsewhere in the user's code.
unused_names: [
#zalsa,
#zalsa_struct,
#Configuration,
#CACHE,
#Db,
]
);
fn create_ingredients<DB>(
routes: &mut salsa::routes::Routes<DB>,
) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
(
#(
{
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.#all_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.#all_field_indices
},
);
salsa::input_field::InputFieldIngredient::new(index, #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.#input_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.#input_index
},
);
salsa::input::InputIngredient::new(index, #debug_name_struct)
},
)
}
}
}
}
/// For the entity, we create a tuple that contains the function ingredients
/// for each "other" field and the entity ingredient. This is the index of
/// the entity ingredient within that tuple.
fn input_index(&self) -> Literal {
Literal::usize_unsuffixed(self.all_fields().count())
}
/// For the entity, we create a tuple that contains the function ingredients
/// for each field and an entity ingredient. These are the indices
/// of the function ingredients within that tuple.
fn all_field_indices(&self) -> Vec<Literal> {
self.all_fields()
.zip(0..)
.map(|(_, i)| Literal::usize_unsuffixed(i))
.collect()
}
/// Names of setters of all fields that should be generated. Returns an optional Ident for the field name
/// that is None when the field should not generate a setter.
///
/// Setters are not created for fields with #[id] tag so they'll be safe to include in debug formatting
pub(crate) fn all_set_field_names(&self) -> Vec<Option<&syn::Ident>> {
self.all_fields()
.map(|ef| (!ef.has_id_attr).then(|| ef.set_name()))
.collect()
}
/// Implementation of `SalsaStructInDb`.
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.the_ident();
let jar_ty = self.jar_ty();
parse_quote! {
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
where
DB: ?Sized + salsa::DbWithJar<#jar_ty>,
{
fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {
// Do nothing here, at least for now.
// If/when we add ability to delete inputs, this would become relevant.
}
}
}
},
))
}
}

View file

@ -1,35 +1,37 @@
use crate::salsa_struct::{SalsaStruct, TheStructKind};
use crate::{
db_lifetime,
hygiene::Hygiene,
options::Options,
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions},
};
use proc_macro2::TokenStream;
// #[salsa::interned(jar = Jar0, data = TyData0)]
// #[derive(Eq, PartialEq, Hash, Debug, Clone)]
// struct Ty0 {
// field1: Type1,
// #[id(ref)] field2: Type2,
// ...
// }
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
/// * the "id struct" `struct Foo(salsa::Id)`
/// * the entity ingredient, which maps the id fields to the `Id`
/// * for each value field, a function ingredient
pub(crate) fn interned(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match SalsaStruct::new(args, input, "interned")
.and_then(|el| InternedStruct(el).generate_interned())
{
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
let args = syn::parse_macro_input!(args as InternedArgs);
let hygiene = Hygiene::from1(&input);
let struct_item = syn::parse_macro_input!(input as syn::ItemStruct);
let m = Macro {
hygiene,
args,
struct_item,
};
match m.try_macro() {
Ok(v) => v.into(),
Err(e) => e.to_compile_error().into(),
}
}
struct InternedStruct(SalsaStruct<Self>);
type InternedArgs = Options<InternedStruct>;
impl std::ops::Deref for InternedStruct {
type Target = SalsaStruct<Self>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct InternedStruct;
impl crate::options::AllowedOptions for InternedStruct {
const RETURN_REF: bool = false;
@ -38,7 +40,7 @@ impl crate::options::AllowedOptions for InternedStruct {
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const SINGLETON: bool = true;
const JAR: bool = true;
@ -53,284 +55,67 @@ impl crate::options::AllowedOptions for InternedStruct {
const CONSTRUCTOR_NAME: bool = true;
}
impl InternedStruct {
fn generate_interned(&self) -> syn::Result<TokenStream> {
self.validate_interned()?;
let config_struct = self.config_struct();
let the_struct = self.the_struct(&config_struct.ident)?;
let data_struct = self.data_struct();
let configuration_impl = self.configuration_impl(&data_struct.ident, &config_struct.ident);
let ingredients_for_impl = self.ingredients_for_impl(&config_struct.ident);
let as_id_impl = self.as_id_impl();
let from_id_impl = self.impl_of_from_id();
let lookup_id_impl = self.lookup_id_impl();
let send_sync_impls = self.send_sync_impls();
let named_fields_impl = self.inherent_impl_for_named_fields();
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
let debug_impl = self.debug_impl();
let update_impl = self.update_impl();
impl SalsaStructAllowedOptions for InternedStruct {
const KIND: &'static str = "interned";
const ALLOW_ID: bool = false;
const HAS_LIFETIME: bool = true;
}
struct Macro {
hygiene: Hygiene,
args: InternedArgs,
struct_item: syn::ItemStruct,
}
impl Macro {
#[allow(non_snake_case)]
fn try_macro(&self) -> syn::Result<TokenStream> {
let salsa_struct = SalsaStruct::new(&self.struct_item, &self.args)?;
let attrs = &self.struct_item.attrs;
let vis = &self.struct_item.vis;
let struct_ident = &self.struct_item.ident;
let db_lt = db_lifetime::db_lifetime(&self.struct_item.generics);
let new_fn = salsa_struct.constructor_name();
let field_ids = salsa_struct.field_ids();
let field_indices = salsa_struct.field_indices();
let num_fields = salsa_struct.num_fields();
let field_setter_ids = salsa_struct.field_setter_ids();
let field_options = salsa_struct.field_options();
let field_tys = salsa_struct.field_tys();
let zalsa = self.hygiene.ident("zalsa");
let zalsa_struct = self.hygiene.ident("zalsa_struct");
let Configuration = self.hygiene.ident("Configuration");
let CACHE = self.hygiene.ident("CACHE");
let Db = self.hygiene.ident("Db");
Ok(crate::debug::dump_tokens(
self.the_ident(),
struct_ident,
quote! {
#the_struct
#config_struct
#data_struct
#[allow(warnings, clippy::all)]
const _: () = {
#configuration_impl
#ingredients_for_impl
#as_id_impl
#from_id_impl
#lookup_id_impl
#(#send_sync_impls)*
#named_fields_impl
#salsa_struct_in_db_impl
#as_debug_with_db_impl
#update_impl
#debug_impl
};
salsa::plumbing::setup_interned_struct!(
attrs: [#(#attrs),*],
vis: #vis,
Struct: #struct_ident,
db_lt: #db_lt,
new_fn: #new_fn,
field_options: [#(#field_options),*],
field_ids: [#(#field_ids),*],
field_setter_ids: [#(#field_setter_ids),*],
field_tys: [#(#field_tys:ty),*],
field_indices: [#(#field_indices),*],
num_fields: #num_fields,
unused_names: [
#zalsa,
#zalsa_struct,
#Configuration,
#CACHE,
#Db,
]
);
},
))
}
fn validate_interned(&self) -> syn::Result<()> {
self.disallow_id_fields("interned")?;
self.require_db_lifetime()?;
Ok(())
}
/// The name of the "data" struct (this comes from the `data = Foo` option or,
/// if that is not provided, by concatenating `Data` to the name of the struct).
fn data_ident(&self) -> syn::Ident {
match &self.args().data {
Some(d) => d.clone(),
None => syn::Ident::new(
&format!("__{}Data", self.the_ident()),
self.the_ident().span(),
),
}
}
/// Generates the `struct FooData` struct (or enum).
/// This type inherits all the attributes written by the user.
///
/// When using named fields, we synthesize the struct and field names.
///
/// When no named fields are available, copy the existing type.
fn data_struct(&self) -> syn::ItemStruct {
let data_ident = self.data_ident();
let (_, _, impl_generics, _, where_clause) = self.the_ident_and_generics();
let visibility = self.visibility();
let all_field_names = self.all_field_names();
let all_field_tys = self.all_field_tys();
let db_lt = self.db_lt();
parse_quote_spanned! { data_ident.span() =>
#[derive(Eq, PartialEq, Hash, Clone)]
#visibility struct #data_ident #impl_generics
where
#where_clause
{
#(
#all_field_names: #all_field_tys,
)*
__phantom: std::marker::PhantomData<& #db_lt ()>,
}
}
}
fn configuration_impl(
&self,
data_ident: &syn::Ident,
config_ident: &syn::Ident,
) -> syn::ItemImpl {
let the_ident = self.the_ident();
let lt_db = &self.named_db_lifetime();
let (_, _, _, type_generics, _) = self.the_ident_and_generics();
let debug_name = crate::literal(the_ident);
parse_quote_spanned!(
config_ident.span() =>
impl salsa::interned::Configuration for #config_ident {
const DEBUG_NAME: &'static str = #debug_name;
type Data<#lt_db> = #data_ident #type_generics;
type Struct<#lt_db> = #the_ident < #lt_db >;
unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull<salsa::interned::ValueStruct<Self>>) -> Self::Struct<'db> {
#the_ident(ptr, std::marker::PhantomData)
}
fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db salsa::interned::ValueStruct<Self> {
unsafe { s.0.as_ref() }
}
}
)
}
/// If this is an interned struct, then generate methods to access each field,
/// as well as a `new` method.
fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl {
let db_lt = self.db_lt();
let vis: &syn::Visibility = self.visibility();
let (the_ident, _, impl_generics, type_generics, where_clause) =
self.the_ident_and_generics();
let db_dyn_ty = self.db_dyn_ty();
let jar_ty = self.jar_ty();
let field_getters: Vec<syn::ImplItemFn> = self
.all_fields()
.map(|field: &crate::salsa_struct::SalsaField| {
let field_name = field.name();
let field_ty = field.ty();
let field_vis = field.vis();
let field_get_name = field.get_name();
if field.is_clone_field() {
parse_quote_spanned! { field_get_name.span() =>
#field_vis fn #field_get_name(self, _db: & #db_lt #db_dyn_ty) -> #field_ty {
std::clone::Clone::clone(&unsafe { self.0.as_ref() }.data().#field_name)
}
}
} else {
parse_quote_spanned! { field_get_name.span() =>
#field_vis fn #field_get_name(self, _db: & #db_lt #db_dyn_ty) -> & #db_lt #field_ty {
&unsafe { self.0.as_ref() }.data().#field_name
}
}
}
})
.collect();
let field_names = self.all_field_names();
let field_tys = self.all_field_tys();
let data_ident = self.data_ident();
let constructor_name = self.constructor_name();
let new_method: syn::ImplItemFn = parse_quote_spanned! { constructor_name.span() =>
#vis fn #constructor_name(
db: &#db_lt #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< #the_ident #type_generics >>::ingredient(jar);
ingredients.intern(runtime, #data_ident {
#(#field_names,)*
__phantom: std::marker::PhantomData,
})
}
};
let salsa_id = quote!(
pub fn salsa_id(&self) -> salsa::Id {
salsa::id::AsId::as_id(unsafe { &*self })
}
);
parse_quote! {
#[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)]
impl #impl_generics #the_ident #type_generics
where
#where_clause
{
#(#field_getters)*
#new_method
#salsa_id
}
}
}
/// Generates an impl of `salsa::storage::IngredientsFor`.
///
/// For a memoized type, the only ingredient is an `InternedIngredient`.
fn ingredients_for_impl(&self, config_ident: &syn::Ident) -> syn::ItemImpl {
let (the_ident, _, impl_generics, type_generics, where_clause) =
self.the_ident_and_generics();
let jar_ty = self.jar_ty();
parse_quote! {
impl #impl_generics salsa::storage::IngredientsFor for #the_ident #type_generics
where
#where_clause
{
type Jar = #jar_ty;
type Ingredients = salsa::interned::InternedIngredient<#config_ident>;
fn create_ingredients<DB>(
routes: &mut salsa::routes::Routes<DB>,
) -> Self::Ingredients
where
DB: salsa::storage::JarFromJars<Self::Jar>,
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar)
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
<_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar)
},
);
salsa::interned::InternedIngredient::new(index)
}
}
}
}
fn db_lt(&self) -> syn::Lifetime {
match self.the_struct_kind() {
TheStructKind::Pointer(db_lt) => db_lt,
TheStructKind::Id => {
// This should be impossible due to the conditions enforced
// in `validate_interned()`.
panic!("interned structs must have `'db` lifetimes");
}
}
}
/// Implementation of `LookupId`.
fn lookup_id_impl(&self) -> syn::ItemImpl {
let db_lt = self.db_lt();
let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics();
let db = syn::Ident::new("DB", ident.span());
let jar_ty = self.jar_ty();
parse_quote_spanned! { ident.span() =>
impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics
where
#db: ?Sized + salsa::DbWithJar<#jar_ty>,
#where_clause
{
fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar);
ingredients.interned_value(id)
}
}
}
}
/// Implementation of `SalsaStructInDb`.
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let (the_ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics();
#[allow(non_snake_case)]
let DB = syn::Ident::new("DB", the_ident.span());
let jar_ty = self.jar_ty();
parse_quote! {
impl<#DB, #parameters> salsa::salsa_struct::SalsaStructInDb<DB> for #the_ident #type_generics
where
#DB: ?Sized + salsa::DbWithJar<#jar_ty>,
#where_clause
{
fn register_dependent_fn(_db: &#DB, _index: salsa::routes::IngredientIndex) {
// Do nothing here, at least for now.
// If/when we add ability to delete inputs, this would become relevant.
}
}
}
}
}

View file

@ -39,21 +39,17 @@ pub(crate) fn literal(ident: &proc_macro2::Ident) -> proc_macro2::Literal {
}
mod accumulator;
mod configuration;
mod db;
mod db_lifetime;
mod debug;
mod debug_with_db;
mod input;
mod interned;
mod options;
mod salsa_struct;
mod tracked;
mod tracked_fn;
mod tracked_fn1;
mod tracked_struct;
mod update;
mod xform;
#[proc_macro_attribute]
pub fn accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
@ -88,12 +84,3 @@ pub fn update(input: TokenStream) -> TokenStream {
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_derive(DebugWithDb)]
pub fn debug(input: TokenStream) -> TokenStream {
let item = syn::parse_macro_input!(input as syn::DeriveInput);
match debug_with_db::debug_with_db(item) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}

View file

@ -110,10 +110,6 @@ impl<A: AllowedOptions> Options<A> {
parse_quote! {crate::Jar}
}
pub(crate) fn should_backdate(&self) -> bool {
self.no_eq.is_none()
}
}
impl<A: AllowedOptions> syn::parse::Parse for Options<A> {

View file

@ -26,560 +26,41 @@
//! * this could be optimized, particularly for interned fields
use crate::{
db_lifetime::{self, db_lifetime, default_db_lifetime},
db_lifetime,
options::{AllowedOptions, Options},
xform::ChangeLt,
};
use proc_macro2::{Ident, Span, TokenStream};
use syn::{
punctuated::Punctuated, spanned::Spanned, token::Comma, GenericParam, ImplGenerics,
TypeGenerics, WhereClause,
};
use proc_macro2::{Ident, Literal, Span, TokenStream};
use syn::spanned::Spanned;
pub(crate) struct SalsaStruct<A: AllowedOptions> {
args: Options<A>,
struct_item: syn::ItemStruct,
customizations: Vec<Customization>,
fields: Vec<SalsaField>,
module: syn::Ident,
pub(crate) struct SalsaStruct<'s, A: SalsaStructAllowedOptions> {
struct_item: &'s syn::ItemStruct,
args: &'s Options<A>,
fields: Vec<SalsaField<'s>>,
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum Customization {
DebugWithDb,
pub(crate) trait SalsaStructAllowedOptions: AllowedOptions {
/// The kind of struct (e.g., interned, input, tracked).
const KIND: &'static str;
/// Are `#[id]` fields allowed?
const ALLOW_ID: bool;
/// Does this kind of struct have a `'db` lifetime?
const HAS_LIFETIME: bool;
}
pub(crate) struct SalsaField<'s> {
field: &'s syn::Field,
pub(crate) has_id_attr: bool,
pub(crate) has_ref_attr: bool,
pub(crate) has_no_eq_attr: bool,
get_name: syn::Ident,
set_name: syn::Ident,
}
const BANNED_FIELD_NAMES: &[&str] = &["from", "new"];
/// Classifies the kind of field stored in this salsa
/// struct.
#[derive(PartialEq, Eq)]
pub enum TheStructKind {
/// Stores an "id"
Id,
/// Stores a "pointer" with the given lifetime
Pointer(syn::Lifetime),
}
impl std::fmt::Debug for TheStructKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TheStructKind::Id => write!(f, "Id"),
TheStructKind::Pointer(lt) => write!(f, "Pointer({lt})"),
}
}
}
impl<A: AllowedOptions> SalsaStruct<A> {
pub(crate) fn new(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
module: &str,
) -> syn::Result<Self> {
let struct_item = syn::parse(input)?;
Self::with_struct(args, struct_item, module)
}
pub(crate) fn with_struct(
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
module: &str,
) -> syn::Result<Self> {
let module = syn::Ident::new(module, struct_item.ident.span());
let args: Options<A> = syn::parse(args)?;
let customizations = Self::extract_customizations(&struct_item)?;
let fields = Self::extract_fields(&struct_item)?;
Ok(Self {
args,
struct_item,
customizations,
fields,
module,
})
}
pub(crate) fn args(&self) -> &Options<A> {
&self.args
}
pub(crate) fn require_no_generics(&self) -> syn::Result<()> {
if let Some(param) = self.struct_item.generics.params.iter().next() {
return Err(syn::Error::new_spanned(
param,
"generic parameters not allowed here",
));
}
Ok(())
}
/// Require that either there are no generics or exactly one lifetime parameter.
pub(crate) fn require_db_lifetime(&self) -> syn::Result<()> {
db_lifetime::require_db_lifetime(&self.struct_item.generics)
}
pub(crate) fn send_sync_impls(&self) -> Vec<syn::ItemImpl> {
match self.the_struct_kind() {
TheStructKind::Id => vec![],
TheStructKind::Pointer(_db) => {
let (the_ident, _, impl_generics, type_generics, where_clauses) =
self.the_ident_and_generics();
vec![
parse_quote! {
unsafe impl #impl_generics std::marker::Send for #the_ident #type_generics
where
#where_clauses
{}
},
parse_quote! {
unsafe impl #impl_generics std::marker::Sync for #the_ident #type_generics
where
#where_clauses
{}
},
]
}
}
}
/// Some salsa structs require a "Configuration" struct
/// because they make use of GATs. This function
/// synthesizes a name and generates the struct declaration.
pub(crate) fn config_struct(&self) -> syn::ItemStruct {
let config_ident = syn::Ident::new(
&format!("__{}Config", self.the_ident()),
self.the_ident().span(),
);
let visibility = self.visibility();
parse_quote! {
#visibility struct #config_ident {
_uninhabited: std::convert::Infallible,
}
}
}
pub(crate) fn the_struct_kind(&self) -> TheStructKind {
if self.struct_item.generics.params.is_empty() {
TheStructKind::Id
} else {
TheStructKind::Pointer(db_lifetime(&self.struct_item.generics))
}
}
fn extract_customizations(struct_item: &syn::ItemStruct) -> syn::Result<Vec<Customization>> {
Ok(struct_item
.attrs
.iter()
.map(|attr| {
if attr.path().is_ident("customize") {
// FIXME: this should be a comma separated list but I couldn't
// be bothered to remember how syn does this.
let args: syn::Ident = attr.parse_args()?;
if args == "DebugWithDb" {
Ok(vec![Customization::DebugWithDb])
} else {
Err(syn::Error::new_spanned(args, "unrecognized customization"))
}
} else {
Ok(vec![])
}
})
.collect::<Result<Vec<Vec<_>>, _>>()?
.into_iter()
.flatten()
.collect())
}
/// Extract out the fields and their options:
/// If this is a struct, it must use named fields, so we can define field accessors.
/// If it is an enum, then this is not necessary.
fn extract_fields(struct_item: &syn::ItemStruct) -> syn::Result<Vec<SalsaField>> {
match &struct_item.fields {
syn::Fields::Named(n) => Ok(n
.named
.iter()
.map(SalsaField::new)
.collect::<syn::Result<Vec<_>>>()?),
f => Err(syn::Error::new_spanned(
f,
"must have named fields for a struct",
)),
}
}
/// Iterator over all named fields.
///
/// If this is an enum, empty iterator.
pub(crate) fn all_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.fields.iter()
}
/// Names of all fields (id and value).
///
/// If this is an enum, empty vec.
pub(crate) fn all_field_names(&self) -> Vec<&syn::Ident> {
self.all_fields().map(|ef| ef.name()).collect()
}
/// Visibilities of all fields
pub(crate) fn all_field_vises(&self) -> Vec<&syn::Visibility> {
self.all_fields().map(|ef| ef.vis()).collect()
}
/// Names of getters of all fields
pub(crate) fn all_get_field_names(&self) -> Vec<&syn::Ident> {
self.all_fields().map(|ef| ef.get_name()).collect()
}
/// Types of all fields (id and value).
///
/// If this is an enum, empty vec.
pub(crate) fn all_field_tys(&self) -> Vec<&syn::Type> {
self.all_fields().map(|ef| ef.ty()).collect()
}
/// The name of "the struct" (this is the name the user gave, e.g., `Foo`).
pub(crate) fn the_ident(&self) -> &syn::Ident {
&self.struct_item.ident
}
/// Name of the struct the user gave plus:
///
/// * its list of generic parameters
/// * the generics "split for impl".
pub(crate) fn the_ident_and_generics(
&self,
) -> (
&syn::Ident,
&Punctuated<GenericParam, Comma>,
ImplGenerics<'_>,
TypeGenerics<'_>,
Option<&WhereClause>,
) {
let ident = &self.struct_item.ident;
let (impl_generics, type_generics, where_clause) =
self.struct_item.generics.split_for_impl();
(
ident,
&self.struct_item.generics.params,
impl_generics,
type_generics,
where_clause,
)
}
/// Type of the jar for this struct
pub(crate) fn jar_ty(&self) -> syn::Type {
self.args.jar_ty()
}
/// checks if the "singleton" flag was set
pub(crate) fn is_isingleton(&self) -> bool {
self.args.singleton.is_some()
}
pub(crate) fn db_dyn_ty(&self) -> syn::Type {
let jar_ty = self.jar_ty();
parse_quote! {
<#jar_ty as salsa::jar::Jar>::DynDb
}
}
/// Create "the struct" whose field is an id.
/// This is the struct the user will refernece, but only if there
/// are no lifetimes.
pub(crate) fn the_struct_id(&self) -> syn::ItemStruct {
assert_eq!(self.the_struct_kind(), TheStructKind::Id);
let ident = self.the_ident();
let visibility = &self.struct_item.vis;
// Extract the attributes the user gave, but screen out derive, since we are adding our own,
// and the customize attribute that we use for our own purposes.
let attrs: Vec<_> = self
.struct_item
.attrs
.iter()
.filter(|attr| !attr.path().is_ident("derive"))
.filter(|attr| !attr.path().is_ident("customize"))
.collect();
parse_quote_spanned! { ident.span() =>
#(#attrs)*
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#visibility struct #ident(salsa::Id);
}
}
/// Create the struct that the user will reference.
/// If
pub(crate) fn the_struct(&self, config_ident: &syn::Ident) -> syn::Result<syn::ItemStruct> {
if self.struct_item.generics.params.is_empty() {
Ok(self.the_struct_id())
} else {
let ident = self.the_ident();
let visibility = &self.struct_item.vis;
let generics = &self.struct_item.generics;
if generics.params.len() != 1 || generics.lifetimes().count() != 1 {
return Err(syn::Error::new_spanned(
&self.struct_item.generics,
"must have exactly one lifetime parameter",
));
}
let lifetime = generics.lifetimes().next().unwrap();
// Extract the attributes the user gave, but screen out derive, since we are adding our own,
// and the customize attribute that we use for our own purposes.
let attrs: Vec<_> = self
.struct_item
.attrs
.iter()
.filter(|attr| !attr.path().is_ident("derive"))
.filter(|attr| !attr.path().is_ident("customize"))
.collect();
let module = &self.module;
Ok(parse_quote_spanned! { ident.span() =>
#(#attrs)*
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#visibility struct #ident #generics (
std::ptr::NonNull<salsa::#module::ValueStruct < #config_ident >>,
std::marker::PhantomData < & #lifetime salsa::#module::ValueStruct < #config_ident > >
);
})
}
}
/// Generate code to access a `salsa::Id` from `self`
pub(crate) fn access_salsa_id_from_self(&self) -> syn::Expr {
match self.the_struct_kind() {
TheStructKind::Id => parse_quote!(self.0),
TheStructKind::Pointer(_) => {
parse_quote!(salsa::id::AsId::as_id(unsafe { self.0.as_ref() }))
}
}
}
/// Returns the visibility of this item
pub(crate) fn visibility(&self) -> &syn::Visibility {
&self.struct_item.vis
}
/// Returns the `constructor_name` in `Options` if it is `Some`, else `new`
pub(crate) fn constructor_name(&self) -> syn::Ident {
match self.args.constructor_name.clone() {
Some(name) => name,
None => Ident::new("new", self.the_ident().span()),
}
}
/// Returns the lifetime to use for `'db`. This is normally whatever lifetime
/// parameter the user put on the struct, but it might be a generated default
/// if there is no such parameter. Using the name the user gave is important
/// because it may appear in field types and the like.
pub(crate) fn named_db_lifetime(&self) -> syn::Lifetime {
match self.the_struct_kind() {
TheStructKind::Id => self.default_db_lifetime(),
TheStructKind::Pointer(db) => db,
}
}
/// Returns lifetime to use for `'db`, substituting `'_` if there is no name required.
/// This is convenient in function signatures where `'db` may not be in scope.
pub(crate) fn maybe_elided_db_lifetime(&self) -> syn::Lifetime {
match self.the_struct_kind() {
TheStructKind::Id => syn::Lifetime {
apostrophe: self.struct_item.ident.span(),
ident: syn::Ident::new("_", self.struct_item.ident.span()),
},
TheStructKind::Pointer(db) => db,
}
}
/// Normally we try to use whatever lifetime parameter the use gave us
/// to represent `'db`; but if they didn't give us one, we need to use a default
/// name. We choose `'db`.
fn default_db_lifetime(&self) -> syn::Lifetime {
default_db_lifetime(self.struct_item.generics.span())
}
/// Generate `impl salsa::id::AsId for Foo`
pub(crate) fn as_id_impl(&self) -> syn::ItemImpl {
match self.the_struct_kind() {
TheStructKind::Id => {
let ident = self.the_ident();
let (impl_generics, type_generics, where_clause) =
self.struct_item.generics.split_for_impl();
parse_quote_spanned! { ident.span() =>
impl #impl_generics salsa::id::AsId for #ident #type_generics
#where_clause
{
fn as_id(&self) -> salsa::Id {
self.0
}
}
}
}
TheStructKind::Pointer(_) => {
let ident = self.the_ident();
let (impl_generics, type_generics, where_clause) =
self.struct_item.generics.split_for_impl();
parse_quote_spanned! { ident.span() =>
impl #impl_generics salsa::id::AsId for #ident #type_generics
#where_clause
{
fn as_id(&self) -> salsa::Id {
salsa::id::AsId::as_id(unsafe { self.0.as_ref() })
}
}
}
}
}
}
/// Generate `impl salsa::id::AsId for Foo`
pub(crate) fn impl_of_from_id(&self) -> Option<syn::ItemImpl> {
match self.the_struct_kind() {
TheStructKind::Id => {
let ident = self.the_ident();
let (impl_generics, type_generics, where_clause) =
self.struct_item.generics.split_for_impl();
Some(parse_quote_spanned! { ident.span() =>
impl #impl_generics salsa::id::FromId for #ident #type_generics
#where_clause
{
fn from_id(id: salsa::Id) -> Self {
#ident(id)
}
}
})
}
TheStructKind::Pointer(_) => None,
}
}
/// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct.
pub(crate) fn debug_impl(&self) -> syn::ItemImpl {
let ident: &Ident = self.the_ident();
let (impl_generics, type_generics, where_clause) =
self.struct_item.generics.split_for_impl();
let ident_string = ident.to_string();
// `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl
parse_quote_spanned! {ident.span()=>
impl #impl_generics ::std::fmt::Debug for #ident #type_generics
#where_clause
{
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct(#ident_string)
.field("[salsa id]", &self.salsa_id().as_u32())
.finish()
}
}
}
}
/// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct.
pub(crate) fn as_debug_with_db_impl(&self) -> Option<syn::ItemImpl> {
if self.customizations.contains(&Customization::DebugWithDb) {
return None;
}
let ident = self.the_ident();
let (impl_generics, type_generics, where_clause) =
self.struct_item.generics.split_for_impl();
let db_type = self.db_dyn_ty();
let ident_string = ident.to_string();
// `::salsa::debug::helper::SalsaDebug` will use `DebugWithDb` or fallback to `Debug`
let fields = self
.all_fields()
.map(|field| -> TokenStream {
let field_name_string = field.name().to_string();
let field_getter = field.get_name();
let field_ty = ChangeLt::to_elided().in_type(field.ty());
let db_type = ChangeLt::to_elided().in_type(&db_type);
quote_spanned! { field.field.span() =>
debug_struct = debug_struct.field(
#field_name_string,
&::salsa::debug::helper::SalsaDebug::<#field_ty, #db_type>::salsa_debug(
#[allow(clippy::needless_borrow)]
&self.#field_getter(_db),
_db,
)
);
}
})
.collect::<TokenStream>();
// `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl
Some(parse_quote_spanned! {ident.span()=>
impl #impl_generics ::salsa::DebugWithDb<#db_type> for #ident #type_generics
#where_clause
{
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: & #db_type) -> ::std::fmt::Result {
#[allow(unused_imports)]
use ::salsa::debug::helper::Fallback;
#[allow(unused_mut)]
let mut debug_struct = &mut f.debug_struct(#ident_string);
debug_struct = debug_struct.field("[salsa id]", &self.salsa_id().as_u32());
#fields
debug_struct.finish()
}
}
})
}
/// Implementation of `salsa::update::Update`.
pub(crate) fn update_impl(&self) -> syn::ItemImpl {
let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics();
parse_quote! {
unsafe impl #impl_generics salsa::update::Update for #ident #type_generics
#where_clause
{
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
if unsafe { *old_pointer } != new_value {
unsafe { *old_pointer = new_value };
true
} else {
false
}
}
}
}
}
/// Disallow `#[id]` attributes on the fields of this struct.
///
/// If an `#[id]` field is found, return an error.
///
/// # Parameters
///
/// * `kind`, the attribute name (e.g., `input` or `interned`)
pub(crate) fn disallow_id_fields(&self, kind: &str) -> syn::Result<()> {
for ef in self.all_fields() {
if ef.has_id_attr {
return Err(syn::Error::new(
ef.name().span(),
format!("`#[id]` cannot be used with `#[salsa::{kind}]`"),
));
}
}
Ok(())
}
}
#[allow(clippy::type_complexity)]
pub(crate) const FIELD_OPTION_ATTRIBUTES: &[(&str, fn(&syn::Attribute, &mut SalsaField))] = &[
("id", |_, ef| ef.has_id_attr = true),
@ -593,18 +74,148 @@ pub(crate) const FIELD_OPTION_ATTRIBUTES: &[(&str, fn(&syn::Attribute, &mut Sals
}),
];
pub(crate) struct SalsaField {
field: syn::Field,
impl<'s, A> SalsaStruct<'s, A>
where
A: SalsaStructAllowedOptions,
{
pub fn new(struct_item: &'s syn::ItemStruct, args: &'s Options<A>) -> syn::Result<Self> {
let syn::Fields::Named(n) = &struct_item.fields else {
return Err(syn::Error::new_spanned(
&struct_item.ident,
"must have named fields for a struct",
));
};
pub(crate) has_id_attr: bool,
pub(crate) has_ref_attr: bool,
pub(crate) has_no_eq_attr: bool,
get_name: syn::Ident,
set_name: syn::Ident,
let fields = n
.named
.iter()
.map(SalsaField::new)
.collect::<syn::Result<_>>()?;
let this = Self {
struct_item,
args,
fields,
};
this.maybe_disallow_id_fields()?;
this.check_generics()?;
Ok(this)
}
/// Returns the `constructor_name` in `Options` if it is `Some`, else `new`
pub(crate) fn constructor_name(&self) -> syn::Ident {
match self.args.constructor_name.clone() {
Some(name) => name,
None => Ident::new("new", self.struct_item.span()),
}
}
/// Disallow `#[id]` attributes on the fields of this struct.
///
/// If an `#[id]` field is found, return an error.
///
/// # Parameters
///
/// * `kind`, the attribute name (e.g., `input` or `interned`)
fn maybe_disallow_id_fields(&self) -> syn::Result<()> {
if A::ALLOW_ID {
return Ok(());
}
// Check if any field has the `#[id]` attribute.
for ef in &self.fields {
if ef.has_id_attr {
return Err(syn::Error::new_spanned(
&ef.field,
format!("`#[id]` cannot be used with `#[salsa::{}]`", A::KIND),
));
}
}
Ok(())
}
/// Check that the generic parameters look as expected for this kind of struct.
fn check_generics(&self) -> syn::Result<()> {
if A::HAS_LIFETIME {
db_lifetime::require_db_lifetime(&self.struct_item.generics)
} else {
if let Some(param) = self.struct_item.generics.params.iter().next() {
return Err(syn::Error::new_spanned(
param,
"generic parameters not allowed here",
));
}
Ok(())
}
}
pub(crate) fn field_ids(&self) -> Vec<&syn::Ident> {
self.fields
.iter()
.map(|f| f.field.ident.as_ref().unwrap())
.collect()
}
pub(crate) fn field_indices(&self) -> Vec<Literal> {
(0..self.fields.len())
.map(Literal::usize_unsuffixed)
.collect()
}
pub(crate) fn num_fields(&self) -> Literal {
Literal::usize_unsuffixed(self.fields.len())
}
pub(crate) fn id_field_indices(&self) -> Vec<Literal> {
self.fields
.iter()
.zip(0..)
.filter_map(|(f, index)| if f.has_id_attr { Some(index) } else { None })
.map(Literal::usize_unsuffixed)
.collect()
}
pub(crate) fn field_getter_ids(&self) -> Vec<&syn::Ident> {
self.fields.iter().map(|f| &f.get_name).collect()
}
pub(crate) fn field_setter_ids(&self) -> Vec<&syn::Ident> {
self.fields.iter().map(|f| &f.set_name).collect()
}
pub(crate) fn field_tys(&self) -> Vec<&syn::Type> {
self.fields.iter().map(|f| &f.field.ty).collect()
}
pub(crate) fn field_options(&self) -> Vec<TokenStream> {
self.fields
.iter()
.map(|f| {
let clone_ident = if f.has_ref_attr {
syn::Ident::new("no_clone", Span::call_site())
} else {
syn::Ident::new("clone", Span::call_site())
};
let backdate_ident = if f.has_no_eq_attr {
syn::Ident::new("no_backdate", Span::call_site())
} else {
syn::Ident::new("backdate", Span::call_site())
};
quote!((#clone_ident, #backdate_ident))
})
.collect()
}
}
impl SalsaField {
pub(crate) fn new(field: &syn::Field) -> syn::Result<Self> {
impl<'s> SalsaField<'s> {
fn new(field: &'s syn::Field) -> syn::Result<Self> {
let field_name = field.ident.as_ref().unwrap();
let field_name_str = field_name.to_string();
if BANNED_FIELD_NAMES.iter().any(|n| *n == field_name_str) {
@ -620,7 +231,7 @@ impl SalsaField {
let get_name = Ident::new(&field_name_str, field_name.span());
let set_name = Ident::new(&format!("set_{}", field_name_str), field_name.span());
let mut result = SalsaField {
field: field.clone(),
field,
has_id_attr: false,
has_ref_attr: false,
has_no_eq_attr: false,
@ -639,43 +250,4 @@ 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()
}
/// The visibility of this field.
pub(crate) fn vis(&self) -> &syn::Visibility {
&self.field.vis
}
/// The type of this field (all `SalsaField` instances are named).
pub(crate) fn ty(&self) -> &syn::Type {
&self.field.ty
}
/// The name of this field's get method
pub(crate) fn get_name(&self) -> &syn::Ident {
&self.get_name
}
/// The name of this field's get method
pub(crate) fn set_name(&self) -> &syn::Ident {
&self.set_name
}
/// Do you clone the value of this field? (True if it is not a ref field)
pub(crate) fn is_clone_field(&self) -> bool {
!self.has_ref_attr
}
/// Do you potentially backdate the value of this field? (True if it is not a no-eq field)
pub(crate) fn is_backdate_field(&self) -> bool {
!self.has_no_eq_attr
}
}

View file

@ -6,9 +6,9 @@ pub(crate) fn tracked(
) -> proc_macro::TokenStream {
let item = syn::parse_macro_input!(input as Item);
let res = match item {
syn::Item::Struct(item) => crate::tracked_struct::tracked(args, item),
syn::Item::Struct(item) => crate::tracked_struct::tracked_struct(args, item),
syn::Item::Fn(item) => crate::tracked_fn::tracked_fn(args, item),
syn::Item::Impl(item) => crate::tracked_fn1::tracked_impl(args, item),
syn::Item::Impl(item) => todo!(),
_ => Err(syn::Error::new(
item.span(),
"tracked can only be applied to structs, functions, and impls",

View file

@ -295,7 +295,7 @@ impl Macro {
fn output_ty<'item>(&self, item: &'item ItemFn) -> syn::Result<syn::Type> {
match &item.sig.output {
syn::ReturnType::Default => Ok(parse_quote!("()")),
syn::ReturnType::Default => Ok(parse_quote!(())),
syn::ReturnType::Type(_, ty) => Ok(syn::Type::clone(ty)),
}
}

View file

@ -1,925 +0,0 @@
use proc_macro2::{Literal, Span, TokenStream};
use syn::spanned::Spanned;
use syn::visit_mut::VisitMut;
use syn::ReturnType;
use crate::configuration::{self, Configuration, CycleRecoveryStrategy};
use crate::db_lifetime::{self, db_lifetime, require_optional_db_lifetime};
use crate::options::Options;
use crate::xform::ChangeLt;
pub(crate) fn tracked_fn(
args: proc_macro::TokenStream,
mut item_fn: syn::ItemFn,
) -> syn::Result<TokenStream> {
db_lifetime::require_optional_db_lifetime(&item_fn.sig.generics)?;
let fn_ident = item_fn.sig.ident.clone();
let args: FnArgs = syn::parse(args)?;
if item_fn.sig.inputs.is_empty() {
return Err(syn::Error::new(
item_fn.sig.ident.span(),
"tracked functions must have at least a database argument",
));
}
if let syn::FnArg::Receiver(receiver) = &item_fn.sig.inputs[0] {
return Err(syn::Error::new(
receiver.span(),
"#[salsa::tracked] must also be applied to the impl block for tracked methods",
));
}
if let Some(s) = &args.specify {
if function_type(&item_fn) == FunctionType::RequiresInterning {
return Err(syn::Error::new(
s.span(),
"tracked function takes too many arguments to have its value set with `specify`",
));
}
if args.lru.is_some() {
return Err(syn::Error::new(
s.span(),
"`specify` and `lru` cannot be used together",
));
}
}
let (config_ty, fn_struct) = fn_struct(&args, &item_fn)?;
*item_fn.block = getter_fn(&args, &mut item_fn.sig, item_fn.block.span(), &config_ty)?;
Ok(crate::debug::dump_tokens(
fn_ident,
quote! {
#fn_struct
// we generate a `'db` lifetime that clippy
// sometimes doesn't like
#[allow(clippy::needless_lifetimes)]
#item_fn
},
))
}
type FnArgs = Options<TrackedFn>;
struct TrackedFn;
impl crate::options::AllowedOptions for TrackedFn {
const RETURN_REF: bool = true;
const SPECIFY: bool = true;
const NO_EQ: bool = true;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = false;
const DB: bool = false;
const RECOVERY_FN: bool = true;
const LRU: bool = true;
const CONSTRUCTOR_NAME: bool = false;
}
type ImplArgs = Options<TrackedImpl>;
pub(crate) fn tracked_impl(
args: proc_macro::TokenStream,
mut item_impl: syn::ItemImpl,
) -> syn::Result<TokenStream> {
let args: ImplArgs = syn::parse(args)?;
let self_type = match &*item_impl.self_ty {
syn::Type::Path(path) => path,
_ => {
return Err(syn::Error::new(
item_impl.self_ty.span(),
"#[salsa::tracked] can only be applied to salsa structs",
))
}
};
let self_type_name = &self_type.path.segments.last().unwrap().ident;
let name_prefix = match &item_impl.trait_ {
Some((_, trait_name, _)) => format!(
"{}_{}",
self_type_name,
trait_name.segments.last().unwrap().ident
),
None => format!("{}", self_type_name),
};
#[allow(clippy::manual_try_fold)] // we accumulate errors
let extra_impls = item_impl
.items
.iter_mut()
.filter_map(|item| {
let item_method = match item {
syn::ImplItem::Fn(item_method) => item_method,
_ => return None,
};
let salsa_tracked_attr = item_method.attrs.iter().position(|attr| {
let path = &attr.path().segments;
path.len() == 2
&& path[0].arguments.is_none()
&& path[0].ident == "salsa"
&& path[1].arguments.is_none()
&& path[1].ident == "tracked"
})?;
let salsa_tracked_attr = item_method.attrs.remove(salsa_tracked_attr);
let inner_args = match salsa_tracked_attr.meta {
syn::Meta::Path(_) => Ok(FnArgs::default()),
syn::Meta::List(_) | syn::Meta::NameValue(_) => salsa_tracked_attr.parse_args(),
};
let inner_args = match inner_args {
Ok(inner_args) => inner_args,
Err(err) => return Some(Err(err)),
};
let name = format!("{}_{}", name_prefix, item_method.sig.ident);
Some(tracked_method(
&item_impl.generics,
&args,
inner_args,
item_method,
self_type,
&name,
))
})
// Collate all the errors so we can display them all at once
.fold(Ok(Vec::new()), |mut acc, res| {
match (&mut acc, res) {
(Ok(extra_impls), Ok(impls)) => extra_impls.push(impls),
(Ok(_), Err(err)) => acc = Err(err),
(Err(_), Ok(_)) => {}
(Err(errors), Err(err)) => errors.combine(err),
}
acc
})?;
Ok(crate::debug::dump_tokens(
self_type_name,
quote! {
#item_impl
#(#extra_impls)*
},
))
}
struct TrackedImpl;
impl crate::options::AllowedOptions for TrackedImpl {
const RETURN_REF: bool = false;
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const JAR: bool = true;
const DATA: bool = false;
const DB: bool = false;
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = false;
const SINGLETON: bool = false;
}
fn tracked_method(
impl_generics: &syn::Generics,
outer_args: &ImplArgs,
mut args: FnArgs,
item_method: &mut syn::ImplItemFn,
self_type: &syn::TypePath,
name: &str,
) -> syn::Result<TokenStream> {
args.jar_ty = args.jar_ty.or_else(|| outer_args.jar_ty.clone());
if item_method.sig.inputs.len() <= 1 {
return Err(syn::Error::new(
item_method.sig.ident.span(),
"tracked methods must have at least self and a database argument",
));
}
let mut item_fn = syn::ItemFn {
attrs: item_method.attrs.clone(),
vis: item_method.vis.clone(),
sig: item_method.sig.clone(),
block: Box::new(rename_self_in_block(item_method.block.clone())?),
};
item_fn.sig.ident = syn::Ident::new(name, item_fn.sig.ident.span());
// Insert the generics from impl at the start of the fn generics
for parameter in impl_generics.params.iter().rev() {
item_fn.sig.generics.params.insert(0, parameter.clone());
}
// Flip the first and second arguments as the rest of the code expects the
// database to come first and the struct to come second. We also need to
// change the self argument to a normal typed argument called __salsa_self.
let mut original_inputs = item_fn.sig.inputs.into_pairs();
let self_param = match original_inputs.next().unwrap().into_value() {
syn::FnArg::Receiver(r) if r.reference.is_none() => r,
arg => return Err(syn::Error::new(arg.span(), "first argument must be self")),
};
let db_param = original_inputs.next().unwrap().into_value();
let mut inputs = syn::punctuated::Punctuated::new();
inputs.push(db_param);
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: self_param.attrs,
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: Vec::new(),
by_ref: None,
mutability: self_param.mutability,
ident: syn::Ident::new("__salsa_self", self_param.self_token.span),
subpat: None,
})),
colon_token: Default::default(),
ty: Box::new(syn::Type::Path(self_type.clone())),
}));
inputs.push_punct(Default::default());
inputs.extend(original_inputs);
item_fn.sig.inputs = inputs;
let (config_ty, fn_struct) = crate::tracked_fn1::fn_struct(&args, &item_fn)?;
// we generate a `'db` lifetime that clippy
// sometimes doesn't like
item_method
.attrs
.push(syn::parse_quote! {#[allow(clippy::needless_lifetimes)]});
item_method.block = getter_fn(
&args,
&mut item_method.sig,
item_method.block.span(),
&config_ty,
)?;
Ok(fn_struct)
}
/// Rename all occurrences of `self` to `__salsa_self` in a block
/// so that it can be used in a free function.
fn rename_self_in_block(mut block: syn::Block) -> syn::Result<syn::Block> {
struct RenameIdent(syn::Result<()>);
impl syn::visit_mut::VisitMut for RenameIdent {
fn visit_ident_mut(&mut self, i: &mut syn::Ident) {
if i == "__salsa_self" {
let err = syn::Error::new(
i.span(),
"Existing variable name clashes with 'self' -> '__salsa_self' renaming",
);
match &mut self.0 {
Ok(()) => self.0 = Err(err),
Err(errors) => errors.combine(err),
}
}
if i == "self" {
*i = syn::Ident::new("__salsa_self", i.span());
}
}
}
let mut rename = RenameIdent(Ok(()));
rename.visit_block_mut(&mut block);
rename.0.map(move |()| block)
}
/// Create the struct representing the function and all of its impls.
///
/// This returns the name of the constructed type and the code defining everything.
fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, TokenStream)> {
require_optional_db_lifetime(&item_fn.sig.generics)?;
let db_lt = &db_lifetime(&item_fn.sig.generics);
let struct_item = configuration_struct(item_fn);
let configuration = fn_configuration(args, item_fn);
let struct_item_ident = &struct_item.ident;
let config_ty: syn::Type = parse_quote!(#struct_item_ident);
let configuration_impl = configuration.to_impl(&config_ty);
let interned_configuration_impl = interned_configuration_impl(db_lt, item_fn, &config_ty);
let ingredients_for_impl = ingredients_for_impl(args, item_fn, &config_ty);
let item_impl = setter_impl(args, item_fn, &config_ty)?;
Ok((
config_ty,
quote! {
#struct_item
#configuration_impl
#interned_configuration_impl
#ingredients_for_impl
#item_impl
},
))
}
fn interned_configuration_impl(
db_lt: &syn::Lifetime,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::ItemImpl {
let arg_tys = item_fn.sig.inputs.iter().skip(1).map(|arg| match arg {
syn::FnArg::Receiver(_) => unreachable!(),
syn::FnArg::Typed(pat_ty) => pat_ty.ty.clone(),
});
let intern_data_ty: syn::Type = parse_quote!(
(#(#arg_tys),*)
);
let intern_data_ty = ChangeLt::elided_to(db_lt).in_type(&intern_data_ty);
let debug_name = crate::literal(&item_fn.sig.ident);
parse_quote!(
impl salsa::interned::Configuration for #config_ty {
const DEBUG_NAME: &'static str = #debug_name;
type Data<#db_lt> = #intern_data_ty;
type Struct<#db_lt> = & #db_lt salsa::interned::ValueStruct<Self>;
unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull<salsa::interned::ValueStruct<Self>>) -> Self::Struct<'db> {
unsafe { ptr.as_ref() }
}
fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db salsa::interned::ValueStruct<Self> {
s
}
}
)
}
fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
let fn_name = item_fn.sig.ident.clone();
let visibility = &item_fn.vis;
let intern_map: syn::Type = match function_type(item_fn) {
FunctionType::Constant => {
parse_quote! { salsa::interned::IdentityInterner<Self> }
}
FunctionType::SalsaStruct => {
parse_quote! { salsa::interned::IdentityInterner<Self> }
}
FunctionType::RequiresInterning => {
parse_quote! { salsa::interned::InternedIngredient<Self> }
}
};
parse_quote! {
#[allow(non_camel_case_types)]
#visibility struct #fn_name {
intern_map: #intern_map,
function: salsa::function::FunctionIngredient<Self>,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum FunctionType {
Constant,
SalsaStruct,
RequiresInterning,
}
fn function_type(item_fn: &syn::ItemFn) -> FunctionType {
match item_fn.sig.inputs.len() {
0 => unreachable!(
"functions have been checked to have at least a database argument by this point"
),
1 => FunctionType::Constant,
2 => FunctionType::SalsaStruct,
_ => FunctionType::RequiresInterning,
}
}
/// Every tracked fn takes a salsa struct as its second argument.
/// This fn returns the type of that second argument.
fn salsa_struct_ty(item_fn: &syn::ItemFn) -> syn::Type {
if item_fn.sig.inputs.len() == 1 {
return parse_quote! { salsa::salsa_struct::Singleton };
}
match &item_fn.sig.inputs[1] {
syn::FnArg::Receiver(_) => panic!("receiver not expected"),
syn::FnArg::Typed(pat_ty) => (*pat_ty.ty).clone(),
}
}
fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration {
let jar_ty = args.jar_ty();
let db_lt = db_lifetime(&item_fn.sig.generics);
let salsa_struct_ty = salsa_struct_ty(item_fn);
let key_ty = match function_type(item_fn) {
FunctionType::Constant => parse_quote!(()),
FunctionType::SalsaStruct => salsa_struct_ty.clone(),
FunctionType::RequiresInterning => parse_quote!(salsa::id::Id),
};
let key_ty = ChangeLt::elided_to(&db_lt).in_type(&key_ty);
let value_ty = configuration::value_ty(&db_lt, &item_fn.sig);
let fn_ty = item_fn.sig.ident.clone();
// During recovery or execution, we are invoked with a `salsa::Id`
// that represents the interned value. We convert it back to a key
// which is either a single value (if there is one argument)
// or a tuple of values (if multiple arugments). `key_var` is the variable
// name that will store this result, and `key_splat` is a set of tokens
// that will convert it into one or multiple arguments (e.g., `key_var` if there
// is one argument or `key_var.0, key_var.1` if 2) that can be pasted into a function call.
let key_var = syn::Ident::new("__key", item_fn.span());
let key_fields = item_fn.sig.inputs.len() - 1;
let key_splat = if key_fields == 1 {
quote!(#key_var)
} else {
let indices = (0..key_fields)
.map(Literal::usize_unsuffixed)
.collect::<Vec<_>>();
quote!(#(__key.#indices),*)
};
let (cycle_strategy, recover_fn) = if let Some(recovery_fn) = &args.recovery_fn {
// Create the `recover_from_cycle` function, which (a) maps from the interned id to the actual
// keys and then (b) invokes the recover function itself.
let cycle_strategy = CycleRecoveryStrategy::Fallback;
let cycle_fullback = parse_quote! {
fn recover_from_cycle<'db>(__db: &'db salsa::function::DynDb<Self>, __cycle: &salsa::Cycle, __id: salsa::Id) -> Self::Value<'db> {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients =
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
let #key_var = __ingredients.intern_map.data_with_db(__id, __db).clone();
#recovery_fn(__db, __cycle, #key_splat)
}
};
(cycle_strategy, cycle_fullback)
} else {
// When the `recovery_fn` attribute is not set, set `cycle_strategy` to `Panic`
let cycle_strategy = CycleRecoveryStrategy::Panic;
let cycle_panic = configuration::panic_cycle_recovery_fn();
(cycle_strategy, cycle_panic)
};
let backdate_fn = configuration::should_backdate_value_fn(args.should_backdate());
// The type of the configuration struct; this has the same name as the fn itself.
// Make a copy of the fn with a different name; we will invoke this from `execute`.
// We need to change the name because, otherwise, if the function invoked itself
// recursively it would not go through the query system.
let inner_fn_name = &syn::Ident::new("__fn", item_fn.sig.ident.span());
let mut inner_fn = item_fn.clone();
inner_fn.sig.ident = inner_fn_name.clone();
// Create the `execute` function, which (a) maps from the interned id to the actual
// keys and then (b) invokes the function itself (which we embed within).
let execute_fn = parse_quote! {
fn execute<'db>(__db: &'db salsa::function::DynDb<Self>, __id: salsa::Id) -> Self::Value<'db> {
#inner_fn
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients =
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
let #key_var = __ingredients.intern_map.data_with_db(__id, __db).clone();
#inner_fn_name(__db, #key_splat)
}
};
// get the name of the function as a string literal
let debug_name = crate::literal(&item_fn.sig.ident);
Configuration {
debug_name,
db_lt,
jar_ty,
salsa_struct_ty,
input_ty: key_ty,
value_ty,
cycle_strategy,
backdate_fn,
execute_fn,
recover_fn,
}
}
fn ingredients_for_impl(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::ItemImpl {
let jar_ty = args.jar_ty();
let intern_map: syn::Expr = match function_type(item_fn) {
FunctionType::Constant | FunctionType::SalsaStruct => {
parse_quote! {
salsa::interned::IdentityInterner::new()
}
}
FunctionType::RequiresInterning => {
parse_quote! {
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
&ingredients.intern_map
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient_mut(jar);
&mut ingredients.intern_map
}
);
salsa::interned::InternedIngredient::new(index)
}
}
}
};
// set 0 as default to disable LRU
let lru = args.lru.unwrap_or(0);
parse_quote! {
impl salsa::storage::IngredientsFor for #config_ty {
type Ingredients = Self;
type Jar = #jar_ty;
fn create_ingredients<DB>(routes: &mut salsa::routes::Routes<DB>) -> Self::Ingredients
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
Self {
intern_map: #intern_map,
function: {
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient(jar);
&ingredients.function
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients =
<_ as salsa::storage::HasIngredientsFor<Self::Ingredients>>::ingredient_mut(jar);
&mut ingredients.function
});
let ingredient = salsa::function::FunctionIngredient::new(index);
ingredient.set_capacity(#lru);
ingredient
}
}
}
}
}
}
fn setter_impl(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemImpl> {
let ref_getter_fn = ref_getter_fn(args, item_fn, config_ty)?;
let accumulated_fn = accumulated_fn(args, item_fn, config_ty)?;
let specify_fn = specify_fn(args, item_fn, config_ty)?.map(|f| quote! { #f });
let set_lru_fn = set_lru_capacity_fn(args, config_ty)?.map(|f| quote! { #f });
let setter_impl: syn::ItemImpl = parse_quote! {
impl #config_ty {
#[allow(dead_code, clippy::needless_lifetimes)]
#ref_getter_fn
#[allow(dead_code, clippy::needless_lifetimes)]
#accumulated_fn
#set_lru_fn
#specify_fn
}
};
Ok(setter_impl)
}
/// Creates the shim function that looks like the original function but calls
/// into the machinery we've just generated rather than executing the code.
fn getter_fn(
args: &FnArgs,
fn_sig: &mut syn::Signature,
block_span: proc_macro2::Span,
config_ty: &syn::Type,
) -> syn::Result<syn::Block> {
let mut is_method = false;
let mut arg_idents: Vec<_> = fn_sig
.inputs
.iter()
.map(|arg| -> syn::Result<syn::Ident> {
match arg {
syn::FnArg::Receiver(receiver) => {
is_method = true;
Ok(syn::Ident::new("self", receiver.self_token.span()))
}
syn::FnArg::Typed(pat_ty) => Ok(match &*pat_ty.pat {
syn::Pat::Ident(ident) => ident.ident.clone(),
_ => return Err(syn::Error::new(arg.span(), "unsupported argument kind")),
}),
}
})
.collect::<Result<_, _>>()?;
// If this is a method then the order of the database and the salsa struct are reversed
// because the self argument must always come first.
if is_method {
arg_idents.swap(0, 1);
}
Ok(if args.return_ref.is_some() {
make_fn_return_ref(fn_sig)?;
parse_quote_spanned! {
block_span => {
#config_ty::get(#(#arg_idents,)*)
}
}
} else {
parse_quote_spanned! {
block_span => {
Clone::clone(#config_ty::get(#(#arg_idents,)*))
}
}
})
}
/// Creates a `get` associated function that returns `&Value`
/// (to be used when `return_ref` is specified).
///
/// (Helper for `getter_fn`)
fn ref_getter_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let jar_ty = args.jar_ty();
let mut ref_getter_fn = item_fn.clone();
ref_getter_fn.sig.ident = syn::Ident::new("get", item_fn.sig.ident.span());
make_fn_return_ref(&mut ref_getter_fn.sig)?;
let (db_var, arg_names) = fn_args(item_fn)?;
ref_getter_fn.block = parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*));
__ingredients.function.fetch(#db_var, __key)
}
};
Ok(ref_getter_fn)
}
/// Create a `set_lru_capacity` associated function that can be used to change LRU
/// capacity at runtime.
/// Note that this function is only generated if the tracked function has the lru option set.
///
/// # Examples
///
/// ```rust,ignore
/// #[salsa::tracked(lru=32)]
/// fn my_tracked_fn(db: &dyn crate::Db, ...) { }
///
/// my_tracked_fn::set_lru_capacity(16)
/// ```
fn set_lru_capacity_fn(
args: &FnArgs,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemFn>> {
if args.lru.is_none() {
return Ok(None);
}
let jar_ty = args.jar_ty();
let lru_fn = parse_quote! {
#[allow(dead_code, clippy::needless_lifetimes)]
fn set_lru_capacity(__db: &salsa::function::DynDb<Self>, __value: usize) {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients =
<_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
__ingredients.function.set_capacity(__value);
}
};
Ok(Some(lru_fn))
}
fn specify_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<Option<syn::ImplItemFn>> {
if args.specify.is_none() {
return Ok(None);
}
let mut setter_sig = item_fn.sig.clone();
require_optional_db_lifetime(&item_fn.sig.generics)?;
let db_lt = &db_lifetime(&item_fn.sig.generics);
match setter_sig.generics.lifetimes().count() {
0 => setter_sig.generics.params.push(parse_quote!(#db_lt)),
1 => (),
_ => panic!("unreachable -- would have generated an error earlier"),
};
// `specify` has the same signature as the original,
// but it takes a value arg and has no return type.
let jar_ty = args.jar_ty();
let (db_var, arg_names) = fn_args(item_fn)?;
let value_ty = configuration::value_ty(db_lt, &item_fn.sig);
setter_sig.ident = syn::Ident::new("specify", item_fn.sig.ident.span());
let value_arg = syn::Ident::new("__value", item_fn.sig.output.span());
setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty));
setter_sig.output = ReturnType::Default;
Ok(Some(syn::ImplItemFn {
attrs: vec![],
vis: item_fn.vis.clone(),
defaultness: None,
sig: setter_sig,
block: parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*));
__ingredients.function.specify_and_record(#db_var, __key, #value_arg)
}
},
}))
}
/// Given a function def tagged with `#[return_ref]`, modifies `fn_sig` so that
/// it returns an `&Value` instead of `Value`. May introduce a name for the
/// database lifetime if required.
fn make_fn_return_ref(fn_sig: &mut syn::Signature) -> syn::Result<()> {
// An input should be a `&dyn Db`.
// We need to ensure it has a named lifetime parameter.
let (db_lifetime, _) = db_lifetime_and_ty(fn_sig)?;
let (right_arrow, elem) = match fn_sig.output.clone() {
ReturnType::Default => (
syn::Token![->]([Span::call_site(), Span::call_site()]),
parse_quote!(()),
),
ReturnType::Type(rarrow, ty) => (rarrow, ty),
};
let ref_output = syn::TypeReference {
and_token: syn::Token![&](right_arrow.span()),
lifetime: Some(db_lifetime),
mutability: None,
elem,
};
fn_sig.output = syn::ReturnType::Type(right_arrow, Box::new(ref_output.into()));
Ok(())
}
/// Given a function signature, identifies the name given to the `&dyn Db` reference
/// and returns it, along with the type of the database.
/// If the database lifetime did not have a name, then modifies the item function
/// so that it is called `'__db` and returns that.
fn db_lifetime_and_ty(func: &mut syn::Signature) -> syn::Result<(syn::Lifetime, &syn::Type)> {
// If this is a method, then the database should be the second argument.
let db_loc = if matches!(func.inputs[0], syn::FnArg::Receiver(_)) {
1
} else {
0
};
match &mut func.inputs[db_loc] {
syn::FnArg::Receiver(r) => Err(syn::Error::new(r.span(), "two self arguments")),
syn::FnArg::Typed(pat_ty) => match &mut *pat_ty.ty {
syn::Type::Reference(ty) => match &ty.lifetime {
Some(lt) => Ok((lt.clone(), &pat_ty.ty)),
None => {
let and_token_span = ty.and_token.span();
let ident = syn::Ident::new("__db", and_token_span);
func.generics.params.insert(
0,
syn::LifetimeParam {
attrs: vec![],
lifetime: syn::Lifetime {
apostrophe: and_token_span,
ident: ident.clone(),
},
colon_token: None,
bounds: Default::default(),
}
.into(),
);
let db_lifetime = syn::Lifetime {
apostrophe: and_token_span,
ident,
};
ty.lifetime = Some(db_lifetime.clone());
Ok((db_lifetime, &pat_ty.ty))
}
},
_ => Err(syn::Error::new(
pat_ty.span(),
"expected database to be a `&` type",
)),
},
}
}
/// Generates the `accumulated` function, which invokes `accumulated`
/// on the function ingredient to extract the values pushed (transitively)
/// into an accumulator.
fn accumulated_fn(
args: &FnArgs,
item_fn: &syn::ItemFn,
config_ty: &syn::Type,
) -> syn::Result<syn::ItemFn> {
let jar_ty = args.jar_ty();
let mut accumulated_fn = item_fn.clone();
accumulated_fn.sig.ident = syn::Ident::new("accumulated", item_fn.sig.ident.span());
accumulated_fn.sig.generics.params.push(parse_quote! {
__A: salsa::accumulator::Accumulator
});
accumulated_fn.sig.output = parse_quote! {
-> Vec<<__A as salsa::accumulator::Accumulator>::Data>
};
let predicate: syn::WherePredicate = parse_quote!(<#jar_ty as salsa::jar::Jar>::DynDb: salsa::storage::HasJar<<__A as salsa::accumulator::Accumulator>::Jar>);
if let Some(where_clause) = &mut accumulated_fn.sig.generics.where_clause {
where_clause.predicates.push(predicate);
} else {
accumulated_fn.sig.generics.where_clause = parse_quote!(where #predicate);
}
let (db_var, arg_names) = fn_args(item_fn)?;
accumulated_fn.block = parse_quote! {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var);
let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar);
let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*));
__ingredients.function.accumulated::<__A>(#db_var, __key)
}
};
Ok(accumulated_fn)
}
/// Examines the function arguments and returns a tuple of:
///
/// * the name of the database argument
/// * the name(s) of the key arguments
fn fn_args(item_fn: &syn::ItemFn) -> syn::Result<(proc_macro2::Ident, Vec<proc_macro2::Ident>)> {
// Check that we have no receiver and that all arguments have names
if item_fn.sig.inputs.is_empty() {
return Err(syn::Error::new(
item_fn.sig.span(),
"method needs a database argument",
));
}
let mut input_names = vec![];
for input in &item_fn.sig.inputs {
match input {
syn::FnArg::Receiver(r) => {
return Err(syn::Error::new(r.span(), "no self argument expected"));
}
syn::FnArg::Typed(pat_ty) => match &*pat_ty.pat {
syn::Pat::Ident(ident) => {
input_names.push(ident.ident.clone());
}
_ => {
return Err(syn::Error::new(
pat_ty.pat.span(),
"all arguments must be given names",
));
}
},
}
}
// Database is the first argument
let db_var = input_names[0].clone();
let arg_names = input_names[1..].to_owned();
Ok((db_var, arg_names))
}

View file

@ -1,36 +1,32 @@
use proc_macro2::{Literal, Span, TokenStream};
use crate::{
literal,
salsa_struct::{SalsaField, SalsaStruct, TheStructKind},
db_lifetime,
hygiene::Hygiene,
options::Options,
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions},
};
use proc_macro2::TokenStream;
/// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
/// * the "id struct" `struct Foo(salsa::Id)`
/// * the tracked ingredient, which maps the id fields to the `Id`
/// * the entity ingredient, which maps the id fields to the `Id`
/// * for each value field, a function ingredient
pub(crate) fn tracked(
pub(crate) fn tracked_struct(
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
) -> syn::Result<TokenStream> {
let struct_name = struct_item.ident.clone();
let tokens = SalsaStruct::with_struct(args, struct_item, "tracked_struct")
.and_then(|el| TrackedStruct(el).generate_tracked())?;
Ok(crate::debug::dump_tokens(struct_name, tokens))
let hygiene = Hygiene::from2(&struct_item);
let m = Macro {
hygiene,
args: syn::parse(args)?,
struct_item,
};
m.try_macro()
}
struct TrackedStruct(SalsaStruct<Self>);
type TrackedArgs = Options<TrackedStruct>;
impl std::ops::Deref for TrackedStruct {
type Target = SalsaStruct<Self>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct TrackedStruct;
impl crate::options::AllowedOptions for TrackedStruct {
const RETURN_REF: bool = false;
@ -39,7 +35,7 @@ impl crate::options::AllowedOptions for TrackedStruct {
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const SINGLETON: bool = true;
const JAR: bool = true;
@ -54,384 +50,71 @@ impl crate::options::AllowedOptions for TrackedStruct {
const CONSTRUCTOR_NAME: bool = true;
}
impl TrackedStruct {
fn generate_tracked(&self) -> syn::Result<TokenStream> {
self.require_db_lifetime()?;
impl SalsaStructAllowedOptions for TrackedStruct {
const KIND: &'static str = "interned";
let config_struct = self.config_struct();
let the_struct = self.the_struct(&config_struct.ident)?;
let config_impl = self.config_impl(&config_struct);
let inherent_impl = self.tracked_inherent_impl();
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 update_impl = self.update_impl();
let as_id_impl = self.as_id_impl();
let send_sync_impls = self.send_sync_impls();
let from_id_impl = self.impl_of_from_id();
let lookup_id_impl = self.lookup_id_impl();
let debug_impl = self.debug_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#the_struct
const ALLOW_ID: bool = false;
// It'd be nice if this struct definition
// could be moved into the `const _: () = {}` so that it doesn't
// pollute the user's namespace. The reason it cannot is that it appears
// in the field types within `#the_struct`. This seems solveable but it
// would take another trait or indirection (e.g., replacing
// with `<X as SalsaType>::Config`).
#config_struct
const HAS_LIFETIME: bool = true;
}
#[allow(clippy::all, dead_code, warnings)]
const _: () = {
#config_impl
#inherent_impl
#ingredients_for_impl
#salsa_struct_in_db_impl
#tracked_struct_in_db_impl
#update_impl
#as_id_impl
#from_id_impl
#(#send_sync_impls)*
#lookup_id_impl
#as_debug_with_db_impl
#debug_impl
};
})
}
struct Macro {
hygiene: Hygiene,
args: TrackedArgs,
struct_item: syn::ItemStruct,
}
fn config_impl(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl {
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();
let the_ident = self.the_ident();
let lt_db = &self.named_db_lifetime();
let debug_name_struct = literal(self.the_ident());
impl Macro {
#[allow(non_snake_case)]
fn try_macro(&self) -> syn::Result<TokenStream> {
let salsa_struct = SalsaStruct::new(&self.struct_item, &self.args)?;
// 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_fields = syn::Ident::new("old_fields_", Span::call_site());
let new_fields = syn::Ident::new("new_fields_", 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_fields: TokenStream = self
.all_fields()
.zip(0..)
.map(|(field, i)| {
let field_ty = field.ty();
let field_index = Literal::u32_unsuffixed(i);
if field.is_backdate_field() {
quote_spanned! { field.span() =>
if salsa::update::helper::Dispatch::<#field_ty>::maybe_update(
std::ptr::addr_of_mut!((*#old_fields).#field_index),
#new_fields.#field_index,
) {
#revisions[#field_index] = #current_revision;
}
}
} else {
quote_spanned! { field.span() =>
salsa::update::always_update(
&mut #revisions[#field_index],
#current_revision,
unsafe { &mut (*#old_fields).#field_index },
#new_fields.#field_index,
);
}
}
})
.collect();
let attrs = &self.struct_item.attrs;
let vis = &self.struct_item.vis;
let struct_ident = &self.struct_item.ident;
let db_lt = db_lifetime::db_lifetime(&self.struct_item.generics);
let new_fn = salsa_struct.constructor_name();
let field_ids = salsa_struct.field_ids();
let field_indices = salsa_struct.field_indices();
let id_field_indices = salsa_struct.id_field_indices();
let num_fields = salsa_struct.num_fields();
let field_options = salsa_struct.field_options();
let field_tys = salsa_struct.field_tys();
parse_quote! {
impl salsa::tracked_struct::Configuration for #config_ident {
const DEBUG_NAME: &'static str = #debug_name_struct;
let zalsa = self.hygiene.ident("zalsa");
let zalsa_struct = self.hygiene.ident("zalsa_struct");
let Configuration = self.hygiene.ident("Configuration");
let CACHE = self.hygiene.ident("CACHE");
let Db = self.hygiene.ident("Db");
let NonNull = self.hygiene.ident("NonNull");
let Revision = self.hygiene.ident("Revision");
type Fields<#lt_db> = ( #(#field_tys,)* );
type Struct<#lt_db> = #the_ident<#lt_db>;
type Revisions = [salsa::Revision; #arity];
unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull<salsa::tracked_struct::ValueStruct<Self>>) -> Self::Struct<'db> {
#the_ident(ptr, std::marker::PhantomData)
}
fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db salsa::tracked_struct::ValueStruct<Self> {
unsafe { s.0.as_ref() }
}
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]
}
unsafe fn update_fields<#lt_db>(
#current_revision: salsa::Revision,
#revisions: &mut Self::Revisions,
#old_fields: *mut Self::Fields<#lt_db>,
#new_fields: Self::Fields<#lt_db>,
) {
use salsa::update::helper::Fallback as _;
#update_fields
}
}
}
}
/// Generate an inherent impl with methods on the tracked type.
fn tracked_inherent_impl(&self) -> syn::ItemImpl {
let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics();
let TheStructKind::Pointer(lt_db) = self.the_struct_kind() else {
panic!("expected pointer struct");
};
let jar_ty = self.jar_ty();
let db_dyn_ty = self.db_dyn_ty();
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::ImplItemFn> = 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_spanned! { field_get_name.span() =>
#field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty
{
let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index);
&fields.#field_index
}
}
} else {
parse_quote_spanned! { field_get_name.span() =>
#field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty
{
let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index);
fields.#field_index.clone()
}
}
}
)
.collect();
let field_names = self.all_field_names();
let field_tys = self.all_field_tys();
let constructor_name = self.constructor_name();
let salsa_id = self.access_salsa_id_from_self();
let lt_db = self.maybe_elided_db_lifetime();
parse_quote! {
impl #impl_generics #ident #type_generics
#where_clause {
pub fn #constructor_name(__db: &#lt_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< Self >>::ingredient(__jar);
__ingredients.0.new_struct(
__runtime,
(#(#field_names,)*),
)
}
pub fn salsa_id(&self) -> salsa::Id {
#salsa_id
}
#(#field_getters)*
}
}
}
/// Generate the `IngredientsFor` impl for this tracked struct.
///
/// 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_struct: &syn::ItemStruct) -> syn::ItemImpl {
use crate::literal;
let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics();
let jar_ty = self.jar_ty();
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_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect();
parse_quote! {
impl #impl_generics salsa::storage::IngredientsFor for #ident #type_generics
#where_clause {
type Jar = #jar_ty;
type Ingredients = (
salsa::tracked_struct::TrackedStructIngredient<#config_struct_name>,
[salsa::tracked_struct::TrackedFieldIngredient<#config_struct_name>; #arity],
Ok(crate::debug::dump_tokens(
struct_ident,
quote! {
salsa::plumbing::setup_tracked_struct!(
attrs: [#(#attrs),*],
vis: #vis,
Struct: #struct_ident,
db_lt: #db_lt,
new_fn: #new_fn,
field_ids: [#(#field_ids),*],
field_tys: [#(#field_tys),*],
field_indices: [#(#field_indices),*],
id_field_indices: [#(#id_field_indices),*],
field_options: [#(#field_options),*],
num_fields: #num_fields,
unused_names: [
#zalsa,
#zalsa_struct,
#Configuration,
#CACHE,
#Db,
#NonNull,
#Revision,
]
);
fn create_ingredients<DB>(
routes: &mut salsa::routes::Routes<DB>,
) -> Self::Ingredients
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)
};
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.#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.#tracked_fields_ingredients[#field_indices]
},
);
struct_ingredient.new_field_ingredient(index, #field_indices, #debug_name_fields)
},
)*
];
(struct_ingredient, field_ingredients)
}
}
}
}
/// Implementation of `LookupId`.
fn lookup_id_impl(&self) -> syn::ItemImpl {
let TheStructKind::Pointer(db_lt) = self.the_struct_kind() else {
panic!("expected a Pointer impl")
};
let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics();
let db = syn::Ident::new("DB", ident.span());
let jar_ty = self.jar_ty();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote_spanned! { ident.span() =>
impl<#db, #parameters> salsa::id::LookupId<#db_lt> for #ident #type_generics
#where_clause
{
fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self {
let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar);
ingredients.#tracked_struct_ingredient.lookup_struct(runtime, id)
}
}
}
}
/// Implementation of `SalsaStructInDb`.
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics();
let db = syn::Ident::new("DB", ident.span());
let jar_ty = self.jar_ty();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote! {
impl<#db, #parameters> salsa::salsa_struct::SalsaStructInDb<#db> for #ident #type_generics
where
#db: ?Sized + salsa::DbWithJar<#jar_ty>,
#where_clause
{
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 #type_generics>>::ingredient(jar);
ingredients.#tracked_struct_ingredient.register_dependent_fn(index)
}
}
}
}
/// Implementation of `TrackedStructInDb`.
fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl {
let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics();
let db = syn::Ident::new("DB", ident.span());
let jar_ty = self.jar_ty();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote! {
impl<#db, #parameters> salsa::tracked_struct::TrackedStructInDb<#db> for #ident #type_generics
where
#db: ?Sized + salsa::DbWithJar<#jar_ty>,
#where_clause
{
fn database_key_index(db: &#db, id: salsa::Id) -> salsa::DatabaseKeyIndex {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar);
ingredients.#tracked_struct_ingredient.database_key_index(id)
}
}
}
}
/// The index of the tracked struct ingredient in the ingredient tuple.
fn tracked_struct_ingredient_index(&self) -> Literal {
Literal::usize_unsuffixed(0)
}
/// 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 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 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> {
self.all_fields()
.zip(0..)
.filter(|(field, _)| field.is_id_field())
.map(|(_, index)| Literal::usize_unsuffixed(index))
.collect()
}
}
impl SalsaField {
/// true if this is an id field
fn is_id_field(&self) -> bool {
self.has_id_attr
},
))
}
}

View file

@ -1,7 +1,11 @@
use proc_macro2::{Literal, Span, TokenStream};
use proc_macro2::{Literal, TokenStream};
use synstructure::BindStyle;
use crate::hygiene::Hygiene;
pub(crate) fn update_derive(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let hygiene = Hygiene::from2(&input);
if let syn::Data::Union(_) = &input.data {
return Err(syn::Error::new_spanned(
&input.ident,
@ -15,8 +19,8 @@ pub(crate) fn update_derive(input: syn::DeriveInput) -> syn::Result<TokenStream>
v.bind_with(|_| BindStyle::Move);
}
let old_pointer = syn::Ident::new("old_pointer", Span::call_site());
let new_value = syn::Ident::new("new_value", Span::call_site());
let old_pointer = hygiene.ident("old_pointer");
let new_value = hygiene.ident("new_value");
let fields: TokenStream = structure
.variants()
@ -51,7 +55,7 @@ pub(crate) fn update_derive(input: syn::DeriveInput) -> syn::Result<TokenStream>
quote! {
#tokens |
unsafe {
salsa::update::helper::Dispatch::<#field_ty>::maybe_update(
salsa::plumbing::UpdateDispatch::<#field_ty>::maybe_update(
#binding,
#new_value.#field_index,
)
@ -74,7 +78,7 @@ pub(crate) fn update_derive(input: syn::DeriveInput) -> syn::Result<TokenStream>
let tokens = quote! {
unsafe impl #impl_generics salsa::update::Update for #ident #ty_generics #where_clause {
unsafe fn maybe_update(#old_pointer: *mut Self, #new_value: Self) -> bool {
use ::salsa::update::helper::Fallback as _;
use ::salsa::plumbing::UpdateFallback as _;
let old_pointer = unsafe { &mut *#old_pointer };
match #old_pointer {
#fields

View file

@ -1,10 +1,8 @@
use std::sync::{Arc, Mutex};
use salsa::DebugWithDb;
// ANCHOR: db_struct
#[derive(Default)]
#[salsa::db(crate::Jar)]
#[salsa::db]
pub(crate) struct Database {
storage: salsa::Storage<Self>,
@ -36,6 +34,7 @@ impl Database {
}
// ANCHOR: db_impl
#[salsa::db]
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
eprintln!("Event: {event:?}");
@ -51,14 +50,3 @@ impl salsa::Database for Database {
}
}
// ANCHOR_END: db_impl
// ANCHOR: par_db_impl
impl salsa::ParallelDatabase for Database {
fn snapshot(&self) -> salsa::Snapshot<Self> {
salsa::Snapshot::new(Database {
storage: self.storage.snapshot(),
logs: self.logs.clone(),
})
}
}
// ANCHOR_END: par_db_impl

View file

@ -49,7 +49,7 @@ pub enum StatementData<'db> {
Print(Expression<'db>),
}
#[derive(Eq, PartialEq, Debug, Hash, new, salsa::Update, salsa::DebugWithDb)]
#[derive(Eq, PartialEq, Debug, Hash, new, salsa::Update)]
pub struct Expression<'db> {
pub span: Span<'db>,

View file

@ -1,7 +1,6 @@
//! Basic test of accumulator functionality.
use std::{
any::Any,
fmt::{self, Debug},
marker::PhantomData,
};

View file

@ -1,7 +1,4 @@
use std::{any::Any, cell::Cell, ptr::NonNull};
use crossbeam::atomic::AtomicCell;
use parking_lot::Mutex;
use std::{cell::Cell, ptr::NonNull};
use crate::{storage::DatabaseGen, Durability, Event, Revision};
@ -38,108 +35,6 @@ pub trait Database: DatabaseGen {
}
}
/// Indicates a database that also supports parallel query
/// evaluation. All of Salsa's base query support is capable of
/// parallel execution, but for it to work, your query key/value types
/// must also be `Send`, as must any additional data in your database.
pub trait ParallelDatabase: Database + Send {
/// Creates a second handle to the database that holds the
/// database fixed at a particular revision. So long as this
/// "frozen" handle exists, any attempt to [`set`] an input will
/// block.
///
/// [`set`]: struct.QueryTable.html#method.set
///
/// This is the method you are meant to use most of the time in a
/// parallel setting where modifications may arise asynchronously
/// (e.g., a language server). In this context, it is common to
/// wish to "fork off" a snapshot of the database performing some
/// series of queries in parallel and arranging the results. Using
/// this method for that purpose ensures that those queries will
/// see a consistent view of the database (it is also advisable
/// for those queries to use the [`Runtime::unwind_if_cancelled`]
/// method to check for cancellation).
///
/// # Panics
///
/// It is not permitted to create a snapshot from inside of a
/// query. Attepting to do so will panic.
///
/// # Deadlock warning
///
/// The intended pattern for snapshots is that, once created, they
/// are sent to another thread and used from there. As such, the
/// `snapshot` acquires a "read lock" on the database --
/// therefore, so long as the `snapshot` is not dropped, any
/// attempt to `set` a value in the database will block. If the
/// `snapshot` is owned by the same thread that is attempting to
/// `set`, this will cause a problem.
///
/// # How to implement this
///
/// Typically, this method will create a second copy of your
/// database type (`MyDatabaseType`, in the example below),
/// cloning over each of the fields from `self` into this new
/// copy. For the field that stores the salsa runtime, you should
/// use [the `Runtime::snapshot` method][rfm] to create a snapshot of the
/// runtime. Finally, package up the result using `Snapshot::new`,
/// which is a simple wrapper type that only gives `&self` access
/// to the database within (thus preventing the use of methods
/// that may mutate the inputs):
///
/// [rfm]: struct.Runtime.html#method.snapshot
///
/// ```rust,ignore
/// impl ParallelDatabase for MyDatabaseType {
/// fn snapshot(&self) -> Snapshot<Self> {
/// Snapshot::new(
/// MyDatabaseType {
/// runtime: self.storage.snapshot(),
/// other_field: self.other_field.clone(),
/// }
/// )
/// }
/// }
/// ```
fn snapshot(&self) -> Snapshot<Self>;
}
/// Simple wrapper struct that takes ownership of a database `DB` and
/// only gives `&self` access to it. See [the `snapshot` method][fm]
/// for more details.
///
/// [fm]: trait.ParallelDatabase.html#method.snapshot
#[derive(Debug)]
pub struct Snapshot<DB: ?Sized>
where
DB: ParallelDatabase,
{
db: DB,
}
impl<DB> Snapshot<DB>
where
DB: ParallelDatabase,
{
/// Creates a `Snapshot` that wraps the given database handle
/// `db`. From this point forward, only shared references to `db`
/// will be possible.
pub fn new(db: DB) -> Self {
Snapshot { db }
}
}
impl<DB> std::ops::Deref for Snapshot<DB>
where
DB: ParallelDatabase,
{
type Target = DB;
fn deref(&self) -> &DB {
&self.db
}
}
thread_local! {
static DATABASE: Cell<AttachedDatabase> = Cell::new(AttachedDatabase::null());
}

View file

@ -8,7 +8,7 @@ use crate::{
key::DatabaseKeyIndex,
runtime::local_state::QueryOrigin,
salsa_struct::SalsaStructInDb,
storage::IngredientIndex,
storage::{DatabaseGen, IngredientIndex},
Cycle, Database, Event, EventKind, Id, Revision,
};
@ -39,7 +39,7 @@ pub trait Configuration: Any {
/// The "salsa struct type" that this function is associated with.
/// This can be just `salsa::Id` for functions that intern their arguments
/// and are not clearly associated with any one salsa struct.
type SalsaStruct<'db>: SalsaStructInDb<Self::DbView>;
type SalsaStruct<'db>: SalsaStructInDb;
/// The input to the function
type Input<'db>: Send + Sync;
@ -131,10 +131,6 @@ pub fn should_backdate_value<V: Eq>(old_value: &V, new_value: &V) -> bool {
old_value == new_value
}
/// This type is used to make configuration types for the functions in entities;
/// e.g. you can do `Config<X, 0>` and `Config<X, 1>`.
pub struct Config<const C: usize>(std::marker::PhantomData<[(); C]>);
impl<C> IngredientImpl<C>
where
C: Configuration,
@ -202,7 +198,10 @@ where
/// so we can remove any data keyed by them.
fn register<'db>(&self, db: &'db C::DbView) {
if !self.registered.fetch_or(true) {
<C::SalsaStruct<'db> as SalsaStructInDb<_>>::register_dependent_fn(db, self.index)
<C::SalsaStruct<'db> as SalsaStructInDb>::register_dependent_fn(
db.as_salsa_database(),
self.index,
)
}
}
}

View file

@ -47,6 +47,6 @@ where
},
});
key.remove_stale_output(db.as_salsa_database(), output.try_into().unwrap());
output.remove_stale_output(db.as_salsa_database(), key);
}
}

View file

@ -204,10 +204,8 @@ where
// by this function cannot be read until this function is marked green,
// so even if we mark them as valid here, the function will re-execute
// and overwrite the contents.
database_key_index.mark_validated_output(
db.as_salsa_database(),
dependency_index.try_into().unwrap(),
);
dependency_index
.mark_validated_output(db.as_salsa_database(), database_key_index);
}
}
}

View file

@ -23,7 +23,7 @@ where
value: C::Output<'db>,
origin: impl Fn(DatabaseKeyIndex) -> QueryOrigin,
) where
C::Input<'db>: TrackedStructInDb<C::DbView>,
C::Input<'db>: TrackedStructInDb,
{
let runtime = db.runtime();
@ -44,7 +44,7 @@ where
// * Q4 invokes Q2 and then Q1
//
// Now, if We invoke Q3 first, We get one result for Q2, but if We invoke Q4 first, We get a different value. That's no good.
let database_key_index = <C::Input<'db>>::database_key_index(db, key);
let database_key_index = <C::Input<'db>>::database_key_index(db.as_salsa_database(), key);
let dependency_index = database_key_index.into();
if !runtime.is_output_of_active_query(dependency_index) {
panic!("can only use `specfiy` on entities created during current query");
@ -95,7 +95,7 @@ where
/// Used for explicit calls to `specify`, but not needed for pre-declared tracked struct fields.
pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Output<'db>)
where
C::Input<'db>: TrackedStructInDb<C::DbView>,
C::Input<'db>: TrackedStructInDb,
{
self.specify(db, key, value, |database_key_index| {
QueryOrigin::Assigned(database_key_index)

84
src/handle.rs Normal file
View file

@ -0,0 +1,84 @@
use std::sync::Arc;
use parking_lot::{Condvar, Mutex};
use crate::storage::HasStorage;
/// A database "handle" allows coordination of multiple async tasks accessing the same database.
/// So long as you are just doing reads, you can freely clone.
/// When you attempt to modify the database, you call `get_mut`, which will set the cancellation flag,
/// causing other handles to get panics. Once all other handles are dropped, you can proceed.
pub struct Handle<Db: HasStorage> {
db: Arc<Db>,
coordinate: Arc<Coordinate>,
}
struct Coordinate {
/// Counter of the number of clones of actor. Begins at 1.
/// Incremented when cloned, decremented when dropped.
clones: Mutex<usize>,
cvar: Condvar,
}
impl<Db: HasStorage> Handle<Db> {
pub fn new(db: Db) -> Self {
Self {
db: Arc::new(db),
coordinate: Arc::new(Coordinate {
clones: Mutex::new(1),
cvar: Default::default(),
}),
}
}
/// Returns a mutable reference to the inner database.
/// If other handles are active, this method sets the cancellation flag
/// and blocks until they are dropped.
pub fn get_mut(&mut self) -> &mut Db {
self.cancel_others();
Arc::get_mut(&mut self.db).expect("no other handles")
}
// ANCHOR: cancel_other_workers
/// Sets cancellation flag and blocks until all other workers with access
/// to this storage have completed.
///
/// This could deadlock if there is a single worker with two handles to the
/// same database!
fn cancel_others(&mut self) {
let storage = self.db.storage();
storage.runtime().set_cancellation_flag();
let mut clones = self.coordinate.clones.lock();
while *clones != 1 {
self.coordinate.cvar.wait(&mut clones);
}
}
// ANCHOR_END: cancel_other_workers
}
impl<Db: HasStorage> Drop for Handle<Db> {
fn drop(&mut self) {
*self.coordinate.clones.lock() -= 1;
self.coordinate.cvar.notify_all();
}
}
impl<Db: HasStorage> std::ops::Deref for Handle<Db> {
type Target = Db;
fn deref(&self) -> &Self::Target {
&self.db
}
}
impl<Db: HasStorage> Clone for Handle<Db> {
fn clone(&self) -> Self {
*self.coordinate.clones.lock() += 1;
Self {
db: Arc::clone(&self.db),
coordinate: Arc::clone(&self.coordinate),
}
}
}

View file

@ -155,7 +155,7 @@ impl<C: Configuration> IngredientImpl<C> {
id: C::Struct,
field_index: usize,
) -> &'db C::Fields {
let field_ingredient_index = self.ingredient_index + field_index;
let field_ingredient_index = self.ingredient_index.successor(field_index);
let id = id.as_id();
let value = self.struct_map.get(id);
let stamp = &value.stamps[field_index];
@ -172,7 +172,7 @@ impl<C: Configuration> IngredientImpl<C> {
/// Peek at the field values without recording any read dependency.
/// Used for debug printouts.
pub fn peek_fields(&self, id: C::Struct) -> &C::Fields {
pub fn leak_fields(&self, id: C::Struct) -> &C::Fields {
let id = id.as_id();
let value = self.struct_map.get(id);
&value.fields

View file

@ -9,9 +9,6 @@ use std::fmt;
use super::struct_map::StructMap;
pub trait FieldData: Send + Sync + 'static {}
impl<T: Send + Sync + 'static> FieldData for T {}
/// Ingredient used to represent the fields of a `#[salsa::input]`.
///
/// These fields can only be mutated by a call to a setter with an `&mut`
@ -37,27 +34,11 @@ where
struct_map: StructMap<C>,
) -> Self {
Self {
index: struct_index + field_index,
index: struct_index.successor(field_index),
field_index,
struct_map,
}
}
fn database_key_index(&self, key: C::Struct) -> DatabaseKeyIndex {
DatabaseKeyIndex {
ingredient_index: self.index,
key_index: key.as_id(),
}
}
}
/// More limited wrapper around transmute that copies lifetime from `a` to `b`.
///
/// # Safety condition
///
/// `b` must be owned by `a`
unsafe fn transmute_lifetime<'a, 'b, A, B>(_a: &'a A, b: &'b B) -> &'a B {
std::mem::transmute(b)
}
impl<C> Ingredient for FieldIngredientImpl<C>

View file

@ -6,9 +6,10 @@ use std::ptr::NonNull;
use crate::alloc::Alloc;
use crate::durability::Durability;
use crate::id::{AsId, LookupId};
use crate::id::AsId;
use crate::ingredient::{fmt_index, IngredientRequiresReset};
use crate::key::DependencyIndex;
use crate::plumbing::Jar;
use crate::runtime::local_state::QueryOrigin;
use crate::runtime::Runtime;
use crate::storage::IngredientIndex;
@ -39,17 +40,21 @@ pub trait Configuration: Sized + 'static {
/// Requires that `ptr` represents a "confirmed" value in this revision,
/// which means that it will remain valid and immutable for the remainder of this
/// revision, represented by the lifetime `'db`.
unsafe fn struct_from_raw<'db>(ptr: NonNull<ValueStruct<Self>>) -> Self::Struct<'db>;
unsafe fn struct_from_raw<'db>(ptr: NonNull<Value<Self>>) -> Self::Struct<'db>;
/// Deref the struct to yield the underlying value struct.
/// Since we are still part of the `'db` lifetime in which the struct was created,
/// this deref is safe, and the value-struct fields are immutable and verified.
fn deref_struct(s: Self::Struct<'_>) -> &ValueStruct<Self>;
fn deref_struct(s: Self::Struct<'_>) -> &Value<Self>;
}
pub trait InternedData: Sized + Eq + Hash + Clone {}
impl<T: Eq + Hash + Clone> InternedData for T {}
pub struct JarImpl<C: Configuration> {
phantom: PhantomData<C>,
}
/// The interned ingredient has the job of hashing values of type `Data` to produce an `Id`.
/// It used to store interned structs but also to store the id fields of a tracked struct.
/// Interned values endure until they are explicitly removed in some way.
@ -65,7 +70,7 @@ pub struct IngredientImpl<C: Configuration> {
/// Maps from an interned id to its data.
///
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
value_map: FxDashMap<Id, Alloc<ValueStruct<C>>>,
value_map: FxDashMap<Id, Alloc<Value<C>>>,
/// counter for the next id.
counter: AtomicCell<u32>,
@ -78,7 +83,7 @@ pub struct IngredientImpl<C: Configuration> {
}
/// Struct storing the interned fields.
pub struct ValueStruct<C>
pub struct Value<C>
where
C: Configuration,
{
@ -86,6 +91,20 @@ where
fields: C::Data<'static>,
}
impl<C: Configuration> Default for JarImpl<C> {
fn default() -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<C: Configuration> Jar for JarImpl<C> {
fn create_ingredients(&self, first_index: IngredientIndex) -> Vec<Box<dyn Ingredient>> {
vec![Box::new(IngredientImpl::<C>::new(first_index)) as _]
}
}
impl<C> IngredientImpl<C>
where
C: Configuration,
@ -137,13 +156,10 @@ where
dashmap::mapref::entry::Entry::Vacant(entry) => {
let next_id = self.counter.fetch_add(1);
let next_id = crate::id::Id::from_u32(next_id);
let value = self
.value_map
.entry(next_id)
.or_insert(Alloc::new(ValueStruct {
id: next_id,
fields: internal_data,
}));
let value = self.value_map.entry(next_id).or_insert(Alloc::new(Value {
id: next_id,
fields: internal_data,
}));
let value_raw = value.as_raw();
drop(value);
entry.insert(next_id);
@ -265,38 +281,7 @@ where
}
}
pub struct IdentityInterner<C>
where
C: Configuration,
{
data: PhantomData<C>,
}
impl<C> IdentityInterner<C>
where
C: Configuration,
{
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
IdentityInterner { data: PhantomData }
}
pub fn intern_id<'db>(&'db self, _runtime: &'db Runtime, id: C::Data<'db>) -> crate::Id
where
C::Data<'db>: AsId,
{
id.as_id()
}
pub fn data_with_db<'db>(&'db self, id: crate::Id, db: &'db dyn Database) -> C::Data<'db>
where
C::Data<'db>: LookupId<'db>,
{
<C::Data<'db>>::lookup_id(id, db)
}
}
impl<C> ValueStruct<C>
impl<C> Value<C>
where
C: Configuration,
{
@ -311,7 +296,7 @@ where
}
}
impl<C> AsId for ValueStruct<C>
impl<C> AsId for Value<C>
where
C: Configuration,
{

View file

@ -34,6 +34,11 @@ impl DependencyIndex {
self.key_index
}
pub(crate) fn remove_stale_output(&self, db: &dyn Database, executor: DatabaseKeyIndex) {
db.lookup_ingredient(self.ingredient_index)
.remove_stale_output(db, executor, self.key_index)
}
pub(crate) fn mark_validated_output(
&self,
db: &dyn Database,
@ -92,16 +97,6 @@ impl DatabaseKeyIndex {
self.ingredient_index.cycle_recovery_strategy(db)
}
pub(crate) fn mark_validated_output(&self, db: &dyn Database, executor: DatabaseKeyIndex) {
db.lookup_ingredient(self.ingredient_index)
.mark_validated_output(db, executor, Some(self.key_index))
}
pub(crate) fn remove_stale_output(&self, db: &dyn Database, executor: DatabaseKeyIndex) {
db.lookup_ingredient(self.ingredient_index)
.remove_stale_output(db, executor, Some(self.key_index))
}
pub(crate) fn origin(&self, db: &dyn Database) -> Option<QueryOrigin> {
db.lookup_ingredient(self.ingredient_index)
.origin(self.key_index)

View file

@ -7,6 +7,7 @@ mod database;
mod durability;
mod event;
mod function;
mod handle;
mod hash;
mod id;
mod ingredient;
@ -26,11 +27,10 @@ mod views;
pub use self::cancelled::Cancelled;
pub use self::cycle::Cycle;
pub use self::database::Database;
pub use self::database::ParallelDatabase;
pub use self::database::Snapshot;
pub use self::durability::Durability;
pub use self::event::Event;
pub use self::event::EventKind;
pub use self::handle::Handle;
pub use self::id::Id;
pub use self::input::setter::Setter;
pub use self::key::DatabaseKeyIndex;
@ -42,7 +42,6 @@ pub use salsa_macros::db;
pub use salsa_macros::input;
pub use salsa_macros::interned;
pub use salsa_macros::tracked;
pub use salsa_macros::DebugWithDb;
pub use salsa_macros::Update;
/// Internal names used by salsa macros.
@ -54,15 +53,18 @@ pub mod plumbing {
pub use crate::array::Array;
pub use crate::cycle::Cycle;
pub use crate::cycle::CycleRecoveryStrategy;
pub use crate::database::attach_database;
pub use crate::database::current_revision;
pub use crate::database::with_attached_database;
pub use crate::database::Database;
pub use crate::function::should_backdate_value;
pub use crate::id::AsId;
pub use crate::id::FromId;
pub use crate::id::Id;
pub use crate::id::LookupId;
pub use crate::ingredient::Ingredient;
pub use crate::ingredient::Jar;
pub use crate::key::DatabaseKeyIndex;
pub use crate::revision::Revision;
pub use crate::runtime::stamp;
pub use crate::runtime::Runtime;
@ -74,12 +76,17 @@ pub mod plumbing {
pub use crate::storage::IngredientCache;
pub use crate::storage::IngredientIndex;
pub use crate::storage::Storage;
pub use crate::tracked_struct::TrackedStructInDb;
pub use crate::update::always_update;
pub use crate::update::helper::Dispatch as UpdateDispatch;
pub use crate::update::helper::Fallback as UpdateFallback;
pub use salsa_macro_rules::maybe_backdate;
pub use salsa_macro_rules::maybe_clone;
pub use salsa_macro_rules::maybe_cloned_ty;
pub use salsa_macro_rules::setup_input;
pub use salsa_macro_rules::setup_input_struct;
pub use salsa_macro_rules::setup_interned_fn;
pub use salsa_macro_rules::setup_interned_struct;
pub use salsa_macro_rules::setup_struct_fn;
pub use salsa_macro_rules::setup_tracked_struct;
pub use salsa_macro_rules::unexpected_cycle_recovery;
@ -95,7 +102,8 @@ pub mod plumbing {
pub mod interned {
pub use crate::interned::Configuration;
pub use crate::interned::IngredientImpl;
pub use crate::interned::ValueStruct;
pub use crate::interned::JarImpl;
pub use crate::interned::Value;
}
pub mod function {
@ -108,5 +116,6 @@ pub mod plumbing {
pub use crate::tracked_struct::Configuration;
pub use crate::tracked_struct::IngredientImpl;
pub use crate::tracked_struct::JarImpl;
pub use crate::tracked_struct::Value;
}
}

View file

@ -1,7 +1,7 @@
use crate::{storage::IngredientIndex, Database};
pub trait SalsaStructInDb<DB: ?Sized + Database> {
fn register_dependent_fn(db: &DB, index: IngredientIndex);
pub trait SalsaStructInDb {
fn register_dependent_fn(db: &dyn Database, index: IngredientIndex);
}
/// A ZST that implements [`SalsaStructInDb`]
@ -10,6 +10,6 @@ pub trait SalsaStructInDb<DB: ?Sized + Database> {
/// (ones that only take a database as an argument).
pub struct Singleton;
impl<DB: ?Sized + Database> SalsaStructInDb<DB> for Singleton {
fn register_dependent_fn(_db: &DB, _index: IngredientIndex) {}
impl SalsaStructInDb for Singleton {
fn register_dependent_fn(_db: &dyn Database, _index: IngredientIndex) {}
}

View file

@ -12,8 +12,6 @@ use crate::runtime::Runtime;
use crate::views::{Views, ViewsOf};
use crate::Database;
use super::ParallelDatabase;
pub fn views<Db: ?Sized + Database>(db: &Db) -> &Views {
DatabaseGen::views(db)
}
@ -35,6 +33,15 @@ pub unsafe trait DatabaseGen: Any {
/// Returns the same data pointer as `self`.
fn as_salsa_database(&self) -> &dyn Database;
/// Upcast to a `dyn Database`.
///
/// Only required because upcasts not yet stabilized (*grr*).
///
/// # Safety
///
/// Returns the same data pointer as `self`.
fn as_salsa_database_mut(&mut self) -> &mut dyn Database;
/// Upcast to a `dyn DatabaseGen`.
///
/// Only required because upcasts not yet stabilized (*grr*).
@ -96,6 +103,10 @@ unsafe impl<T: HasStorage> DatabaseGen for T {
self
}
fn as_salsa_database_mut(&mut self) -> &mut dyn Database {
self
}
fn as_salsa_database_gen(&self) -> &dyn DatabaseGen {
self
}
@ -142,6 +153,7 @@ impl dyn Database {
/// # Panics
///
/// If the view has not been added to the database (see [`DatabaseView`][])
#[track_caller]
pub fn as_view<DbView: ?Sized + Database>(&self) -> &DbView {
self.views().try_view_as(self).unwrap()
}
@ -186,13 +198,9 @@ impl IngredientIndex {
pub(crate) fn cycle_recovery_strategy(self, db: &dyn Database) -> CycleRecoveryStrategy {
db.lookup_ingredient(self).cycle_recovery_strategy()
}
}
impl std::ops::Add<usize> for IngredientIndex {
type Output = IngredientIndex;
fn add(self, rhs: usize) -> Self::Output {
IngredientIndex(self.0.checked_add(u32::try_from(rhs).unwrap()).unwrap())
pub fn successor(self, index: usize) -> Self {
IngredientIndex(self.0 + 1 + index as u32)
}
}
@ -223,23 +231,12 @@ struct Shared<Db: Database> {
/// first ingredient index will be. This allows ingredients to store their own indices.
/// This may be worth refactoring in the future because it naturally adds more overhead to
/// adding new kinds of ingredients.
jar_map: Arc<Mutex<FxHashMap<TypeId, IngredientIndex>>>,
jar_map: Mutex<FxHashMap<TypeId, IngredientIndex>>,
/// Vector of ingredients.
///
/// Immutable unless the mutex on `ingredients_map` is held.
ingredients_vec: Arc<ConcurrentVec<Box<dyn Ingredient>>>,
/// Conditional variable that is used to coordinate cancellation.
/// When the main thread writes to the database, it blocks until each of the snapshots can be cancelled.
cvar: Arc<Condvar>,
/// A dummy varible that we use to coordinate how many outstanding database handles exist.
/// This is set to `None` when dropping only.
sync: Option<Arc<()>>,
/// Mutex that is used to protect the `jars` field when waiting for snapshots to be dropped.
noti_lock: Arc<parking_lot::Mutex<()>>,
ingredients_vec: ConcurrentVec<Box<dyn Ingredient>>,
}
// ANCHOR: default
@ -249,11 +246,8 @@ impl<Db: Database> Default for Storage<Db> {
shared: Shared {
upcasts: Default::default(),
nonce: NONCE.nonce(),
cvar: Arc::new(Default::default()),
noti_lock: Arc::new(parking_lot::Mutex::new(())),
jar_map: Default::default(),
ingredients_vec: Default::default(),
sync: Some(Arc::new(())),
},
runtime: Runtime::default(),
}
@ -290,7 +284,8 @@ impl<Db: Database> Storage<Db> {
assert_eq!(
expected_index.as_usize(),
actual_index,
"index predicted for ingredient (`{:?}`) does not align with assigned index (`{:?}`)",
"ingredient `{:?}` was predicted to have index `{:?}` but actually has index `{:?}`",
self.shared.ingredients_vec.get(actual_index).unwrap(),
expected_index,
actual_index,
);
@ -312,90 +307,21 @@ impl<Db: Database> Storage<Db> {
&mut self,
index: IngredientIndex,
) -> (&mut dyn Ingredient, &mut Runtime) {
// FIXME: rework how we handle parallelism
let ingredients_vec = Arc::get_mut(&mut self.shared.ingredients_vec).unwrap();
self.runtime.new_revision();
(
&mut **ingredients_vec.get_mut(index.as_usize()).unwrap(),
&mut **self
.shared
.ingredients_vec
.get_mut(index.as_usize())
.unwrap(),
&mut self.runtime,
)
}
pub fn snapshot(&self) -> Storage<Db>
where
Db: ParallelDatabase,
{
Self {
shared: self.shared.clone(),
runtime: self.runtime.snapshot(),
}
}
pub fn runtime(&self) -> &Runtime {
&self.runtime
}
// ANCHOR: cancel_other_workers
/// Sets cancellation flag and blocks until all other workers with access
/// to this storage have completed.
///
/// This could deadlock if there is a single worker with two handles to the
/// same database!
fn cancel_other_workers(&mut self) {
loop {
self.runtime.set_cancellation_flag();
// Acquire lock before we check if we have unique access to the jars.
// If we do not yet have unique access, we will go to sleep and wait for
// the snapshots to be dropped, which will signal the cond var associated
// with this lock.
//
// NB: We have to acquire the lock first to ensure that we can check for
// unique access and go to sleep waiting on the condvar atomically,
// as described in PR #474.
let mut guard = self.shared.noti_lock.lock();
// If we have unique access to the ingredients vec, we are done.
if Arc::get_mut(self.shared.sync.as_mut().unwrap()).is_some() {
return;
}
// Otherwise, wait until some other storage entities have dropped.
//
// The cvar `self.shared.cvar` is notified by the `Drop` impl.
self.shared.cvar.wait(&mut guard);
}
}
// ANCHOR_END: cancel_other_workers
}
impl<Db: Database> Clone for Shared<Db> {
fn clone(&self) -> Self {
Self {
upcasts: self.upcasts.clone(),
nonce: self.nonce.clone(),
jar_map: self.jar_map.clone(),
ingredients_vec: self.ingredients_vec.clone(),
cvar: self.cvar.clone(),
noti_lock: self.noti_lock.clone(),
sync: self.sync.clone(),
}
}
}
impl<Db: Database> Drop for Storage<Db> {
fn drop(&mut self) {
// Careful: if this is a snapshot on the main handle,
// we need to notify `shared.cvar` to make sure that the
// master thread wakes up. *And*, when it does wake-up, we need to be sure
// that the ref count on `self.shared.sync` has already been decremented.
// So we take the value of `self.shared.sync` now and then notify the cvar.
//
// If this is the master thread, this dance has no real effect.
let _guard = self.shared.noti_lock.lock();
drop(self.shared.sync.take());
self.shared.cvar.notify_all();
}
}
/// Caches a pointer to an ingredient in a database.

View file

@ -26,7 +26,7 @@ pub mod tracked_field;
/// Trait that defines the key properties of a tracked struct.
/// Implemented by the `#[salsa::tracked]` macro when applied
/// to a struct.
pub trait Configuration: Jar + Sized + 'static {
pub trait Configuration: Sized + 'static {
const DEBUG_NAME: &'static str;
const FIELD_DEBUG_NAMES: &'static [&'static str];
@ -130,9 +130,9 @@ impl<C: Configuration> Jar for JarImpl<C> {
}
}
pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
pub trait TrackedStructInDb: SalsaStructInDb {
/// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`.
fn database_key_index(db: &DB, id: Id) -> DatabaseKeyIndex;
fn database_key_index(db: &dyn Database, id: Id) -> DatabaseKeyIndex;
}
/// Created for each tracked struct.
@ -412,6 +412,13 @@ where
pub fn register_dependent_fn(&self, index: IngredientIndex) {
self.dependent_fns.push(index);
}
/// Return reference to the field data ignoring dependency tracking.
/// Used for debugging.
pub fn leak_fields<'db>(&'db self, s: C::Struct<'db>) -> &'db C::Fields<'db> {
let value = C::deref_struct(s);
unsafe { value.to_self_ref(&value.fields) }
}
}
impl<C> Ingredient for IngredientImpl<C>
@ -502,7 +509,7 @@ where
/// 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, field_index: usize) -> &'db C::Fields<'db> {
let field_ingredient_index = self.struct_ingredient_index + field_index;
let field_ingredient_index = self.struct_ingredient_index.successor(field_index);
let changed_at = self.revisions[field_index];
runtime.report_tracked_read(

View file

@ -36,7 +36,7 @@ where
struct_map: &StructMapView<C>,
) -> Self {
Self {
ingredient_index: struct_index + field_index,
ingredient_index: struct_index.successor(field_index),
field_index,
struct_map: struct_map.clone(),
}

View file

@ -1,18 +1,6 @@
//! Test that `DeriveWithDb` is correctly derived.
use expect_test::expect;
use salsa::DebugWithDb;
#[salsa::jar(db = Db)]
struct Jar(
MyInput,
ComplexStruct,
leak_debug_string,
DerivedCustom<'_>,
leak_derived_custom,
);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
@ -30,16 +18,15 @@ struct ComplexStruct {
not_salsa: NotSalsa,
}
#[salsa::db(Jar)]
#[salsa::db]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
#[salsa::db]
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn input() {
let db = Database::default();
@ -59,7 +46,7 @@ fn input() {
}
#[salsa::tracked]
fn leak_debug_string(db: &dyn Db, input: MyInput) -> String {
fn leak_debug_string(db: &dyn salsa::Database, input: MyInput) -> String {
format!("{:?}", input.debug(db))
}
@ -87,14 +74,14 @@ fn untracked_dependencies() {
}
#[salsa::tracked]
#[customize(DebugWithDb)]
#[customize(Debug)]
struct DerivedCustom<'db> {
my_input: MyInput,
value: u32,
}
impl<'db> DebugWithDb<dyn Db> for DerivedCustom<'db> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &dyn Db) -> std::fmt::Result {
impl std::fmt::Debug for DerivedCustom<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?} / {:?}",

View file

@ -2,24 +2,15 @@
//!
//! * when we delete memoized data, also delete outputs from that data
use salsa::DebugWithDb;
mod common;
use common::{HasLogger, Logger};
use expect_test::expect;
use salsa::Setter;
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(
MyInput,
MyTracked<'_>,
final_result,
create_tracked_structs,
contribution_from_struct,
copy_field,
);
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
#[salsa::db]
trait Db: salsa::Database + HasLogger {}
#[salsa::input(singleton)]
struct MyInput {
@ -60,25 +51,27 @@ fn copy_field<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 {
tracked.field(db)
}
#[salsa::db(Jar)]
#[salsa::db]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
logger: Logger,
}
#[salsa::db]
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
match event.kind {
salsa::EventKind::WillDiscardStaleOutput { .. }
| salsa::EventKind::DidDiscard { .. } => {
self.push_log(format!("salsa_event({:?})", event.kind.debug(self)));
self.push_log(format!("salsa_event({:?})", event.kind));
}
_ => {}
}
}
}
#[salsa::db]
impl Db for Database {}
impl HasLogger for Database {
@ -96,8 +89,8 @@ fn basic() {
assert_eq!(final_result(&db, input), 2 * 2 + 2);
db.assert_logs(expect![[r#"
[
"final_result(MyInput { [salsa id]: 0 })",
"intermediate_result(MyInput { [salsa id]: 0 })",
"final_result(MyInput { [salsa id]: 0, field: 3 })",
"intermediate_result(MyInput { [salsa id]: 0, field: 3 })",
]"#]]);
// Creates only 2 tracked structs in this revision, should delete 1
@ -118,12 +111,12 @@ fn basic() {
assert_eq!(final_result(&db, input), 2);
db.assert_logs(expect![[r#"
[
"intermediate_result(MyInput { [salsa id]: 0 })",
"intermediate_result(MyInput { [salsa id]: 0, field: 2 })",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })",
"salsa_event(DidDiscard { key: MyTracked(2) })",
"salsa_event(DidDiscard { key: contribution_from_struct(2) })",
"salsa_event(DidDiscard { key: MyTracked(5) })",
"salsa_event(DidDiscard { key: copy_field(5) })",
"final_result(MyInput { [salsa id]: 0 })",
"final_result(MyInput { [salsa id]: 0, field: 2 })",
]"#]]);
}

View file

@ -2,23 +2,15 @@
//!
//! * entities not created in a revision are deleted, as is any memoized data keyed on them.
use salsa::DebugWithDb;
mod common;
use common::{HasLogger, Logger};
use expect_test::expect;
use salsa::Setter;
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(
MyInput,
MyTracked<'_>,
final_result,
create_tracked_structs,
contribution_from_struct,
);
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
#[salsa::db]
trait Db: salsa::Database + HasLogger {}
#[salsa::input]
struct MyInput {
@ -53,25 +45,27 @@ fn contribution_from_struct<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u3
tracked.field(db) * 2
}
#[salsa::db(Jar)]
#[salsa::db]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
logger: Logger,
}
#[salsa::db]
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
match event.kind {
salsa::EventKind::WillDiscardStaleOutput { .. }
| salsa::EventKind::DidDiscard { .. } => {
self.push_log(format!("salsa_event({:?})", event.kind.debug(self)));
self.push_log(format!("salsa_event({:?})", event.kind));
}
_ => {}
}
}
}
#[salsa::db]
impl Db for Database {}
impl HasLogger for Database {
@ -89,8 +83,8 @@ fn basic() {
assert_eq!(final_result(&db, input), 2 * 2 + 2);
db.assert_logs(expect![[r#"
[
"final_result(MyInput { [salsa id]: 0 })",
"intermediate_result(MyInput { [salsa id]: 0 })",
"final_result(MyInput { [salsa id]: 0, field: 3 })",
"intermediate_result(MyInput { [salsa id]: 0, field: 3 })",
]"#]]);
// Creates only 2 tracked structs in this revision, should delete 1
@ -104,10 +98,10 @@ fn basic() {
assert_eq!(final_result(&db, input), 2);
db.assert_logs(expect![[r#"
[
"intermediate_result(MyInput { [salsa id]: 0 })",
"intermediate_result(MyInput { [salsa id]: 0, field: 2 })",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })",
"salsa_event(DidDiscard { key: MyTracked(2) })",
"salsa_event(DidDiscard { key: contribution_from_struct(2) })",
"final_result(MyInput { [salsa id]: 0 })",
"final_result(MyInput { [salsa id]: 0, field: 2 })",
]"#]]);
}

View file

@ -5,25 +5,24 @@ mod common;
use common::{HasLogger, Logger};
use expect_test::expect;
use salsa::Setter;
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked<'_>, final_result, intermediate_result);
#[salsa::db]
trait Db: salsa::Database + HasLogger {}
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
#[salsa::input(jar = Jar)]
#[salsa::input]
struct MyInput {
field: u32,
}
#[salsa::tracked(jar = Jar)]
#[salsa::tracked]
fn final_result(db: &dyn Db, input: MyInput) -> u32 {
db.push_log(format!("final_result({:?})", input));
intermediate_result(db, input).field(db) * 2
}
#[salsa::tracked(jar = Jar)]
#[salsa::tracked]
struct MyTracked<'db> {
field: u32,
}
@ -34,15 +33,17 @@ fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked<'_> {
MyTracked::new(db, input.field(db) / 2)
}
#[salsa::db(Jar)]
#[salsa::db]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
logger: Logger,
}
#[salsa::db]
impl salsa::Database for Database {}
#[salsa::db]
impl Db for Database {}
impl HasLogger for Database {

View file

@ -5,37 +5,15 @@ mod common;
use common::{HasLogger, Logger};
use expect_test::expect;
use salsa::Setter;
use test_log::test;
#[salsa::db]
trait Db: HasLogger {}
trait Db: salsa::Database + HasLogger {}
// #[salsa::input]
// struct MyInput {
// field: u32,
// }
salsa::plumbing::setup_input! {
attrs: [],
vis: ,
Struct: MyInput,
new_fn: new,
field_options: [(clone, not_applicable)],
field_ids: [field],
field_setter_ids: [set_field],
field_tys: [u32],
field_indices: [0],
num_fields: 1,
unused_names: [
zalsa1,
zalsa_struct1,
Configuration1,
CACHE1,
Db1,
NonNull1,
Revision1,
ValueStruct1,
]
#[salsa::input]
struct MyInput {
field: u32,
}
#[salsa::tracked]