Skip to content

Commit 53e1348

Browse files
authored
Unrolled build for #152558
Rollup merge of #152558 - Unique-Usman:ua/decmacrorepeatable, r=estebank 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.
2 parents 41198cb + 88e96b8 commit 53e1348

10 files changed

Lines changed: 252 additions & 7 deletions

File tree

compiler/rustc_expand/src/errors.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,59 @@ 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 metavariable 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+
#[label("argument not found")]
46+
pub(crate) struct VarTypoSuggestionUnrepeatable {
47+
#[primary_span]
48+
pub span: Span,
49+
}
50+
51+
#[derive(Subdiagnostic)]
52+
#[label("this similarly named macro metavariable is unrepeatable")]
53+
pub(crate) struct VarTypoSuggestionUnrepeatableLabel {
54+
#[primary_span]
55+
pub span: Span,
56+
}
57+
58+
#[derive(Subdiagnostic)]
59+
#[label("expected a repeatable metavariable: {$msg}")]
60+
pub(crate) struct VarNoTypo {
61+
#[primary_span]
62+
pub span: Span,
63+
pub msg: String,
64+
}
65+
66+
#[derive(Subdiagnostic)]
67+
#[label(
68+
"this macro metavariable is not repeatable and there are no other repeatable metavariables"
69+
)]
70+
pub(crate) struct NoRepeatableVar {
71+
#[primary_span]
72+
pub span: Span,
2073
}
2174

2275
#[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: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_ast::token::{
66
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
77
use rustc_ast::{ExprKind, StmtKind, TyKind, UnOp};
88
use rustc_data_structures::fx::FxHashMap;
9-
use rustc_errors::{Diag, DiagCtxtHandle, PResult, pluralize};
9+
use rustc_errors::{Diag, DiagCtxtHandle, PResult, listify, pluralize};
1010
use rustc_parse::lexer::nfc_normalize;
1111
use rustc_parse::parser::ParseNtResult;
1212
use rustc_session::parse::ParseSess;
@@ -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,66 @@ 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 = Some(VarTypoSuggestionUnrepeatable { span: ident.span });
344+
if let Some(&orig_ident) = non_repeatables.iter().find(|n| n.symbol() == name) {
345+
typo_unrepeatable_label = Some(VarTypoSuggestionUnrepeatableLabel {
346+
span: orig_ident.ident().span,
347+
});
348+
}
349+
} else {
350+
if !repeatable_names.is_empty()
351+
&& let Some(msg) = listify(&repeatable_names, |s| format!("`${s}`"))
352+
{
353+
var_no_typo = Some(VarNoTypo { span: ident.span, msg });
354+
} else {
355+
no_repeatable_var = Some(NoRepeatableVar { span: ident.span });
356+
}
357+
}
358+
}
359+
return Err(dcx.create_err(NoSyntaxVarsExprRepeat {
360+
span: seq.span(),
361+
typo_unrepeatable,
362+
typo_repeatable,
363+
typo_unrepeatable_label,
364+
var_no_typo,
365+
no_repeatable_var,
366+
}));
305367
}
306368

307369
LockstepIterSize::Contradiction(msg) => {

compiler/rustc_span/src/symbol.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,6 +2788,14 @@ impl MacroRulesNormalizedIdent {
27882788
pub fn new(ident: Ident) -> Self {
27892789
MacroRulesNormalizedIdent(ident.normalize_to_macro_rules())
27902790
}
2791+
2792+
pub fn symbol(&self) -> Symbol {
2793+
self.0.name
2794+
}
2795+
2796+
pub fn ident(&self) -> Ident {
2797+
self.0
2798+
}
27912799
}
27922800

27932801
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 a repeatable metavariable: `$arg`
5+
};
6+
}
7+
8+
macro_rules! mnr {
9+
(begin $arg:ident end) => { //~ NOTE this similarly named macro metavariable is unrepeatable
10+
[$($ard),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
11+
//~^ NOTE: argument not found
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 this macro metavariable is not repeatable and there are no other repeatable metavariables
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 a repeatable metavariable: `$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 similarly named macro metavariable is unrepeatable
14+
LL | [$($ard),*]
15+
| ^^---^
16+
| |
17+
| argument not found
18+
19+
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
20+
--> $DIR/typo-in-repeat-expr-2.rs:17:11
21+
|
22+
LL | [$($typo),*]
23+
| ^^----^
24+
| |
25+
| this macro metavariable is not repeatable and there are no other repeatable metavariables
26+
27+
error: aborting due to 3 previous errors
28+
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 metavariable 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 metavariable 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 metavariable 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)