swc_common/syntax_pos/
hygiene.rs

1// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! Machinery for hygienic macros, inspired by the `MTWT[1]` paper.
12//!
13//! `[1]` Matthew Flatt, Ryan Culpepper, David Darais, and Robert Bruce Findler.
14//! 2012. *Macros that work together: Compile-time bindings, partial expansion,
15//! and definition contexts*. J. Funct. Program. 22, 2 (March 2012), 181-216.
16//! DOI=10.1017/S0956796812000093 <https://doi.org/10.1017/S0956796812000093>
17
18#[allow(unused)]
19use std::{
20    collections::{HashMap, HashSet},
21    fmt,
22};
23
24use rustc_hash::FxHashMap;
25use serde::{Deserialize, Serialize};
26
27use super::GLOBALS;
28use crate::EqIgnoreSpan;
29
30/// A SyntaxContext represents a chain of macro expansions (represented by
31/// marks).
32#[derive(Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash, Serialize, Deserialize)]
33#[serde(transparent)]
34#[cfg_attr(
35    any(feature = "rkyv-impl"),
36    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
37)]
38#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
39#[cfg_attr(feature = "rkyv-impl", repr(C))]
40#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
41pub struct SyntaxContext(#[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))] u32);
42
43#[cfg(feature = "arbitrary")]
44#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
45impl<'a> arbitrary::Arbitrary<'a> for SyntaxContext {
46    fn arbitrary(_: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
47        Ok(SyntaxContext::empty())
48    }
49}
50
51better_scoped_tls::scoped_tls!(static EQ_IGNORE_SPAN_IGNORE_CTXT: ());
52
53impl EqIgnoreSpan for SyntaxContext {
54    fn eq_ignore_span(&self, other: &Self) -> bool {
55        self == other || EQ_IGNORE_SPAN_IGNORE_CTXT.is_set()
56    }
57}
58
59impl SyntaxContext {
60    /// In `op`, [EqIgnoreSpan] of [Ident] will ignore the syntax context.
61    pub fn within_ignored_ctxt<F, Ret>(op: F) -> Ret
62    where
63        F: FnOnce() -> Ret,
64    {
65        EQ_IGNORE_SPAN_IGNORE_CTXT.set(&(), op)
66    }
67}
68
69#[allow(unused)]
70#[derive(Copy, Clone, Debug)]
71struct SyntaxContextData {
72    outer_mark: Mark,
73    prev_ctxt: SyntaxContext,
74    opaque: SyntaxContext,
75}
76
77/// A mark is a unique id associated with a macro expansion.
78#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
79pub struct Mark(u32);
80
81#[allow(unused)]
82#[derive(Clone, Debug)]
83pub(crate) struct MarkData {
84    pub(crate) parent: Mark,
85}
86
87#[cfg_attr(
88    any(feature = "rkyv-impl"),
89    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
90)]
91#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
92#[cfg_attr(feature = "rkyv-impl", repr(C))]
93pub struct MutableMarkContext(pub u32, pub u32, pub u32);
94
95// List of proxy calls injected by the host in the plugin's runtime context.
96// When related calls being executed inside of the plugin, it'll call these
97// proxies instead which'll call actual host fn.
98extern "C" {
99    // Instead of trying to copy-serialize `Mark`, this fn directly consume
100    // inner raw value as well as fn and let each context constructs struct
101    // on their side.
102    fn __mark_fresh_proxy(mark: u32) -> u32;
103    fn __mark_parent_proxy(self_mark: u32) -> u32;
104    fn __syntax_context_apply_mark_proxy(self_syntax_context: u32, mark: u32) -> u32;
105    fn __syntax_context_outer_proxy(self_mark: u32) -> u32;
106
107    // These are proxy fn uses serializable context to pass forward mutated param
108    // with return value back to the guest.
109    fn __mark_is_descendant_of_proxy(self_mark: u32, ancestor: u32, allocated_ptr: i32);
110    fn __mark_least_ancestor(a: u32, b: u32, allocated_ptr: i32);
111    fn __syntax_context_remove_mark_proxy(self_mark: u32, allocated_ptr: i32);
112}
113
114impl Mark {
115    /// Shortcut for `Mark::fresh(Mark::root())`
116    #[track_caller]
117    #[allow(clippy::new_without_default)]
118    pub fn new() -> Self {
119        Mark::fresh(Mark::root())
120    }
121
122    #[track_caller]
123    pub fn fresh(parent: Mark) -> Self {
124        // Note: msvc tries to link against proxied fn for normal build,
125        // have to limit build target to wasm only to avoid it.
126        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
127        return Mark(unsafe { __mark_fresh_proxy(parent.as_u32()) });
128
129        // https://github.com/swc-project/swc/pull/3492#discussion_r802224857
130        // We loosen conditions here for the cases like running plugin's test without
131        // targeting wasm32-*.
132        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
133        return with_marks(|marks| {
134            marks.push(MarkData { parent });
135            Mark(marks.len() as u32 - 1)
136        });
137    }
138
139    /// The mark of the theoretical expansion that generates freshly parsed,
140    /// unexpanded AST.
141    #[inline]
142    pub const fn root() -> Self {
143        Mark(0)
144    }
145
146    #[inline]
147    pub fn as_u32(self) -> u32 {
148        self.0
149    }
150
151    #[inline]
152    pub fn from_u32(raw: u32) -> Mark {
153        Mark(raw)
154    }
155
156    #[inline]
157    pub fn parent(self) -> Mark {
158        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
159        return Mark(unsafe { __mark_parent_proxy(self.0) });
160
161        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
162        return with_marks(|marks| marks[self.0 as usize].parent);
163    }
164
165    #[allow(unused_assignments)]
166    #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
167    pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
168        // This code path executed inside of the guest memory context.
169        // In here, preallocate memory for the context.
170
171        use crate::plugin::serialized::VersionedSerializable;
172        let serialized = crate::plugin::serialized::PluginSerializedBytes::try_serialize(
173            &VersionedSerializable::new(MutableMarkContext(0, 0, 0)),
174        )
175        .expect("Should be serializable");
176        let (ptr, len) = serialized.as_ptr();
177
178        // Calling host proxy fn. Inside of host proxy, host will
179        // write the result into allocated context in the guest memory space.
180        unsafe {
181            __mark_is_descendant_of_proxy(self.0, ancestor.0, ptr as _);
182        }
183
184        // Deserialize result, assign / return values as needed.
185        let context: MutableMarkContext =
186            crate::plugin::serialized::PluginSerializedBytes::from_raw_ptr(
187                ptr,
188                len.try_into().expect("Should able to convert ptr length"),
189            )
190            .deserialize()
191            .expect("Should able to deserialize")
192            .into_inner();
193
194        self = Mark::from_u32(context.0);
195
196        return context.2 != 0;
197    }
198
199    #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
200    pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
201        with_marks(|marks| {
202            while self != ancestor {
203                if self == Mark::root() {
204                    return false;
205                }
206                self = marks[self.0 as usize].parent;
207            }
208            true
209        })
210    }
211
212    #[allow(unused_mut, unused_assignments)]
213    #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
214    pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
215        use crate::plugin::serialized::VersionedSerializable;
216
217        let serialized = crate::plugin::serialized::PluginSerializedBytes::try_serialize(
218            &VersionedSerializable::new(MutableMarkContext(0, 0, 0)),
219        )
220        .expect("Should be serializable");
221        let (ptr, len) = serialized.as_ptr();
222
223        unsafe {
224            __mark_least_ancestor(a.0, b.0, ptr as _);
225        }
226
227        let context: MutableMarkContext =
228            crate::plugin::serialized::PluginSerializedBytes::from_raw_ptr(
229                ptr,
230                len.try_into().expect("Should able to convert ptr length"),
231            )
232            .deserialize()
233            .expect("Should able to deserialize")
234            .into_inner();
235        a = Mark::from_u32(context.0);
236        b = Mark::from_u32(context.1);
237
238        return Mark(context.2);
239    }
240
241    /// Computes a mark such that both input marks are descendants of (or equal
242    /// to) the returned mark. That is, the following holds:
243    ///
244    /// ```rust,ignore
245    /// let la = least_ancestor(a, b);
246    /// assert!(a.is_descendant_of(la))
247    /// assert!(b.is_descendant_of(la))
248    /// ```
249    #[allow(unused_mut)]
250    #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
251    pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
252        with_marks(|marks| {
253            // Compute the path from a to the root
254            let mut a_path = HashSet::<Mark>::default();
255            while a != Mark::root() {
256                a_path.insert(a);
257                a = marks[a.0 as usize].parent;
258            }
259
260            // While the path from b to the root hasn't intersected, move up the tree
261            while !a_path.contains(&b) {
262                b = marks[b.0 as usize].parent;
263            }
264
265            b
266        })
267    }
268}
269
270#[allow(unused)]
271#[derive(Debug)]
272pub(crate) struct HygieneData {
273    syntax_contexts: Vec<SyntaxContextData>,
274    markings: FxHashMap<(SyntaxContext, Mark), SyntaxContext>,
275}
276
277impl Default for HygieneData {
278    fn default() -> Self {
279        Self::new()
280    }
281}
282
283impl HygieneData {
284    pub(crate) fn new() -> Self {
285        HygieneData {
286            syntax_contexts: vec![SyntaxContextData {
287                outer_mark: Mark::root(),
288                prev_ctxt: SyntaxContext(0),
289                opaque: SyntaxContext(0),
290            }],
291            markings: HashMap::default(),
292        }
293    }
294
295    fn with<T, F: FnOnce(&mut HygieneData) -> T>(f: F) -> T {
296        GLOBALS.with(|globals| {
297            #[cfg(feature = "parking_lot")]
298            return f(&mut globals.hygiene_data.lock());
299
300            #[cfg(not(feature = "parking_lot"))]
301            return f(&mut globals.hygiene_data.lock().unwrap());
302        })
303    }
304}
305
306#[track_caller]
307#[allow(unused)]
308pub(crate) fn with_marks<T, F: FnOnce(&mut Vec<MarkData>) -> T>(f: F) -> T {
309    GLOBALS.with(|globals| {
310        #[cfg(feature = "parking_lot")]
311        return f(&mut globals.marks.lock());
312
313        #[cfg(not(feature = "parking_lot"))]
314        return f(&mut globals.marks.lock().unwrap());
315    })
316}
317
318// pub fn clear_markings() {
319//     HygieneData::with(|data| data.markings = HashMap::default());
320// }
321
322impl SyntaxContext {
323    pub const fn empty() -> Self {
324        SyntaxContext(0)
325    }
326
327    /// Returns `true` if `self` is marked with `mark`.
328    ///
329    /// Panics if `mark` is not a valid mark.
330    pub fn has_mark(self, mark: Mark) -> bool {
331        debug_assert_ne!(
332            mark,
333            Mark::root(),
334            "Cannot check if a span contains a `ROOT` mark"
335        );
336
337        let mut ctxt = self;
338
339        loop {
340            if ctxt == SyntaxContext::empty() {
341                return false;
342            }
343
344            let m = ctxt.remove_mark();
345            if m == mark {
346                return true;
347            }
348            if m == Mark::root() {
349                return false;
350            }
351        }
352    }
353
354    #[inline]
355    pub fn as_u32(self) -> u32 {
356        self.0
357    }
358
359    #[inline]
360    pub fn from_u32(raw: u32) -> SyntaxContext {
361        SyntaxContext(raw)
362    }
363
364    /// Extend a syntax context with a given mark and default transparency for
365    /// that mark.
366    pub fn apply_mark(self, mark: Mark) -> SyntaxContext {
367        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
368        return unsafe { SyntaxContext(__syntax_context_apply_mark_proxy(self.0, mark.0)) };
369
370        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
371        {
372            assert_ne!(mark, Mark::root());
373            self.apply_mark_internal(mark)
374        }
375    }
376
377    #[allow(unused)]
378    fn apply_mark_internal(self, mark: Mark) -> SyntaxContext {
379        HygieneData::with(|data| {
380            let syntax_contexts = &mut data.syntax_contexts;
381            let mut opaque = syntax_contexts[self.0 as usize].opaque;
382
383            let prev_ctxt = opaque;
384            *data.markings.entry((prev_ctxt, mark)).or_insert_with(|| {
385                let new_opaque = SyntaxContext(syntax_contexts.len() as u32);
386                syntax_contexts.push(SyntaxContextData {
387                    outer_mark: mark,
388                    prev_ctxt,
389                    opaque: new_opaque,
390                });
391                new_opaque
392            })
393        })
394    }
395
396    #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
397    pub fn remove_mark(&mut self) -> Mark {
398        use crate::plugin::serialized::VersionedSerializable;
399
400        let context = VersionedSerializable::new(MutableMarkContext(0, 0, 0));
401        let serialized = crate::plugin::serialized::PluginSerializedBytes::try_serialize(&context)
402            .expect("Should be serializable");
403        let (ptr, len) = serialized.as_ptr();
404
405        unsafe {
406            __syntax_context_remove_mark_proxy(self.0, ptr as _);
407        }
408
409        let context: MutableMarkContext =
410            crate::plugin::serialized::PluginSerializedBytes::from_raw_ptr(
411                ptr,
412                len.try_into().expect("Should able to convert ptr length"),
413            )
414            .deserialize()
415            .expect("Should able to deserialize")
416            .into_inner();
417
418        *self = SyntaxContext(context.0);
419
420        return Mark::from_u32(context.2);
421    }
422
423    /// Pulls a single mark off of the syntax context. This effectively moves
424    /// the context up one macro definition level. That is, if we have a
425    /// nested macro definition as follows:
426    ///
427    /// ```rust,ignore
428    /// macro_rules! f {
429    ///    macro_rules! g {
430    ///        ...
431    ///    }
432    /// }
433    /// ```
434    ///
435    /// and we have a SyntaxContext that is referring to something declared by
436    /// an invocation of g (call it g1), calling remove_mark will result in
437    /// the SyntaxContext for the invocation of f that created g1.
438    /// Returns the mark that was removed.
439    #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
440    pub fn remove_mark(&mut self) -> Mark {
441        HygieneData::with(|data| {
442            let outer_mark = data.syntax_contexts[self.0 as usize].outer_mark;
443            *self = data.syntax_contexts[self.0 as usize].prev_ctxt;
444            outer_mark
445        })
446    }
447
448    /// Adjust this context for resolution in a scope created by the given
449    /// expansion. For example, consider the following three resolutions of
450    /// `f`:
451    ///
452    /// ```rust,ignore
453    /// mod foo {
454    ///     pub fn f() {}
455    /// } // `f`'s `SyntaxContext` is empty.
456    /// m!(f);
457    /// macro m($f:ident) {
458    ///     mod bar {
459    ///         pub fn f() {} // `f`'s `SyntaxContext` has a single `Mark` from `m`.
460    ///         pub fn $f() {} // `$f`'s `SyntaxContext` is empty.
461    ///     }
462    ///     foo::f(); // `f`'s `SyntaxContext` has a single `Mark` from `m`
463    ///               //^ Since `mod foo` is outside this expansion, `adjust` removes the mark from `f`,
464    ///               //| and it resolves to `::foo::f`.
465    ///     bar::f(); // `f`'s `SyntaxContext` has a single `Mark` from `m`
466    ///               //^ Since `mod bar` not outside this expansion, `adjust` does not change `f`,
467    ///               //| and it resolves to `::bar::f`.
468    ///     bar::$f(); // `f`'s `SyntaxContext` is empty.
469    ///                //^ Since `mod bar` is not outside this expansion, `adjust` does not change `$f`,
470    ///                //| and it resolves to `::bar::$f`.
471    /// }
472    /// ```
473    /// This returns the expansion whose definition scope we use to privacy
474    /// check the resolution, or `None` if we privacy check as usual (i.e.
475    /// not w.r.t. a macro definition scope).
476    pub fn adjust(&mut self, expansion: Mark) -> Option<Mark> {
477        let mut scope = None;
478        while !expansion.is_descendant_of(self.outer()) {
479            scope = Some(self.remove_mark());
480        }
481        scope
482    }
483
484    /// Adjust this context for resolution in a scope created by the given
485    /// expansion via a glob import with the given `SyntaxContext`.
486    /// For example:
487    ///
488    /// ```rust,ignore
489    /// m!(f);
490    /// macro m($i:ident) {
491    ///     mod foo {
492    ///         pub fn f() {} // `f`'s `SyntaxContext` has a single `Mark` from `m`.
493    ///         pub fn $i() {} // `$i`'s `SyntaxContext` is empty.
494    ///     }
495    ///     n(f);
496    ///     macro n($j:ident) {
497    ///         use foo::*;
498    ///         f(); // `f`'s `SyntaxContext` has a mark from `m` and a mark from `n`
499    ///              //^ `glob_adjust` removes the mark from `n`, so this resolves to `foo::f`.
500    ///         $i(); // `$i`'s `SyntaxContext` has a mark from `n`
501    ///               //^ `glob_adjust` removes the mark from `n`, so this resolves to `foo::$i`.
502    ///         $j(); // `$j`'s `SyntaxContext` has a mark from `m`
503    ///               //^ This cannot be glob-adjusted, so this is a resolution error.
504    ///     }
505    /// }
506    /// ```
507    /// This returns `None` if the context cannot be glob-adjusted.
508    /// Otherwise, it returns the scope to use when privacy checking (see
509    /// `adjust` for details).
510    pub fn glob_adjust(
511        &mut self,
512        expansion: Mark,
513        mut glob_ctxt: SyntaxContext,
514    ) -> Option<Option<Mark>> {
515        let mut scope = None;
516        while !expansion.is_descendant_of(glob_ctxt.outer()) {
517            scope = Some(glob_ctxt.remove_mark());
518            if self.remove_mark() != scope.unwrap() {
519                return None;
520            }
521        }
522        if self.adjust(expansion).is_some() {
523            return None;
524        }
525        Some(scope)
526    }
527
528    /// Undo `glob_adjust` if possible:
529    ///
530    /// ```rust,ignore
531    /// if let Some(privacy_checking_scope) = self.reverse_glob_adjust(expansion, glob_ctxt) {
532    ///     assert!(self.glob_adjust(expansion, glob_ctxt) == Some(privacy_checking_scope));
533    /// }
534    /// ```
535    pub fn reverse_glob_adjust(
536        &mut self,
537        expansion: Mark,
538        mut glob_ctxt: SyntaxContext,
539    ) -> Option<Option<Mark>> {
540        if self.adjust(expansion).is_some() {
541            return None;
542        }
543
544        let mut marks = Vec::new();
545        while !expansion.is_descendant_of(glob_ctxt.outer()) {
546            marks.push(glob_ctxt.remove_mark());
547        }
548
549        let scope = marks.last().cloned();
550        while let Some(mark) = marks.pop() {
551            *self = self.apply_mark(mark);
552        }
553        Some(scope)
554    }
555
556    #[inline]
557    pub fn outer(self) -> Mark {
558        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
559        return unsafe { Mark(__syntax_context_outer_proxy(self.0)) };
560
561        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
562        HygieneData::with(|data| data.syntax_contexts[self.0 as usize].outer_mark)
563    }
564}
565
566impl fmt::Debug for SyntaxContext {
567    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
568        write!(f, "#{}", self.0)
569    }
570}
571
572impl Default for Mark {
573    #[track_caller]
574    fn default() -> Self {
575        Mark::new()
576    }
577}