mbe/expander.rs
1//! This module takes a (parsed) definition of `macro_rules` invocation, a
2//! `tt::TokenTree` representing an argument of macro invocation, and produces a
3//! `tt::TokenTree` for the result of the expansion.
4
5mod matcher;
6mod transcriber;
7
8use intern::Symbol;
9use rustc_hash::FxHashMap;
10use span::Span;
11
12use crate::{
13 ExpandError, ExpandErrorKind, ExpandResult, MacroCallStyle, MatchedArmIndex,
14 parser::MetaVarKind,
15};
16
17pub(crate) fn expand_rules(
18 db: &dyn salsa::Database,
19 rules: &[crate::Rule],
20 input: &tt::TopSubtree<Span>,
21 marker: impl Fn(&mut Span) + Copy,
22 call_style: MacroCallStyle,
23 call_site: Span,
24) -> ExpandResult<(tt::TopSubtree<Span>, MatchedArmIndex)> {
25 let mut match_: Option<(matcher::Match<'_>, &crate::Rule, usize)> = None;
26 for (idx, rule) in rules.iter().enumerate() {
27 // Skip any rules that aren't relevant to the call style (fn-like/attr/derive).
28 if call_style != rule.style {
29 continue;
30 }
31
32 let new_match = matcher::match_(db, &rule.lhs, input);
33
34 if new_match.err.is_none() {
35 // If we find a rule that applies without errors, we're done.
36 // Unconditionally returning the transcription here makes the
37 // `test_repeat_bad_var` test fail.
38 let ExpandResult { value, err: transcribe_err } =
39 transcriber::transcribe(&rule.rhs, &new_match.bindings, marker, call_site);
40 if transcribe_err.is_none() {
41 return ExpandResult::ok((value, Some(idx as u32)));
42 }
43 }
44 // Use the rule if we matched more tokens, or bound variables count
45 if let Some((prev_match, _, _)) = &match_ {
46 if (new_match.unmatched_tts, -(new_match.bound_count as i32))
47 < (prev_match.unmatched_tts, -(prev_match.bound_count as i32))
48 {
49 match_ = Some((new_match, rule, idx));
50 }
51 } else {
52 match_ = Some((new_match, rule, idx));
53 }
54 }
55 if let Some((match_, rule, idx)) = match_ {
56 // if we got here, there was no match without errors
57 let ExpandResult { value, err: transcribe_err } =
58 transcriber::transcribe(&rule.rhs, &match_.bindings, marker, call_site);
59 ExpandResult { value: (value, idx.try_into().ok()), err: match_.err.or(transcribe_err) }
60 } else {
61 ExpandResult::new(
62 (tt::TopSubtree::empty(tt::DelimSpan::from_single(call_site)), None),
63 ExpandError::new(call_site, ExpandErrorKind::NoMatchingRule),
64 )
65 }
66}
67
68/// The actual algorithm for expansion is not too hard, but is pretty tricky.
69/// `Bindings` structure is the key to understanding what we are doing here.
70///
71/// On the high level, it stores mapping from meta variables to the bits of
72/// syntax it should be substituted with. For example, if `$e:expr` is matched
73/// with `1 + 1` by macro_rules, the `Binding` will store `$e -> 1 + 1`.
74///
75/// The tricky bit is dealing with repetitions (`$()*`). Consider this example:
76///
77/// ```not_rust
78/// macro_rules! foo {
79/// ($($ i:ident $($ e:expr),*);*) => {
80/// $(fn $ i() { $($ e);*; })*
81/// }
82/// }
83/// foo! { foo 1,2,3; bar 4,5,6 }
84/// ```
85///
86/// Here, the `$i` meta variable is matched first with `foo` and then with
87/// `bar`, and `$e` is matched in turn with `1`, `2`, `3`, `4`, `5`, `6`.
88///
89/// To represent such "multi-mappings", we use a recursive structures: we map
90/// variables not to values, but to *lists* of values or other lists (that is,
91/// to the trees).
92///
93/// For the above example, the bindings would store
94///
95/// ```not_rust
96/// i -> [foo, bar]
97/// e -> [[1, 2, 3], [4, 5, 6]]
98/// ```
99///
100/// We construct `Bindings` in the `match_lhs`. The interesting case is
101/// `TokenTree::Repeat`, where we use `push_nested` to create the desired
102/// nesting structure.
103///
104/// The other side of the puzzle is `expand_subtree`, where we use the bindings
105/// to substitute meta variables in the output template. When expanding, we
106/// maintain a `nesting` stack of indices which tells us which occurrence from
107/// the `Bindings` we should take. We push to the stack when we enter a
108/// repetition.
109///
110/// In other words, `Bindings` is a *multi* mapping from `Symbol` to
111/// `tt::TokenTree`, where the index to select a particular `TokenTree` among
112/// many is not a plain `usize`, but a `&[usize]`.
113#[derive(Debug, Default, Clone)]
114struct Bindings<'a> {
115 inner: FxHashMap<Symbol, Binding<'a>>,
116}
117
118#[derive(Debug, Clone)]
119enum Binding<'a> {
120 Fragment(Fragment<'a>),
121 Nested(Vec<Binding<'a>>),
122 Empty,
123 Missing(MetaVarKind),
124}
125
126#[derive(Debug, Default, Clone)]
127enum Fragment<'a> {
128 #[default]
129 Empty,
130 /// token fragments are just copy-pasted into the output
131 Tokens(tt::TokenTreesView<'a, Span>),
132 /// Expr ast fragments are surrounded with `()` on transcription to preserve precedence.
133 /// Note that this impl is different from the one currently in `rustc` --
134 /// `rustc` doesn't translate fragments into token trees at all.
135 ///
136 /// At one point in time, we tried to use "fake" delimiters here à la
137 /// proc-macro delimiter=none. As we later discovered, "none" delimiters are
138 /// tricky to handle in the parser, and rustc doesn't handle those either.
139 ///
140 /// The span of the outer delimiters is marked on transcription.
141 Expr(tt::TokenTreesView<'a, Span>),
142 /// There are roughly two types of paths: paths in expression context, where a
143 /// separator `::` between an identifier and its following generic argument list
144 /// is mandatory, and paths in type context, where `::` can be omitted.
145 ///
146 /// Unlike rustc, we need to transform the parsed fragments back into tokens
147 /// during transcription. When the matched path fragment is a type-context path
148 /// and is trasncribed as an expression-context path, verbatim transcription
149 /// would cause a syntax error. We need to fix it up just before transcribing;
150 /// see `transcriber::fix_up_and_push_path_tt()`.
151 Path(tt::TokenTreesView<'a, Span>),
152 TokensOwned(tt::TopSubtree<Span>),
153}
154
155impl Fragment<'_> {
156 fn is_empty(&self) -> bool {
157 match self {
158 Fragment::Empty => true,
159 Fragment::Tokens(it) => it.len() == 0,
160 Fragment::Expr(it) => it.len() == 0,
161 Fragment::Path(it) => it.len() == 0,
162 Fragment::TokensOwned(it) => it.0.is_empty(),
163 }
164 }
165}