Skip to content

Commit caf23f2

Browse files
committed
Auto merge of #12745 - jonas-schievink:metavar-exprs, r=jonas-schievink
feat: Implement `ignore` and `index` metavar expression Part of #11952 Fixes #12675
2 parents f7bb932 + df66eb7 commit caf23f2

File tree

7 files changed

+108
-5
lines changed

7 files changed

+108
-5
lines changed

crates/hir-def/src/macro_expansion_tests/mbe.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1612,3 +1612,21 @@ impl Foo {
16121612
"#]],
16131613
)
16141614
}
1615+
1616+
#[test]
1617+
fn test_metavar_exprs() {
1618+
check(
1619+
r#"
1620+
macro_rules! m {
1621+
( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* );
1622+
}
1623+
const _: i32 = m!(a b c);
1624+
"#,
1625+
expect![[r#"
1626+
macro_rules! m {
1627+
( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* );
1628+
}
1629+
const _: i32 = -0--1--2;
1630+
"#]],
1631+
);
1632+
}

crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs

+8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ macro_rules! m {
1616
($($i:ident)*) => ($_);
1717
($($true:ident)*) => ($true);
1818
($($false:ident)*) => ($false);
19+
(double_dollar) => ($$);
1920
($) => (m!($););
21+
($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*);
2022
}
2123
m!($);
2224
"#,
@@ -29,7 +31,9 @@ macro_rules! m {
2931
($($i:ident)*) => ($_);
3032
($($true:ident)*) => ($true);
3133
($($false:ident)*) => ($false);
34+
(double_dollar) => ($$);
3235
($) => (m!($););
36+
($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*);
3337
}
3438
m!($);
3539
"#]],
@@ -59,6 +63,8 @@ f3!();
5963
6064
macro_rules! m1 { ($$i) => () }
6165
m1!();
66+
macro_rules! m2 { () => ( ${invalid()} ) }
67+
m2!();
6268
"#,
6369
expect![[r#"
6470
macro_rules! i1 { invalid }
@@ -80,6 +86,8 @@ macro_rules! f3 { ($i:_) => () }
8086
8187
macro_rules! m1 { ($$i) => () }
8288
/* error: invalid macro definition: `$$` is not allowed on the pattern side */
89+
macro_rules! m2 { () => ( ${invalid()} ) }
90+
/* error: invalid macro definition: invalid metavariable expression */
8391
"#]],
8492
)
8593
}

crates/mbe/src/benchmark.rs

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri
179179
});
180180
parent.token_trees.push(subtree.into());
181181
}
182+
Op::Ignore { .. } | Op::Index { .. } => {}
182183
};
183184

184185
// Simple linear congruential generator for determistic result

crates/mbe/src/expander/matcher.rs

+2
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ fn match_loop_inner<'t>(
502502
}
503503
try_push!(next_items, item);
504504
}
505+
OpDelimited::Op(Op::Ignore { .. } | Op::Index { .. }) => {}
505506
OpDelimited::Open => {
506507
if matches!(src.clone().next(), Some(tt::TokenTree::Subtree(..))) {
507508
item.dot.next();
@@ -747,6 +748,7 @@ fn collect_vars(collector_fun: &mut impl FnMut(SmolStr), pattern: &MetaTemplate)
747748
Op::Leaf(_) => (),
748749
Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens),
749750
Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens),
751+
Op::Ignore { .. } | Op::Index { .. } => {}
750752
}
751753
}
752754
}

crates/mbe/src/expander/transcriber.rs

+17
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,23 @@ fn expand_subtree(
103103
err = err.or(e);
104104
push_fragment(arena, fragment)
105105
}
106+
Op::Ignore { name, id } => {
107+
// Expand the variable, but ignore the result. This registers the repetition count.
108+
expand_var(ctx, name, *id);
109+
}
110+
Op::Index { depth } => {
111+
let index = ctx
112+
.nesting
113+
.get(ctx.nesting.len() - 1 - (*depth as usize))
114+
.map_or(0, |nest| nest.idx);
115+
arena.push(
116+
tt::Leaf::Literal(tt::Literal {
117+
text: index.to_string().into(),
118+
id: tt::TokenId::unspecified(),
119+
})
120+
.into(),
121+
);
122+
}
106123
}
107124
}
108125
// drain the elements added in this instance of expand_subtree

crates/mbe/src/parser.rs

+55-5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ impl MetaTemplate {
5151
#[derive(Clone, Debug, PartialEq, Eq)]
5252
pub(crate) enum Op {
5353
Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
54+
Ignore { name: SmolStr, id: tt::TokenId },
55+
Index { depth: u32 },
5456
Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
5557
Leaf(tt::Leaf),
5658
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
113115
Some(it) => it,
114116
};
115117
match second {
116-
tt::TokenTree::Subtree(subtree) => {
117-
let (separator, kind) = parse_repeat(src)?;
118-
let tokens = MetaTemplate::parse(subtree, mode)?;
119-
Op::Repeat { tokens, separator, kind }
120-
}
118+
tt::TokenTree::Subtree(subtree) => match subtree.delimiter_kind() {
119+
Some(tt::DelimiterKind::Parenthesis) => {
120+
let (separator, kind) = parse_repeat(src)?;
121+
let tokens = MetaTemplate::parse(subtree, mode)?;
122+
Op::Repeat { tokens, separator, kind }
123+
}
124+
Some(tt::DelimiterKind::Brace) => match mode {
125+
Mode::Template => {
126+
parse_metavar_expr(&mut TtIter::new(subtree)).map_err(|()| {
127+
ParseError::unexpected("invalid metavariable expression")
128+
})?
129+
}
130+
Mode::Pattern => {
131+
return Err(ParseError::unexpected(
132+
"`${}` metavariable expressions are not allowed in matchers",
133+
))
134+
}
135+
},
136+
_ => {
137+
return Err(ParseError::expected(
138+
"expected `$()` repetition or `${}` expression",
139+
))
140+
}
141+
},
121142
tt::TokenTree::Leaf(leaf) => match leaf {
122143
tt::Leaf::Ident(ident) if ident.text == "crate" => {
123144
// 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
209230
}
210231
Err(ParseError::InvalidRepeat)
211232
}
233+
234+
fn parse_metavar_expr(src: &mut TtIter) -> Result<Op, ()> {
235+
let func = src.expect_ident()?;
236+
let args = src.expect_subtree()?;
237+
238+
if args.delimiter_kind() != Some(tt::DelimiterKind::Parenthesis) {
239+
return Err(());
240+
}
241+
242+
let mut args = TtIter::new(args);
243+
244+
let op = match &*func.text {
245+
"ignore" => {
246+
let ident = args.expect_ident()?;
247+
Op::Ignore { name: ident.text.clone(), id: ident.id }
248+
}
249+
"index" => {
250+
let depth = if args.len() == 0 { 0 } else { args.expect_u32_literal()? };
251+
Op::Index { depth }
252+
}
253+
_ => return Err(()),
254+
};
255+
256+
if args.next().is_some() {
257+
return Err(());
258+
}
259+
260+
Ok(op)
261+
}

crates/mbe/src/tt_iter.rs

+7
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ impl<'a> TtIter<'a> {
7373
}
7474
}
7575

76+
pub(crate) fn expect_u32_literal(&mut self) -> Result<u32, ()> {
77+
match self.expect_literal()? {
78+
tt::Leaf::Literal(lit) => lit.text.parse().map_err(drop),
79+
_ => Err(()),
80+
}
81+
}
82+
7683
pub(crate) fn expect_punct(&mut self) -> Result<&'a tt::Punct, ()> {
7784
match self.expect_leaf()? {
7885
tt::Leaf::Punct(it) => Ok(it),

0 commit comments

Comments
 (0)