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}
75
76/// A mark is a unique id associated with a macro expansion.
77#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
78pub struct Mark(u32);
79
80#[allow(unused)]
81#[derive(Clone, Copy, Debug)]
82pub(crate) struct MarkData {
83    pub(crate) parent: Mark,
84}
85
86#[cfg_attr(
87    any(feature = "rkyv-impl"),
88    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
89)]
90#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
91#[cfg_attr(feature = "rkyv-impl", repr(C))]
92pub struct MutableMarkContext(pub u32, pub u32, pub u32);
93
94// List of proxy calls injected by the host in the plugin's runtime context.
95// When related calls being executed inside of the plugin, it'll call these
96// proxies instead which'll call actual host fn.
97extern "C" {
98    // Instead of trying to copy-serialize `Mark`, this fn directly consume
99    // inner raw value as well as fn and let each context constructs struct
100    // on their side.
101    fn __mark_fresh_proxy(mark: u32) -> u32;
102    fn __mark_parent_proxy(self_mark: u32) -> u32;
103    fn __syntax_context_apply_mark_proxy(self_syntax_context: u32, mark: u32) -> u32;
104    fn __syntax_context_outer_proxy(self_mark: u32) -> u32;
105
106    // These are proxy fn uses serializable context to pass forward mutated param
107    // with return value back to the guest.
108    fn __mark_is_descendant_of_proxy(self_mark: u32, ancestor: u32, allocated_ptr: i32);
109    fn __mark_least_ancestor(a: u32, b: u32, allocated_ptr: i32);
110    fn __syntax_context_remove_mark_proxy(self_mark: u32, allocated_ptr: i32);
111}
112
113impl Mark {
114    /// Shortcut for `Mark::fresh(Mark::root())`
115    #[track_caller]
116    #[allow(clippy::new_without_default)]
117    pub fn new() -> Self {
118        Mark::fresh(Mark::root())
119    }
120
121    #[track_caller]
122    pub fn fresh(parent: Mark) -> Self {
123        // Note: msvc tries to link against proxied fn for normal build,
124        // have to limit build target to wasm only to avoid it.
125        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
126        return Mark(unsafe { __mark_fresh_proxy(parent.as_u32()) });
127
128        // https://github.com/swc-project/swc/pull/3492#discussion_r802224857
129        // We loosen conditions here for the cases like running plugin's test without
130        // targeting wasm32-*.
131        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
132        return with_marks(|marks| {
133            marks.push(MarkData { parent });
134            Mark(marks.len() as u32 - 1)
135        });
136    }
137
138    /// The mark of the theoretical expansion that generates freshly parsed,
139    /// unexpanded AST.
140    #[inline]
141    pub const fn root() -> Self {
142        Mark(0)
143    }
144
145    #[inline]
146    pub fn as_u32(self) -> u32 {
147        self.0
148    }
149
150    #[inline]
151    pub fn from_u32(raw: u32) -> Mark {
152        Mark(raw)
153    }
154
155    #[inline]
156    pub fn parent(self) -> Mark {
157        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
158        return Mark(unsafe { __mark_parent_proxy(self.0) });
159
160        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
161        return with_marks(|marks| marks[self.0 as usize].parent);
162    }
163
164    #[allow(unused_assignments)]
165    #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
166    pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
167        // This code path executed inside of the guest memory context.
168        // In here, preallocate memory for the context.
169
170        use crate::plugin::serialized::VersionedSerializable;
171        let serialized = crate::plugin::serialized::PluginSerializedBytes::try_serialize(
172            &VersionedSerializable::new(MutableMarkContext(0, 0, 0)),
173        )
174        .expect("Should be serializable");
175        let (ptr, len) = serialized.as_ptr();
176
177        // Calling host proxy fn. Inside of host proxy, host will
178        // write the result into allocated context in the guest memory space.
179        unsafe {
180            __mark_is_descendant_of_proxy(self.0, ancestor.0, ptr as _);
181        }
182
183        // Deserialize result, assign / return values as needed.
184        let context: MutableMarkContext =
185            crate::plugin::serialized::PluginSerializedBytes::from_raw_ptr(
186                ptr,
187                len.try_into().expect("Should able to convert ptr length"),
188            )
189            .deserialize()
190            .expect("Should able to deserialize")
191            .into_inner();
192
193        self = Mark::from_u32(context.0);
194
195        return context.2 != 0;
196    }
197
198    #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
199    pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
200        with_marks(|marks| {
201            while self != ancestor {
202                if self == Mark::root() {
203                    return false;
204                }
205                self = marks[self.0 as usize].parent;
206            }
207            true
208        })
209    }
210
211    #[allow(unused_mut, unused_assignments)]
212    #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
213    pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
214        use crate::plugin::serialized::VersionedSerializable;
215
216        let serialized = crate::plugin::serialized::PluginSerializedBytes::try_serialize(
217            &VersionedSerializable::new(MutableMarkContext(0, 0, 0)),
218        )
219        .expect("Should be serializable");
220        let (ptr, len) = serialized.as_ptr();
221
222        unsafe {
223            __mark_least_ancestor(a.0, b.0, ptr as _);
224        }
225
226        let context: MutableMarkContext =
227            crate::plugin::serialized::PluginSerializedBytes::from_raw_ptr(
228                ptr,
229                len.try_into().expect("Should able to convert ptr length"),
230            )
231            .deserialize()
232            .expect("Should able to deserialize")
233            .into_inner();
234        a = Mark::from_u32(context.0);
235        b = Mark::from_u32(context.1);
236
237        return Mark(context.2);
238    }
239
240    /// Computes a mark such that both input marks are descendants of (or equal
241    /// to) the returned mark. That is, the following holds:
242    ///
243    /// ```rust,ignore
244    /// let la = least_ancestor(a, b);
245    /// assert!(a.is_descendant_of(la))
246    /// assert!(b.is_descendant_of(la))
247    /// ```
248    #[allow(unused_mut)]
249    #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
250    pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
251        with_marks(|marks| {
252            // Compute the path from a to the root
253            let mut a_path = HashSet::<Mark>::default();
254            while a != Mark::root() {
255                a_path.insert(a);
256                a = marks[a.0 as usize].parent;
257            }
258
259            // While the path from b to the root hasn't intersected, move up the tree
260            while !a_path.contains(&b) {
261                b = marks[b.0 as usize].parent;
262            }
263
264            b
265        })
266    }
267}
268
269#[derive(Clone, Debug)]
270pub(crate) struct HygieneData {
271    syntax_contexts: Vec<SyntaxContextData>,
272    markings: FxHashMap<(SyntaxContext, Mark), SyntaxContext>,
273}
274
275impl Default for HygieneData {
276    fn default() -> Self {
277        Self::new()
278    }
279}
280
281impl HygieneData {
282    pub(crate) fn new() -> Self {
283        HygieneData {
284            syntax_contexts: vec![SyntaxContextData {
285                outer_mark: Mark::root(),
286                prev_ctxt: SyntaxContext(0),
287            }],
288            markings: HashMap::default(),
289        }
290    }
291
292    fn with<T, F: FnOnce(&mut HygieneData) -> T>(f: F) -> T {
293        GLOBALS.with(|globals| {
294            return f(&mut globals.hygiene_data.lock().unwrap());
295        })
296    }
297}
298
299#[track_caller]
300#[allow(unused)]
301pub(crate) fn with_marks<T, F: FnOnce(&mut Vec<MarkData>) -> T>(f: F) -> T {
302    GLOBALS.with(|globals| {
303        return f(&mut globals.marks.lock().unwrap());
304    })
305}
306
307// pub fn clear_markings() {
308//     HygieneData::with(|data| data.markings = HashMap::default());
309// }
310
311impl SyntaxContext {
312    pub const fn empty() -> Self {
313        SyntaxContext(0)
314    }
315
316    /// Returns `true` if `self` is marked with `mark`.
317    ///
318    /// Panics if `mark` is not a valid mark.
319    pub fn has_mark(self, mark: Mark) -> bool {
320        debug_assert_ne!(
321            mark,
322            Mark::root(),
323            "Cannot check if a span contains a `ROOT` mark"
324        );
325
326        let mut ctxt = self;
327
328        loop {
329            if ctxt == SyntaxContext::empty() {
330                return false;
331            }
332
333            let m = ctxt.remove_mark();
334            if m == mark {
335                return true;
336            }
337            if m == Mark::root() {
338                return false;
339            }
340        }
341    }
342
343    #[inline]
344    pub fn as_u32(self) -> u32 {
345        self.0
346    }
347
348    #[inline]
349    pub fn from_u32(raw: u32) -> SyntaxContext {
350        SyntaxContext(raw)
351    }
352
353    /// Extend a syntax context with a given mark and default transparency for
354    /// that mark.
355    pub fn apply_mark(self, mark: Mark) -> SyntaxContext {
356        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
357        return unsafe { SyntaxContext(__syntax_context_apply_mark_proxy(self.0, mark.0)) };
358
359        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
360        {
361            assert_ne!(mark, Mark::root());
362            self.apply_mark_internal(mark)
363        }
364    }
365
366    #[allow(unused)]
367    fn apply_mark_internal(self, mark: Mark) -> SyntaxContext {
368        HygieneData::with(|data| {
369            *data.markings.entry((self, mark)).or_insert_with(|| {
370                let syntax_contexts = &mut data.syntax_contexts;
371                let new_opaque = SyntaxContext(syntax_contexts.len() as u32);
372                syntax_contexts.push(SyntaxContextData {
373                    outer_mark: mark,
374                    prev_ctxt: self,
375                });
376                new_opaque
377            })
378        })
379    }
380
381    #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
382    pub fn remove_mark(&mut self) -> Mark {
383        use crate::plugin::serialized::VersionedSerializable;
384
385        let context = VersionedSerializable::new(MutableMarkContext(0, 0, 0));
386        let serialized = crate::plugin::serialized::PluginSerializedBytes::try_serialize(&context)
387            .expect("Should be serializable");
388        let (ptr, len) = serialized.as_ptr();
389
390        unsafe {
391            __syntax_context_remove_mark_proxy(self.0, ptr as _);
392        }
393
394        let context: MutableMarkContext =
395            crate::plugin::serialized::PluginSerializedBytes::from_raw_ptr(
396                ptr,
397                len.try_into().expect("Should able to convert ptr length"),
398            )
399            .deserialize()
400            .expect("Should able to deserialize")
401            .into_inner();
402
403        *self = SyntaxContext(context.0);
404
405        return Mark::from_u32(context.2);
406    }
407
408    /// Pulls a single mark off of the syntax context. This effectively moves
409    /// the context up one macro definition level. That is, if we have a
410    /// nested macro definition as follows:
411    ///
412    /// ```rust,ignore
413    /// macro_rules! f {
414    ///    macro_rules! g {
415    ///        ...
416    ///    }
417    /// }
418    /// ```
419    ///
420    /// and we have a SyntaxContext that is referring to something declared by
421    /// an invocation of g (call it g1), calling remove_mark will result in
422    /// the SyntaxContext for the invocation of f that created g1.
423    /// Returns the mark that was removed.
424    #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
425    pub fn remove_mark(&mut self) -> Mark {
426        HygieneData::with(|data| {
427            let outer_mark = data.syntax_contexts[self.0 as usize].outer_mark;
428            *self = data.syntax_contexts[self.0 as usize].prev_ctxt;
429            outer_mark
430        })
431    }
432
433    #[inline]
434    pub fn outer(self) -> Mark {
435        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
436        return unsafe { Mark(__syntax_context_outer_proxy(self.0)) };
437
438        #[cfg(not(all(feature = "__plugin_mode", target_arch = "wasm32")))]
439        HygieneData::with(|data| data.syntax_contexts[self.0 as usize].outer_mark)
440    }
441}
442
443impl fmt::Debug for SyntaxContext {
444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        write!(f, "#{}", self.0)
446    }
447}
448
449impl Default for Mark {
450    #[track_caller]
451    fn default() -> Self {
452        Mark::new()
453    }
454}