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}