Skip to content

Commit e859218

Browse files
authored
Allow creation of tracked associated functions (without self) (#859)
* Rename file "setup_method_body.rs" to "setup_tracked_method_body.rs" * Rename macro `setup_method_body!` to `setup_tracked_method_body!` * Add support for tracked associated functions * Rename struct `MethodArguments` to `AssociatedFunctionArguments` (since every method is an associated function, but not every associated function is a method)
1 parent 678f51a commit e859218

File tree

6 files changed

+193
-37
lines changed

6 files changed

+193
-37
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ mod return_mode;
1919
mod setup_accumulator_impl;
2020
mod setup_input_struct;
2121
mod setup_interned_struct;
22-
mod setup_method_body;
22+
mod setup_tracked_assoc_fn_body;
2323
mod setup_tracked_fn;
24+
mod setup_tracked_method_body;
2425
mod setup_tracked_struct;
2526
mod unexpected_cycle_recovery;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#[macro_export]
2+
macro_rules! setup_tracked_assoc_fn_body {
3+
(
4+
salsa_tracked_attr: #[$salsa_tracked_attr:meta],
5+
self_ty: $self_ty:ty,
6+
db_lt: $($db_lt:lifetime)?,
7+
db: $db:ident,
8+
db_ty: ($($db_ty:tt)*),
9+
input_ids: [$($input_id:ident),*],
10+
input_tys: [$($input_ty:ty),*],
11+
output_ty: $output_ty:ty,
12+
inner_fn_name: $inner_fn_name:ident,
13+
inner_fn: $inner_fn:item,
14+
15+
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
16+
// We have the procedural macro generate names for those items that are
17+
// not used elsewhere in the user's code.
18+
unused_names: [
19+
$InnerTrait:ident,
20+
]
21+
) => {
22+
{
23+
trait $InnerTrait<$($db_lt)?> {
24+
fn $inner_fn_name(db: $($db_ty)*, $($input_id: $input_ty),*) -> $output_ty;
25+
}
26+
27+
impl<$($db_lt)?> $InnerTrait<$($db_lt)?> for $self_ty {
28+
$inner_fn
29+
}
30+
31+
#[$salsa_tracked_attr]
32+
fn $inner_fn_name<$($db_lt)?>(db: $($db_ty)*, $($input_id: $input_ty),*) -> $output_ty {
33+
<$self_ty as $InnerTrait>::$inner_fn_name(db, $($input_id),*)
34+
}
35+
36+
$inner_fn_name($db, $($input_id),*)
37+
}
38+
};
39+
}

components/salsa-macro-rules/src/setup_method_body.rs renamed to components/salsa-macro-rules/src/setup_tracked_method_body.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#[macro_export]
2-
macro_rules! setup_method_body {
2+
macro_rules! setup_tracked_method_body {
33
(
44
salsa_tracked_attr: #[$salsa_tracked_attr:meta],
55
self: $self:ident,

components/salsa-macros/src/tracked_impl.rs

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ struct Macro {
2424
hygiene: Hygiene,
2525
}
2626

27-
struct MethodArguments<'syn> {
28-
self_token: &'syn syn::token::SelfValue,
27+
struct AssociatedFunctionArguments<'syn> {
28+
self_token: Option<&'syn syn::token::SelfValue>,
2929
db_ty: &'syn syn::Type,
3030
db_ident: &'syn syn::Ident,
3131
db_lt: Option<&'syn syn::Lifetime>,
@@ -92,7 +92,7 @@ impl Macro {
9292
let InnerTrait = self.hygiene.ident("InnerTrait");
9393
let inner_fn_name = self.hygiene.ident(&fn_item.sig.ident.to_string());
9494

95-
let MethodArguments {
95+
let AssociatedFunctionArguments {
9696
self_token,
9797
db_ty,
9898
db_ident,
@@ -106,30 +106,54 @@ impl Macro {
106106
inner_fn.vis = syn::Visibility::Inherited;
107107
inner_fn.sig.ident = inner_fn_name.clone();
108108

109-
// Construct the body of the method
110-
111-
let block = parse_quote!({
112-
salsa::plumbing::setup_method_body! {
113-
salsa_tracked_attr: #salsa_tracked_attr,
114-
self: #self_token,
115-
self_ty: #self_ty,
116-
db_lt: #db_lt,
117-
db: #db_ident,
118-
db_ty: (#db_ty),
119-
input_ids: [#(#input_ids),*],
120-
input_tys: [#(#input_tys),*],
121-
output_ty: #output_ty,
122-
inner_fn_name: #inner_fn_name,
123-
inner_fn: #inner_fn,
124-
125-
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
126-
// We have the procedural macro generate names for those items that are
127-
// not used elsewhere in the user's code.
128-
unused_names: [
129-
#InnerTrait,
130-
]
131-
}
132-
});
109+
// Construct the body of the method or associated function
110+
111+
let block = if let Some(self_token) = self_token {
112+
parse_quote!({
113+
salsa::plumbing::setup_tracked_method_body! {
114+
salsa_tracked_attr: #salsa_tracked_attr,
115+
self: #self_token,
116+
self_ty: #self_ty,
117+
db_lt: #db_lt,
118+
db: #db_ident,
119+
db_ty: (#db_ty),
120+
input_ids: [#(#input_ids),*],
121+
input_tys: [#(#input_tys),*],
122+
output_ty: #output_ty,
123+
inner_fn_name: #inner_fn_name,
124+
inner_fn: #inner_fn,
125+
126+
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
127+
// We have the procedural macro generate names for those items that are
128+
// not used elsewhere in the user's code.
129+
unused_names: [
130+
#InnerTrait,
131+
]
132+
}
133+
})
134+
} else {
135+
parse_quote!({
136+
salsa::plumbing::setup_tracked_assoc_fn_body! {
137+
salsa_tracked_attr: #salsa_tracked_attr,
138+
self_ty: #self_ty,
139+
db_lt: #db_lt,
140+
db: #db_ident,
141+
db_ty: (#db_ty),
142+
input_ids: [#(#input_ids),*],
143+
input_tys: [#(#input_tys),*],
144+
output_ty: #output_ty,
145+
inner_fn_name: #inner_fn_name,
146+
inner_fn: #inner_fn,
147+
148+
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
149+
// We have the procedural macro generate names for those items that are
150+
// not used elsewhere in the user's code.
151+
unused_names: [
152+
#InnerTrait,
153+
]
154+
}
155+
})
156+
};
133157

134158
// Update the method that will actually appear in the impl to have the new body
135159
// and its true return type
@@ -144,18 +168,25 @@ impl Macro {
144168
&self,
145169
impl_item: &'syn syn::ItemImpl,
146170
fn_item: &'syn syn::ImplItemFn,
147-
) -> syn::Result<MethodArguments<'syn>> {
171+
) -> syn::Result<AssociatedFunctionArguments<'syn>> {
148172
let db_lt = self.extract_db_lifetime(impl_item, fn_item)?;
149173

150-
let self_token = self.check_self_argument(fn_item)?;
174+
let is_method = matches!(&fn_item.sig.inputs[0], syn::FnArg::Receiver(_));
175+
176+
let (self_token, db_input_index, skipped_inputs) = if is_method {
177+
(Some(self.check_self_argument(fn_item)?), 1, 2)
178+
} else {
179+
(None, 0, 1)
180+
};
151181

152-
let (db_ident, db_ty) = self.check_db_argument(&fn_item.sig.inputs[1])?;
182+
let (db_ident, db_ty) = self.check_db_argument(&fn_item.sig.inputs[db_input_index])?;
153183

154-
let input_ids: Vec<syn::Ident> = crate::fn_util::input_ids(&self.hygiene, &fn_item.sig, 2);
155-
let input_tys = crate::fn_util::input_tys(&fn_item.sig, 2)?;
184+
let input_ids: Vec<syn::Ident> =
185+
crate::fn_util::input_ids(&self.hygiene, &fn_item.sig, skipped_inputs);
186+
let input_tys = crate::fn_util::input_tys(&fn_item.sig, skipped_inputs)?;
156187
let output_ty = crate::fn_util::output_ty(db_lt, &fn_item.sig)?;
157188

158-
Ok(MethodArguments {
189+
Ok(AssociatedFunctionArguments {
159190
self_token,
160191
db_ident,
161192
db_lt,

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ pub mod plumbing {
7676
pub use salsa_macro_rules::{
7777
macro_if, maybe_backdate, maybe_default, maybe_default_tt, return_mode_expression,
7878
return_mode_ty, setup_accumulator_impl, setup_input_struct, setup_interned_struct,
79-
setup_method_body, setup_tracked_fn, setup_tracked_struct, unexpected_cycle_initial,
80-
unexpected_cycle_recovery,
79+
setup_tracked_assoc_fn_body, setup_tracked_fn, setup_tracked_method_body,
80+
setup_tracked_struct, unexpected_cycle_initial, unexpected_cycle_recovery,
8181
};
8282

8383
pub use crate::__maybe_lazy_static;

tests/tracked_assoc_fn.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Test that a `tracked` fn on a `salsa::input`
2+
//! compiles and executes successfully.
3+
#![allow(warnings)]
4+
5+
use common::LogDatabase as _;
6+
use expect_test::expect;
7+
8+
mod common;
9+
10+
trait TrackedTrait<'db> {
11+
type Output;
12+
13+
fn tracked_trait_fn(db: &'db dyn salsa::Database, input: MyInput) -> Self::Output;
14+
}
15+
16+
#[salsa::input]
17+
struct MyInput {
18+
field: u32,
19+
}
20+
21+
#[salsa::tracked]
22+
struct MyOutput<'db> {
23+
field: u32,
24+
}
25+
26+
#[salsa::tracked]
27+
impl MyInput {
28+
#[salsa::tracked]
29+
fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> Self {
30+
Self::new(db, 2 * input.field(db))
31+
}
32+
33+
#[salsa::tracked(returns(ref))]
34+
fn tracked_fn_ref(db: &dyn salsa::Database, input: MyInput) -> Self {
35+
Self::new(db, 3 * input.field(db))
36+
}
37+
}
38+
39+
#[salsa::tracked]
40+
impl<'db> TrackedTrait<'db> for MyOutput<'db> {
41+
type Output = Self;
42+
43+
#[salsa::tracked]
44+
fn tracked_trait_fn(db: &'db dyn salsa::Database, input: MyInput) -> Self::Output {
45+
Self::new(db, 4 * input.field(db))
46+
}
47+
}
48+
49+
// The self-type of a tracked impl doesn't have to be tracked itself:
50+
struct UntrackedHelper;
51+
52+
#[salsa::tracked]
53+
impl<'db> TrackedTrait<'db> for UntrackedHelper {
54+
type Output = MyOutput<'db>;
55+
56+
#[salsa::tracked]
57+
fn tracked_trait_fn(db: &'db dyn salsa::Database, input: MyInput) -> Self::Output {
58+
MyOutput::tracked_trait_fn(db, input)
59+
}
60+
}
61+
62+
#[test]
63+
fn execute() {
64+
let mut db = salsa::DatabaseImpl::new();
65+
let input = MyInput::new(&db, 22);
66+
let output = MyOutput::tracked_trait_fn(&db, input);
67+
let helper_output = UntrackedHelper::tracked_trait_fn(&db, input);
68+
// assert_eq!(object.tracked_fn(&db), 44);
69+
// assert_eq!(*object.tracked_fn_ref(&db), 66);
70+
assert_eq!(output.field(&db), 88);
71+
assert_eq!(helper_output.field(&db), 88);
72+
}
73+
74+
#[test]
75+
fn debug_name() {
76+
let mut db = common::ExecuteValidateLoggerDatabase::default();
77+
let input = MyInput::new(&db, 22);
78+
let output = MyOutput::tracked_trait_fn(&db, input);
79+
80+
assert_eq!(output.field(&db), 88);
81+
db.assert_logs(expect![[r#"
82+
[
83+
"salsa_event(WillExecute { database_key: tracked_trait_fn_(Id(0)) })",
84+
]"#]]);
85+
}

0 commit comments

Comments
 (0)