Skip to content

Commit 1612f16

Browse files
committed
feature: allow calling ingredient on some interned structs
1 parent 35fd2af commit 1612f16

File tree

6 files changed

+103
-24
lines changed

6 files changed

+103
-24
lines changed

components/salsa-macro-rules/src/setup_interned_struct_sans_lifetime.rs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ macro_rules! setup_interned_struct_sans_lifetime {
1111
// Name of the struct
1212
Struct: $Struct:ident,
1313

14+
// Name of the struct data. This is a parameter because `std::concat_idents`
15+
// is unstable and taking an addition dependency is unnecessary.
16+
StructData: $StructDataIdent:ident,
17+
1418
// Name of the `'db` lifetime that the user gave
1519
db_lt: $db_lt:lifetime,
1620

@@ -62,52 +66,52 @@ macro_rules! setup_interned_struct_sans_lifetime {
6266
std::marker::PhantomData < &'static salsa::plumbing::interned::Value < $Struct > >
6367
);
6468

69+
type $StructDataIdent<$db_lt> = ($($field_ty,)*);
70+
71+
impl salsa::plumbing::interned::Configuration for $Struct {
72+
const DEBUG_NAME: &'static str = stringify!($Struct);
73+
type Data<'a> = $StructDataIdent<'a>;
74+
type Struct<'a> = $Struct;
75+
fn struct_from_id<'db>(id: salsa::Id) -> Self::Struct<'db> {
76+
use salsa::plumbing::FromId;
77+
$Struct(<$Id>::from_id(id), std::marker::PhantomData)
78+
}
79+
fn deref_struct(s: Self::Struct<'_>) -> salsa::Id {
80+
use salsa::plumbing::AsId;
81+
s.0.as_id()
82+
}
83+
}
84+
6585
const _: () = {
6686
use salsa::plumbing as $zalsa;
6787
use $zalsa::interned as $zalsa_struct;
6888

6989
type $Configuration = $Struct;
7090

71-
type StructData<$db_lt> = ($($field_ty,)*);
72-
7391
/// Key to use during hash lookups. Each field is some type that implements `Lookup<T>`
7492
/// for the owned type. This permits interning with an `&str` when a `String` is required and so forth.
7593
struct StructKey<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*>(
7694
$($indexed_ty,)*
7795
std::marker::PhantomData<&$db_lt ()>,
7896
);
7997

80-
impl<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*> $zalsa::interned::Lookup<StructData<$db_lt>>
98+
impl<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*> $zalsa::interned::Lookup<$StructDataIdent<$db_lt>>
8199
for StructKey<$db_lt, $($indexed_ty),*> {
82100

83101
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
84102
$($zalsa::interned::Lookup::hash(&self.$field_index, &mut *h);)*
85103
}
86104

87-
fn eq(&self, data: &StructData<$db_lt>) -> bool {
105+
fn eq(&self, data: &$StructDataIdent<$db_lt>) -> bool {
88106
($($zalsa::interned::Lookup::eq(&self.$field_index, &data.$field_index) && )* true)
89107
}
90108

91109
#[allow(unused_unit)]
92-
fn into_owned(self) -> StructData<$db_lt> {
110+
fn into_owned(self) -> $StructDataIdent<$db_lt> {
93111
($($zalsa::interned::Lookup::into_owned(self.$field_index),)*)
94112
}
95113
}
96114

97-
impl $zalsa_struct::Configuration for $Configuration {
98-
const DEBUG_NAME: &'static str = stringify!($Struct);
99-
type Data<'a> = StructData<'a>;
100-
type Struct<'a> = $Struct;
101-
fn struct_from_id<'db>(id: salsa::Id) -> Self::Struct<'db> {
102-
use $zalsa::FromId;
103-
$Struct(<$Id>::from_id(id), std::marker::PhantomData)
104-
}
105-
fn deref_struct(s: Self::Struct<'_>) -> salsa::Id {
106-
use $zalsa::AsId;
107-
s.0.as_id()
108-
}
109-
}
110-
111115
impl $Configuration {
112116
pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::IngredientImpl<Self>
113117
where

components/salsa-macros/src/interned_sans_lifetime.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ impl Macro {
8585
let attrs = &self.struct_item.attrs;
8686
let vis = &self.struct_item.vis;
8787
let struct_ident = &self.struct_item.ident;
88+
let struct_data_ident = format_ident!("{}Data", struct_ident);
8889
let db_lt = db_lifetime::db_lifetime(&self.struct_item.generics);
8990
let new_fn = salsa_struct.constructor_name();
9091
let field_ids = salsa_struct.field_ids();
@@ -111,6 +112,7 @@ impl Macro {
111112
attrs: [#(#attrs),*],
112113
vis: #vis,
113114
Struct: #struct_ident,
115+
StructData: #struct_data_ident,
114116
db_lt: #db_lt,
115117
id: #id,
116118
new_fn: #new_fn,

components/salsa-macros/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,48 @@ pub fn interned(args: TokenStream, input: TokenStream) -> TokenStream {
6767
interned::interned(args, input)
6868
}
6969

70+
/// A discouraged variant of `#[salsa::interned]`.
71+
///
72+
/// `#[salsa::interned_sans_lifetime]` is intended to be used in codebases that are migrating from
73+
/// the original Salsa to the current version of Salsa. New codebases that are just starting to use
74+
/// Salsa should avoid using this macro and prefer `#[salsa::interned]` instead.
75+
///
76+
/// `#[salsa::interned_sans_lifetime]` differs from `#[salsa::interned]` in a two key ways:
77+
/// 1. As the name suggests, it removes the `'db` lifetime from the interned struct. This lifetime is
78+
/// designed to meant to certain values as "salsa structs", but it also adds the desirable property
79+
/// of misuse resistance: it is difficult to embed an `#[salsa::interned]` struct into an auxiliary
80+
/// structures or collections collection, which can lead to subtle invalidation bugs. However, old
81+
/// Salsa encouraged storing keys to interned values in auxiliary structures and collections, so
82+
/// so converting all usage to Salsa's current API guidelines might not be desirable or feasible.
83+
/// 2. `#[salsa::interned_sans_lifetime]` requires specifiying the ID. In most cases, `salsa::Id`
84+
/// is sufficent, but in rare, performance-sensitive circumstances, it might be desireable to
85+
/// set the Id to a type that implements `salsa::plumbing::AsId` and `salsa::plumbing::FromId`.
86+
///
87+
/// ## Example
88+
///
89+
/// Below is an example of a struct using `#[salsa::interned_sans_lifetime]` with a custom Id:
90+
///
91+
/// ```rust
92+
/// #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
93+
/// struct CustomSalsaIdWrapper(salsa::Id);
94+
///
95+
/// impl AsId for CustomSalsaIdWrapper {
96+
/// fn as_id(&self) -> salsa::Id {
97+
/// self.0
98+
/// }
99+
/// }
100+
///
101+
/// impl FromId for CustomSalsaIdWrapper {
102+
/// fn from_id(id: salsa::Id) -> Self {
103+
/// CustomSalsaIdWrapper(id)
104+
/// }
105+
/// }
106+
///
107+
/// #[salsa::interned_sans_lifetime(id = CustomSalsaIdWrapper)]
108+
/// struct InternedString {
109+
/// data: String,
110+
/// }
111+
/// ```
70112
#[proc_macro_attribute]
71113
pub fn interned_sans_lifetime(args: TokenStream, input: TokenStream) -> TokenStream {
72114
interned_sans_lifetime::interned_sans_lifetime(args, input)

src/table/memo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{zalsa::MemoIngredientIndex, zalsa_local::QueryOrigin};
1313
/// Every tracked function must take a salsa struct as its first argument
1414
/// and memo tables are attached to those salsa structs as auxiliary data.
1515
#[derive(Default)]
16-
pub(crate) struct MemoTable {
16+
pub struct MemoTable {
1717
memos: RwLock<Vec<MemoEntry>>,
1818
}
1919

src/table/sync.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use super::util;
1818
/// Tracks the keys that are currently being processed; used to coordinate between
1919
/// worker threads.
2020
#[derive(Default)]
21-
pub(crate) struct SyncTable {
21+
pub struct SyncTable {
2222
syncs: RwLock<Vec<Option<SyncState>>>,
2323
}
2424

tests/interned-sans-lifetime.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
use expect_test::expect;
2+
use salsa::plumbing::{AsId, FromId};
23
use std::path::{Path, PathBuf};
34
use test_log::test;
45

56
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
67
struct CustomSalsaIdWrapper(salsa::Id);
78

8-
impl salsa::plumbing::AsId for CustomSalsaIdWrapper {
9+
impl AsId for CustomSalsaIdWrapper {
910
fn as_id(&self) -> salsa::Id {
1011
self.0
1112
}
1213
}
1314

14-
impl salsa::plumbing::FromId for CustomSalsaIdWrapper {
15+
impl FromId for CustomSalsaIdWrapper {
1516
fn from_id(id: salsa::Id) -> Self {
1617
CustomSalsaIdWrapper(id)
1718
}
@@ -45,7 +46,7 @@ struct InternedPathBuf {
4546

4647
#[salsa::tracked]
4748
fn intern_stuff(db: &dyn salsa::Database) -> String {
48-
let s1 = InternedString::new(db, "Hello, ".to_string());
49+
let s1 = InternedString::new(db, "Hello, ");
4950
let s2 = InternedString::new(db, "World, ");
5051
let s3 = InternedPair::new(db, (s1, s2));
5152

@@ -70,6 +71,7 @@ fn interning_returns_equal_keys_for_equal_data() {
7071
assert_eq!(s1, s1_2);
7172
assert_eq!(s2, s2_2);
7273
}
74+
7375
#[test]
7476
fn interning_returns_equal_keys_for_equal_data_multi_field() {
7577
let db = salsa::DatabaseImpl::new();
@@ -127,3 +129,32 @@ fn tracked_static_query_works() {
127129
let s1 = InternedString::new(&db, "Hello, World!".to_string());
128130
assert_eq!(length(&db, s1), 13);
129131
}
132+
133+
#[test]
134+
fn public_ingredient() {
135+
let db = salsa::DatabaseImpl::new();
136+
let s = InternedString::new(&db, String::from("Hello, world!"));
137+
let underlying_id = s.0;
138+
139+
let data = InternedString::ingredient(&db).data(&db, underlying_id.as_id());
140+
assert_eq!(data, &(String::from("Hello, world!"),));
141+
}
142+
143+
#[salsa::tracked]
144+
fn intern_more_stuff(db: &dyn salsa::Database) -> (InternedString, InternedString, InternedPair) {
145+
let s1 = InternedString::new(db, "Hello, ");
146+
let s2 = InternedString::new(db, "World, ");
147+
let pair = InternedPair::new(db, (s1, s2));
148+
(s1, s2, pair)
149+
}
150+
151+
#[test]
152+
fn public_ingredients() {
153+
let db = salsa::DatabaseImpl::new();
154+
155+
let (_, _, pair) = intern_more_stuff(&db);
156+
let (interned_s1, interned_s2) = InternedPair::ingredient(&db).fields(&db, pair).0;
157+
158+
assert_eq!(interned_s1.data(&db), "Hello, ".to_owned());
159+
assert_eq!(interned_s2.data(&db), "World, ".to_owned());
160+
}

0 commit comments

Comments
 (0)