Skip to content

Commit d72a0c4

Browse files
committed
Properly calculate best failure in macro matching
Previously, we used spans. This was not good. Sometimes, the span of the token that failed to match may come from a position later in the file which has been transcribed into a token stream way earlier in the file. If precisely this token fails to match, we think that it was the best match because its span is so high, even though other arms might have gotten further in the token stream. We now try to properly use the location in the token stream.
1 parent 32da230 commit d72a0c4

File tree

6 files changed

+80
-15
lines changed

6 files changed

+80
-15
lines changed

compiler/rustc_expand/src/mbe/diagnostics.rs

+30-10
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(super) fn failed_to_match_macro<'cx>(
4343
return result;
4444
}
4545

46-
let Some((token, label, remaining_matcher)) = tracker.best_failure else {
46+
let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else {
4747
return DummyResult::any(sp);
4848
};
4949

@@ -95,11 +95,24 @@ struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
9595
cx: &'a mut ExtCtxt<'cx>,
9696
remaining_matcher: Option<&'matcher MatcherLoc>,
9797
/// Which arm's failure should we report? (the one furthest along)
98-
best_failure: Option<(Token, &'static str, MatcherLoc)>,
98+
best_failure: Option<BestFailure>,
9999
root_span: Span,
100100
result: Option<Box<dyn MacResult + 'cx>>,
101101
}
102102

103+
struct BestFailure {
104+
token: Token,
105+
position_in_tokenstream: usize,
106+
msg: &'static str,
107+
remaining_matcher: MatcherLoc,
108+
}
109+
110+
impl BestFailure {
111+
fn is_better_position(&self, position: usize) -> bool {
112+
position > self.position_in_tokenstream
113+
}
114+
}
115+
103116
impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
104117
fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
105118
if self.remaining_matcher.is_none()
@@ -119,18 +132,25 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
119132
"should not collect detailed info for successful macro match",
120133
);
121134
}
122-
Failure(token, msg) => match self.best_failure {
123-
Some((ref best_token, _, _)) if best_token.span.lo() >= token.span.lo() => {}
124-
_ => {
125-
self.best_failure = Some((
126-
token.clone(),
135+
Failure(token, approx_position, msg) => {
136+
debug!(?token, ?msg, "a new failure of an arm");
137+
138+
if self
139+
.best_failure
140+
.as_ref()
141+
.map_or(true, |failure| failure.is_better_position(*approx_position))
142+
{
143+
self.best_failure = Some(BestFailure {
144+
token: token.clone(),
145+
position_in_tokenstream: *approx_position,
127146
msg,
128-
self.remaining_matcher
147+
remaining_matcher: self
148+
.remaining_matcher
129149
.expect("must have collected matcher already")
130150
.clone(),
131-
))
151+
})
132152
}
133-
},
153+
}
134154
Error(err_sp, msg) => {
135155
let span = err_sp.substitute_dummy(self.root_span);
136156
self.cx.struct_span_err(span, msg).emit();

compiler/rustc_expand/src/mbe/macro_parser.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ pub(crate) enum ParseResult<T> {
310310
Success(T),
311311
/// Arm failed to match. If the second parameter is `token::Eof`, it indicates an unexpected
312312
/// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
313-
Failure(Token, &'static str),
313+
/// The usize is the approximate position of the token in the input token stream.
314+
Failure(Token, usize, &'static str),
314315
/// Fatal error (malformed macro?). Abort compilation.
315316
Error(rustc_span::Span, String),
316317
ErrorReported(ErrorGuaranteed),
@@ -455,6 +456,7 @@ impl TtParser {
455456
&mut self,
456457
matcher: &'matcher [MatcherLoc],
457458
token: &Token,
459+
approx_position: usize,
458460
track: &mut T,
459461
) -> Option<NamedParseResult> {
460462
// Matcher positions that would be valid if the macro invocation was over now. Only
@@ -598,6 +600,7 @@ impl TtParser {
598600
token::Eof,
599601
if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() },
600602
),
603+
approx_position,
601604
"missing tokens in macro arguments",
602605
),
603606
})
@@ -627,7 +630,12 @@ impl TtParser {
627630

628631
// Process `cur_mps` until either we have finished the input or we need to get some
629632
// parsing from the black-box parser done.
630-
let res = self.parse_tt_inner(matcher, &parser.token, track);
633+
let res = self.parse_tt_inner(
634+
matcher,
635+
&parser.token,
636+
parser.approx_token_stream_pos(),
637+
track,
638+
);
631639
if let Some(res) = res {
632640
return res;
633641
}
@@ -642,6 +650,7 @@ impl TtParser {
642650
// parser: syntax error.
643651
return Failure(
644652
parser.token.clone(),
653+
parser.approx_token_stream_pos(),
645654
"no rules expected this token in macro call",
646655
);
647656
}

compiler/rustc_expand/src/mbe/macro_rules.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
326326

327327
return Ok((i, named_matches));
328328
}
329-
Failure(_, _) => {
330-
trace!("Failed to match arm, trying the next one");
329+
Failure(_, reached_position, _) => {
330+
trace!(%reached_position, "Failed to match arm, trying the next one");
331331
// Try the next arm.
332332
}
333333
Error(_, _) => {
@@ -432,7 +432,7 @@ pub fn compile_declarative_macro(
432432
let argument_map =
433433
match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) {
434434
Success(m) => m,
435-
Failure(token, msg) => {
435+
Failure(token, _, msg) => {
436436
let s = parse_failure_msg(&token);
437437
let sp = token.span.substitute_dummy(def.span);
438438
let mut err = sess.parse_sess.span_diagnostic.struct_span_err(sp, &s);

compiler/rustc_parse/src/parser/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,10 @@ impl<'a> Parser<'a> {
15031503
pub fn clear_expected_tokens(&mut self) {
15041504
self.expected_tokens.clear();
15051505
}
1506+
1507+
pub fn approx_token_stream_pos(&self) -> usize {
1508+
self.token_cursor.num_next_calls
1509+
}
15061510
}
15071511

15081512
pub(crate) fn make_unclosed_delims_error(

src/test/ui/macros/best-failure.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
macro_rules! number {
2+
(neg false, $self:ident) => { $self };
3+
($signed:tt => $ty:ty;) => {
4+
number!(neg $signed, $self);
5+
//~^ ERROR no rules expected the token `$`
6+
};
7+
}
8+
9+
number! { false => u8; }
10+
11+
fn main() {}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: no rules expected the token `$`
2+
--> $DIR/best-failure.rs:4:30
3+
|
4+
LL | macro_rules! number {
5+
| ------------------- when calling this macro
6+
...
7+
LL | number!(neg $signed, $self);
8+
| ^^^^^ no rules expected this token in macro call
9+
...
10+
LL | number! { false => u8; }
11+
| ------------------------ in this macro invocation
12+
|
13+
note: while trying to match meta-variable `$self:ident`
14+
--> $DIR/best-failure.rs:2:17
15+
|
16+
LL | (neg false, $self:ident) => { $self };
17+
| ^^^^^^^^^^^
18+
= note: this error originates in the macro `number` (in Nightly builds, run with -Z macro-backtrace for more info)
19+
20+
error: aborting due to previous error
21+

0 commit comments

Comments
 (0)