hir_expand/
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.
22// FIXME: Move this into the span crate? Not quite possible today as that depends on `MacroCallLoc`
23// which contains a bunch of unrelated things
24
25use std::convert::identity;
26
27use span::{Edition, MacroCallId, Span, SyntaxContext};
28
29use crate::db::ExpandDatabase;
30
31pub use span::Transparency;
32
33pub fn span_with_def_site_ctxt(
34    db: &dyn ExpandDatabase,
35    span: Span,
36    expn_id: MacroCallId,
37    edition: Edition,
38) -> Span {
39    span_with_ctxt_from_mark(db, span, expn_id, Transparency::Opaque, edition)
40}
41
42pub fn span_with_call_site_ctxt(
43    db: &dyn ExpandDatabase,
44    span: Span,
45    expn_id: MacroCallId,
46    edition: Edition,
47) -> Span {
48    span_with_ctxt_from_mark(db, span, expn_id, Transparency::Transparent, edition)
49}
50
51pub fn span_with_mixed_site_ctxt(
52    db: &dyn ExpandDatabase,
53    span: Span,
54    expn_id: MacroCallId,
55    edition: Edition,
56) -> Span {
57    span_with_ctxt_from_mark(db, span, expn_id, Transparency::SemiTransparent, edition)
58}
59
60fn span_with_ctxt_from_mark(
61    db: &dyn ExpandDatabase,
62    span: Span,
63    expn_id: MacroCallId,
64    transparency: Transparency,
65    edition: Edition,
66) -> Span {
67    Span {
68        ctx: apply_mark(db, SyntaxContext::root(edition), expn_id, transparency, edition),
69        ..span
70    }
71}
72
73pub(super) fn apply_mark(
74    db: &dyn ExpandDatabase,
75    ctxt: span::SyntaxContext,
76    call_id: span::MacroCallId,
77    transparency: Transparency,
78    edition: Edition,
79) -> SyntaxContext {
80    if transparency == Transparency::Opaque {
81        return apply_mark_internal(db, ctxt, call_id, transparency, edition);
82    }
83
84    let call_site_ctxt = db.lookup_intern_macro_call(call_id.into()).ctxt;
85    let mut call_site_ctxt = if transparency == Transparency::SemiTransparent {
86        call_site_ctxt.normalize_to_macros_2_0(db)
87    } else {
88        call_site_ctxt.normalize_to_macro_rules(db)
89    };
90
91    if call_site_ctxt.is_root() {
92        return apply_mark_internal(db, ctxt, call_id, transparency, edition);
93    }
94
95    // Otherwise, `expn_id` is a macros 1.0 definition and the call site is in a
96    // macros 2.0 expansion, i.e., a macros 1.0 invocation is in a macros 2.0 definition.
97    //
98    // In this case, the tokens from the macros 1.0 definition inherit the hygiene
99    // at their invocation. That is, we pretend that the macros 1.0 definition
100    // was defined at its invocation (i.e., inside the macros 2.0 definition)
101    // so that the macros 2.0 definition remains hygienic.
102    //
103    // See the example at `test/ui/hygiene/legacy_interaction.rs`.
104    for (call_id, transparency) in ctxt.marks(db) {
105        call_site_ctxt = apply_mark_internal(db, call_site_ctxt, call_id, transparency, edition);
106    }
107    apply_mark_internal(db, call_site_ctxt, call_id, transparency, edition)
108}
109
110fn apply_mark_internal(
111    db: &dyn ExpandDatabase,
112    ctxt: SyntaxContext,
113    call_id: MacroCallId,
114    transparency: Transparency,
115    edition: Edition,
116) -> SyntaxContext {
117    let call_id = Some(call_id);
118
119    let mut opaque = ctxt.opaque(db);
120    let mut opaque_and_semitransparent = ctxt.opaque_and_semitransparent(db);
121
122    if transparency >= Transparency::Opaque {
123        let parent = opaque;
124        opaque = SyntaxContext::new(db, call_id, transparency, edition, parent, identity, identity);
125    }
126
127    if transparency >= Transparency::SemiTransparent {
128        let parent = opaque_and_semitransparent;
129        opaque_and_semitransparent =
130            SyntaxContext::new(db, call_id, transparency, edition, parent, |_| opaque, identity);
131    }
132
133    let parent = ctxt;
134    SyntaxContext::new(
135        db,
136        call_id,
137        transparency,
138        edition,
139        parent,
140        |_| opaque,
141        |_| opaque_and_semitransparent,
142    )
143}