Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mbe.rs18
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs8
-rw-r--r--crates/mbe/src/benchmark.rs1
-rw-r--r--crates/mbe/src/expander/matcher.rs2
-rw-r--r--crates/mbe/src/expander/transcriber.rs17
-rw-r--r--crates/mbe/src/parser.rs60
-rw-r--r--crates/mbe/src/tt_iter.rs7
7 files changed, 108 insertions, 5 deletions
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 */
"#]],
)
}
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),