salsa_macro_rules/
setup_interned_struct.rs

1/// Macro for setting up a function that must intern its arguments.
2#[macro_export]
3macro_rules! setup_interned_struct {
4    (
5        // Attributes on the struct
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 struct data. This is a parameter because `std::concat_idents`
15        // is unstable and taking an additional dependency is unnecessary.
16        StructData: $StructDataIdent:ident,
17
18        // Name of the struct type with a `'static` argument (unless this type has no db lifetime,
19        // in which case this is the same as `$Struct`)
20        StructWithStatic: $StructWithStatic:ty,
21
22        // Name of the `'db` lifetime that the user gave
23        db_lt: $db_lt:lifetime,
24
25        // optional db lifetime argument.
26        db_lt_arg: $($db_lt_arg:lifetime)?,
27
28        // the salsa ID
29        id: $Id:path,
30
31        // The minimum number of revisions to keep the value interned.
32        revisions: $($revisions:expr)?,
33
34        // the lifetime used in the desugared interned struct.
35        // if the `db_lt_arg`, is present, this is `db_lt_arg`, but otherwise,
36        // it is `'static`.
37        interior_lt: $interior_lt:lifetime,
38
39        // Name user gave for `new`
40        new_fn: $new_fn:ident,
41
42        // A series of option tuples; see `setup_tracked_struct` macro
43        field_options: [$($field_option:tt),*],
44
45        // Field names
46        field_ids: [$($field_id:ident),*],
47
48        // Names for field setter methods (typically `set_foo`)
49        field_getters: [$($field_getter_vis:vis $field_getter_id:ident),*],
50
51        // Field types
52        field_tys: [$($field_ty:ty),*],
53
54        // Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
55        field_indices: [$($field_index:tt),*],
56
57        // Indexed types for each field (T0, T1, ...)
58        field_indexed_tys: [$($indexed_ty:ident),*],
59
60        // Attrs for each field.
61        field_attrs: [$([$(#[$field_attr:meta]),*]),*],
62
63        // Number of fields
64        num_fields: $N:literal,
65
66        // If true, generate a debug impl.
67        generate_debug_impl: $generate_debug_impl:tt,
68
69        // The function used to implement `C::heap_size`.
70        heap_size_fn: $($heap_size_fn:path)?,
71
72        // If `true`, `serialize_fn` and `deserialize_fn` have been provided.
73        persist: $persist:tt,
74
75        // The path to the `serialize` function for the value's fields.
76        serialize_fn: $($serialize_fn:path)?,
77
78        // The path to the `serialize` function for the value's fields.
79        deserialize_fn: $($deserialize_fn:path)?,
80
81        assert_types_are_update: {$($assert_types_are_update:tt)*},
82
83        // Annoyingly macro-rules hygiene does not extend to items defined in the macro.
84        // We have the procedural macro generate names for those items that are
85        // not used elsewhere in the user's code.
86        unused_names: [
87            $zalsa:ident,
88            $zalsa_struct:ident,
89            $Configuration:ident,
90            $CACHE:ident,
91            $Db:ident,
92        ]
93    ) => {
94        $(#[$attr])*
95        #[derive(Copy, Clone, PartialEq, Eq, Hash)]
96        $vis struct $Struct< $($db_lt_arg)? >(
97            $Id,
98            std::marker::PhantomData<fn() -> &$interior_lt ()>
99        );
100
101        #[allow(clippy::all)]
102        #[allow(dead_code)]
103        const _: () = {
104            use ::salsa::plumbing as $zalsa;
105            use $zalsa::interned as $zalsa_struct;
106
107            type $Configuration = $StructWithStatic;
108
109            impl<$($db_lt_arg)?> $zalsa::HasJar for $Struct<$($db_lt_arg)?> {
110                type Jar = $zalsa_struct::JarImpl<$Configuration>;
111                const KIND: $zalsa::JarKind = $zalsa::JarKind::Struct;
112            }
113
114            $zalsa::register_jar! {
115                $zalsa::ErasedJar::erase::<$StructWithStatic>()
116            }
117
118            type $StructDataIdent<$db_lt> = ($($field_ty,)*);
119
120            /// Key to use during hash lookups. Each field is some type that implements `Lookup<T>`
121            /// for the owned type. This permits interning with an `&str` when a `String` is required and so forth.
122            #[derive(Hash)]
123            struct StructKey<$db_lt, $($indexed_ty),*>(
124                $($indexed_ty,)*
125                ::std::marker::PhantomData<&$db_lt ()>,
126            );
127
128            impl<$db_lt, $($indexed_ty,)*> $zalsa::interned::HashEqLike<StructKey<$db_lt, $($indexed_ty),*>>
129                for $StructDataIdent<$db_lt>
130                where
131                $($field_ty: $zalsa::interned::HashEqLike<$indexed_ty>),*
132                {
133
134                fn hash<H: ::std::hash::Hasher>(&self, h: &mut H) {
135                    $($zalsa::interned::HashEqLike::<$indexed_ty>::hash(&self.$field_index, &mut *h);)*
136                }
137
138                fn eq(&self, data: &StructKey<$db_lt, $($indexed_ty),*>) -> bool {
139                    ($($zalsa::interned::HashEqLike::<$indexed_ty>::eq(&self.$field_index, &data.$field_index) && )* true)
140                }
141            }
142
143            impl<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*> $zalsa::interned::Lookup<$StructDataIdent<$db_lt>>
144                for StructKey<$db_lt, $($indexed_ty),*> {
145
146                #[allow(unused_unit)]
147                fn into_owned(self) -> $StructDataIdent<$db_lt> {
148                    ($($zalsa::interned::Lookup::into_owned(self.$field_index),)*)
149                }
150            }
151
152            impl $zalsa::interned::Configuration for $StructWithStatic {
153                const LOCATION: $zalsa::Location = $zalsa::Location {
154                    file: file!(),
155                    line: line!(),
156                };
157                const DEBUG_NAME: &'static str = stringify!($Struct);
158                const PERSIST: bool = $persist;
159
160                $(
161                    const REVISIONS: ::core::num::NonZeroUsize = ::core::num::NonZeroUsize::new($revisions).unwrap();
162                )?
163
164                type Fields<'a> = $StructDataIdent<'a>;
165                type Struct<'db> = $Struct< $($db_lt_arg)? >;
166
167                $(
168                    fn heap_size(value: &Self::Fields<'_>) -> Option<usize> {
169                        Some($heap_size_fn(value))
170                    }
171                )?
172
173                fn serialize<S: $zalsa::serde::Serializer>(
174                    fields: &Self::Fields<'_>,
175                    serializer: S,
176                ) -> ::std::result::Result<S::Ok, S::Error> {
177                    $zalsa::macro_if! {
178                        if $persist {
179                            $($serialize_fn(fields, serializer))?
180                        } else {
181                            panic!("attempted to serialize value not marked with `persist` attribute")
182                        }
183                    }
184                }
185
186                fn deserialize<'de, D: $zalsa::serde::Deserializer<'de>>(
187                    deserializer: D,
188                ) -> ::std::result::Result<Self::Fields<'static>, D::Error> {
189                    $zalsa::macro_if! {
190                        if $persist {
191                            $($deserialize_fn(deserializer))?
192                        } else {
193                            panic!("attempted to deserialize value not marked with `persist` attribute")
194                        }
195                    }
196                }
197            }
198
199            impl $Configuration {
200                pub fn ingredient(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<Self> {
201                    static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
202                        $zalsa::IngredientCache::new();
203
204                    // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only
205                    // ingredient created by our jar is the struct ingredient.
206                    unsafe {
207                        CACHE.get_or_create(zalsa, || {
208                            zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>()
209                        })
210                    }
211                }
212            }
213
214            impl< $($db_lt_arg)? > $zalsa::AsId for $Struct< $($db_lt_arg)? > {
215                fn as_id(&self) -> ::salsa::Id {
216                    self.0.as_id()
217                }
218            }
219
220            impl< $($db_lt_arg)? > $zalsa::FromId for $Struct< $($db_lt_arg)? > {
221                fn from_id(id: ::salsa::Id) -> Self {
222                    Self(<$Id>::from_id(id), ::std::marker::PhantomData)
223                }
224            }
225
226            unsafe impl< $($db_lt_arg)? > Send for $Struct< $($db_lt_arg)? > {}
227
228            unsafe impl< $($db_lt_arg)? > Sync for $Struct< $($db_lt_arg)? > {}
229
230            $zalsa::macro_if! { $generate_debug_impl =>
231                impl< $($db_lt_arg)? > ::std::fmt::Debug for $Struct< $($db_lt_arg)? > {
232                    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
233                        Self::default_debug_fmt(*self, f)
234                    }
235                }
236            }
237
238            impl< $($db_lt_arg)? > $zalsa::SalsaStructInDb for $Struct< $($db_lt_arg)? > {
239                type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;
240
241                fn lookup_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
242                    aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into()
243                }
244
245                fn entries(
246                    zalsa: &$zalsa::Zalsa
247                ) -> impl Iterator<Item = $zalsa::DatabaseKeyIndex> + '_ {
248                    let ingredient_index = zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>();
249                    <$Configuration>::ingredient(zalsa).entries(zalsa).map(|entry| entry.key())
250                }
251
252                #[inline]
253                fn cast(id: $zalsa::Id, type_id: $zalsa::TypeId) -> $zalsa::Option<Self> {
254                    if type_id == $zalsa::TypeId::of::<$Struct>() {
255                        $zalsa::Some(<$Struct as $zalsa::FromId>::from_id(id))
256                    } else {
257                        $zalsa::None
258                    }
259                }
260
261                #[inline]
262                unsafe fn memo_table(
263                    zalsa: &$zalsa::Zalsa,
264                    id: $zalsa::Id,
265                    current_revision: $zalsa::Revision,
266                ) -> $zalsa::MemoTableWithTypes<'_> {
267                    // SAFETY: Guaranteed by caller.
268                    unsafe { zalsa.table().memos::<$zalsa_struct::Value<$Configuration>>(id, current_revision) }
269                }
270            }
271
272            $zalsa::macro_if! { $persist =>
273                impl<$($db_lt_arg)?> $zalsa::serde::Serialize for $Struct<$($db_lt_arg)?> {
274                    fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
275                    where
276                        S: $zalsa::serde::Serializer,
277                    {
278                        $zalsa::serde::Serialize::serialize(&$zalsa::AsId::as_id(self), serializer)
279                    }
280                }
281
282                impl<'de, $($db_lt_arg)?> $zalsa::serde::Deserialize<'de> for $Struct<$($db_lt_arg)?> {
283                    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
284                    where
285                        D: $zalsa::serde::Deserializer<'de>,
286                    {
287                        let id = $zalsa::Id::deserialize(deserializer)?;
288                        Ok($zalsa::FromId::from_id(id))
289                    }
290                }
291            }
292
293
294            unsafe impl< $($db_lt_arg)? > $zalsa::Update for $Struct< $($db_lt_arg)? > {
295                unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
296                    $($assert_types_are_update)*
297
298                    if unsafe { *old_pointer } != new_value {
299                        unsafe { *old_pointer = new_value };
300                        true
301                    } else {
302                        false
303                    }
304                }
305            }
306
307            impl<$db_lt> $Struct< $($db_lt_arg)? >  {
308                pub fn $new_fn<$Db, $($indexed_ty: $zalsa::interned::Lookup<$field_ty> + ::std::hash::Hash,)*>(db: &$db_lt $Db,  $($field_id: $indexed_ty),*) -> Self
309                where
310                    // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
311                    $Db: ?Sized + ::salsa::Database,
312                    $(
313                        $field_ty: $zalsa::interned::HashEqLike<$indexed_ty>,
314                    )*
315                {
316                    let (zalsa, zalsa_local) = db.zalsas();
317                    $Configuration::ingredient(zalsa).intern(zalsa, zalsa_local,
318                        StructKey::<$db_lt>($($field_id,)* ::std::marker::PhantomData::default()), |_, data| ($($zalsa::interned::Lookup::into_owned(data.$field_index),)*))
319                }
320
321                $(
322                    $(#[$field_attr])*
323                    $field_getter_vis fn $field_getter_id<$Db>(self, db: &'db $Db) -> $zalsa::return_mode_ty!($field_option, 'db, $field_ty)
324                    where
325                        // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
326                        $Db: ?Sized + $zalsa::Database,
327                    {
328                        let zalsa = db.zalsa();
329                        let fields = $Configuration::ingredient(zalsa).fields(zalsa, self);
330                        $zalsa::return_mode_expression!(
331                            $field_option,
332                            $field_ty,
333                            &fields.$field_index,
334                        )
335                    }
336                )*
337            }
338
339            // Duplication can be dropped here once we no longer allow the `no_lifetime` hack
340            $zalsa::macro_if! {
341                iftt ($($db_lt_arg)?) {
342                    impl $Struct<'_> {
343                        /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
344                        pub fn default_debug_fmt(this: Self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result
345                        where
346                            // rustc rejects trivial bounds, but it cannot see through higher-ranked bounds
347                            // with its check :^)
348                            $(for<$db_lt> $field_ty: ::std::fmt::Debug),*
349                        {
350                            $zalsa::with_attached_database(|db| {
351                                let zalsa = db.zalsa();
352                                let fields = $Configuration::ingredient(zalsa).fields(zalsa, this);
353                                let mut f = f.debug_struct(stringify!($Struct));
354                                $(
355                                    let f = f.field(stringify!($field_id), &fields.$field_index);
356                                )*
357                                f.finish()
358                            }).unwrap_or_else(|| {
359                                f.debug_tuple(stringify!($Struct))
360                                    .field(&$zalsa::AsId::as_id(&this))
361                                    .finish()
362                            })
363                        }
364                    }
365                } else {
366                    impl $Struct {
367                        /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
368                        pub fn default_debug_fmt(this: Self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result
369                        where
370                            // rustc rejects trivial bounds, but it cannot see through higher-ranked bounds
371                            // with its check :^)
372                            $(for<$db_lt> $field_ty: ::std::fmt::Debug),*
373                        {
374                            $zalsa::with_attached_database(|db| {
375                                let zalsa = db.zalsa();
376                                let fields = $Configuration::ingredient(zalsa).fields(zalsa, this);
377                                let mut f = f.debug_struct(stringify!($Struct));
378                                $(
379                                    let f = f.field(stringify!($field_id), &fields.$field_index);
380                                )*
381                                f.finish()
382                            }).unwrap_or_else(|| {
383                                f.debug_tuple(stringify!($Struct))
384                                    .field(&$zalsa::AsId::as_id(&this))
385                                    .finish()
386                            })
387                        }
388                    }
389                }
390            }
391        };
392    };
393}