mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-08-04 11:00:05 +00:00
wip
This commit is contained in:
parent
57eb0c45b4
commit
fdc363b65f
43 changed files with 939 additions and 3213 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
170
components/salsa-macro-rules/src/setup_interned_struct.rs
Normal file
170
components/salsa-macro-rules/src/setup_interned_struct.rs
Normal 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),
|
||||
)
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,)*)
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()]),
|
||||
parse_quote!(()),
|
||||
),
|
||||
ReturnType::Type(rarrow, ty) => (rarrow, ty),
|
||||
};
|
||||
|
||||
let ref_output = syn::TypeReference {
|
||||
and_token: syn::Token),
|
||||
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))
|
||||
}
|
|
@ -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
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! Basic test of accumulator functionality.
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt::{self, Debug},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
|
107
src/database.rs
107
src/database.rs
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
84
src/handle.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
15
src/key.rs
15
src/key.rs
|
@ -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)
|
||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
128
src/storage.rs
128
src/storage.rs
|
@ -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.
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
"{:?} / {:?}",
|
||||
|
|
|
@ -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 })",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -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 })",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue