salsa_macro_rules/
setup_tracked_struct.rs

1/// Macro for setting up a function that must intern its arguments.
2#[macro_export]
3macro_rules! setup_tracked_struct {
4    (
5        // Attributes on the function.
6        attrs: [$(#[$attr:meta]),*],
7
8        // Visibility of the struct.
9        vis: $vis:vis,
10
11        // Name of the struct.
12        Struct: $Struct:ident,
13
14        // Name of the `'db` lifetime that the user gave.
15        db_lt: $db_lt:lifetime,
16
17        // Name user gave for `new`.
18        new_fn: $new_fn:ident,
19
20        // Field names.
21        field_ids: [$($field_id:ident),*],
22
23        // Tracked field names.
24        tracked_ids: [$($tracked_id:ident),*],
25
26        // Visibility and names of tracked fields.
27        tracked_getters: [$($tracked_getter_vis:vis $tracked_getter_id:ident),*],
28
29        // Visibility and names of untracked fields.
30        untracked_getters: [$($untracked_getter_vis:vis $untracked_getter_id:ident),*],
31
32        // Field types, may reference `db_lt`.
33        field_tys: [$($field_ty:ty),*],
34
35        // Tracked field types.
36        tracked_tys: [$($tracked_ty:ty),*],
37
38        // Untracked field types.
39        untracked_tys: [$($untracked_ty:ty),*],
40
41        // Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
42        field_indices: [$($field_index:tt),*],
43
44        // Absolute indices of any tracked fields, relative to all other fields of this struct.
45        absolute_tracked_indices: [$($absolute_tracked_index:tt),*],
46
47        // Indices of any tracked fields, relative to only tracked fields on this struct.
48        relative_tracked_indices: [$($relative_tracked_index:tt),*],
49
50        // Absolute indices of any untracked fields.
51        absolute_untracked_indices: [$($absolute_untracked_index:tt),*],
52
53        // Tracked field types.
54        tracked_maybe_updates: [$($tracked_maybe_update:tt),*],
55
56        // Untracked field types.
57        untracked_maybe_updates: [$($untracked_maybe_update:tt),*],
58
59        // A set of "field options" for each tracked field.
60        //
61        // Each field option is a tuple `(return_mode, maybe_backdate)` where:
62        //
63        // * `return_mode` is an identifier as specified in `salsa_macros::options::Option::returns`
64        // * `maybe_backdate` is either the identifier `backdate` or `no_backdate`
65        //
66        // These are used to drive conditional logic for each field via recursive macro invocation
67        // (see e.g. @return_mode below).
68        tracked_options: [$($tracked_option:tt),*],
69
70        // A set of "field options" for each untracked field.
71        //
72        // Each field option is a tuple `(return_mode, maybe_backdate)` where:
73        //
74        // * `return_mode` is an identifier as specified in `salsa_macros::options::Option::returns`
75        // * `maybe_backdate` is either the identifier `backdate` or `no_backdate`
76        //
77        // These are used to drive conditional logic for each field via recursive macro invocation
78        // (see e.g. @return_mode below).
79        untracked_options: [$($untracked_option:tt),*],
80
81        // Attrs for each field.
82        tracked_field_attrs: [$([$(#[$tracked_field_attr:meta]),*]),*],
83        untracked_field_attrs: [$([$(#[$untracked_field_attr:meta]),*]),*],
84
85        // Number of tracked fields.
86        num_tracked_fields: $N:literal,
87
88        // If true, generate a debug impl.
89        generate_debug_impl: $generate_debug_impl:tt,
90
91        // The function used to implement `C::heap_size`.
92        heap_size_fn: $($heap_size_fn:path)?,
93
94        // If `true`, `serialize_fn` and `deserialize_fn` have been provided.
95        persist: $persist:tt,
96
97        // The path to the `serialize` function for the value's fields.
98        serialize_fn: $($serialize_fn:path)?,
99
100        // The path to the `serialize` function for the value's fields.
101        deserialize_fn: $($deserialize_fn:path)?,
102
103        // Annoyingly macro-rules hygiene does not extend to items defined in the macro.
104        // We have the procedural macro generate names for those items that are
105        // not used elsewhere in the user's code.
106        unused_names: [
107            $zalsa:ident,
108            $zalsa_struct:ident,
109            $Configuration:ident,
110            $CACHE:ident,
111            $Db:ident,
112            $Revision:ident,
113        ]
114    ) => {
115        $(#[$attr])*
116        #[derive(Copy, Clone, PartialEq, Eq, Hash)]
117        $vis struct $Struct<$db_lt>(
118            ::salsa::Id,
119            ::std::marker::PhantomData<fn() -> &$db_lt ()>
120        );
121
122        #[allow(dead_code)]
123        #[allow(clippy::all)]
124        const _: () = {
125            use ::salsa::plumbing as $zalsa;
126            use $zalsa::tracked_struct as $zalsa_struct;
127            use $zalsa::Revision as $Revision;
128
129            type $Configuration = $Struct<'static>;
130
131            impl<$db_lt> $zalsa::HasJar for $Struct<$db_lt> {
132                type Jar = $zalsa_struct::JarImpl<$Configuration>;
133                const KIND: $zalsa::JarKind = $zalsa::JarKind::Struct;
134            }
135
136            $zalsa::register_jar! {
137                $zalsa::ErasedJar::erase::<$Struct<'static>>()
138            }
139
140            impl $zalsa_struct::Configuration for $Configuration {
141                const LOCATION: $zalsa::Location = $zalsa::Location {
142                    file: file!(),
143                    line: line!(),
144                };
145                const DEBUG_NAME: &'static str = stringify!($Struct);
146
147                const TRACKED_FIELD_NAMES: &'static [&'static str] = &[
148                    $(stringify!($tracked_id),)*
149                ];
150
151                const TRACKED_FIELD_INDICES: &'static [usize] = &[
152                    $($relative_tracked_index,)*
153                ];
154
155                const PERSIST: bool = $persist;
156
157                type Fields<$db_lt> = ($($field_ty,)*);
158
159                type Revisions = [$Revision; $N];
160
161                type Struct<$db_lt> = $Struct<$db_lt>;
162
163                fn untracked_fields(fields: &Self::Fields<'_>) -> impl ::std::hash::Hash {
164                    ( $( &fields.$absolute_untracked_index ),* )
165                }
166
167                fn new_revisions(current_revision: $Revision) -> Self::Revisions {
168                    [current_revision; $N]
169                }
170
171                unsafe fn update_fields<$db_lt>(
172                    current_revision: $Revision,
173                    revisions: &mut Self::Revisions,
174                    old_fields: *mut Self::Fields<$db_lt>,
175                    new_fields: Self::Fields<$db_lt>,
176                ) -> bool {
177                    use $zalsa::UpdateFallback as _;
178                    unsafe {
179                        $(
180                            $crate::maybe_backdate!(
181                                $tracked_option,
182                                $tracked_maybe_update,
183                                (*old_fields).$absolute_tracked_index,
184                                new_fields.$absolute_tracked_index,
185                                revisions[$relative_tracked_index],
186                                current_revision,
187                                $zalsa,
188                            );
189                        )*;
190
191                        // If any untracked field has changed, return `true`, indicating that the tracked struct
192                        // itself should be considered changed.
193                        $(
194                            $untracked_maybe_update(
195                                &mut (*old_fields).$absolute_untracked_index,
196                                new_fields.$absolute_untracked_index,
197                            )
198                            |
199                        )* false
200                    }
201                }
202
203                $(
204                    fn heap_size(value: &Self::Fields<'_>) -> Option<usize> {
205                        Some($heap_size_fn(value))
206                    }
207                )?
208
209                fn serialize<S: $zalsa::serde::Serializer>(
210                    fields: &Self::Fields<'_>,
211                    serializer: S,
212                ) -> ::std::result::Result<S::Ok, S::Error> {
213                    $zalsa::macro_if! {
214                        if $persist {
215                            $($serialize_fn(fields, serializer))?
216                        } else {
217                            panic!("attempted to serialize value not marked with `persist` attribute")
218                        }
219                    }
220                }
221
222                fn deserialize<'de, D: $zalsa::serde::Deserializer<'de>>(
223                    deserializer: D,
224                ) -> ::std::result::Result<Self::Fields<'static>, D::Error> {
225                    $zalsa::macro_if! {
226                        if $persist {
227                            $($deserialize_fn(deserializer))?
228                        } else {
229                            panic!("attempted to deserialize value not marked with `persist` attribute")
230                        }
231                    }
232                }
233            }
234
235            impl $Configuration {
236                pub fn ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<Self> {
237                    Self::ingredient_(db.zalsa())
238                }
239
240                fn ingredient_(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<Self> {
241                    static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
242                        $zalsa::IngredientCache::new();
243
244                    // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only
245                    // ingredient created by our jar is the struct ingredient.
246                    unsafe {
247                        CACHE.get_or_create(zalsa, || {
248                            zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>()
249                        })
250                    }
251                }
252            }
253
254            impl<$db_lt> $zalsa::FromId for $Struct<$db_lt> {
255                #[inline]
256                fn from_id(id: ::salsa::Id) -> Self {
257                    $Struct(id, ::std::marker::PhantomData)
258                }
259            }
260
261            impl $zalsa::AsId for $Struct<'_> {
262                #[inline]
263                fn as_id(&self) -> $zalsa::Id {
264                    self.0
265                }
266            }
267
268            impl $zalsa::SalsaStructInDb for $Struct<'_> {
269                type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;
270
271                fn lookup_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
272                    aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into()
273                }
274
275                fn entries(
276                    zalsa: &$zalsa::Zalsa
277                ) -> impl Iterator<Item = $zalsa::DatabaseKeyIndex> + '_ {
278                    let ingredient_index = zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>();
279                    <$Configuration>::ingredient_(zalsa).entries(zalsa).map(|entry| entry.key())
280                }
281
282                #[inline]
283                fn cast(id: $zalsa::Id, type_id: $zalsa::TypeId) -> $zalsa::Option<Self> {
284                    if type_id == $zalsa::TypeId::of::<$Struct<'static>>() {
285                        $zalsa::Some(<$Struct<'static> as $zalsa::FromId>::from_id(id))
286                    } else {
287                        $zalsa::None
288                    }
289                }
290
291                #[inline]
292                unsafe fn memo_table(
293                    zalsa: &$zalsa::Zalsa,
294                    id: $zalsa::Id,
295                    current_revision: $zalsa::Revision,
296                ) -> $zalsa::MemoTableWithTypes<'_> {
297                    // SAFETY: Guaranteed by caller.
298                    unsafe { zalsa.table().memos::<$zalsa_struct::Value<$Configuration>>(id, current_revision) }
299                }
300            }
301
302            impl $zalsa::TrackedStructInDb for $Struct<'_> {
303                fn database_key_index(zalsa: &$zalsa::Zalsa, id: $zalsa::Id) -> $zalsa::DatabaseKeyIndex {
304                    $Configuration::ingredient_(zalsa).database_key_index(id)
305                }
306            }
307
308            $zalsa::macro_if! { $persist =>
309                impl $zalsa::serde::Serialize for $Struct<'_> {
310                    fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
311                    where
312                        S: $zalsa::serde::Serializer,
313                    {
314                        $zalsa::serde::Serialize::serialize(&$zalsa::AsId::as_id(self), serializer)
315                    }
316                }
317
318                impl<'de> $zalsa::serde::Deserialize<'de> for $Struct<'_> {
319                    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
320                    where
321                        D: $zalsa::serde::Deserializer<'de>,
322                    {
323                        let id = $zalsa::Id::deserialize(deserializer)?;
324                        Ok($zalsa::FromId::from_id(id))
325                    }
326                }
327            }
328
329
330            unsafe impl Send for $Struct<'_> {}
331
332            unsafe impl Sync for $Struct<'_> {}
333
334            $zalsa::macro_if! { $generate_debug_impl =>
335                impl ::std::fmt::Debug for $Struct<'_> {
336                    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
337                        Self::default_debug_fmt(*self, f)
338                    }
339                }
340            }
341
342            unsafe impl $zalsa::Update for $Struct<'_> {
343                unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
344                    if unsafe { *old_pointer } != new_value {
345                        unsafe { *old_pointer = new_value };
346                        true
347                    } else {
348                        false
349                    }
350                }
351            }
352
353            impl<$db_lt> $Struct<$db_lt> {
354                pub fn $new_fn<$Db>(db: &$db_lt $Db, $($field_id: $field_ty),*) -> Self
355                where
356                    // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
357                    $Db: ?Sized + $zalsa::Database,
358                {
359                    let (zalsa, zalsa_local) = db.zalsas();
360                    $Configuration::ingredient_(zalsa).new_struct(
361                        zalsa,zalsa_local,
362                        ($($field_id,)*)
363                    )
364                }
365
366                $(
367                    $(#[$tracked_field_attr])*
368                    $tracked_getter_vis fn $tracked_getter_id<$Db>(self, db: &$db_lt $Db) -> $crate::return_mode_ty!($tracked_option, $db_lt, $tracked_ty)
369                    where
370                        // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
371                        $Db: ?Sized + $zalsa::Database,
372                    {
373                        let (zalsa, zalsa_local) = db.zalsas();
374                        let fields = $Configuration::ingredient_(zalsa).tracked_field(zalsa, zalsa_local, self, $relative_tracked_index);
375                        $crate::return_mode_expression!(
376                            $tracked_option,
377                            $tracked_ty,
378                            &fields.$absolute_tracked_index,
379                        )
380                    }
381                )*
382
383                $(
384                    $(#[$untracked_field_attr])*
385                    $untracked_getter_vis fn $untracked_getter_id<$Db>(self, db: &$db_lt $Db) -> $crate::return_mode_ty!($untracked_option, $db_lt, $untracked_ty)
386                    where
387                        // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
388                        $Db: ?Sized + $zalsa::Database,
389                    {
390                        let zalsa = db.zalsa();
391                        let fields = $Configuration::ingredient_(zalsa).untracked_field(zalsa, self);
392                        $crate::return_mode_expression!(
393                            $untracked_option,
394                            $untracked_ty,
395                            &fields.$absolute_untracked_index,
396                        )
397                    }
398                )*
399            }
400
401            #[allow(unused_lifetimes)]
402            impl<'_db> $Struct<'_db> {
403                /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
404                pub fn default_debug_fmt(this: Self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result
405                where
406                    // `zalsa::with_attached_database` has a local lifetime for the database
407                    // so we need this function to be higher-ranked over the db lifetime
408                    // Thus the actual lifetime of `Self` does not matter here so we discard
409                    // it with the `'_db` lifetime name as we cannot shadow lifetimes.
410                    $(for<$db_lt> $field_ty: ::std::fmt::Debug),*
411                {
412                    $zalsa::with_attached_database(|db| {
413                        let zalsa = db.zalsa();
414                        let fields = $Configuration::ingredient_(zalsa).leak_fields(zalsa, this);
415                        let mut f = f.debug_struct(stringify!($Struct));
416                        let f = f.field("[salsa id]", &$zalsa::AsId::as_id(&this));
417                        $(
418                            let f = f.field(stringify!($field_id), &fields.$field_index);
419                        )*
420                        f.finish()
421                    }).unwrap_or_else(|| {
422                        f.debug_struct(stringify!($Struct))
423                            .field("[salsa id]", &$zalsa::AsId::as_id(&this))
424                            .finish()
425                    })
426                }
427            }
428        };
429    };
430}