Skip to main content

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