Skip to content

Commit 791ee34

Browse files
rustc_expand: improve diagnostics for non-repeatable metavars in repetition
Enhance `NoSyntaxVarsExprRepeat` by suggesting similarly named metavariables, distinguishing repeatable vs non-repeatable bindings, and listing available repeatable variables when no match exists. Co-authored-by: Esteban Küber <esteban@kuber.com.ar> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
1 parent ce0bf0b commit 791ee34

File tree

10 files changed

+262
-6
lines changed

10 files changed

+262
-6
lines changed

compiler/rustc_expand/src/errors.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,62 @@ pub(crate) struct CfgAttrNoAttributes;
1717
pub(crate) struct NoSyntaxVarsExprRepeat {
1818
#[primary_span]
1919
pub span: Span,
20+
#[subdiagnostic]
21+
pub typo_repeatable: Option<VarTypoSuggestionRepeatable>,
22+
#[subdiagnostic]
23+
pub typo_unrepeatable: Option<VarTypoSuggestionUnrepeatable>,
24+
#[subdiagnostic]
25+
pub typo_unrepeatable_label: Option<VarTypoSuggestionUnrepeatableLabel>,
26+
#[subdiagnostic]
27+
pub var_no_typo: Option<VarNoTypo>,
28+
#[subdiagnostic]
29+
pub no_repeatable_var: Option<NoRepeatableVar>,
30+
}
31+
32+
#[derive(Subdiagnostic)]
33+
#[multipart_suggestion(
34+
"there's a macro argument with a similar name",
35+
applicability = "maybe-incorrect",
36+
style = "verbose"
37+
)]
38+
pub(crate) struct VarTypoSuggestionRepeatable {
39+
#[suggestion_part(code = "{name}")]
40+
pub span: Span,
41+
pub name: Symbol,
42+
}
43+
44+
#[derive(Subdiagnostic)]
45+
#[multipart_suggestion(
46+
"there's a macro argument with a similar name that is unrepeatable",
47+
applicability = "maybe-incorrect",
48+
style = "verbose"
49+
)]
50+
pub(crate) struct VarTypoSuggestionUnrepeatable {
51+
#[suggestion_part(code = "{name}")]
52+
pub span: Span,
53+
pub name: Symbol,
54+
}
55+
56+
#[derive(Subdiagnostic)]
57+
#[label("this macro argument is unrepeatable")]
58+
pub(crate) struct VarTypoSuggestionUnrepeatableLabel {
59+
#[primary_span]
60+
pub span: Span,
61+
}
62+
63+
#[derive(Subdiagnostic)]
64+
#[label("expected one of repeatable variables arguments: {$msg}")]
65+
pub(crate) struct VarNoTypo {
66+
#[primary_span]
67+
pub span: Span,
68+
pub msg: String,
69+
}
70+
71+
#[derive(Subdiagnostic)]
72+
#[label("variable is not repeatable and there is no repeatable variable arguments")]
73+
pub(crate) struct NoRepeatableVar {
74+
#[primary_span]
75+
pub span: Span,
2076
}
2177

2278
#[derive(Diagnostic)]

compiler/rustc_expand/src/mbe.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ use rustc_span::{Ident, Span};
2121
/// Contains the sub-token-trees of a "delimited" token tree such as `(a b c)`.
2222
/// The delimiters are not represented explicitly in the `tts` vector.
2323
#[derive(PartialEq, Encodable, Decodable, Debug)]
24-
struct Delimited {
24+
pub(crate) struct Delimited {
2525
delim: Delimiter,
2626
/// FIXME: #67062 has details about why this is sub-optimal.
2727
tts: Vec<TokenTree>,
2828
}
2929

3030
#[derive(PartialEq, Encodable, Decodable, Debug)]
31-
struct SequenceRepetition {
31+
pub(crate) struct SequenceRepetition {
3232
/// The sequence of token trees
3333
tts: Vec<TokenTree>,
3434
/// The optional separator
@@ -66,7 +66,7 @@ pub(crate) enum KleeneOp {
6666
/// Similar to `tokenstream::TokenTree`, except that `Sequence`, `MetaVar`, `MetaVarDecl`, and
6767
/// `MetaVarExpr` are "first-class" token trees. Useful for parsing macros.
6868
#[derive(Debug, PartialEq, Encodable, Decodable)]
69-
enum TokenTree {
69+
pub(crate) enum TokenTree {
7070
/// A token. Unlike `tokenstream::TokenTree::Token` this lacks a `Spacing`.
7171
/// See the comments about `Spacing` in the `transcribe` function.
7272
Token(Token),
@@ -118,4 +118,24 @@ impl TokenTree {
118118
fn token(kind: TokenKind, span: Span) -> TokenTree {
119119
TokenTree::Token(Token::new(kind, span))
120120
}
121+
122+
// Used only in diagnostics.
123+
fn meta_vars(&self, vars: &mut Vec<Ident>) {
124+
match self {
125+
Self::Token(_) => {}
126+
Self::MetaVar(_, ident) => vars.push(*ident),
127+
Self::MetaVarDecl { name, .. } => vars.push(*name),
128+
Self::Delimited(_, _, delimited) => {
129+
for tt in &delimited.tts {
130+
tt.meta_vars(vars);
131+
}
132+
}
133+
Self::Sequence(_, sequence) => {
134+
for tt in &sequence.tts {
135+
tt.meta_vars(vars);
136+
}
137+
}
138+
Self::MetaVarExpr(_, _) => {}
139+
}
140+
}
121141
}

compiler/rustc_expand/src/mbe/macro_parser.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,15 @@ pub(crate) enum NamedMatch {
388388
MatchedSingle(ParseNtResult),
389389
}
390390

391+
impl NamedMatch {
392+
pub(super) fn is_repeatable(&self) -> bool {
393+
match self {
394+
NamedMatch::MatchedSeq(_) => true,
395+
NamedMatch::MatchedSingle(_) => false,
396+
}
397+
}
398+
}
399+
391400
/// Performs a token equality check, ignoring syntax context (that is, an unhygienic comparison)
392401
fn token_name_eq(t1: &Token, t2: &Token) -> bool {
393402
if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) = (t1.ident(), t2.ident()) {

compiler/rustc_expand/src/mbe/transcribe.rs

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use smallvec::{SmallVec, smallvec};
1818

1919
use crate::errors::{
2020
CountRepetitionMisplaced, MacroVarStillRepeating, MetaVarsDifSeqMatchers, MustRepeatOnce,
21-
MveUnrecognizedVar, NoSyntaxVarsExprRepeat,
21+
MveUnrecognizedVar, NoRepeatableVar, NoSyntaxVarsExprRepeat, VarNoTypo,
22+
VarTypoSuggestionRepeatable, VarTypoSuggestionUnrepeatable, VarTypoSuggestionUnrepeatableLabel,
2223
};
2324
use crate::mbe::macro_parser::NamedMatch;
2425
use crate::mbe::macro_parser::NamedMatch::*;
@@ -246,7 +247,7 @@ pub(super) fn transcribe<'a>(
246247
match tree {
247248
// Replace the sequence with its expansion.
248249
seq @ mbe::TokenTree::Sequence(_, seq_rep) => {
249-
transcribe_sequence(&mut tscx, seq, seq_rep)?;
250+
transcribe_sequence(&mut tscx, seq, seq_rep, interp)?;
250251
}
251252

252253
// Replace the meta-var with the matched token tree from the invocation.
@@ -293,6 +294,8 @@ fn transcribe_sequence<'tx, 'itp>(
293294
tscx: &mut TranscrCtx<'tx, 'itp>,
294295
seq: &mbe::TokenTree,
295296
seq_rep: &'itp mbe::SequenceRepetition,
297+
// Used only for better diagnostics in the face of typos.
298+
interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
296299
) -> PResult<'tx, ()> {
297300
let dcx = tscx.psess.dcx();
298301

@@ -301,7 +304,70 @@ fn transcribe_sequence<'tx, 'itp>(
301304
// macro writer has made a mistake.
302305
match lockstep_iter_size(seq, tscx.interp, &tscx.repeats) {
303306
LockstepIterSize::Unconstrained => {
304-
return Err(dcx.create_err(NoSyntaxVarsExprRepeat { span: seq.span() }));
307+
let mut repeatables = Vec::new();
308+
let mut non_repeatables = Vec::new();
309+
310+
#[allow(rustc::potential_query_instability)]
311+
for (name, matcher) in interp.iter() {
312+
if matcher.is_repeatable() {
313+
repeatables.push(name);
314+
} else {
315+
non_repeatables.push(name);
316+
}
317+
}
318+
319+
let repeatable_names: Vec<Symbol> =
320+
repeatables.iter().map(|&name| name.symbol()).collect();
321+
let non_repeatable_names: Vec<Symbol> =
322+
non_repeatables.iter().map(|&name| name.symbol()).collect();
323+
let mut meta_vars = vec![];
324+
seq.meta_vars(&mut meta_vars);
325+
let mut typo_repeatable = None;
326+
let mut typo_unrepeatable = None;
327+
let mut typo_unrepeatable_label = None;
328+
let mut var_no_typo = None;
329+
let mut no_repeatable_var = None;
330+
331+
for ident in meta_vars {
332+
if let Some(name) = rustc_span::edit_distance::find_best_match_for_name(
333+
&repeatable_names[..],
334+
ident.name,
335+
None,
336+
) {
337+
typo_repeatable = Some(VarTypoSuggestionRepeatable { span: ident.span, name });
338+
} else if let Some(name) = rustc_span::edit_distance::find_best_match_for_name(
339+
&non_repeatable_names[..],
340+
ident.name,
341+
None,
342+
) {
343+
typo_unrepeatable =
344+
Some(VarTypoSuggestionUnrepeatable { span: ident.span, name });
345+
if let Some(&orig_ident) = non_repeatables.iter().find(|n| n.symbol() == name) {
346+
typo_unrepeatable_label = Some(VarTypoSuggestionUnrepeatableLabel {
347+
span: orig_ident.ident().span,
348+
});
349+
}
350+
} else {
351+
if !repeatable_names.is_empty() {
352+
let msg = repeatable_names
353+
.iter()
354+
.map(|sym| format!("${}", sym))
355+
.collect::<Vec<_>>()
356+
.join(", ");
357+
var_no_typo = Some(VarNoTypo { span: ident.span, msg });
358+
} else {
359+
no_repeatable_var = Some(NoRepeatableVar { span: ident.span });
360+
}
361+
}
362+
}
363+
return Err(dcx.create_err(NoSyntaxVarsExprRepeat {
364+
span: seq.span(),
365+
typo_unrepeatable,
366+
typo_repeatable,
367+
typo_unrepeatable_label,
368+
var_no_typo,
369+
no_repeatable_var,
370+
}));
305371
}
306372

307373
LockstepIterSize::Contradiction(msg) => {

compiler/rustc_span/src/symbol.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2778,6 +2778,14 @@ impl MacroRulesNormalizedIdent {
27782778
pub fn new(ident: Ident) -> Self {
27792779
MacroRulesNormalizedIdent(ident.normalize_to_macro_rules())
27802780
}
2781+
2782+
pub fn symbol(&self) -> Symbol {
2783+
self.0.name
2784+
}
2785+
2786+
pub fn ident(&self) -> Ident {
2787+
self.0
2788+
}
27812789
}
27822790

27832791
impl fmt::Debug for MacroRulesNormalizedIdent {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
macro_rules! mn {
2+
(begin $($arg:ident),* end) => {
3+
[$($typo),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
4+
//~^ NOTE expected one of repeatable variables arguments: $arg
5+
};
6+
}
7+
8+
macro_rules! mnr {
9+
(begin $arg:ident end) => { //~ NOTE this macro argument is unrepeatable
10+
[$($ard),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
11+
//~^ HELP there's a macro argument with a similar name
12+
};
13+
}
14+
15+
macro_rules! err {
16+
(begin $arg:ident end) => {
17+
[$($typo),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
18+
//~^ NOTE variable is not repeatable and there is no repeatable variable arguments
19+
};
20+
}
21+
22+
fn main() {
23+
let x = 1;
24+
let _ = mn![begin x end];
25+
let _ = mnr![begin x end];
26+
let _ = err![begin x end];
27+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
2+
--> $DIR/typo-in-repeat-expr-2.rs:3:11
3+
|
4+
LL | [$($typo),*]
5+
| ^^----^
6+
| |
7+
| expected one of repeatable variables arguments: $arg
8+
9+
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
10+
--> $DIR/typo-in-repeat-expr-2.rs:10:11
11+
|
12+
LL | (begin $arg:ident end) => {
13+
| --- this macro argument is unrepeatable
14+
LL | [$($ard),*]
15+
| ^^^^^^
16+
|
17+
help: there's a macro argument with a similar name that is unrepeatable
18+
|
19+
LL - [$($ard),*]
20+
LL + [$($arg),*]
21+
|
22+
23+
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
24+
--> $DIR/typo-in-repeat-expr-2.rs:17:11
25+
|
26+
LL | [$($typo),*]
27+
| ^^----^
28+
| |
29+
| variable is not repeatable and there is no repeatable variable arguments
30+
31+
error: aborting due to 3 previous errors
32+
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 attempted to repeat an expression containing no syntax variables matched as repeating at this depth
5+
//~^ HELP there's a macro argument with a 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 attempted to repeat an expression containing no syntax variables matched as repeating at this depth
5+
//~^ HELP there's a macro argument with a similar name
6+
};
7+
}
8+
9+
fn main() {
10+
let x = 1;
11+
let _ = m![begin x end];
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
2+
--> $DIR/typo-in-repeat-expr.rs:4:11
3+
|
4+
LL | [$($arg),*]
5+
| ^^^^^^
6+
|
7+
help: there's a macro argument with a similar name
8+
|
9+
LL - [$($arg),*]
10+
LL + [$($ard),*]
11+
|
12+
13+
error: aborting due to 1 previous error
14+

0 commit comments

Comments
 (0)