Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions compiler/rustc_expand/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,62 @@ pub(crate) struct CfgAttrNoAttributes;
pub(crate) struct NoSyntaxVarsExprRepeat {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub typo_repeatable: Option<VarTypoSuggestionRepeatable>,
#[subdiagnostic]
pub typo_unrepeatable: Option<VarTypoSuggestionUnrepeatable>,
#[subdiagnostic]
pub typo_unrepeatable_label: Option<VarTypoSuggestionUnrepeatableLabel>,
#[subdiagnostic]
pub var_no_typo: Option<VarNoTypo>,
#[subdiagnostic]
pub no_repeatable_var: Option<NoRepeatableVar>,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(
"there's a macro argument with a similar name",
applicability = "maybe-incorrect",
style = "verbose"
)]
pub(crate) struct VarTypoSuggestionRepeatable {
#[suggestion_part(code = "{name}")]
pub span: Span,
pub name: Symbol,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(
"there's a macro argument with a similar name that is unrepeatable",
applicability = "maybe-incorrect",
style = "verbose"
)]
pub(crate) struct VarTypoSuggestionUnrepeatable {
#[suggestion_part(code = "{name}")]
pub span: Span,
pub name: Symbol,
}

#[derive(Subdiagnostic)]
#[label("this macro argument is unrepeatable")]
pub(crate) struct VarTypoSuggestionUnrepeatableLabel {
#[primary_span]
pub span: Span,
}

#[derive(Subdiagnostic)]
#[label("expected one of repeatable variables arguments: {$msg}")]
pub(crate) struct VarNoTypo {
#[primary_span]
pub span: Span,
pub msg: String,
}

#[derive(Subdiagnostic)]
#[label("variable is not repeatable and there is no repeatable variable arguments")]
pub(crate) struct NoRepeatableVar {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
Expand Down
26 changes: 23 additions & 3 deletions compiler/rustc_expand/src/mbe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ use rustc_span::{Ident, Span};
/// Contains the sub-token-trees of a "delimited" token tree such as `(a b c)`.
/// The delimiters are not represented explicitly in the `tts` vector.
#[derive(PartialEq, Encodable, Decodable, Debug)]
struct Delimited {
pub(crate) struct Delimited {
delim: Delimiter,
/// FIXME: #67062 has details about why this is sub-optimal.
tts: Vec<TokenTree>,
}

#[derive(PartialEq, Encodable, Decodable, Debug)]
struct SequenceRepetition {
pub(crate) struct SequenceRepetition {
/// The sequence of token trees
tts: Vec<TokenTree>,
/// The optional separator
Expand Down Expand Up @@ -66,7 +66,7 @@ pub(crate) enum KleeneOp {
/// Similar to `tokenstream::TokenTree`, except that `Sequence`, `MetaVar`, `MetaVarDecl`, and
/// `MetaVarExpr` are "first-class" token trees. Useful for parsing macros.
#[derive(Debug, PartialEq, Encodable, Decodable)]
enum TokenTree {
pub(crate) enum TokenTree {
/// A token. Unlike `tokenstream::TokenTree::Token` this lacks a `Spacing`.
/// See the comments about `Spacing` in the `transcribe` function.
Token(Token),
Expand Down Expand Up @@ -118,4 +118,24 @@ impl TokenTree {
fn token(kind: TokenKind, span: Span) -> TokenTree {
TokenTree::Token(Token::new(kind, span))
}

// Used only in diagnostics.
fn meta_vars(&self, vars: &mut Vec<Ident>) {
match self {
Self::Token(_) => {}
Self::MetaVar(_, ident) => vars.push(*ident),
Self::MetaVarDecl { name, .. } => vars.push(*name),
Self::Delimited(_, _, delimited) => {
for tt in &delimited.tts {
tt.meta_vars(vars);
}
}
Self::Sequence(_, sequence) => {
for tt in &sequence.tts {
tt.meta_vars(vars);
}
}
Self::MetaVarExpr(_, _) => {}
}
}
}
9 changes: 9 additions & 0 deletions compiler/rustc_expand/src/mbe/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,15 @@ pub(crate) enum NamedMatch {
MatchedSingle(ParseNtResult),
}

impl NamedMatch {
pub(super) fn is_repeatable(&self) -> bool {
match self {
NamedMatch::MatchedSeq(_) => true,
NamedMatch::MatchedSingle(_) => false,
}
}
}

/// Performs a token equality check, ignoring syntax context (that is, an unhygienic comparison)
fn token_name_eq(t1: &Token, t2: &Token) -> bool {
if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) = (t1.ident(), t2.ident()) {
Expand Down
72 changes: 69 additions & 3 deletions compiler/rustc_expand/src/mbe/transcribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use smallvec::{SmallVec, smallvec};

use crate::errors::{
CountRepetitionMisplaced, MacroVarStillRepeating, MetaVarsDifSeqMatchers, MustRepeatOnce,
MveUnrecognizedVar, NoSyntaxVarsExprRepeat,
MveUnrecognizedVar, NoRepeatableVar, NoSyntaxVarsExprRepeat, VarNoTypo,
VarTypoSuggestionRepeatable, VarTypoSuggestionUnrepeatable, VarTypoSuggestionUnrepeatableLabel,
};
use crate::mbe::macro_parser::NamedMatch;
use crate::mbe::macro_parser::NamedMatch::*;
Expand Down Expand Up @@ -246,7 +247,7 @@ pub(super) fn transcribe<'a>(
match tree {
// Replace the sequence with its expansion.
seq @ mbe::TokenTree::Sequence(_, seq_rep) => {
transcribe_sequence(&mut tscx, seq, seq_rep)?;
transcribe_sequence(&mut tscx, seq, seq_rep, interp)?;
}

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

Expand All @@ -301,7 +304,70 @@ fn transcribe_sequence<'tx, 'itp>(
// macro writer has made a mistake.
match lockstep_iter_size(seq, tscx.interp, &tscx.repeats) {
LockstepIterSize::Unconstrained => {
return Err(dcx.create_err(NoSyntaxVarsExprRepeat { span: seq.span() }));
let mut repeatables = Vec::new();
let mut non_repeatables = Vec::new();

#[allow(rustc::potential_query_instability)]
for (name, matcher) in interp.iter() {
if matcher.is_repeatable() {
repeatables.push(name);
} else {
non_repeatables.push(name);
}
}

let repeatable_names: Vec<Symbol> =
repeatables.iter().map(|&name| name.symbol()).collect();
let non_repeatable_names: Vec<Symbol> =
non_repeatables.iter().map(|&name| name.symbol()).collect();
let mut meta_vars = vec![];
seq.meta_vars(&mut meta_vars);
let mut typo_repeatable = None;
let mut typo_unrepeatable = None;
let mut typo_unrepeatable_label = None;
let mut var_no_typo = None;
let mut no_repeatable_var = None;

for ident in meta_vars {
if let Some(name) = rustc_span::edit_distance::find_best_match_for_name(
&repeatable_names[..],
ident.name,
None,
) {
typo_repeatable = Some(VarTypoSuggestionRepeatable { span: ident.span, name });
} else if let Some(name) = rustc_span::edit_distance::find_best_match_for_name(
&non_repeatable_names[..],
ident.name,
None,
) {
typo_unrepeatable =
Some(VarTypoSuggestionUnrepeatable { span: ident.span, name });
if let Some(&orig_ident) = non_repeatables.iter().find(|n| n.symbol() == name) {
typo_unrepeatable_label = Some(VarTypoSuggestionUnrepeatableLabel {
span: orig_ident.ident().span,
});
}
} else {
if !repeatable_names.is_empty() {
let msg = repeatable_names
.iter()
.map(|sym| format!("${}", sym))
.collect::<Vec<_>>()
.join(", ");
var_no_typo = Some(VarNoTypo { span: ident.span, msg });
} else {
no_repeatable_var = Some(NoRepeatableVar { span: ident.span });
}
}
}
return Err(dcx.create_err(NoSyntaxVarsExprRepeat {
span: seq.span(),
typo_unrepeatable,
typo_repeatable,
typo_unrepeatable_label,
var_no_typo,
no_repeatable_var,
}));
}

LockstepIterSize::Contradiction(msg) => {
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2778,6 +2778,14 @@ impl MacroRulesNormalizedIdent {
pub fn new(ident: Ident) -> Self {
MacroRulesNormalizedIdent(ident.normalize_to_macro_rules())
}

pub fn symbol(&self) -> Symbol {
self.0.name
}

pub fn ident(&self) -> Ident {
self.0
}
}

impl fmt::Debug for MacroRulesNormalizedIdent {
Expand Down
27 changes: 27 additions & 0 deletions tests/ui/macros/typo-in-repeat-expr-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
macro_rules! mn {
(begin $($arg:ident),* end) => {
[$($typo),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
//~^ NOTE expected one of repeatable variables arguments: $arg
};
}

macro_rules! mnr {
(begin $arg:ident end) => { //~ NOTE this macro argument is unrepeatable
[$($ard),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
//~^ HELP there's a macro argument with a similar name
};
}

macro_rules! err {
(begin $arg:ident end) => {
[$($typo),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
//~^ NOTE variable is not repeatable and there is no repeatable variable arguments
};
}

fn main() {
let x = 1;
let _ = mn![begin x end];
let _ = mnr![begin x end];
let _ = err![begin x end];
}
32 changes: 32 additions & 0 deletions tests/ui/macros/typo-in-repeat-expr-2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> $DIR/typo-in-repeat-expr-2.rs:3:11
|
LL | [$($typo),*]
| ^^----^
| |
| expected one of repeatable variables arguments: $arg

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> $DIR/typo-in-repeat-expr-2.rs:10:11
|
LL | (begin $arg:ident end) => {
| --- this macro argument is unrepeatable
LL | [$($ard),*]
| ^^^^^^
|
help: there's a macro argument with a similar name that is unrepeatable
|
LL - [$($ard),*]
LL + [$($arg),*]
|

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> $DIR/typo-in-repeat-expr-2.rs:17:11
|
LL | [$($typo),*]
| ^^----^
| |
| variable is not repeatable and there is no repeatable variable arguments

error: aborting due to 3 previous errors

12 changes: 12 additions & 0 deletions tests/ui/macros/typo-in-repeat-expr.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//@ run-rustfix
macro_rules! m {
(begin $($ard:ident),* end) => {
[$($ard),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
//~^ HELP there's a macro argument with a similar name
};
}

fn main() {
let x = 1;
let _ = m![begin x end];
}
12 changes: 12 additions & 0 deletions tests/ui/macros/typo-in-repeat-expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//@ run-rustfix
macro_rules! m {
(begin $($ard:ident),* end) => {
[$($arg),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
//~^ HELP there's a macro argument with a similar name
};
}

fn main() {
let x = 1;
let _ = m![begin x end];
}
14 changes: 14 additions & 0 deletions tests/ui/macros/typo-in-repeat-expr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> $DIR/typo-in-repeat-expr.rs:4:11
|
LL | [$($arg),*]
| ^^^^^^
|
help: there's a macro argument with a similar name
|
LL - [$($arg),*]
LL + [$($ard),*]
|

error: aborting due to 1 previous error

Loading