Skip to content
Merged
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
53 changes: 53 additions & 0 deletions compiler/rustc_expand/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,59 @@ 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 metavariable 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)]
#[label("argument not found")]
pub(crate) struct VarTypoSuggestionUnrepeatable {
#[primary_span]
pub span: Span,
}

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

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

#[derive(Subdiagnostic)]
#[label(
"this macro metavariable is not repeatable and there are no other repeatable metavariables"
)]
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
70 changes: 66 additions & 4 deletions compiler/rustc_expand/src/mbe/transcribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_ast::token::{
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
use rustc_ast::{ExprKind, StmtKind, TyKind, UnOp};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Diag, DiagCtxtHandle, PResult, pluralize};
use rustc_errors::{Diag, DiagCtxtHandle, PResult, listify, pluralize};
use rustc_parse::lexer::nfc_normalize;
use rustc_parse::parser::ParseNtResult;
use rustc_session::parse::ParseSess;
Expand All @@ -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,66 @@ 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 });
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 Some(msg) = listify(&repeatable_names, |s| format!("`${s}`"))
{
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 a repeatable metavariable: `$arg`
};
}

macro_rules! mnr {
(begin $arg:ident end) => { //~ NOTE this similarly named macro metavariable is unrepeatable
[$($ard),*] //~ ERROR attempted to repeat an expression containing no syntax variables matched as repeating at this depth
//~^ NOTE: argument not found
};
}

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 this macro metavariable is not repeatable and there are no other repeatable metavariables
};
}

fn main() {
let x = 1;
let _ = mn![begin x end];
let _ = mnr![begin x end];
let _ = err![begin x end];
}
28 changes: 28 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,28 @@
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 a repeatable metavariable: `$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 similarly named macro metavariable is unrepeatable
LL | [$($ard),*]
| ^^---^
| |
| argument not found

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),*]
| ^^----^
| |
| this macro metavariable is not repeatable and there are no other repeatable metavariables

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 metavariable 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 metavariable 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 metavariable with a similar name
|
LL - [$($arg),*]
LL + [$($ard),*]
|

error: aborting due to 1 previous error

Loading