Skip to content

Commit 0fbe120

Browse files
rustc_expand: improve diagnostics for non-repeatable metavars
Co-authored-by: Esteban Küber <esteban@kuber.com.ar> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
1 parent 791ee34 commit 0fbe120

File tree

10 files changed

+233
-7
lines changed

10 files changed

+233
-7
lines changed

compiler/rustc_expand/src/base.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,16 @@ impl<'cx> MacroExpanderResult<'cx> {
278278
// Emit the SEMICOLON_IN_EXPRESSIONS_FROM_MACROS deprecation lint.
279279
let is_local = true;
280280

281-
let parser = ParserAnyMacro::from_tts(cx, tts, site_span, arm_span, is_local, macro_ident);
281+
let parser = ParserAnyMacro::from_tts(
282+
cx,
283+
tts,
284+
site_span,
285+
arm_span,
286+
is_local,
287+
macro_ident,
288+
vec![],
289+
vec![],
290+
);
282291
ExpandResult::Ready(Box::new(parser))
283292
}
284293
}

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,13 @@ impl<'dcx> CollectTrackerAndEmitter<'dcx, '_> {
222222

223223
pub(super) fn emit_frag_parse_err(
224224
mut e: Diag<'_>,
225-
parser: &Parser<'_>,
225+
parser: &mut Parser<'_>,
226226
orig_parser: &mut Parser<'_>,
227227
site_span: Span,
228228
arm_span: Span,
229229
kind: AstFragmentKind,
230+
bindings: Vec<Ident>,
231+
matched_rule_bindings: Vec<Ident>,
230232
) -> ErrorGuaranteed {
231233
// FIXME(davidtwco): avoid depending on the error message text
232234
if parser.token == token::Eof
@@ -285,6 +287,52 @@ pub(super) fn emit_frag_parse_err(
285287
},
286288
_ => annotate_err_with_kind(&mut e, kind, site_span),
287289
};
290+
291+
let matched_rule_bindings_names: Vec<_> =
292+
matched_rule_bindings.iter().map(|bind| bind.name).collect();
293+
let bindings_name: Vec<_> = bindings.iter().map(|bind| bind.name).collect();
294+
if parser.token.kind == token::Dollar {
295+
parser.bump();
296+
if let token::Ident(name, _) = parser.token.kind {
297+
if let Some(matched_name) = rustc_span::edit_distance::find_best_match_for_name(
298+
&matched_rule_bindings_names[..],
299+
name,
300+
None,
301+
) {
302+
e.span_suggestion_verbose(
303+
parser.token.span,
304+
"this could be a typo, there is a macro argument with similar name",
305+
format!("{matched_name}"),
306+
Applicability::MaybeIncorrect,
307+
);
308+
} else if bindings_name.contains(&name) {
309+
e.span_label(
310+
parser.token.span,
311+
format!("there is an exact macro argument in the other matcher"),
312+
);
313+
} else if let Some(matched_name) =
314+
rustc_span::edit_distance::find_best_match_for_name(&bindings_name[..], name, None)
315+
{
316+
e.span_suggestion_verbose(
317+
parser.token.span,
318+
"this could be a typo, there is a macro argument with similar name in the other matcher",
319+
format!("{matched_name}"),
320+
Applicability::MaybeIncorrect,
321+
);
322+
} else {
323+
let msg = matched_rule_bindings_names
324+
.iter()
325+
.map(|sym| format!("${}", sym))
326+
.collect::<Vec<_>>()
327+
.join(", ");
328+
329+
e.span_label(
330+
parser.token.span,
331+
format!("this could be a typo, there is no macro argument with similar name, available macro arguments are: {msg}"),
332+
);
333+
}
334+
}
335+
}
288336
e.emit()
289337
}
290338

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub(crate) struct ParserAnyMacro<'a> {
5656
arm_span: Span,
5757
/// Whether or not this macro is defined in the current crate
5858
is_local: bool,
59+
bindings: Vec<Ident>,
60+
matched_rule_bindings: Vec<Ident>,
5961
}
6062

6163
impl<'a> ParserAnyMacro<'a> {
@@ -68,13 +70,22 @@ impl<'a> ParserAnyMacro<'a> {
6870
arm_span,
6971
is_trailing_mac,
7072
is_local,
73+
bindings,
74+
matched_rule_bindings,
7175
} = *self;
7276
let snapshot = &mut parser.create_snapshot_for_diagnostic();
7377
let fragment = match parse_ast_fragment(parser, kind) {
7478
Ok(f) => f,
7579
Err(err) => {
7680
let guar = diagnostics::emit_frag_parse_err(
77-
err, parser, snapshot, site_span, arm_span, kind,
81+
err,
82+
parser,
83+
snapshot,
84+
site_span,
85+
arm_span,
86+
kind,
87+
bindings,
88+
matched_rule_bindings,
7889
);
7990
return kind.dummy(site_span, guar);
8091
}
@@ -109,6 +120,9 @@ impl<'a> ParserAnyMacro<'a> {
109120
arm_span: Span,
110121
is_local: bool,
111122
macro_ident: Ident,
123+
// bindings and lhs is for diagnostics
124+
bindings: Vec<Ident>,
125+
matched_rule_bindings: Vec<Ident>,
112126
) -> Self {
113127
Self {
114128
parser: Parser::new(&cx.sess.psess, tts, None),
@@ -122,6 +136,8 @@ impl<'a> ParserAnyMacro<'a> {
122136
is_trailing_mac: cx.current_expansion.is_trailing_mac,
123137
arm_span,
124138
is_local,
139+
bindings,
140+
matched_rule_bindings,
125141
}
126142
}
127143
}
@@ -360,7 +376,7 @@ fn expand_macro<'cx>(
360376

361377
match try_success_result {
362378
Ok((rule_index, rule, named_matches)) => {
363-
let MacroRule::Func { rhs, .. } = rule else {
379+
let MacroRule::Func { lhs, rhs, .. } = rule else {
364380
panic!("try_match_macro returned non-func rule");
365381
};
366382
let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
@@ -388,8 +404,33 @@ fn expand_macro<'cx>(
388404
cx.resolver.record_macro_rule_usage(node_id, rule_index);
389405
}
390406

407+
let mut bindings = vec![];
408+
for rule in rules {
409+
let MacroRule::Func { lhs, .. } = rule else { continue };
410+
for param in lhs {
411+
let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
412+
tracing::info!(?bind);
413+
bindings.push(*bind);
414+
}
415+
}
416+
417+
let mut matched_rule_bindings = vec![];
418+
for param in lhs {
419+
let MatcherLoc::MetaVarDecl { bind, .. } = param else { continue };
420+
matched_rule_bindings.push(*bind);
421+
}
422+
391423
// Let the context choose how to interpret the result. Weird, but useful for X-macros.
392-
Box::new(ParserAnyMacro::from_tts(cx, tts, sp, arm_span, is_local, name))
424+
Box::new(ParserAnyMacro::from_tts(
425+
cx,
426+
tts,
427+
sp,
428+
arm_span,
429+
is_local,
430+
name,
431+
bindings,
432+
matched_rule_bindings,
433+
))
393434
}
394435
Err(CanRetry::No(guar)) => {
395436
debug!("Will not retry matching as an error was emitted already");

tests/ui/macros/issue-6596-1.stderr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ error: expected expression, found `$`
22
--> $DIR/issue-6596-1.rs:3:9
33
|
44
LL | $nonexistent
5-
| ^^^^^^^^^^^^ expected expression
5+
| ^-----------
6+
| ||
7+
| |this could be a typo, there is no macro argument with similar name, available macro arguments are: $inp
8+
| expected expression
69
...
710
LL | e!(foo);
811
| ------- in this macro invocation
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
macro_rules! err {
2+
(begin $follow:ident end $arg:expr) => {
3+
[$arg]
4+
};
5+
(begin1 $arg1:ident end $agr2:expr) => {
6+
[$follow] //~ ERROR: expected expression, found `$`
7+
//~^ NOTE: there is an exact macro argument in the other matcher
8+
//~| NOTE: expected expression
9+
};
10+
}
11+
12+
macro_rules! err1 {
13+
(begin $follow:ident end $arg:expr) => {
14+
[$arg]
15+
};
16+
(begin1 $arg1:ident end) => {
17+
[$follo] //~ ERROR: expected expression, found `$`
18+
//~| NOTE: expected expression
19+
//~| HELP: this could be a typo, there is a macro argument with similar name in the other matcher
20+
};
21+
}
22+
23+
macro_rules! err2 {
24+
(begin $follow:ident end $arg:expr) => {
25+
[$arg]
26+
};
27+
(begin1 $arg1:ident end) => {
28+
[$xyz] //~ ERROR: expected expression, found `$`
29+
//~^ NOTE: expected expression
30+
//~| NOTE: this could be a typo, there is no macro argument with similar name, available macro arguments are: $arg1
31+
};
32+
}
33+
34+
fn main () {
35+
let _ = err![begin1 x end ig]; //~ NOTE: in this expansion of err!
36+
let _ = err1![begin1 x end]; //~ NOTE: in this expansion of err1!
37+
//~| NOTE: in this expansion of err1!
38+
let _ = err2![begin1 x end]; //~ NOTE: in this expansion of err2!
39+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
error: expected expression, found `$`
2+
--> $DIR/typo-in-norepeat-expr-2.rs:6:10
3+
|
4+
LL | [$follow]
5+
| ^------
6+
| ||
7+
| |there is an exact macro argument in the other matcher
8+
| expected expression
9+
...
10+
LL | let _ = err![begin1 x end ig];
11+
| ---------------------- in this macro invocation
12+
|
13+
= note: this error originates in the macro `err` (in Nightly builds, run with -Z macro-backtrace for more info)
14+
15+
error: expected expression, found `$`
16+
--> $DIR/typo-in-norepeat-expr-2.rs:17:10
17+
|
18+
LL | [$follo]
19+
| ^^^^^^ expected expression
20+
...
21+
LL | let _ = err1![begin1 x end];
22+
| -------------------- in this macro invocation
23+
|
24+
= note: this error originates in the macro `err1` (in Nightly builds, run with -Z macro-backtrace for more info)
25+
help: this could be a typo, there is a macro argument with similar name in the other matcher
26+
|
27+
LL | [$follow]
28+
| +
29+
30+
error: expected expression, found `$`
31+
--> $DIR/typo-in-norepeat-expr-2.rs:28:10
32+
|
33+
LL | [$xyz]
34+
| ^---
35+
| ||
36+
| |this could be a typo, there is no macro argument with similar name, available macro arguments are: $arg1
37+
| expected expression
38+
...
39+
LL | let _ = err2![begin1 x end];
40+
| -------------------- in this macro invocation
41+
|
42+
= note: this error originates in the macro `err2` (in Nightly builds, run with -Z macro-backtrace for more info)
43+
44+
error: aborting due to 3 previous errors
45+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ run-rustfix
2+
macro_rules! m {
3+
(begin $ard:ident end) => {
4+
[$ard] //~ ERROR: expected expression, found `$`
5+
//~^ HELP: this could be a typo, there is a macro argument with similar name
6+
};
7+
}
8+
9+
fn main() {
10+
let x = 1;
11+
let _ = m![begin x end];
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ run-rustfix
2+
macro_rules! m {
3+
(begin $ard:ident end) => {
4+
[$arg] //~ ERROR: expected expression, found `$`
5+
//~^ HELP: this could be a typo, there is a macro argument with similar name
6+
};
7+
}
8+
9+
fn main() {
10+
let x = 1;
11+
let _ = m![begin x end];
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: expected expression, found `$`
2+
--> $DIR/typo-in-norepeat-expr.rs:4:10
3+
|
4+
LL | [$arg]
5+
| ^^^^ expected expression
6+
...
7+
LL | let _ = m![begin x end];
8+
| --------------- in this macro invocation
9+
|
10+
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
11+
help: this could be a typo, there is a macro argument with similar name
12+
|
13+
LL - [$arg]
14+
LL + [$ard]
15+
|
16+
17+
error: aborting due to 1 previous error
18+

tests/ui/macros/typo-in-repeat-expr.stderr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ LL + [$($ard),*]
1111
|
1212

1313
error: aborting due to 1 previous error
14-

0 commit comments

Comments
 (0)