Skip to main content

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