diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 9c1fe54738e2..30d39d52f385 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 636a66ad5351..8aff784087c3 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 a10f0e834dff..ac691578d887 100644 --- a/crates/mbe/src/benchmark.rs +++ b/crates/mbe/src/benchmark.rs @@ -179,6 +179,7 @@ fn invocation_fixtures(rules: &FxHashMap) -> 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 aefb3d059ff3..3b857ad794df 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 b1b3f63fd3a5..93d29b6ffe96 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 6c7be5984188..df3872b3e6ab 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, id: tt::TokenId }, + Ignore { name: SmolStr, id: tt::TokenId }, + Index { depth: u32 }, Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option }, Leaf(tt::Leaf), Subtree { tokens: MetaTemplate, delimiter: Option }, @@ -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, RepeatKind), Par } Err(ParseError::InvalidRepeat) } + +fn parse_metavar_expr(src: &mut TtIter) -> Result { + 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 fc5590b71845..7aceb676c749 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 { + 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),