Repository: rust-lang/rust-analyzer
Pull request #12745 (2022-07-11 16:32:21)

feat: Implement `ignore`  and `index` metavar expression
Part of https://github.com/rust-lang/rust-analyzer/issues/11952

Fixes https://github.com/rust-lang/rust-analyzer/issues/12675


Patch:
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
index a10f0e834d..ac691578d8 100644
--- a/crates/mbe/src/benchmark.rs
+++ b/crates/mbe/src/benchmark.rs
@@ -179,6 +179,7 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri
                 });
                 parent.token_trees.push(subtree.into());
             }
+            Op::Ignore { .. } | Op::Index { .. } => {}
         };
 
         // Simple linear congruential generator for determistic result
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index aefb3d059f..3b857ad794 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -502,6 +502,7 @@ fn match_loop_inner<'t>(
                 }
                 try_push!(next_items, item);
             }
+            OpDelimited::Op(Op::Ignore { .. } | Op::Index { .. }) => {}
             OpDelimited::Open => {
                 if matches!(src.clone().next(), Some(tt::TokenTree::Subtree(..))) {
                     item.dot.next();
@@ -747,6 +748,7 @@ fn collect_vars(collector_fun: &mut impl FnMut(SmolStr), pattern: &MetaTemplate)
             Op::Leaf(_) => (),
             Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens),
             Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens),
+            Op::Ignore { .. } | Op::Index { .. } => {}
         }
     }
 }
diff --git a/crates/mbe/src/expander/transcriber.rs b/crates/mbe/src/expander/transcriber.rs
index b1b3f63fd3..93d29b6ffe 100644
--- a/crates/mbe/src/expander/transcriber.rs
+++ b/crates/mbe/src/expander/transcriber.rs
@@ -103,6 +103,23 @@ fn expand_subtree(
                 err = err.or(e);
                 push_fragment(arena, fragment)
             }
+            Op::Ignore { name, id } => {
+                // Expand the variable, but ignore the result. This registers the repetition count.
+                expand_var(ctx, name, *id);
+            }
+            Op::Index { depth } => {
+                let index = ctx
+                    .nesting
+                    .get(ctx.nesting.len() - 1 - (*depth as usize))
+                    .map_or(0, |nest| nest.idx);
+                arena.push(
+                    tt::Leaf::Literal(tt::Literal {
+                        text: index.to_string().into(),
+                        id: tt::TokenId::unspecified(),
+                    })
+                    .into(),
+                );
+            }
         }
     }
     // drain the elements added in this instance of expand_subtree
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 6c7be59841..df3872b3e6 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -51,6 +51,8 @@ impl MetaTemplate {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum Op {
     Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
+    Ignore { name: SmolStr, id: tt::TokenId },
+    Index { depth: u32 },
     Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
     Leaf(tt::Leaf),
     Subtree { tokens: MetaTemplate, delimiter: Option<tt::Delimiter> },
@@ -113,11 +115,30 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
                 Some(it) => it,
             };
             match second {
-                tt::TokenTree::Subtree(subtree) => {
-                    let (separator, kind) = parse_repeat(src)?;
-                    let tokens = MetaTemplate::parse(subtree, mode)?;
-                    Op::Repeat { tokens, separator, kind }
-                }
+                tt::TokenTree::Subtree(subtree) => match subtree.delimiter_kind() {
+                    Some(tt::DelimiterKind::Parenthesis) => {
+                        let (separator, kind) = parse_repeat(src)?;
+                        let tokens = MetaTemplate::parse(subtree, mode)?;
+                        Op::Repeat { tokens, separator, kind }
+                    }
+                    Some(tt::DelimiterKind::Brace) => match mode {
+                        Mode::Template => {
+                            parse_metavar_expr(&mut TtIter::new(subtree)).map_err(|()| {
+                                ParseError::unexpected("invalid metavariable expression")
+                            })?
+                        }
+                        Mode::Pattern => {
+                            return Err(ParseError::unexpected(
+                                "`${}` metavariable expressions are not allowed in matchers",
+                            ))
+                        }
+                    },
+                    _ => {
+                        return Err(ParseError::expected(
+                            "expected `$()` repetition or `${}` expression",
+                        ))
+                    }
+                },
                 tt::TokenTree::Leaf(leaf) => match leaf {
                     tt::Leaf::Ident(ident) if ident.text == "crate" => {
                         // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
@@ -209,3 +230,32 @@ fn parse_repeat(src: &mut TtIter) -> Result<(Option<Separator>, RepeatKind), Par
     }
     Err(ParseError::InvalidRepeat)
 }
+
+fn parse_metavar_expr(src: &mut TtIter) -> Result<Op, ()> {
+    let func = src.expect_ident()?;
+    let args = src.expect_subtree()?;
+
+    if args.delimiter_kind() != Some(tt::DelimiterKind::Parenthesis) {
+        return Err(());
+    }
+
+    let mut args = TtIter::new(args);
+
+    let op = match &*func.text {
+        "ignore" => {
+            let ident = args.expect_ident()?;
+            Op::Ignore { name: ident.text.clone(), id: ident.id }
+        }
+        "index" => {
+            let depth = if args.len() == 0 { 0 } else { args.expect_u32_literal()? };
+            Op::Index { depth }
+        }
+        _ => return Err(()),
+    };
+
+    if args.next().is_some() {
+        return Err(());
+    }
+
+    Ok(op)
+}
diff --git a/crates/mbe/src/tt_iter.rs b/crates/mbe/src/tt_iter.rs
index fc5590b718..7aceb676c7 100644
--- a/crates/mbe/src/tt_iter.rs
+++ b/crates/mbe/src/tt_iter.rs
@@ -73,6 +73,13 @@ impl<'a> TtIter<'a> {
         }
     }
 
+    pub(crate) fn expect_u32_literal(&mut self) -> Result<u32, ()> {
+        match self.expect_literal()? {
+            tt::Leaf::Literal(lit) => lit.text.parse().map_err(drop),
+            _ => Err(()),
+        }
+    }
+
     pub(crate) fn expect_punct(&mut self) -> Result<&'a tt::Punct, ()> {
         match self.expect_leaf()? {
             tt::Leaf::Punct(it) => Ok(it),


Test patch:
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index 9c1fe54738..30d39d52f3 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -1612,3 +1612,21 @@ impl Foo {
 "#]],
     )
 }
+
+#[test]
+fn test_metavar_exprs() {
+    check(
+        r#"
+macro_rules! m {
+    ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* );
+}
+const _: i32 = m!(a b c);
+    "#,
+        expect![[r#"
+macro_rules! m {
+    ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* );
+}
+const _: i32 = -0--1--2;
+    "#]],
+    );
+}
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
index 636a66ad53..8aff784087 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
@@ -16,7 +16,9 @@ macro_rules! m {
     ($($i:ident)*) => ($_);
     ($($true:ident)*) => ($true);
     ($($false:ident)*) => ($false);
+    (double_dollar) => ($$);
     ($) => (m!($););
+    ($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*);
 }
 m!($);
 "#,
@@ -29,7 +31,9 @@ macro_rules! m {
     ($($i:ident)*) => ($_);
     ($($true:ident)*) => ($true);
     ($($false:ident)*) => ($false);
+    (double_dollar) => ($$);
     ($) => (m!($););
+    ($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*);
 }
 m!($);
 "#]],
@@ -59,6 +63,8 @@ f3!();
 
 macro_rules! m1 { ($$i) => () }
 m1!();
+macro_rules! m2 { () => ( ${invalid()} ) }
+m2!();
 "#,
         expect![[r#"
 macro_rules! i1 { invalid }
@@ -80,6 +86,8 @@ macro_rules! f3 { ($i:_) => () }
 
 macro_rules! m1 { ($$i) => () }
 /* error: invalid macro definition: `$$` is not allowed on the pattern side */
+macro_rules! m2 { () => ( ${invalid()} ) }
+/* error: invalid macro definition: invalid metavariable expression */
 "#]],
     )
 }