Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit ec7148b

Browse files
committed
mbe: split Op::Leaf and handle multi-character puncts
1 parent 47c6c8e commit ec7148b

File tree

6 files changed

+180
-53
lines changed

6 files changed

+180
-53
lines changed

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,3 +1630,48 @@ const _: i32 = -0--1--2;
16301630
"#]],
16311631
);
16321632
}
1633+
1634+
#[test]
1635+
fn test_punct_without_space() {
1636+
// Puncts are "glued" greedily.
1637+
check(
1638+
r#"
1639+
macro_rules! foo {
1640+
(: : :) => { "1 1 1" };
1641+
(: ::) => { "1 2" };
1642+
(:: :) => { "2 1" };
1643+
1644+
(: : : :) => { "1 1 1 1" };
1645+
(:: : :) => { "2 1 1" };
1646+
(: :: :) => { "1 2 1" };
1647+
(: : ::) => { "1 1 2" };
1648+
(:: ::) => { "2 2" };
1649+
}
1650+
1651+
fn test() {
1652+
foo!(:::);
1653+
foo!(: :::);
1654+
foo!(::::);
1655+
}
1656+
"#,
1657+
expect![[r#"
1658+
macro_rules! foo {
1659+
(: : :) => { "1 1 1" };
1660+
(: ::) => { "1 2" };
1661+
(:: :) => { "2 1" };
1662+
1663+
(: : : :) => { "1 1 1 1" };
1664+
(:: : :) => { "2 1 1" };
1665+
(: :: :) => { "1 2 1" };
1666+
(: : ::) => { "1 1 2" };
1667+
(:: ::) => { "2 2" };
1668+
}
1669+
1670+
fn test() {
1671+
"2 1";
1672+
"1 2 1";
1673+
"2 2";
1674+
}
1675+
"#]],
1676+
);
1677+
}

crates/mbe/src/benchmark.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,13 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri
141141
None => (),
142142
Some(kind) => panic!("Unhandled kind {kind:?}"),
143143
},
144-
Op::Leaf(leaf) => parent.token_trees.push(leaf.clone().into()),
144+
Op::Literal(it) => parent.token_trees.push(tt::Leaf::from(it.clone()).into()),
145+
Op::Ident(it) => parent.token_trees.push(tt::Leaf::from(it.clone()).into()),
146+
Op::Punct(puncts) => {
147+
for punct in puncts {
148+
parent.token_trees.push(tt::Leaf::from(punct.clone()).into());
149+
}
150+
}
145151
Op::Repeat { tokens, kind, separator } => {
146152
let max = 10;
147153
let cnt = match kind {

crates/mbe/src/expander/matcher.rs

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ use crate::{
6868
expander::{Binding, Bindings, ExpandResult, Fragment},
6969
parser::{MetaVarKind, Op, RepeatKind, Separator},
7070
tt_iter::TtIter,
71-
ExpandError, MetaTemplate,
71+
ExpandError, MetaTemplate, ValueResult,
7272
};
7373

7474
impl Bindings {
@@ -500,18 +500,69 @@ fn match_loop_inner<'t>(
500500
}
501501
}
502502
}
503-
OpDelimited::Op(Op::Leaf(leaf)) => {
504-
if let Err(err) = match_leaf(leaf, &mut src.clone()) {
505-
res.add_err(err);
503+
OpDelimited::Op(Op::Literal(lhs)) => {
504+
if let Ok(rhs) = src.clone().expect_leaf() {
505+
if matches!(rhs, tt::Leaf::Literal(it) if it.text == lhs.text) {
506+
item.dot.next();
507+
} else {
508+
res.add_err(ExpandError::UnexpectedToken);
509+
item.is_error = true;
510+
}
511+
} else {
512+
res.add_err(ExpandError::binding_error(format!("expected literal: `{lhs}`")));
506513
item.is_error = true;
514+
}
515+
try_push!(next_items, item);
516+
}
517+
OpDelimited::Op(Op::Ident(lhs)) => {
518+
if let Ok(rhs) = src.clone().expect_leaf() {
519+
if matches!(rhs, tt::Leaf::Ident(it) if it.text == lhs.text) {
520+
item.dot.next();
521+
} else {
522+
res.add_err(ExpandError::UnexpectedToken);
523+
item.is_error = true;
524+
}
507525
} else {
508-
item.dot.next();
526+
res.add_err(ExpandError::binding_error(format!("expected ident: `{lhs}`")));
527+
item.is_error = true;
509528
}
510529
try_push!(next_items, item);
511530
}
531+
OpDelimited::Op(Op::Punct(lhs)) => {
532+
let mut fork = src.clone();
533+
let error = if let Ok(rhs) = fork.expect_glued_punct() {
534+
let first_is_single_quote = rhs[0].char == '\'';
535+
let lhs = lhs.iter().map(|it| it.char);
536+
let rhs = rhs.iter().map(|it| it.char);
537+
if lhs.clone().eq(rhs) {
538+
// HACK: here we use `meta_result` to pass `TtIter` back to caller because
539+
// it might have been advanced multiple times. `ValueResult` is
540+
// insignificant.
541+
item.meta_result = Some((fork, ValueResult::ok(None)));
542+
item.dot.next();
543+
next_items.push(item);
544+
continue;
545+
}
546+
547+
if first_is_single_quote {
548+
// If the first punct token is a single quote, that's a part of a lifetime
549+
// ident, not a punct.
550+
ExpandError::UnexpectedToken
551+
} else {
552+
let lhs: SmolStr = lhs.collect();
553+
ExpandError::binding_error(format!("expected punct: `{lhs}`"))
554+
}
555+
} else {
556+
ExpandError::UnexpectedToken
557+
};
558+
559+
res.add_err(error);
560+
item.is_error = true;
561+
error_items.push(item);
562+
}
512563
OpDelimited::Op(Op::Ignore { .. } | Op::Index { .. }) => {}
513564
OpDelimited::Open => {
514-
if matches!(src.clone().next(), Some(tt::TokenTree::Subtree(..))) {
565+
if matches!(src.peek_n(0), Some(tt::TokenTree::Subtree(..))) {
515566
item.dot.next();
516567
try_push!(next_items, item);
517568
}
@@ -616,21 +667,33 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree) -> Match {
616667
}
617668
// Dump all possible `next_items` into `cur_items` for the next iteration.
618669
else if !next_items.is_empty() {
619-
// Now process the next token
620-
cur_items.extend(next_items.drain(..));
621-
622-
match src.next() {
623-
Some(tt::TokenTree::Subtree(subtree)) => {
624-
stack.push(src.clone());
625-
src = TtIter::new(subtree);
670+
if let Some((iter, _)) = next_items[0].meta_result.take() {
671+
// We've matched a possibly "glued" punct. The matched punct (hence
672+
// `meta_result` also) must be the same for all items.
673+
// FIXME: If there are multiple items, it's definitely redundant (and it's hacky!
674+
// `meta_result` isn't supposed to be used this way).
675+
676+
// We already bumped, so no need to call `.next()` like in the other branch.
677+
src = iter;
678+
for item in next_items.iter_mut() {
679+
item.meta_result = None;
626680
}
627-
None => {
628-
if let Some(iter) = stack.pop() {
629-
src = iter;
681+
} else {
682+
match src.next() {
683+
Some(tt::TokenTree::Subtree(subtree)) => {
684+
stack.push(src.clone());
685+
src = TtIter::new(subtree);
686+
}
687+
None => {
688+
if let Some(iter) = stack.pop() {
689+
src = iter;
690+
}
630691
}
692+
_ => (),
631693
}
632-
_ => (),
633694
}
695+
// Now process the next token
696+
cur_items.extend(next_items.drain(..));
634697
}
635698
// Finally, we have the case where we need to call the black-box parser to get some
636699
// nonterminal.
@@ -663,27 +726,6 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree) -> Match {
663726
}
664727
}
665728

666-
fn match_leaf(lhs: &tt::Leaf, src: &mut TtIter<'_>) -> Result<(), ExpandError> {
667-
let rhs = src
668-
.expect_leaf()
669-
.map_err(|()| ExpandError::binding_error(format!("expected leaf: `{lhs}`")))?;
670-
match (lhs, rhs) {
671-
(
672-
tt::Leaf::Punct(tt::Punct { char: lhs, .. }),
673-
tt::Leaf::Punct(tt::Punct { char: rhs, .. }),
674-
) if lhs == rhs => Ok(()),
675-
(
676-
tt::Leaf::Ident(tt::Ident { text: lhs, .. }),
677-
tt::Leaf::Ident(tt::Ident { text: rhs, .. }),
678-
) if lhs == rhs => Ok(()),
679-
(
680-
tt::Leaf::Literal(tt::Literal { text: lhs, .. }),
681-
tt::Leaf::Literal(tt::Literal { text: rhs, .. }),
682-
) if lhs == rhs => Ok(()),
683-
_ => Err(ExpandError::UnexpectedToken),
684-
}
685-
}
686-
687729
fn match_meta_var(kind: MetaVarKind, input: &mut TtIter<'_>) -> ExpandResult<Option<Fragment>> {
688730
let fragment = match kind {
689731
MetaVarKind::Path => parser::PrefixEntryPoint::Path,
@@ -756,10 +798,10 @@ fn collect_vars(collector_fun: &mut impl FnMut(SmolStr), pattern: &MetaTemplate)
756798
for op in pattern.iter() {
757799
match op {
758800
Op::Var { name, .. } => collector_fun(name.clone()),
759-
Op::Leaf(_) => (),
760801
Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens),
761802
Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens),
762-
Op::Ignore { .. } | Op::Index { .. } => {}
803+
Op::Ignore { .. } | Op::Index { .. } | Op::Literal(_) | Op::Ident(_) | Op::Punct(_) => {
804+
}
763805
}
764806
}
765807
}

crates/mbe/src/expander/transcriber.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,13 @@ fn expand_subtree(
134134
let mut err = None;
135135
for op in template.iter() {
136136
match op {
137-
Op::Leaf(tt) => arena.push(tt.clone().into()),
137+
Op::Literal(it) => arena.push(tt::Leaf::from(it.clone()).into()),
138+
Op::Ident(it) => arena.push(tt::Leaf::from(it.clone()).into()),
139+
Op::Punct(puncts) => {
140+
for punct in puncts {
141+
arena.push(tt::Leaf::from(punct.clone()).into());
142+
}
143+
}
138144
Op::Subtree { tokens, delimiter } => {
139145
let ExpandResult { value: tt, err: e } =
140146
expand_subtree(ctx, tokens, *delimiter, arena);

crates/mbe/src/parser.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Parser recognizes special macro syntax, `$var` and `$(repeat)*`, in token
22
//! trees.
33
4-
use smallvec::SmallVec;
4+
use smallvec::{smallvec, SmallVec};
55
use syntax::SmolStr;
66

77
use crate::{tt_iter::TtIter, ParseError};
@@ -39,7 +39,7 @@ impl MetaTemplate {
3939
let mut src = TtIter::new(tt);
4040

4141
let mut res = Vec::new();
42-
while let Some(first) = src.next() {
42+
while let Some(first) = src.peek_n(0) {
4343
let op = next_op(first, &mut src, mode)?;
4444
res.push(op);
4545
}
@@ -54,8 +54,10 @@ pub(crate) enum Op {
5454
Ignore { name: SmolStr, id: tt::TokenId },
5555
Index { depth: u32 },
5656
Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
57-
Leaf(tt::Leaf),
5857
Subtree { tokens: MetaTemplate, delimiter: Option<tt::Delimiter> },
58+
Literal(tt::Literal),
59+
Punct(SmallVec<[tt::Punct; 3]>),
60+
Ident(tt::Ident),
5961
}
6062

6163
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -124,12 +126,17 @@ enum Mode {
124126
Template,
125127
}
126128

127-
fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Result<Op, ParseError> {
128-
let res = match first {
129-
tt::TokenTree::Leaf(leaf @ tt::Leaf::Punct(tt::Punct { char: '$', .. })) => {
129+
fn next_op<'a>(
130+
first_peeked: &tt::TokenTree,
131+
src: &mut TtIter<'a>,
132+
mode: Mode,
133+
) -> Result<Op, ParseError> {
134+
let res = match first_peeked {
135+
tt::TokenTree::Leaf(tt::Leaf::Punct(p @ tt::Punct { char: '$', .. })) => {
136+
src.next().expect("first token already peeked");
130137
// Note that the '$' itself is a valid token inside macro_rules.
131138
let second = match src.next() {
132-
None => return Ok(Op::Leaf(leaf.clone())),
139+
None => return Ok(Op::Punct(smallvec![p.clone()])),
133140
Some(it) => it,
134141
};
135142
match second {
@@ -160,7 +167,7 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
160167
tt::TokenTree::Leaf(leaf) => match leaf {
161168
tt::Leaf::Ident(ident) if ident.text == "crate" => {
162169
// We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
163-
Op::Leaf(tt::Leaf::from(tt::Ident { text: "$crate".into(), id: ident.id }))
170+
Op::Ident(tt::Ident { text: "$crate".into(), id: ident.id })
164171
}
165172
tt::Leaf::Ident(ident) => {
166173
let kind = eat_fragment_kind(src, mode)?;
@@ -180,16 +187,33 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
180187
"`$$` is not allowed on the pattern side",
181188
))
182189
}
183-
Mode::Template => Op::Leaf(tt::Leaf::Punct(*punct)),
190+
Mode::Template => Op::Punct(smallvec![*punct]),
184191
},
185192
tt::Leaf::Punct(_) | tt::Leaf::Literal(_) => {
186193
return Err(ParseError::expected("expected ident"))
187194
}
188195
},
189196
}
190197
}
191-
tt::TokenTree::Leaf(tt) => Op::Leaf(tt.clone()),
198+
199+
tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => {
200+
src.next().expect("first token already peeked");
201+
Op::Literal(it.clone())
202+
}
203+
204+
tt::TokenTree::Leaf(tt::Leaf::Ident(it)) => {
205+
src.next().expect("first token already peeked");
206+
Op::Ident(it.clone())
207+
}
208+
209+
tt::TokenTree::Leaf(tt::Leaf::Punct(_)) => {
210+
// There's at least one punct so this shouldn't fail.
211+
let puncts = src.expect_glued_punct().unwrap();
212+
Op::Punct(puncts)
213+
}
214+
192215
tt::TokenTree::Subtree(subtree) => {
216+
src.next().expect("first token already peeked");
193217
let tokens = MetaTemplate::parse(subtree, mode)?;
194218
Op::Subtree { tokens, delimiter: subtree.delimiter }
195219
}

crates/mbe/src/tt_iter.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ impl<'a> TtIter<'a> {
8888
}
8989
}
9090

91+
/// Returns consecutive `Punct`s that can be glued together.
92+
///
93+
/// This method currently may return a single quotation, which is part of lifetime ident and
94+
/// conceptually not a punct in the context of mbe. Callers should handle this.
9195
pub(crate) fn expect_glued_punct(&mut self) -> Result<SmallVec<[tt::Punct; 3]>, ()> {
9296
let tt::TokenTree::Leaf(tt::Leaf::Punct(first)) = self.next().ok_or(())?.clone() else {
9397
return Err(());
@@ -182,7 +186,7 @@ impl<'a> TtIter<'a> {
182186
ExpandResult { value: res, err }
183187
}
184188

185-
pub(crate) fn peek_n(&self, n: usize) -> Option<&tt::TokenTree> {
189+
pub(crate) fn peek_n(&self, n: usize) -> Option<&'a tt::TokenTree> {
186190
self.inner.as_slice().get(n)
187191
}
188192
}

0 commit comments

Comments
 (0)