span/
hygiene.rs

1//! Machinery for hygienic macros.
2//!
3//! Inspired by Matthew Flatt et al., “Macros That Work Together: Compile-Time Bindings, Partial
4//! Expansion, and Definition Contexts,” *Journal of Functional Programming* 22, no. 2
5//! (March 1, 2012): 181–216, <https://doi.org/10.1017/S0956796812000093>.
6//!
7//! Also see <https://rustc-dev-guide.rust-lang.org/macro-expansion.html#hygiene-and-hierarchies>
8//!
9//! # The Expansion Order Hierarchy
10//!
11//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy
12//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as
13//! [`MacroFile`]s are interned [`MacroCallLoc`]s.
14//!
15//! # The Macro Definition Hierarchy
16//!
17//! `SyntaxContextData` in rustc and rust-analyzer. Basically the same in both.
18//!
19//! # The Call-site Hierarchy
20//!
21//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer.
22use std::fmt;
23
24use crate::Edition;
25
26/// A syntax context describes a hierarchy tracking order of macro definitions.
27#[cfg(feature = "salsa")]
28#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
29pub struct SyntaxContext(
30    /// # Invariant
31    ///
32    /// This is either a valid `salsa::Id` or a root `SyntaxContext`.
33    u32,
34    std::marker::PhantomData<&'static salsa::plumbing::interned::Value<SyntaxContext>>,
35);
36
37#[cfg(feature = "salsa")]
38const _: () = {
39    use crate::MacroCallId;
40    use salsa::plumbing as zalsa_;
41    use salsa::plumbing::interned as zalsa_struct_;
42
43    #[derive(Clone, Eq, Debug)]
44    pub struct SyntaxContextData {
45        outer_expn: Option<MacroCallId>,
46        outer_transparency: Transparency,
47        edition: Edition,
48        parent: SyntaxContext,
49        opaque: SyntaxContext,
50        opaque_and_semitransparent: SyntaxContext,
51    }
52
53    impl PartialEq for SyntaxContextData {
54        fn eq(&self, other: &Self) -> bool {
55            self.outer_expn == other.outer_expn
56                && self.outer_transparency == other.outer_transparency
57                && self.edition == other.edition
58                && self.parent == other.parent
59        }
60    }
61
62    impl std::hash::Hash for SyntaxContextData {
63        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64            self.outer_expn.hash(state);
65            self.outer_transparency.hash(state);
66            self.edition.hash(state);
67            self.parent.hash(state);
68        }
69    }
70    /// Key to use during hash lookups. Each field is some type that implements `Lookup<T>`
71    /// for the owned type. This permits interning with an `&str` when a `String` is required and so forth.
72    #[derive(Hash)]
73    struct StructKey<'db, T0, T1, T2, T3>(T0, T1, T2, T3, std::marker::PhantomData<&'db ()>);
74
75    impl<'db, T0, T1, T2, T3> zalsa_::interned::HashEqLike<StructKey<'db, T0, T1, T2, T3>>
76        for SyntaxContextData
77    where
78        Option<MacroCallId>: zalsa_::interned::HashEqLike<T0>,
79        Transparency: zalsa_::interned::HashEqLike<T1>,
80        Edition: zalsa_::interned::HashEqLike<T2>,
81        SyntaxContext: zalsa_::interned::HashEqLike<T3>,
82    {
83        fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
84            zalsa_::interned::HashEqLike::<T0>::hash(&self.outer_expn, &mut *h);
85            zalsa_::interned::HashEqLike::<T1>::hash(&self.outer_transparency, &mut *h);
86            zalsa_::interned::HashEqLike::<T2>::hash(&self.edition, &mut *h);
87            zalsa_::interned::HashEqLike::<T3>::hash(&self.parent, &mut *h);
88        }
89        fn eq(&self, data: &StructKey<'db, T0, T1, T2, T3>) -> bool {
90            zalsa_::interned::HashEqLike::<T0>::eq(&self.outer_expn, &data.0)
91                && zalsa_::interned::HashEqLike::<T1>::eq(&self.outer_transparency, &data.1)
92                && zalsa_::interned::HashEqLike::<T2>::eq(&self.edition, &data.2)
93                && zalsa_::interned::HashEqLike::<T3>::eq(&self.parent, &data.3)
94        }
95    }
96    impl zalsa_struct_::Configuration for SyntaxContext {
97        const LOCATION: salsa::plumbing::Location =
98            salsa::plumbing::Location { file: file!(), line: line!() };
99        const DEBUG_NAME: &'static str = "SyntaxContextData";
100        const REVISIONS: std::num::NonZeroUsize = std::num::NonZeroUsize::MAX;
101        type Fields<'a> = SyntaxContextData;
102        type Struct<'a> = SyntaxContext;
103    }
104    impl SyntaxContext {
105        pub fn ingredient<Db>(db: &Db) -> &zalsa_struct_::IngredientImpl<Self>
106        where
107            Db: ?Sized + zalsa_::Database,
108        {
109            static CACHE: zalsa_::IngredientCache<zalsa_struct_::IngredientImpl<SyntaxContext>> =
110                zalsa_::IngredientCache::new();
111            CACHE.get_or_create(db.zalsa(), || {
112                db.zalsa()
113                    .lookup_jar_by_type::<zalsa_struct_::JarImpl<SyntaxContext>>()
114                    .get_or_create()
115            })
116        }
117    }
118    impl zalsa_::AsId for SyntaxContext {
119        fn as_id(&self) -> salsa::Id {
120            self.as_salsa_id().expect("`SyntaxContext::as_id()` called on a root `SyntaxContext`")
121        }
122    }
123    impl zalsa_::FromId for SyntaxContext {
124        fn from_id(id: salsa::Id) -> Self {
125            Self::from_salsa_id(id)
126        }
127    }
128    unsafe impl Send for SyntaxContext {}
129
130    unsafe impl Sync for SyntaxContext {}
131
132    impl zalsa_::SalsaStructInDb for SyntaxContext {
133        type MemoIngredientMap = salsa::plumbing::MemoIngredientSingletonIndex;
134
135        fn lookup_or_create_ingredient_index(
136            zalsa: &salsa::plumbing::Zalsa,
137        ) -> salsa::plumbing::IngredientIndices {
138            zalsa
139                .lookup_jar_by_type::<zalsa_struct_::JarImpl<SyntaxContext>>()
140                .get_or_create()
141                .into()
142        }
143
144        #[inline]
145        fn cast(id: salsa::Id, type_id: std::any::TypeId) -> Option<Self> {
146            if type_id == std::any::TypeId::of::<SyntaxContext>() {
147                Some(<Self as salsa::plumbing::FromId>::from_id(id))
148            } else {
149                None
150            }
151        }
152    }
153
154    unsafe impl salsa::plumbing::Update for SyntaxContext {
155        unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
156            if unsafe { *old_pointer } != new_value {
157                unsafe { *old_pointer = new_value };
158                true
159            } else {
160                false
161            }
162        }
163    }
164    impl<'db> SyntaxContext {
165        pub fn new<
166            Db,
167            T0: zalsa_::interned::Lookup<Option<MacroCallId>> + std::hash::Hash,
168            T1: zalsa_::interned::Lookup<Transparency> + std::hash::Hash,
169            T2: zalsa_::interned::Lookup<Edition> + std::hash::Hash,
170            T3: zalsa_::interned::Lookup<SyntaxContext> + std::hash::Hash,
171        >(
172            db: &'db Db,
173            outer_expn: T0,
174            outer_transparency: T1,
175            edition: T2,
176            parent: T3,
177            opaque: impl FnOnce(SyntaxContext) -> SyntaxContext,
178            opaque_and_semitransparent: impl FnOnce(SyntaxContext) -> SyntaxContext,
179        ) -> Self
180        where
181            Db: ?Sized + salsa::Database,
182            Option<MacroCallId>: zalsa_::interned::HashEqLike<T0>,
183            Transparency: zalsa_::interned::HashEqLike<T1>,
184            Edition: zalsa_::interned::HashEqLike<T2>,
185            SyntaxContext: zalsa_::interned::HashEqLike<T3>,
186        {
187            SyntaxContext::ingredient(db).intern(
188                db.as_dyn_database(),
189                StructKey::<'db>(
190                    outer_expn,
191                    outer_transparency,
192                    edition,
193                    parent,
194                    std::marker::PhantomData,
195                ),
196                |id, data| SyntaxContextData {
197                    outer_expn: zalsa_::interned::Lookup::into_owned(data.0),
198                    outer_transparency: zalsa_::interned::Lookup::into_owned(data.1),
199                    edition: zalsa_::interned::Lookup::into_owned(data.2),
200                    parent: zalsa_::interned::Lookup::into_owned(data.3),
201                    opaque: opaque(zalsa_::FromId::from_id(id)),
202                    opaque_and_semitransparent: opaque_and_semitransparent(
203                        zalsa_::FromId::from_id(id),
204                    ),
205                },
206            )
207        }
208
209        /// Invariant: Only [`SyntaxContext::ROOT`] has a [`None`] outer expansion.
210        // FIXME: The None case needs to encode the context crate id. We can encode that as the MSB of
211        // MacroCallId is reserved anyways so we can do bit tagging here just fine.
212        // The bigger issue is that this will cause interning to now create completely separate chains
213        // per crate. Though that is likely not a problem as `MacroCallId`s are already crate calling dependent.
214        pub fn outer_expn<Db>(self, db: &'db Db) -> Option<MacroCallId>
215        where
216            Db: ?Sized + zalsa_::Database,
217        {
218            let id = self.as_salsa_id()?;
219            let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
220            fields.outer_expn
221        }
222
223        pub fn outer_transparency<Db>(self, db: &'db Db) -> Transparency
224        where
225            Db: ?Sized + zalsa_::Database,
226        {
227            let Some(id) = self.as_salsa_id() else { return Transparency::Opaque };
228            let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
229            fields.outer_transparency
230        }
231
232        pub fn edition<Db>(self, db: &'db Db) -> Edition
233        where
234            Db: ?Sized + zalsa_::Database,
235        {
236            match self.as_salsa_id() {
237                Some(id) => {
238                    let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
239                    fields.edition
240                }
241                None => Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()),
242            }
243        }
244
245        pub fn parent<Db>(self, db: &'db Db) -> SyntaxContext
246        where
247            Db: ?Sized + zalsa_::Database,
248        {
249            match self.as_salsa_id() {
250                Some(id) => {
251                    let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
252                    fields.parent
253                }
254                None => self,
255            }
256        }
257
258        /// This context, but with all transparent and semi-transparent expansions filtered away.
259        pub fn opaque<Db>(self, db: &'db Db) -> SyntaxContext
260        where
261            Db: ?Sized + zalsa_::Database,
262        {
263            match self.as_salsa_id() {
264                Some(id) => {
265                    let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
266                    fields.opaque
267                }
268                None => self,
269            }
270        }
271
272        /// This context, but with all transparent expansions filtered away.
273        pub fn opaque_and_semitransparent<Db>(self, db: &'db Db) -> SyntaxContext
274        where
275            Db: ?Sized + zalsa_::Database,
276        {
277            match self.as_salsa_id() {
278                Some(id) => {
279                    let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
280                    fields.opaque_and_semitransparent
281                }
282                None => self,
283            }
284        }
285    }
286};
287
288impl SyntaxContext {
289    #[inline]
290    pub fn is_root(self) -> bool {
291        (SyntaxContext::MAX_ID - Edition::LATEST as u32) <= self.into_u32()
292            && self.into_u32() <= (SyntaxContext::MAX_ID - Edition::Edition2015 as u32)
293    }
294
295    #[inline]
296    pub fn remove_root_edition(&mut self) {
297        if self.is_root() {
298            *self = Self::root(Edition::Edition2015);
299        }
300    }
301
302    /// The root context, which is the parent of all other contexts. All [`FileId`]s have this context.
303    #[inline]
304    pub const fn root(edition: Edition) -> Self {
305        let edition = edition as u32;
306        // SAFETY: Roots are valid `SyntaxContext`s
307        unsafe { SyntaxContext::from_u32(SyntaxContext::MAX_ID - edition) }
308    }
309}
310
311#[cfg(feature = "salsa")]
312impl<'db> SyntaxContext {
313    const MAX_ID: u32 = salsa::Id::MAX_U32 - 1;
314
315    #[inline]
316    pub const fn into_u32(self) -> u32 {
317        self.0
318    }
319
320    /// # Safety
321    ///
322    /// The ID must be a valid `SyntaxContext`.
323    #[inline]
324    pub const unsafe fn from_u32(u32: u32) -> Self {
325        // INVARIANT: Our precondition.
326        Self(u32, std::marker::PhantomData)
327    }
328
329    #[inline]
330    fn as_salsa_id(self) -> Option<salsa::Id> {
331        if self.is_root() {
332            None
333        } else {
334            // SAFETY: By our invariant, this is either a root (which we verified it's not) or a valid `salsa::Id`.
335            unsafe { Some(salsa::Id::from_index(self.0)) }
336        }
337    }
338
339    #[inline]
340    fn from_salsa_id(id: salsa::Id) -> Self {
341        // SAFETY: This comes from a Salsa ID.
342        unsafe { Self::from_u32(id.index()) }
343    }
344
345    #[inline]
346    pub fn outer_mark(
347        self,
348        db: &'db dyn salsa::Database,
349    ) -> (Option<crate::MacroCallId>, Transparency) {
350        (self.outer_expn(db), self.outer_transparency(db))
351    }
352
353    #[inline]
354    pub fn normalize_to_macros_2_0(self, db: &'db dyn salsa::Database) -> SyntaxContext {
355        self.opaque(db)
356    }
357
358    #[inline]
359    pub fn normalize_to_macro_rules(self, db: &'db dyn salsa::Database) -> SyntaxContext {
360        self.opaque_and_semitransparent(db)
361    }
362
363    pub fn is_opaque(self, db: &'db dyn salsa::Database) -> bool {
364        !self.is_root() && self.outer_transparency(db).is_opaque()
365    }
366
367    pub fn remove_mark(
368        &mut self,
369        db: &'db dyn salsa::Database,
370    ) -> (Option<crate::MacroCallId>, Transparency) {
371        let data = *self;
372        *self = data.parent(db);
373        (data.outer_expn(db), data.outer_transparency(db))
374    }
375
376    pub fn marks(
377        self,
378        db: &'db dyn salsa::Database,
379    ) -> impl Iterator<Item = (crate::MacroCallId, Transparency)> {
380        let mut marks = self.marks_rev(db).collect::<Vec<_>>();
381        marks.reverse();
382        marks.into_iter()
383    }
384
385    pub fn marks_rev(
386        self,
387        db: &'db dyn salsa::Database,
388    ) -> impl Iterator<Item = (crate::MacroCallId, Transparency)> {
389        std::iter::successors(Some(self), move |&mark| Some(mark.parent(db)))
390            .take_while(|&it| !it.is_root())
391            .map(|ctx| {
392                let mark = ctx.outer_mark(db);
393                // We stop before taking the root expansion, as such we cannot encounter a `None` outer
394                // expansion, as only the ROOT has it.
395                (mark.0.unwrap(), mark.1)
396            })
397    }
398}
399#[cfg(not(feature = "salsa"))]
400#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
401pub struct SyntaxContext(u32);
402
403#[allow(dead_code)]
404const SALSA_MAX_ID_MIRROR: u32 = u32::MAX - 0xFF;
405#[cfg(feature = "salsa")]
406const _: () = assert!(salsa::Id::MAX_U32 == SALSA_MAX_ID_MIRROR);
407
408#[cfg(not(feature = "salsa"))]
409impl SyntaxContext {
410    const MAX_ID: u32 = SALSA_MAX_ID_MIRROR - 1;
411
412    pub const fn into_u32(self) -> u32 {
413        self.0
414    }
415
416    /// # Safety
417    ///
418    /// None. This is always safe to call without the `salsa` feature.
419    pub const unsafe fn from_u32(u32: u32) -> Self {
420        Self(u32)
421    }
422}
423
424/// A property of a macro expansion that determines how identifiers
425/// produced by that expansion are resolved.
426#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
427pub enum Transparency {
428    /// Identifier produced by a transparent expansion is always resolved at call-site.
429    /// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this.
430    Transparent,
431    /// Identifier produced by a semi-transparent expansion may be resolved
432    /// either at call-site or at definition-site.
433    /// If it's a local variable, label or `$crate` then it's resolved at def-site.
434    /// Otherwise it's resolved at call-site.
435    /// `macro_rules` macros behave like this, built-in macros currently behave like this too,
436    /// but that's an implementation detail.
437    SemiTransparent,
438    /// Identifier produced by an opaque expansion is always resolved at definition-site.
439    /// Def-site spans in procedural macros, identifiers from `macro` by default use this.
440    Opaque,
441}
442
443impl Transparency {
444    /// Returns `true` if the transparency is [`Opaque`].
445    ///
446    /// [`Opaque`]: Transparency::Opaque
447    pub fn is_opaque(&self) -> bool {
448        matches!(self, Self::Opaque)
449    }
450}
451
452impl fmt::Display for SyntaxContext {
453    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454        if self.is_root() {
455            write!(f, "ROOT{}", Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()).number())
456        } else {
457            write!(f, "{}", self.into_u32())
458        }
459    }
460}
461
462impl std::fmt::Debug for SyntaxContext {
463    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464        if f.alternate() {
465            fmt::Display::fmt(self, f)
466        } else {
467            f.debug_tuple("SyntaxContext").field(&self.0).finish()
468        }
469    }
470}