From 2b46d9204a82c50d57b66ba31b8dbbc5bcbd56bc Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:17:17 -0400 Subject: [PATCH] Improve suggestion for `$`-prefixed fragment specifiers --- compiler/rustc_expand/src/mbe/quoted.rs | 46 +++++++++++++++---- tests/ui/macros/macro-missing-fragment.rs | 4 ++ tests/ui/macros/macro-missing-fragment.stderr | 16 ++++++- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index a698db543759..92d19820848b 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -2,6 +2,7 @@ use rustc_ast::tokenstream::TokenStreamIter; use rustc_ast::{NodeId, tokenstream}; use rustc_ast_pretty::pprust; +use rustc_errors::Applicability; use rustc_feature::Features; use rustc_session::Session; use rustc_session::parse::feature_err; @@ -88,16 +89,17 @@ fn parse( continue; }; - // Push a metavariable with no fragment specifier at the given span - let mut missing_fragment_specifier = |span| { + let fallback_metavar_decl = + |span| TokenTree::MetaVarDecl { span, name: ident, kind: NonterminalKind::TT }; + // Emit a missing-fragment diagnostic and return a `TokenTree` fallback so parsing can + // continue. + let missing_fragment_specifier = |span, add_span| { sess.dcx().emit_err(errors::MissingFragmentSpecifier { span, - add_span: span.shrink_to_hi(), + add_span, valid: VALID_FRAGMENT_NAMES_MSG, }); - - // Fall back to a `TokenTree` since that will match anything if we continue expanding. - result.push(TokenTree::MetaVarDecl { span, name: ident, kind: NonterminalKind::TT }); + fallback_metavar_decl(span) }; // Not consuming the next token immediately, as it may not be a colon @@ -112,13 +114,39 @@ fn parse( // since if it's not a token then it will be an invalid declaration. let Some(tokenstream::TokenTree::Token(token, _)) = iter.next() else { // Invalid, return a nice source location as `var:` - missing_fragment_specifier(colon_span.with_lo(start_sp.lo())); + result.push(missing_fragment_specifier( + colon_span.with_lo(start_sp.lo()), + colon_span.shrink_to_hi(), + )); continue; }; let Some((fragment, _)) = token.ident() else { // No identifier for the fragment specifier; - missing_fragment_specifier(token.span); + if token.kind == token::Dollar + && iter.peek().is_some_and(|next| { + matches!( + next, + tokenstream::TokenTree::Token(next_token, _) + if next_token.ident().is_some() + ) + }) + { + let mut err = + sess.dcx().struct_span_err(token.span, "missing fragment specifier"); + err.note("fragment specifiers must be provided"); + err.help(VALID_FRAGMENT_NAMES_MSG); + err.span_suggestion_verbose( + token.span, + "fragment specifiers should not be prefixed with `$`", + "", + Applicability::MaybeIncorrect, + ); + err.emit(); + result.push(fallback_metavar_decl(token.span)); + } else { + result.push(missing_fragment_specifier(token.span, token.span.shrink_to_hi())); + } continue; }; @@ -146,7 +174,7 @@ fn parse( } else { // Whether it's none or some other tree, it doesn't belong to // the current meta variable, returning the original span. - missing_fragment_specifier(start_sp); + result.push(missing_fragment_specifier(start_sp, start_sp.shrink_to_hi())); } } result diff --git a/tests/ui/macros/macro-missing-fragment.rs b/tests/ui/macros/macro-missing-fragment.rs index 7ed9074020e4..827c7fc31927 100644 --- a/tests/ui/macros/macro-missing-fragment.rs +++ b/tests/ui/macros/macro-missing-fragment.rs @@ -13,6 +13,10 @@ macro_rules! unused_macro { ( $name ) => {}; //~ ERROR missing fragment } +macro_rules! accidental_dollar_prefix { + ( $test:$tt ) => {}; //~ ERROR missing fragment +} + fn main() { used_arm!(); used_macro_unused_arm!(); diff --git a/tests/ui/macros/macro-missing-fragment.stderr b/tests/ui/macros/macro-missing-fragment.stderr index 886292378d1a..b1b9bf3d8aa9 100644 --- a/tests/ui/macros/macro-missing-fragment.stderr +++ b/tests/ui/macros/macro-missing-fragment.stderr @@ -37,5 +37,19 @@ help: try adding a specifier here LL | ( $name:spec ) => {}; | +++++ -error: aborting due to 3 previous errors +error: missing fragment specifier + --> $DIR/macro-missing-fragment.rs:17:13 + | +LL | ( $test:$tt ) => {}; + | ^ + | + = note: fragment specifiers must be provided + = help: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis`, along with `expr_2021` and `pat_param` for edition compatibility +help: fragment specifiers should not be prefixed with `$` + | +LL - ( $test:$tt ) => {}; +LL + ( $test:tt ) => {}; + | + +error: aborting due to 4 previous errors