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 lifetime used in the desugared interned struct.
32        // if the `db_lt_arg`, is present, this is `db_lt_arg`, but otherwise,
33        // it is `'static`.
34        interior_lt: $interior_lt:lifetime,
35
36        // Name user gave for `new`
37        new_fn: $new_fn:ident,
38
39        // A series of option tuples; see `setup_tracked_struct` macro
40        field_options: [$($field_option:tt),*],
41
42        // Field names
43        field_ids: [$($field_id:ident),*],
44
45        // Names for field setter methods (typically `set_foo`)
46        field_getters: [$($field_getter_vis:vis $field_getter_id:ident),*],
47
48        // Field types
49        field_tys: [$($field_ty:ty),*],
50
51        // Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
52        field_indices: [$($field_index:tt),*],
53
54        // Indexed types for each field (T0, T1, ...)
55        field_indexed_tys: [$($indexed_ty:ident),*],
56
57        // Number of fields
58        num_fields: $N:literal,
59
60        // If true, generate a debug impl.
61        generate_debug_impl: $generate_debug_impl:tt,
62
63        // Annoyingly macro-rules hygiene does not extend to items defined in the macro.
64        // We have the procedural macro generate names for those items that are
65        // not used elsewhere in the user's code.
66        unused_names: [
67            $zalsa:ident,
68            $zalsa_struct:ident,
69            $Configuration:ident,
70            $CACHE:ident,
71            $Db:ident,
72        ]
73    ) => {
74        $(#[$attr])*
75        #[derive(Copy, Clone, PartialEq, Eq, Hash)]
76        $vis struct $Struct< $($db_lt_arg)? >(
77            $Id,
78            std::marker::PhantomData < & $interior_lt salsa::plumbing::interned::Value <$StructWithStatic> >
79        );
80
81        #[allow(clippy::all)]
82        #[allow(dead_code)]
83        const _: () = {
84            use salsa::plumbing as $zalsa;
85            use $zalsa::interned as $zalsa_struct;
86
87            type $Configuration = $StructWithStatic;
88
89            type $StructDataIdent<$db_lt> = ($($field_ty,)*);
90
91            /// Key to use during hash lookups. Each field is some type that implements `Lookup<T>`
92            /// for the owned type. This permits interning with an `&str` when a `String` is required and so forth.
93            #[derive(Hash)]
94            struct StructKey<$db_lt, $($indexed_ty),*>(
95                $($indexed_ty,)*
96                std::marker::PhantomData<&$db_lt ()>,
97            );
98
99            impl<$db_lt, $($indexed_ty,)*> $zalsa::interned::HashEqLike<StructKey<$db_lt, $($indexed_ty),*>>
100                for $StructDataIdent<$db_lt>
101                where
102                $($field_ty: $zalsa::interned::HashEqLike<$indexed_ty>),*
103                {
104
105                fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
106                    $($zalsa::interned::HashEqLike::<$indexed_ty>::hash(&self.$field_index, &mut *h);)*
107                }
108
109                fn eq(&self, data: &StructKey<$db_lt, $($indexed_ty),*>) -> bool {
110                    ($($zalsa::interned::HashEqLike::<$indexed_ty>::eq(&self.$field_index, &data.$field_index) && )* true)
111                }
112            }
113
114            impl<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*> $zalsa::interned::Lookup<$StructDataIdent<$db_lt>>
115                for StructKey<$db_lt, $($indexed_ty),*> {
116
117                #[allow(unused_unit)]
118                fn into_owned(self) -> $StructDataIdent<$db_lt> {
119                    ($($zalsa::interned::Lookup::into_owned(self.$field_index),)*)
120                }
121            }
122
123            impl salsa::plumbing::interned::Configuration for $StructWithStatic {
124                const LOCATION: $zalsa::Location = $zalsa::Location {
125                    file: file!(),
126                    line: line!(),
127                };
128                const DEBUG_NAME: &'static str = stringify!($Struct);
129                type Fields<'a> = $StructDataIdent<'a>;
130                type Struct<'db> = $Struct< $($db_lt_arg)? >;
131            }
132
133            impl $Configuration {
134                // Suppress the lint against `cfg(loom)`.
135                #[allow(unexpected_cfgs)]
136                pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::IngredientImpl<Self>
137                where
138                    Db: ?Sized + $zalsa::Database,
139                {
140                    $zalsa::__maybe_lazy_static! {
141                        static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
142                            $zalsa::IngredientCache::new();
143                    }
144
145                    let zalsa = db.zalsa();
146                    CACHE.get_or_create(zalsa, || {
147                        zalsa.add_or_lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>()
148                    })
149                }
150            }
151
152            impl< $($db_lt_arg)? > $zalsa::AsId for $Struct< $($db_lt_arg)? > {
153                fn as_id(&self) -> salsa::Id {
154                    self.0.as_id()
155                }
156            }
157
158            impl< $($db_lt_arg)? > $zalsa::FromId for $Struct< $($db_lt_arg)? > {
159                fn from_id(id: salsa::Id) -> Self {
160                    Self(<$Id>::from_id(id), std::marker::PhantomData)
161                }
162            }
163
164            unsafe impl< $($db_lt_arg)? > Send for $Struct< $($db_lt_arg)? > {}
165
166            unsafe impl< $($db_lt_arg)? > Sync for $Struct< $($db_lt_arg)? > {}
167
168            $zalsa::macro_if! { $generate_debug_impl =>
169                impl< $($db_lt_arg)? > std::fmt::Debug for $Struct< $($db_lt_arg)? > {
170                    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171                        Self::default_debug_fmt(*self, f)
172                    }
173                }
174            }
175
176            impl< $($db_lt_arg)? > $zalsa::SalsaStructInDb for $Struct< $($db_lt_arg)? > {
177                type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;
178
179                fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
180                    aux.add_or_lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into()
181                }
182
183                #[inline]
184                fn cast(id: $zalsa::Id, type_id: $zalsa::TypeId) -> $zalsa::Option<Self> {
185                    if type_id == $zalsa::TypeId::of::<$Struct>() {
186                        $zalsa::Some(<$Struct as $zalsa::FromId>::from_id(id))
187                    } else {
188                        $zalsa::None
189                    }
190                }
191            }
192
193            unsafe impl< $($db_lt_arg)? > $zalsa::Update for $Struct< $($db_lt_arg)? > {
194                unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
195                    if unsafe { *old_pointer } != new_value {
196                        unsafe { *old_pointer = new_value };
197                        true
198                    } else {
199                        false
200                    }
201                }
202            }
203
204            impl<$db_lt> $Struct< $($db_lt_arg)? >  {
205                pub fn $new_fn<$Db, $($indexed_ty: $zalsa::interned::Lookup<$field_ty> + std::hash::Hash,)*>(db: &$db_lt $Db,  $($field_id: $indexed_ty),*) -> Self
206                where
207                    // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
208                    $Db: ?Sized + salsa::Database,
209                    $(
210                        $field_ty: $zalsa::interned::HashEqLike<$indexed_ty>,
211                    )*
212                {
213                    $Configuration::ingredient(db).intern(db.as_dyn_database(),
214                        StructKey::<$db_lt>($($field_id,)* std::marker::PhantomData::default()), |_, data| ($($zalsa::interned::Lookup::into_owned(data.$field_index),)*))
215                }
216
217                $(
218                    $field_getter_vis fn $field_getter_id<$Db>(self, db: &'db $Db) -> $zalsa::return_mode_ty!($field_option, 'db, $field_ty)
219                    where
220                        // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
221                        $Db: ?Sized + $zalsa::Database,
222                    {
223                        let fields = $Configuration::ingredient(db).fields(db.as_dyn_database(), self);
224                        $zalsa::return_mode_expression!(
225                            $field_option,
226                            $field_ty,
227                            &fields.$field_index,
228                        )
229                    }
230                )*
231
232                /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
233                pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234                    $zalsa::with_attached_database(|db| {
235                        let fields = $Configuration::ingredient(db).fields(db.as_dyn_database(), this);
236                        let mut f = f.debug_struct(stringify!($Struct));
237                        $(
238                            let f = f.field(stringify!($field_id), &fields.$field_index);
239                        )*
240                        f.finish()
241                    }).unwrap_or_else(|| {
242                        f.debug_tuple(stringify!($Struct))
243                            .field(&$zalsa::AsId::as_id(&this))
244                            .finish()
245                    })
246                }
247            }
248        };
249    };
250}