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