From f73cbd2243be8a87e533b3488f46f7b39e61abf0 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 6 Jul 2025 15:15:01 -0700 Subject: [PATCH 01/13] Add feature gate `macro_attr` for declarative `macro_rules!` attribute macros --- compiler/rustc_feature/src/unstable.rs | 2 ++ compiler/rustc_span/src/symbol.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e985e04ba3307..24e460d6649b3 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -555,6 +555,8 @@ declare_features! ( (unstable, link_arg_attribute, "1.76.0", Some(99427)), /// Allows fused `loop`/`match` for direct intraprocedural jumps. (incomplete, loop_match, "CURRENT_RUSTC_VERSION", Some(132306)), + /// Allow `macro_rules!` attribute rules + (unstable, macro_attr, "CURRENT_RUSTC_VERSION", Some(83527)), /// Give access to additional metadata about declarative macro meta-variables. (unstable, macro_metavar_expr, "1.61.0", Some(83527)), /// Provides a way to concatenate identifiers using metavariable expressions. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d54175548e30e..5224e2c737fff 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1310,6 +1310,7 @@ symbols! { lt, m68k_target_feature, macro_at_most_once_rep, + macro_attr, macro_attributes_in_derive_output, macro_concat, macro_escape, From c651059f824d7ca3642f6233484587be81a41d44 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 6 Jul 2025 19:11:30 -0700 Subject: [PATCH 02/13] mbe: Parse macro attribute rules This handles various kinds of errors, but does not allow applying the attributes yet. --- compiler/rustc_expand/messages.ftl | 3 + compiler/rustc_expand/src/errors.rs | 18 ++++ compiler/rustc_expand/src/mbe/diagnostics.rs | 3 +- compiler/rustc_expand/src/mbe/macro_check.rs | 6 +- compiler/rustc_expand/src/mbe/macro_rules.rs | 83 +++++++++++++++---- .../feature-gates/feature-gate-macro-attr.rs | 4 + .../feature-gate-macro-attr.stderr | 13 +++ tests/ui/parser/macro/macro-attr-bad.rs | 32 +++++++ tests/ui/parser/macro/macro-attr-bad.stderr | 80 ++++++++++++++++++ 9 files changed, 224 insertions(+), 18 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-macro-attr.rs create mode 100644 tests/ui/feature-gates/feature-gate-macro-attr.stderr create mode 100644 tests/ui/parser/macro/macro-attr-bad.rs create mode 100644 tests/ui/parser/macro/macro-attr-bad.stderr diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 5a53670c865d4..f192d8099b7b9 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -70,6 +70,9 @@ expand_invalid_fragment_specifier = invalid fragment specifier `{$fragment}` .help = {$help} +expand_macro_args_bad_delim = macro argument matchers require parentheses +expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)` + expand_macro_body_stability = macros cannot have body stability attributes .label = invalid body stability attribute diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index fd1391a554a8a..e58269991fcb0 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -482,3 +482,21 @@ mod metavar_exprs { pub key: MacroRulesNormalizedIdent, } } + +#[derive(Diagnostic)] +#[diag(expand_macro_args_bad_delim)] +pub(crate) struct MacroArgsBadDelim { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub sugg: MacroArgsBadDelimSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(expand_macro_args_bad_delim_sugg, applicability = "machine-applicable")] +pub(crate) struct MacroArgsBadDelimSugg { + #[suggestion_part(code = "(")] + pub open: Span, + #[suggestion_part(code = ")")] + pub close: Span, +} diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 7a280d671f413..468488ebf7d0d 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -81,11 +81,12 @@ pub(super) fn failed_to_match_macro( // Check whether there's a missing comma in this macro call, like `println!("{}" a);` if let Some((arg, comma_span)) = arg.add_comma() { for rule in rules { + let MacroRule::Func { lhs, .. } = rule else { continue }; let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed); let mut tt_parser = TtParser::new(name); if let Success(_) = - tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, &mut NoopTracker) + tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker) { if comma_span.is_dummy() { err.note("you might be missing a comma"); diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index bbdff866feba4..25987a5036635 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -193,15 +193,19 @@ struct MacroState<'a> { /// Arguments: /// - `psess` is used to emit diagnostics and lints /// - `node_id` is used to emit lints -/// - `lhs` and `rhs` represent the rule +/// - `args`, `lhs`, and `rhs` represent the rule pub(super) fn check_meta_variables( psess: &ParseSess, node_id: NodeId, + args: Option<&TokenTree>, lhs: &TokenTree, rhs: &TokenTree, ) -> Result<(), ErrorGuaranteed> { let mut guar = None; let mut binders = Binders::default(); + if let Some(args) = args { + check_binders(psess, node_id, args, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar); + } check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar); check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar); guar.map_or(Ok(()), Err) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index febe6f8b88cc7..68567169d510e 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -6,8 +6,8 @@ use std::{mem, slice}; use ast::token::IdentIsRaw; use rustc_ast::token::NtPatKind::*; use rustc_ast::token::TokenKind::*; -use rustc_ast::token::{self, NonterminalKind, Token, TokenKind}; -use rustc_ast::tokenstream::{DelimSpan, TokenStream}; +use rustc_ast::token::{self, Delimiter, NonterminalKind, Token, TokenKind}; +use rustc_ast::tokenstream::{self, DelimSpan, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId}; use rustc_ast_pretty::pprust; use rustc_attr_data_structures::{AttributeKind, find_attr}; @@ -22,7 +22,7 @@ use rustc_lint_defs::builtin::{ use rustc_parse::exp; use rustc_parse::parser::{Parser, Recovery}; use rustc_session::Session; -use rustc_session::parse::ParseSess; +use rustc_session::parse::{ParseSess, feature_err}; use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::{Ident, Span, kw, sym}; @@ -34,11 +34,13 @@ use crate::base::{ DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension, SyntaxExtensionKind, TTMacroExpander, }; +use crate::errors; use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment}; +use crate::mbe::macro_check::check_meta_variables; use crate::mbe::macro_parser::{Error, ErrorReported, Failure, MatcherLoc, Success, TtParser}; use crate::mbe::quoted::{RulePart, parse_one_tt}; use crate::mbe::transcribe::transcribe; -use crate::mbe::{self, KleeneOp, macro_check}; +use crate::mbe::{self, KleeneOp}; pub(crate) struct ParserAnyMacro<'a> { parser: Parser<'a>, @@ -122,10 +124,12 @@ impl<'a> ParserAnyMacro<'a> { } } -pub(super) struct MacroRule { - pub(super) lhs: Vec, - lhs_span: Span, - rhs: mbe::TokenTree, +pub(super) enum MacroRule { + /// A function-style rule, for use with `m!()` + Func { lhs: Vec, lhs_span: Span, rhs: mbe::TokenTree }, + /// An attr rule, for use with `#[m]` + #[expect(unused)] + Attr { args: Vec, body: Vec, lhs_span: Span, rhs: mbe::TokenTree }, } pub struct MacroRulesMacroExpander { @@ -139,8 +143,9 @@ pub struct MacroRulesMacroExpander { impl MacroRulesMacroExpander { pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> { // If the rhs contains an invocation like `compile_error!`, don't report it as unused. - let rule = &self.rules[rule_i]; - if has_compile_error_macro(&rule.rhs) { None } else { Some((&self.name, rule.lhs_span)) } + let (MacroRule::Func { lhs_span, ref rhs, .. } | MacroRule::Attr { lhs_span, ref rhs, .. }) = + self.rules[rule_i]; + if has_compile_error_macro(rhs) { None } else { Some((&self.name, lhs_span)) } } } @@ -244,14 +249,17 @@ fn expand_macro<'cx>( match try_success_result { Ok((rule_index, rule, named_matches)) => { - let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else { + let MacroRule::Func { rhs, .. } = rule else { + panic!("try_match_macro returned non-func rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { cx.dcx().span_bug(sp, "malformed macro rhs"); }; - let arm_span = rule.rhs.span(); + let arm_span = rhs_span.entire(); // rhs has holes ( `$id` and `$(...)` that need filled) let id = cx.current_expansion.id; - let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) { + let tts = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) { Ok(tts) => tts, Err(err) => { let guar = err.emit(); @@ -326,6 +334,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // Try each arm's matchers. let mut tt_parser = TtParser::new(name); for (i, rule) in rules.iter().enumerate() { + let MacroRule::Func { lhs, .. } = rule else { continue }; let _tracing_span = trace_span!("Matching arm", %i); // Take a snapshot of the state of pre-expansion gating at this point. @@ -334,7 +343,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // are not recorded. On the first `Success(..)`ful matcher, the spans are merged. let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); - let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, track); + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); track.after_arm(&result); @@ -403,6 +412,26 @@ pub fn compile_declarative_macro( let mut rules = Vec::new(); while p.token != token::Eof { + let args = if p.eat_keyword_noexpect(sym::attr) { + if !features.macro_attr() { + let msg = "`macro_rules!` attributes are unstable"; + let e = feature_err(sess, sym::macro_attr, span, msg); + return dummy_syn_ext(e.emit()); + } + if let Some(guar) = check_no_eof(sess, &p, "expected macro attr args") { + return dummy_syn_ext(guar); + } + let args = p.parse_token_tree(); + check_emission(check_args_parens(sess, &args)); + let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition); + check_emission(check_lhs(sess, node_id, &args)); + if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") { + return dummy_syn_ext(guar); + } + Some(args) + } else { + None + }; let lhs_tt = p.parse_token_tree(); let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition); check_emission(check_lhs(sess, node_id, &lhs_tt)); @@ -415,7 +444,7 @@ pub fn compile_declarative_macro( let rhs_tt = p.parse_token_tree(); let rhs_tt = parse_one_tt(rhs_tt, RulePart::Body, sess, node_id, features, edition); check_emission(check_rhs(sess, &rhs_tt)); - check_emission(macro_check::check_meta_variables(&sess.psess, node_id, &lhs_tt, &rhs_tt)); + check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs_tt)); let lhs_span = lhs_tt.span(); // Convert the lhs into `MatcherLoc` form, which is better for doing the // actual matching. @@ -424,7 +453,16 @@ pub fn compile_declarative_macro( } else { return dummy_syn_ext(guar.unwrap()); }; - rules.push(MacroRule { lhs, lhs_span, rhs: rhs_tt }); + if let Some(args) = args { + let lhs_span = args.span().to(lhs_span); + let mbe::TokenTree::Delimited(.., delimited) = args else { + return dummy_syn_ext(guar.unwrap()); + }; + let args = mbe::macro_parser::compute_locs(&delimited.tts); + rules.push(MacroRule::Attr { args, body: lhs, lhs_span, rhs: rhs_tt }); + } else { + rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt }); + } if p.token == token::Eof { break; } @@ -468,6 +506,19 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option Result<(), ErrorGuaranteed> { + // This does not handle the non-delimited case; that gets handled separately by `check_lhs`. + if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args + && *delim != Delimiter::Parenthesis + { + return Err(sess.dcx().emit_err(errors::MacroArgsBadDelim { + span: dspan.entire(), + sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close }, + })); + } + Ok(()) +} + fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> { let e1 = check_lhs_nt_follows(sess, node_id, lhs); let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs)); diff --git a/tests/ui/feature-gates/feature-gate-macro-attr.rs b/tests/ui/feature-gates/feature-gate-macro-attr.rs new file mode 100644 index 0000000000000..8419d85114725 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-attr.rs @@ -0,0 +1,4 @@ +#![crate_type = "lib"] + +macro_rules! myattr { attr() {} => {} } +//~^ ERROR `macro_rules!` attributes are unstable diff --git a/tests/ui/feature-gates/feature-gate-macro-attr.stderr b/tests/ui/feature-gates/feature-gate-macro-attr.stderr new file mode 100644 index 0000000000000..b58418527c5b0 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-attr.stderr @@ -0,0 +1,13 @@ +error[E0658]: `macro_rules!` attributes are unstable + --> $DIR/feature-gate-macro-attr.rs:3:1 + | +LL | macro_rules! myattr { attr() {} => {} } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #83527 for more information + = help: add `#![feature(macro_attr)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/parser/macro/macro-attr-bad.rs b/tests/ui/parser/macro/macro-attr-bad.rs new file mode 100644 index 0000000000000..6bab777c89d64 --- /dev/null +++ b/tests/ui/parser/macro/macro-attr-bad.rs @@ -0,0 +1,32 @@ +#![crate_type = "lib"] +#![feature(macro_attr)] + +macro_rules! attr_incomplete_1 { attr } +//~^ ERROR macro definition ended unexpectedly + +macro_rules! attr_incomplete_2 { attr() } +//~^ ERROR macro definition ended unexpectedly + +macro_rules! attr_incomplete_3 { attr() {} } +//~^ ERROR expected `=>` + +macro_rules! attr_incomplete_4 { attr() {} => } +//~^ ERROR macro definition ended unexpectedly + +macro_rules! attr_noparens_1 { attr{} {} => {} } +//~^ ERROR macro argument matchers require parentheses + +macro_rules! attr_noparens_2 { attr[] {} => {} } +//~^ ERROR macro argument matchers require parentheses + +macro_rules! attr_noparens_3 { attr _ {} => {} } +//~^ ERROR invalid macro matcher + +macro_rules! attr_dup_matcher_1 { attr() {$x:ident $x:ident} => {} } +//~^ ERROR duplicate matcher binding + +macro_rules! attr_dup_matcher_2 { attr($x:ident $x:ident) {} => {} } +//~^ ERROR duplicate matcher binding + +macro_rules! attr_dup_matcher_3 { attr($x:ident) {$x:ident} => {} } +//~^ ERROR duplicate matcher binding diff --git a/tests/ui/parser/macro/macro-attr-bad.stderr b/tests/ui/parser/macro/macro-attr-bad.stderr new file mode 100644 index 0000000000000..8ffd8e82e6ba3 --- /dev/null +++ b/tests/ui/parser/macro/macro-attr-bad.stderr @@ -0,0 +1,80 @@ +error: macro definition ended unexpectedly + --> $DIR/macro-attr-bad.rs:4:38 + | +LL | macro_rules! attr_incomplete_1 { attr } + | ^ expected macro attr args + +error: macro definition ended unexpectedly + --> $DIR/macro-attr-bad.rs:7:40 + | +LL | macro_rules! attr_incomplete_2 { attr() } + | ^ expected macro attr body + +error: expected `=>`, found end of macro arguments + --> $DIR/macro-attr-bad.rs:10:43 + | +LL | macro_rules! attr_incomplete_3 { attr() {} } + | ^ expected `=>` + +error: macro definition ended unexpectedly + --> $DIR/macro-attr-bad.rs:13:46 + | +LL | macro_rules! attr_incomplete_4 { attr() {} => } + | ^ expected right-hand side of macro rule + +error: macro argument matchers require parentheses + --> $DIR/macro-attr-bad.rs:16:36 + | +LL | macro_rules! attr_noparens_1 { attr{} {} => {} } + | ^^ + | +help: the delimiters should be `(` and `)` + | +LL - macro_rules! attr_noparens_1 { attr{} {} => {} } +LL + macro_rules! attr_noparens_1 { attr() {} => {} } + | + +error: macro argument matchers require parentheses + --> $DIR/macro-attr-bad.rs:19:36 + | +LL | macro_rules! attr_noparens_2 { attr[] {} => {} } + | ^^ + | +help: the delimiters should be `(` and `)` + | +LL - macro_rules! attr_noparens_2 { attr[] {} => {} } +LL + macro_rules! attr_noparens_2 { attr() {} => {} } + | + +error: invalid macro matcher; matchers must be contained in balanced delimiters + --> $DIR/macro-attr-bad.rs:22:37 + | +LL | macro_rules! attr_noparens_3 { attr _ {} => {} } + | ^ + +error: duplicate matcher binding + --> $DIR/macro-attr-bad.rs:25:52 + | +LL | macro_rules! attr_dup_matcher_1 { attr() {$x:ident $x:ident} => {} } + | -------- ^^^^^^^^ duplicate binding + | | + | previous binding + +error: duplicate matcher binding + --> $DIR/macro-attr-bad.rs:28:49 + | +LL | macro_rules! attr_dup_matcher_2 { attr($x:ident $x:ident) {} => {} } + | -------- ^^^^^^^^ duplicate binding + | | + | previous binding + +error: duplicate matcher binding + --> $DIR/macro-attr-bad.rs:31:51 + | +LL | macro_rules! attr_dup_matcher_3 { attr($x:ident) {$x:ident} => {} } + | -------- ^^^^^^^^ duplicate binding + | | + | previous binding + +error: aborting due to 10 previous errors + From d06c474882348521ca4ff9879f96a2132b612167 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Fri, 11 Jul 2025 01:40:12 -0700 Subject: [PATCH 03/13] mbe: Handle applying attribute rules with paths Add infrastructure to apply an attribute macro given argument tokens and body tokens. Teach the resolver to consider `macro_rules` macros when looking for an attribute via a path. This does not yet handle local `macro_rules` attributes. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 77 ++++++++- compiler/rustc_expand/src/mbe/macro_rules.rs | 166 +++++++++++++++++-- compiler/rustc_resolve/messages.ftl | 3 + compiler/rustc_resolve/src/errors.rs | 8 + compiler/rustc_resolve/src/lib.rs | 3 +- compiler/rustc_resolve/src/macros.rs | 12 +- 6 files changed, 247 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 468488ebf7d0d..e2de969111d88 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -14,7 +14,7 @@ use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx}; use crate::expand::{AstFragmentKind, parse_ast_fragment}; use crate::mbe::macro_parser::ParseResult::*; use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser}; -use crate::mbe::macro_rules::{Tracker, try_match_macro}; +use crate::mbe::macro_rules::{Tracker, try_match_macro, try_match_macro_attr}; pub(super) fn failed_to_match_macro( psess: &ParseSess, @@ -105,6 +105,70 @@ pub(super) fn failed_to_match_macro( (sp, guar) } +pub(super) fn failed_to_match_macro_attr( + psess: &ParseSess, + sp: Span, + def_span: Span, + name: Ident, + args: TokenStream, + body: TokenStream, + rules: &[MacroRule], +) -> ErrorGuaranteed { + // An error occurred, try the expansion again, tracking the expansion closely for better + // diagnostics. + let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); + + let result = try_match_macro_attr(psess, name, &args, &body, rules, &mut tracker); + if result.is_ok() { + // Nonterminal parser recovery might turn failed matches into successful ones, + // but for that it must have emitted an error already + assert!( + tracker.dcx.has_errors().is_some(), + "Macro matching returned a success on the second try" + ); + } + + if let Some((_, guar)) = tracker.result { + // An irrecoverable error occurred and has been emitted. + return guar; + } + + let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure + else { + return psess.dcx().span_delayed_bug(sp, "failed to match a macro attr"); + }; + + let span = token.span.substitute_dummy(sp); + + let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); + err.span_label(span, label); + if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { + err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro"); + } + + annotate_doc_comment(&mut err, psess.source_map(), span); + + if let Some(span) = remaining_matcher.span() { + err.span_note(span, format!("while trying to match {remaining_matcher}")); + } else { + err.note(format!("while trying to match {remaining_matcher}")); + } + + if let MatcherLoc::Token { token: expected_token } = &remaining_matcher + && (matches!(expected_token.kind, token::OpenInvisible(_)) + || matches!(token.kind, token::OpenInvisible(_))) + { + err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens"); + err.note("see for more information"); + + if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { + err.help("try using `:tt` instead in the macro definition"); + } + } + + err.emit() +} + /// The tracker used for the slow error path that collects useful info for diagnostics. struct CollectTrackerAndEmitter<'dcx, 'matcher> { dcx: DiagCtxtHandle<'dcx>, @@ -117,13 +181,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { struct BestFailure { token: Token, - position_in_tokenstream: u32, + position_in_tokenstream: (bool, u32), msg: &'static str, remaining_matcher: MatcherLoc, } impl BestFailure { - fn is_better_position(&self, position: u32) -> bool { + fn is_better_position(&self, position: (bool, u32)) -> bool { position > self.position_in_tokenstream } } @@ -143,7 +207,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } } - fn after_arm(&mut self, result: &NamedParseResult) { + fn after_arm(&mut self, in_body: bool, result: &NamedParseResult) { match result { Success(_) => { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -156,14 +220,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match Failure((token, approx_position, msg)) => { debug!(?token, ?msg, "a new failure of an arm"); + let position_in_tokenstream = (in_body, *approx_position); if self .best_failure .as_ref() - .is_none_or(|failure| failure.is_better_position(*approx_position)) + .is_none_or(|failure| failure.is_better_position(position_in_tokenstream)) { self.best_failure = Some(BestFailure { token: *token, - position_in_tokenstream: *approx_position, + position_in_tokenstream, msg, remaining_matcher: self .remaining_matcher diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 68567169d510e..fae2c2e3d0391 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -28,11 +28,12 @@ use rustc_span::hygiene::Transparency; use rustc_span::{Ident, Span, kw, sym}; use tracing::{debug, instrument, trace, trace_span}; +use super::diagnostics::{failed_to_match_macro, failed_to_match_macro_attr}; use super::macro_parser::{NamedMatches, NamedParseResult}; use super::{SequenceRepetition, diagnostics}; use crate::base::{ - DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension, - SyntaxExtensionKind, TTMacroExpander, + AttrProcMacro, DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, + SyntaxExtension, SyntaxExtensionKind, TTMacroExpander, }; use crate::errors; use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment}; @@ -128,7 +129,6 @@ pub(super) enum MacroRule { /// A function-style rule, for use with `m!()` Func { lhs: Vec, lhs_span: Span, rhs: mbe::TokenTree }, /// An attr rule, for use with `#[m]` - #[expect(unused)] Attr { args: Vec, body: Vec, lhs_span: Span, rhs: mbe::TokenTree }, } @@ -169,6 +169,28 @@ impl TTMacroExpander for MacroRulesMacroExpander { } } +impl AttrProcMacro for MacroRulesMacroExpander { + fn expand( + &self, + cx: &mut ExtCtxt<'_>, + sp: Span, + args: TokenStream, + body: TokenStream, + ) -> Result { + expand_macro_attr( + cx, + sp, + self.span, + self.node_id, + self.name, + self.transparency, + args, + body, + &self.rules, + ) + } +} + struct DummyExpander(ErrorGuaranteed); impl TTMacroExpander for DummyExpander { @@ -201,7 +223,7 @@ pub(super) trait Tracker<'matcher> { /// This is called after an arm has been parsed, either successfully or unsuccessfully. When /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`). - fn after_arm(&mut self, _result: &NamedParseResult) {} + fn after_arm(&mut self, _in_body: bool, _result: &NamedParseResult) {} /// For tracing. fn description() -> &'static str; @@ -286,14 +308,76 @@ fn expand_macro<'cx>( } Err(CanRetry::Yes) => { // Retry and emit a better error. - let (span, guar) = - diagnostics::failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules); + let (span, guar) = failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules); cx.macro_error_and_trace_macros_diag(); DummyResult::any(span, guar) } } } +/// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`. +#[instrument(skip(cx, transparency, args, body, rules))] +fn expand_macro_attr( + cx: &mut ExtCtxt<'_>, + sp: Span, + def_span: Span, + node_id: NodeId, + name: Ident, + transparency: Transparency, + args: TokenStream, + body: TokenStream, + rules: &[MacroRule], +) -> Result { + let psess = &cx.sess.psess; + // Macros defined in the current crate have a real node id, + // whereas macros from an external crate have a dummy id. + let is_local = node_id != DUMMY_NODE_ID; + + if cx.trace_macros() { + let msg = format!( + "expanding `$[{name}({})] {}`", + pprust::tts_to_string(&args), + pprust::tts_to_string(&body), + ); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + // Track nothing for the best performance. + match try_match_macro_attr(psess, name, &args, &body, rules, &mut NoopTracker) { + Ok((i, rule, named_matches)) => { + let MacroRule::Attr { rhs, .. } = rule else { + panic!("try_macro_match_attr returned non-attr rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { + cx.dcx().span_bug(sp, "malformed macro rhs"); + }; + + let id = cx.current_expansion.id; + let tts = transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) + .map_err(|e| e.emit())?; + + if cx.trace_macros() { + let msg = format!("to `{}`", pprust::tts_to_string(&tts)); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + if is_local { + cx.resolver.record_macro_rule_usage(node_id, i); + } + + Ok(tts) + } + Err(CanRetry::No(guar)) => Err(guar), + Err(CanRetry::Yes) => { + // Retry and emit a better error. + let guar = + failed_to_match_macro_attr(cx.psess(), sp, def_span, name, args, body, rules); + cx.trace_macros_diag(); + Err(guar) + } + } +} + pub(super) enum CanRetry { Yes, /// We are not allowed to retry macro expansion as a fatal error has been emitted already. @@ -345,7 +429,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); - track.after_arm(&result); + track.after_arm(true, &result); match result { Success(named_matches) => { @@ -380,6 +464,60 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( Err(CanRetry::Yes) } +/// Try expanding the macro attribute. Returns the index of the successful arm and its +/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job +/// to use `track` accordingly to record all errors correctly. +#[instrument(level = "debug", skip(psess, attr_args, attr_body, rules, track), fields(tracking = %T::description()))] +pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( + psess: &ParseSess, + name: Ident, + attr_args: &TokenStream, + attr_body: &TokenStream, + rules: &'matcher [MacroRule], + track: &mut T, +) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { + // This uses the same strategy as `try_match_macro` + let args_parser = parser_from_cx(psess, attr_args.clone(), T::recovery()); + let body_parser = parser_from_cx(psess, attr_body.clone(), T::recovery()); + let mut tt_parser = TtParser::new(name); + for (i, rule) in rules.iter().enumerate() { + let MacroRule::Attr { args, body, .. } = rule else { continue }; + + let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); + + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, track); + track.after_arm(false, &result); + + let mut named_matches = match result { + Success(named_matches) => named_matches, + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()); + continue; + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + }; + + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); + track.after_arm(true, &result); + + match result { + Success(body_named_matches) => { + psess.gated_spans.merge(gated_spans_snapshot); + named_matches.extend(body_named_matches); + return Ok((i, rule, named_matches)); + } + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + } + } + + Err(CanRetry::Yes) +} + /// Converts a macro item into a syntax extension. pub fn compile_declarative_macro( sess: &Session, @@ -390,13 +528,13 @@ pub fn compile_declarative_macro( span: Span, node_id: NodeId, edition: Edition, -) -> (SyntaxExtension, usize) { - let mk_syn_ext = |expander| { - let kind = SyntaxExtensionKind::LegacyBang(expander); +) -> (SyntaxExtension, Option>, usize) { + let mk_syn_ext = |kind| { let is_local = is_defined_in_current_crate(node_id); SyntaxExtension::new(sess, kind, span, Vec::new(), edition, ident.name, attrs, is_local) }; - let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), 0); + let mk_bang_ext = |expander| mk_syn_ext(SyntaxExtensionKind::LegacyBang(expander)); + let dummy_syn_ext = |guar| (mk_bang_ext(Arc::new(DummyExpander(guar))), None, 0); let macro_rules = macro_def.macro_rules; let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) }; @@ -409,10 +547,12 @@ pub fn compile_declarative_macro( let mut guar = None; let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err()); + let mut has_attr_rules = false; let mut rules = Vec::new(); while p.token != token::Eof { let args = if p.eat_keyword_noexpect(sym::attr) { + has_attr_rules = true; if !features.macro_attr() { let msg = "`macro_rules!` attributes are unstable"; let e = feature_err(sess, sym::macro_attr, span, msg); @@ -490,7 +630,9 @@ pub fn compile_declarative_macro( let expander = Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules }); - (mk_syn_ext(expander), nrules) + let opt_attr_ext = + has_attr_rules.then(|| Arc::new(mk_syn_ext(SyntaxExtensionKind::Attr(expander.clone())))); + (mk_bang_ext(expander), opt_attr_ext, nrules) } fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option { diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index 39e9a9cc58afd..e360914c9f519 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -53,6 +53,9 @@ resolve_binding_shadows_something_unacceptable = resolve_binding_shadows_something_unacceptable_suggestion = try specify the pattern arguments +resolve_builtin_macro_with_non_invocation_rules = + built-in macro `{$ident}` has non-invocation rules + resolve_cannot_be_reexported_crate_public = `{$ident}` is only public within the crate, and cannot be re-exported outside diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index d6b1e4de6ea1a..8a9495afe31a8 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1054,6 +1054,14 @@ pub(crate) struct NameReservedInAttributeNamespace { pub(crate) ident: Ident, } +#[derive(Diagnostic)] +#[diag(resolve_builtin_macro_with_non_invocation_rules)] +pub(crate) struct BuiltinMacroWithNonInvocationRules { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + #[derive(Diagnostic)] #[diag(resolve_cannot_find_builtin_macro_with_name)] pub(crate) struct CannotFindBuiltinMacroWithName { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 8115b87dcae06..6feb418db0998 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1027,13 +1027,14 @@ struct DeriveData { struct MacroData { ext: Arc, + attr_ext: Option>, nrules: usize, macro_rules: bool, } impl MacroData { fn new(ext: Arc) -> MacroData { - MacroData { ext, nrules: 0, macro_rules: false } + MacroData { ext, attr_ext: None, nrules: 0, macro_rules: false } } } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 20504ea609df3..d7b74ed6e371d 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -834,7 +834,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } _ => None, }, - None => self.get_macro(res).map(|macro_data| Arc::clone(¯o_data.ext)), + None => self.get_macro(res).map(|macro_data| match kind { + Some(MacroKind::Attr) if let Some(ref ext) = macro_data.attr_ext => ext.clone(), + _ => macro_data.ext.clone(), + }), }; Ok((ext, res)) } @@ -1167,7 +1170,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { node_id: NodeId, edition: Edition, ) -> MacroData { - let (mut ext, mut nrules) = compile_declarative_macro( + let (mut ext, attr_ext, mut nrules) = compile_declarative_macro( self.tcx.sess, self.tcx.features(), macro_def, @@ -1179,6 +1182,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); if let Some(builtin_name) = ext.builtin_name { + if attr_ext.is_some() { + self.dcx().emit_err(errors::BuiltinMacroWithNonInvocationRules { span, ident }); + } // The macro was marked with `#[rustc_builtin_macro]`. if let Some(builtin_ext_kind) = self.builtin_macros.get(&builtin_name) { // The macro is a built-in, replace its expander function @@ -1190,7 +1196,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - MacroData { ext: Arc::new(ext), nrules, macro_rules: macro_def.macro_rules } + MacroData { ext: Arc::new(ext), attr_ext, nrules, macro_rules: macro_def.macro_rules } } fn path_accessible( From d87bc44b7987330fb2fd8538d9329ab9cc0cf612 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 27 Jul 2025 12:06:02 -0700 Subject: [PATCH 04/13] mbe: Handle local `macro_rules` attr resolution Teach the resolver to consider `macro_rules` macros when looking for a local attribute. When looking for an attribute and considering a `macro_rules` macro, load the macro in order to see if it has attribute rules. --- compiler/rustc_resolve/src/ident.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index f5bc46bf05304..37ec728ab77b5 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -625,9 +625,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; match result { - Ok((binding, flags)) - if sub_namespace_match(binding.macro_kind(), macro_kind) => - { + Ok((binding, flags)) => { + let binding_macro_kind = binding.macro_kind(); + // If we're looking for an attribute, that might be supported by a + // `macro_rules!` macro. + if !(sub_namespace_match(binding_macro_kind, macro_kind) + || (binding_macro_kind == Some(MacroKind::Bang) + && macro_kind == Some(MacroKind::Attr) + && this + .get_macro(binding.res()) + .is_some_and(|macro_data| macro_data.attr_ext.is_some()))) + { + return None; + } + if finalize.is_none() || matches!(scope_set, ScopeSet::Late(..)) { return Some(Ok(binding)); } @@ -704,7 +715,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { innermost_result = Some((binding, flags)); } } - Ok(..) | Err(Determinacy::Determined) => {} + Err(Determinacy::Determined) => {} Err(Determinacy::Undetermined) => determinacy = Determinacy::Undetermined, } From a9662bcaaed6e589a94ae8e26953ff862e3c3bcb Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 02:00:13 -0700 Subject: [PATCH 05/13] mbe: Add test for `macro_rules` attributes Test macros via path and local macros. --- tests/ui/macros/macro-rules-attr.rs | 90 +++++++++++++++++++++ tests/ui/macros/macro-rules-attr.run.stdout | 15 ++++ tests/ui/macros/macro-rules-attr.stderr | 21 +++++ 3 files changed, 126 insertions(+) create mode 100644 tests/ui/macros/macro-rules-attr.rs create mode 100644 tests/ui/macros/macro-rules-attr.run.stdout create mode 100644 tests/ui/macros/macro-rules-attr.stderr diff --git a/tests/ui/macros/macro-rules-attr.rs b/tests/ui/macros/macro-rules-attr.rs new file mode 100644 index 0000000000000..1d6f09b53e1a5 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr.rs @@ -0,0 +1,90 @@ +//@ run-pass +//@ check-run-results +#![feature(macro_attr)] +#![warn(unused)] + +#[macro_export] +macro_rules! exported_attr { + attr($($args:tt)*) { $($body:tt)* } => { + println!( + "exported_attr: args={:?}, body={:?}", + stringify!($($args)*), + stringify!($($body)*), + ); + }; + { $($args:tt)* } => { + println!("exported_attr!({:?})", stringify!($($args)*)); + }; + attr() {} => { + unused_rule(); + }; + attr() {} => { + compile_error!(); + }; + {} => { + unused_rule(); + }; + {} => { + compile_error!(); + }; +} + +macro_rules! local_attr { + attr($($args:tt)*) { $($body:tt)* } => { + println!( + "local_attr: args={:?}, body={:?}", + stringify!($($args)*), + stringify!($($body)*), + ); + }; + { $($args:tt)* } => { + println!("local_attr!({:?})", stringify!($($args)*)); + }; + attr() {} => { //~ WARN: never used + unused_rule(); + }; + attr() {} => { + compile_error!(); + }; + {} => { //~ WARN: never used + unused_rule(); + }; + {} => { + compile_error!(); + }; +} + +fn main() { + #[crate::exported_attr] + struct S; + #[::exported_attr(arguments, key = "value")] + fn func(_arg: u32) {} + #[self::exported_attr(1)] + #[self::exported_attr(2)] + struct Twice; + + crate::exported_attr!(); + crate::exported_attr!(invoked, arguments); + + #[exported_attr] + struct S; + #[exported_attr(arguments, key = "value")] + fn func(_arg: u32) {} + #[exported_attr(1)] + #[exported_attr(2)] + struct Twice; + + exported_attr!(); + exported_attr!(invoked, arguments); + + #[local_attr] + struct S; + #[local_attr(arguments, key = "value")] + fn func(_arg: u32) {} + #[local_attr(1)] + #[local_attr(2)] + struct Twice; + + local_attr!(); + local_attr!(invoked, arguments); +} diff --git a/tests/ui/macros/macro-rules-attr.run.stdout b/tests/ui/macros/macro-rules-attr.run.stdout new file mode 100644 index 0000000000000..77aa94d54f801 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr.run.stdout @@ -0,0 +1,15 @@ +exported_attr: args="", body="struct S;" +exported_attr: args="arguments, key = \"value\"", body="fn func(_arg: u32) {}" +exported_attr: args="1", body="#[self::exported_attr(2)] struct Twice;" +exported_attr!("") +exported_attr!("invoked, arguments") +exported_attr: args="", body="struct S;" +exported_attr: args="arguments, key = \"value\"", body="fn func(_arg: u32) {}" +exported_attr: args="1", body="#[exported_attr(2)] struct Twice;" +exported_attr!("") +exported_attr!("invoked, arguments") +local_attr: args="", body="struct S;" +local_attr: args="arguments, key = \"value\"", body="fn func(_arg: u32) {}" +local_attr: args="1", body="#[local_attr(2)] struct Twice;" +local_attr!("") +local_attr!("invoked, arguments") diff --git a/tests/ui/macros/macro-rules-attr.stderr b/tests/ui/macros/macro-rules-attr.stderr new file mode 100644 index 0000000000000..5941017b1349b --- /dev/null +++ b/tests/ui/macros/macro-rules-attr.stderr @@ -0,0 +1,21 @@ +warning: rule #3 of macro `local_attr` is never used + --> $DIR/macro-rules-attr.rs:43:9 + | +LL | attr() {} => { + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/macro-rules-attr.rs:4:9 + | +LL | #![warn(unused)] + | ^^^^^^ + = note: `#[warn(unused_macro_rules)]` implied by `#[warn(unused)]` + +warning: rule #5 of macro `local_attr` is never used + --> $DIR/macro-rules-attr.rs:49:5 + | +LL | {} => { + | ^^ + +warning: 2 warnings emitted + From 84915114e9aee2cc616fdaf47dd470d0a5325f28 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 03:00:24 -0700 Subject: [PATCH 06/13] In error messages, don't assume attributes are always proc macros Now that `macro_rules` macros can define attribute rules, make sure error messages account for that. --- compiler/rustc_errors/src/emitter.rs | 2 +- .../fail/alloc/alloc_error_handler_custom.stderr | 2 +- .../alloc-error-handler-bad-signature-1.stderr | 4 ++-- .../alloc-error-handler-bad-signature-2.stderr | 4 ++-- .../alloc-error-handler-bad-signature-3.stderr | 2 +- tests/ui/allocator/not-an-allocator.stderr | 8 ++++---- tests/ui/allocator/two-allocators.stderr | 2 +- tests/ui/custom_test_frameworks/mismatch.stderr | 2 +- tests/ui/macros/macro-rules-attr-error.rs | 13 +++++++++++++ tests/ui/macros/macro-rules-attr-error.stderr | 13 +++++++++++++ tests/ui/proc-macro/span-from-proc-macro.stderr | 2 +- .../termination-trait-test-wrong-type.stderr | 2 +- tests/ui/test-attrs/issue-12997-2.stderr | 2 +- tests/ui/test-attrs/test-function-signature.stderr | 2 +- 14 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 tests/ui/macros/macro-rules-attr-error.rs create mode 100644 tests/ui/macros/macro-rules-attr-error.stderr diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 95400ac2ca3b0..030a7738b1d83 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -417,7 +417,7 @@ pub trait Emitter { if !redundant_span || always_backtrace { let msg: Cow<'static, _> = match trace.kind { ExpnKind::Macro(MacroKind::Attr, _) => { - "this procedural macro expansion".into() + "this attribute macro expansion".into() } ExpnKind::Macro(MacroKind::Derive, _) => { "this derive macro expansion".into() diff --git a/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr b/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr index 29c56ca81f752..a2a4be30eca4a 100644 --- a/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr +++ b/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr @@ -11,7 +11,7 @@ note: inside `_::__rg_oom` --> tests/fail/alloc/alloc_error_handler_custom.rs:LL:CC | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | fn alloc_error_handler(layout: Layout) -> ! { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: inside `alloc::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC diff --git a/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr b/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr index 15314fac37b2c..aad45c422e2b8 100644 --- a/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr +++ b/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-1.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: &Layout, LL | || ) -> () @@ -23,7 +23,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-1.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: &Layout, LL | || ) -> () diff --git a/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr b/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr index 2ab4263841128..581d19474191f 100644 --- a/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr +++ b/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-2.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: Layout, LL | || ) { @@ -31,7 +31,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-2.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: Layout, LL | || ) { diff --git a/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr b/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr index 3a410174f548e..91147df71eb4e 100644 --- a/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr +++ b/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr @@ -2,7 +2,7 @@ error[E0061]: this function takes 0 arguments but 1 argument was supplied --> $DIR/alloc-error-handler-bad-signature-3.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | fn oom() -> ! { | _-^^^^^^^^^^^^ LL | | loop {} diff --git a/tests/ui/allocator/not-an-allocator.stderr b/tests/ui/allocator/not-an-allocator.stderr index 079bf9334ebc3..f33a698ed7817 100644 --- a/tests/ui/allocator/not-an-allocator.stderr +++ b/tests/ui/allocator/not-an-allocator.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | @@ -12,7 +12,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | @@ -23,7 +23,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | @@ -34,7 +34,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | diff --git a/tests/ui/allocator/two-allocators.stderr b/tests/ui/allocator/two-allocators.stderr index 5308232a20b59..1a9a5910eecc4 100644 --- a/tests/ui/allocator/two-allocators.stderr +++ b/tests/ui/allocator/two-allocators.stderr @@ -4,7 +4,7 @@ error: cannot define multiple global allocators LL | static A: System = System; | -------------------------- previous global allocator defined here LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static B: System = System; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot define a new global allocator diff --git a/tests/ui/custom_test_frameworks/mismatch.stderr b/tests/ui/custom_test_frameworks/mismatch.stderr index c7798635fbf17..7cdfaa9e804ee 100644 --- a/tests/ui/custom_test_frameworks/mismatch.stderr +++ b/tests/ui/custom_test_frameworks/mismatch.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `TestDescAndFn: Testable` is not satisfied --> $DIR/mismatch.rs:9:1 | LL | #[test] - | ------- in this procedural macro expansion + | ------- in this attribute macro expansion LL | fn wrong_kind(){} | ^^^^^^^^^^^^^^^^^ the trait `Testable` is not implemented for `TestDescAndFn` | diff --git a/tests/ui/macros/macro-rules-attr-error.rs b/tests/ui/macros/macro-rules-attr-error.rs new file mode 100644 index 0000000000000..59fb32a5bfcab --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-error.rs @@ -0,0 +1,13 @@ +#![feature(macro_attr)] + +macro_rules! local_attr { + attr() { $($body:tt)* } => { + compile_error!(concat!("local_attr: ", stringify!($($body)*))); + }; + //~^^ ERROR: local_attr +} + +fn main() { + #[local_attr] + struct S; +} diff --git a/tests/ui/macros/macro-rules-attr-error.stderr b/tests/ui/macros/macro-rules-attr-error.stderr new file mode 100644 index 0000000000000..7d033ae3f03db --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-error.stderr @@ -0,0 +1,13 @@ +error: local_attr: struct S; + --> $DIR/macro-rules-attr-error.rs:5:9 + | +LL | compile_error!(concat!("local_attr: ", stringify!($($body)*))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | #[local_attr] + | ------------- in this attribute macro expansion + | + = note: this error originates in the attribute macro `local_attr` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + diff --git a/tests/ui/proc-macro/span-from-proc-macro.stderr b/tests/ui/proc-macro/span-from-proc-macro.stderr index 452c04df8779e..c79ab04eadf4e 100644 --- a/tests/ui/proc-macro/span-from-proc-macro.stderr +++ b/tests/ui/proc-macro/span-from-proc-macro.stderr @@ -10,7 +10,7 @@ LL | field: MissingType ::: $DIR/span-from-proc-macro.rs:8:1 | LL | #[error_from_attribute] - | ----------------------- in this procedural macro expansion + | ----------------------- in this attribute macro expansion error[E0412]: cannot find type `OtherMissingType` in this scope --> $DIR/auxiliary/span-from-proc-macro.rs:42:21 diff --git a/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr b/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr index b89c5e8dda840..020c2e6f24198 100644 --- a/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr +++ b/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `f32: Termination` is not satisfied --> $DIR/termination-trait-test-wrong-type.rs:6:31 | LL | #[test] - | ------- in this procedural macro expansion + | ------- in this attribute macro expansion LL | fn can_parse_zero_as_f32() -> Result { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Termination` is not implemented for `f32` | diff --git a/tests/ui/test-attrs/issue-12997-2.stderr b/tests/ui/test-attrs/issue-12997-2.stderr index 1123630a4a1f1..41d0074ad1a20 100644 --- a/tests/ui/test-attrs/issue-12997-2.stderr +++ b/tests/ui/test-attrs/issue-12997-2.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/issue-12997-2.rs:8:1 | LL | #[bench] - | -------- in this procedural macro expansion + | -------- in this attribute macro expansion LL | fn bar(x: isize) { } | ^^^^^^^^^^^^^^^^^^^^ | | diff --git a/tests/ui/test-attrs/test-function-signature.stderr b/tests/ui/test-attrs/test-function-signature.stderr index c025163c0bd9c..55d09970b3203 100644 --- a/tests/ui/test-attrs/test-function-signature.stderr +++ b/tests/ui/test-attrs/test-function-signature.stderr @@ -26,7 +26,7 @@ error[E0277]: the trait bound `i32: Termination` is not satisfied --> $DIR/test-function-signature.rs:9:13 | LL | #[test] - | ------- in this procedural macro expansion + | ------- in this attribute macro expansion LL | fn bar() -> i32 { | ^^^ the trait `Termination` is not implemented for `i32` | From 356403a39ab71c8d276c9e8f7e1c49c29c817581 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 03:56:04 -0700 Subject: [PATCH 07/13] mbe: Emit an error if an invoked macro has no invocation rules Add a corresponding test. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 19 ++++++++++++++++--- tests/ui/macros/macro-rules-attr-error.rs | 2 ++ tests/ui/macros/macro-rules-attr-error.stderr | 11 ++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index e2de969111d88..b7be07c9373e1 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -7,7 +7,7 @@ use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; use rustc_span::source_map::SourceMap; -use rustc_span::{ErrorGuaranteed, Ident, Span}; +use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span}; use tracing::debug; use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx}; @@ -25,6 +25,12 @@ pub(super) fn failed_to_match_macro( rules: &[MacroRule], ) -> (Span, ErrorGuaranteed) { debug!("failed to match macro"); + let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { + psess.source_map().guess_head_span(def_span) + } else { + DUMMY_SP + }; + // An error occurred, try the expansion again, tracking the expansion closely for better // diagnostics. let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); @@ -47,6 +53,13 @@ pub(super) fn failed_to_match_macro( let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else { + if !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) { + let mut err = psess.dcx().struct_span_err(sp, "invoked macro has no invocation rules"); + if !def_head_span.is_dummy() { + err.span_label(def_head_span, "this macro has no rules to invoke"); + } + return (sp, err.emit()); + } return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro")); }; @@ -54,8 +67,8 @@ pub(super) fn failed_to_match_macro( let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); err.span_label(span, label); - if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { - err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro"); + if !def_head_span.is_dummy() { + err.span_label(def_head_span, "when calling this macro"); } annotate_doc_comment(&mut err, psess.source_map(), span); diff --git a/tests/ui/macros/macro-rules-attr-error.rs b/tests/ui/macros/macro-rules-attr-error.rs index 59fb32a5bfcab..f8e1028656cfa 100644 --- a/tests/ui/macros/macro-rules-attr-error.rs +++ b/tests/ui/macros/macro-rules-attr-error.rs @@ -10,4 +10,6 @@ macro_rules! local_attr { fn main() { #[local_attr] struct S; + + local_attr!(arg); //~ ERROR: invoked macro has no invocation rules } diff --git a/tests/ui/macros/macro-rules-attr-error.stderr b/tests/ui/macros/macro-rules-attr-error.stderr index 7d033ae3f03db..3e342271d4549 100644 --- a/tests/ui/macros/macro-rules-attr-error.stderr +++ b/tests/ui/macros/macro-rules-attr-error.stderr @@ -9,5 +9,14 @@ LL | #[local_attr] | = note: this error originates in the attribute macro `local_attr` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 1 previous error +error: invoked macro has no invocation rules + --> $DIR/macro-rules-attr-error.rs:14:5 + | +LL | macro_rules! local_attr { + | ----------------------- this macro has no rules to invoke +... +LL | local_attr!(arg); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors From fe5ebbf06ac322e13934a4101d4e887978da12dc Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 04:12:12 -0700 Subject: [PATCH 08/13] mbe: Fix error message for using a macro with no `attr` rules as an attribute Avoid saying "a declarative macro cannot be used as an attribute macro"; instead, say that the macro has no `attr` rules. --- compiler/rustc_resolve/messages.ftl | 2 +- .../ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index e360914c9f519..381c5ceb1d439 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -246,7 +246,7 @@ resolve_lowercase_self = .suggestion = try using `Self` resolve_macro_cannot_use_as_attr = - `{$ident}` exists, but a declarative macro cannot be used as an attribute macro + `{$ident}` exists, but has no `attr` rules resolve_macro_cannot_use_as_derive = `{$ident}` exists, but a declarative macro cannot be used as a derive macro diff --git a/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr b/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr index e5b913b208dca..77f8bef83a452 100644 --- a/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr +++ b/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr @@ -11,7 +11,7 @@ error: cannot find attribute `sample` in this scope --> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:5:3 | LL | macro_rules! sample { () => {} } - | ------ `sample` exists, but a declarative macro cannot be used as an attribute macro + | ------ `sample` exists, but has no `attr` rules LL | LL | #[sample] | ^^^^^^ From 82d889045a6a49dc77d2e986e083608688a7de48 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 13:30:28 -0700 Subject: [PATCH 09/13] mbe: Refine error message for attribute argument delimiters --- compiler/rustc_expand/messages.ftl | 2 +- tests/ui/parser/macro/macro-attr-bad.rs | 4 ++-- tests/ui/parser/macro/macro-attr-bad.stderr | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index f192d8099b7b9..1f8f3be68092a 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -70,7 +70,7 @@ expand_invalid_fragment_specifier = invalid fragment specifier `{$fragment}` .help = {$help} -expand_macro_args_bad_delim = macro argument matchers require parentheses +expand_macro_args_bad_delim = macro attribute argument matchers require parentheses expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)` expand_macro_body_stability = diff --git a/tests/ui/parser/macro/macro-attr-bad.rs b/tests/ui/parser/macro/macro-attr-bad.rs index 6bab777c89d64..4313a4d04abf4 100644 --- a/tests/ui/parser/macro/macro-attr-bad.rs +++ b/tests/ui/parser/macro/macro-attr-bad.rs @@ -14,10 +14,10 @@ macro_rules! attr_incomplete_4 { attr() {} => } //~^ ERROR macro definition ended unexpectedly macro_rules! attr_noparens_1 { attr{} {} => {} } -//~^ ERROR macro argument matchers require parentheses +//~^ ERROR macro attribute argument matchers require parentheses macro_rules! attr_noparens_2 { attr[] {} => {} } -//~^ ERROR macro argument matchers require parentheses +//~^ ERROR macro attribute argument matchers require parentheses macro_rules! attr_noparens_3 { attr _ {} => {} } //~^ ERROR invalid macro matcher diff --git a/tests/ui/parser/macro/macro-attr-bad.stderr b/tests/ui/parser/macro/macro-attr-bad.stderr index 8ffd8e82e6ba3..4d286b6664937 100644 --- a/tests/ui/parser/macro/macro-attr-bad.stderr +++ b/tests/ui/parser/macro/macro-attr-bad.stderr @@ -22,7 +22,7 @@ error: macro definition ended unexpectedly LL | macro_rules! attr_incomplete_4 { attr() {} => } | ^ expected right-hand side of macro rule -error: macro argument matchers require parentheses +error: macro attribute argument matchers require parentheses --> $DIR/macro-attr-bad.rs:16:36 | LL | macro_rules! attr_noparens_1 { attr{} {} => {} } @@ -34,7 +34,7 @@ LL - macro_rules! attr_noparens_1 { attr{} {} => {} } LL + macro_rules! attr_noparens_1 { attr() {} => {} } | -error: macro argument matchers require parentheses +error: macro attribute argument matchers require parentheses --> $DIR/macro-attr-bad.rs:19:36 | LL | macro_rules! attr_noparens_2 { attr[] {} => {} } From 6792cf500593d29d9c105ffa8f840ee70d59ee66 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 14:36:33 -0700 Subject: [PATCH 10/13] Call `Arc::clone` rather than `.clone()` to make clippy happy --- compiler/rustc_expand/src/mbe/macro_rules.rs | 11 ++++++----- compiler/rustc_resolve/src/macros.rs | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index fae2c2e3d0391..1ab63caac2755 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -628,11 +628,12 @@ pub fn compile_declarative_macro( // Return the number of rules for unused rule linting, if this is a local macro. let nrules = if is_defined_in_current_crate(node_id) { rules.len() } else { 0 }; - let expander = - Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules }); - let opt_attr_ext = - has_attr_rules.then(|| Arc::new(mk_syn_ext(SyntaxExtensionKind::Attr(expander.clone())))); - (mk_bang_ext(expander), opt_attr_ext, nrules) + let exp = Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules }); + let opt_attr_ext = has_attr_rules.then(|| { + let exp = Arc::clone(&exp); + Arc::new(mk_syn_ext(SyntaxExtensionKind::Attr(exp))) + }); + (mk_bang_ext(exp), opt_attr_ext, nrules) } fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option { diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index d7b74ed6e371d..0e03f83e33bb4 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -835,8 +835,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { _ => None, }, None => self.get_macro(res).map(|macro_data| match kind { - Some(MacroKind::Attr) if let Some(ref ext) = macro_data.attr_ext => ext.clone(), - _ => macro_data.ext.clone(), + Some(MacroKind::Attr) if let Some(ref ext) = macro_data.attr_ext => Arc::clone(ext), + _ => Arc::clone(¯o_data.ext), }), }; Ok((ext, res)) From 7b6c6d62252f74798c0d1333bb5a6c536ef92226 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 15:01:54 -0700 Subject: [PATCH 11/13] mbe: Unify failed_to_match_macro variants `failed_to_match_macro` and `failed_to_match_macro_attr` had a fair bit in common. Unify the two, and handle the differences with conditionals. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 81 +++----------------- compiler/rustc_expand/src/mbe/macro_rules.rs | 9 ++- 2 files changed, 17 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index b7be07c9373e1..0f490c57c38e9 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -21,7 +21,8 @@ pub(super) fn failed_to_match_macro( sp: Span, def_span: Span, name: Ident, - arg: TokenStream, + attr_args: Option<&TokenStream>, + body: &TokenStream, rules: &[MacroRule], ) -> (Span, ErrorGuaranteed) { debug!("failed to match macro"); @@ -35,7 +36,11 @@ pub(super) fn failed_to_match_macro( // diagnostics. let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); - let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker); + let try_success_result = if let Some(attr_args) = attr_args { + try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker) + } else { + try_match_macro(psess, name, body, rules, &mut tracker) + }; if try_success_result.is_ok() { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -53,7 +58,7 @@ pub(super) fn failed_to_match_macro( let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else { - if !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) { + if attr_args.is_none() && !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) { let mut err = psess.dcx().struct_span_err(sp, "invoked macro has no invocation rules"); if !def_head_span.is_dummy() { err.span_label(def_head_span, "this macro has no rules to invoke"); @@ -92,10 +97,12 @@ pub(super) fn failed_to_match_macro( } // Check whether there's a missing comma in this macro call, like `println!("{}" a);` - if let Some((arg, comma_span)) = arg.add_comma() { + if attr_args.is_none() + && let Some((body, comma_span)) = body.add_comma() + { for rule in rules { let MacroRule::Func { lhs, .. } = rule else { continue }; - let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed); + let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed); let mut tt_parser = TtParser::new(name); if let Success(_) = @@ -118,70 +125,6 @@ pub(super) fn failed_to_match_macro( (sp, guar) } -pub(super) fn failed_to_match_macro_attr( - psess: &ParseSess, - sp: Span, - def_span: Span, - name: Ident, - args: TokenStream, - body: TokenStream, - rules: &[MacroRule], -) -> ErrorGuaranteed { - // An error occurred, try the expansion again, tracking the expansion closely for better - // diagnostics. - let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); - - let result = try_match_macro_attr(psess, name, &args, &body, rules, &mut tracker); - if result.is_ok() { - // Nonterminal parser recovery might turn failed matches into successful ones, - // but for that it must have emitted an error already - assert!( - tracker.dcx.has_errors().is_some(), - "Macro matching returned a success on the second try" - ); - } - - if let Some((_, guar)) = tracker.result { - // An irrecoverable error occurred and has been emitted. - return guar; - } - - let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure - else { - return psess.dcx().span_delayed_bug(sp, "failed to match a macro attr"); - }; - - let span = token.span.substitute_dummy(sp); - - let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); - err.span_label(span, label); - if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { - err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro"); - } - - annotate_doc_comment(&mut err, psess.source_map(), span); - - if let Some(span) = remaining_matcher.span() { - err.span_note(span, format!("while trying to match {remaining_matcher}")); - } else { - err.note(format!("while trying to match {remaining_matcher}")); - } - - if let MatcherLoc::Token { token: expected_token } = &remaining_matcher - && (matches!(expected_token.kind, token::OpenInvisible(_)) - || matches!(token.kind, token::OpenInvisible(_))) - { - err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens"); - err.note("see for more information"); - - if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { - err.help("try using `:tt` instead in the macro definition"); - } - } - - err.emit() -} - /// The tracker used for the slow error path that collects useful info for diagnostics. struct CollectTrackerAndEmitter<'dcx, 'matcher> { dcx: DiagCtxtHandle<'dcx>, diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 1ab63caac2755..db3bf50c495e0 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -28,7 +28,7 @@ use rustc_span::hygiene::Transparency; use rustc_span::{Ident, Span, kw, sym}; use tracing::{debug, instrument, trace, trace_span}; -use super::diagnostics::{failed_to_match_macro, failed_to_match_macro_attr}; +use super::diagnostics::failed_to_match_macro; use super::macro_parser::{NamedMatches, NamedParseResult}; use super::{SequenceRepetition, diagnostics}; use crate::base::{ @@ -308,7 +308,8 @@ fn expand_macro<'cx>( } Err(CanRetry::Yes) => { // Retry and emit a better error. - let (span, guar) = failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules); + let (span, guar) = + failed_to_match_macro(cx.psess(), sp, def_span, name, None, &arg, rules); cx.macro_error_and_trace_macros_diag(); DummyResult::any(span, guar) } @@ -370,8 +371,8 @@ fn expand_macro_attr( Err(CanRetry::No(guar)) => Err(guar), Err(CanRetry::Yes) => { // Retry and emit a better error. - let guar = - failed_to_match_macro_attr(cx.psess(), sp, def_span, name, args, body, rules); + let (_, guar) = + failed_to_match_macro(cx.psess(), sp, def_span, name, Some(&args), &body, rules); cx.trace_macros_diag(); Err(guar) } From abf7f7b39b32a26648d3c10c05ac19a9803e535f Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 15:28:20 -0700 Subject: [PATCH 12/13] mbe: Add a test confirming that a macro attribute can apply itself recursively This allows a macro attribute to implement default arguments by reapplying itself with the defaults filled in, for instance. --- tests/ui/macros/macro-rules-attr-nested.rs | 24 +++++++++++++++++++ .../macros/macro-rules-attr-nested.run.stdout | 3 +++ 2 files changed, 27 insertions(+) create mode 100644 tests/ui/macros/macro-rules-attr-nested.rs create mode 100644 tests/ui/macros/macro-rules-attr-nested.run.stdout diff --git a/tests/ui/macros/macro-rules-attr-nested.rs b/tests/ui/macros/macro-rules-attr-nested.rs new file mode 100644 index 0000000000000..af5c30f00dd9d --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-nested.rs @@ -0,0 +1,24 @@ +//@ run-pass +//@ check-run-results +#![feature(macro_attr)] + +macro_rules! nest { + attr() { struct $name:ident; } => { + println!("nest"); + #[nest(1)] + struct $name; + }; + attr(1) { struct $name:ident; } => { + println!("nest(1)"); + #[nest(2)] + struct $name; + }; + attr(2) { struct $name:ident; } => { + println!("nest(2)"); + }; +} + +fn main() { + #[nest] + struct S; +} diff --git a/tests/ui/macros/macro-rules-attr-nested.run.stdout b/tests/ui/macros/macro-rules-attr-nested.run.stdout new file mode 100644 index 0000000000000..46017f9ae084f --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-nested.run.stdout @@ -0,0 +1,3 @@ +nest +nest(1) +nest(2) From 9317dbca83153dc9e5943b335f95bcb36b802d72 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 28 Jul 2025 15:33:09 -0700 Subject: [PATCH 13/13] mbe: Add a test checking for infinite recursion in macro attributes --- .../macros/macro-rules-attr-infinite-recursion.rs | 12 ++++++++++++ .../macro-rules-attr-infinite-recursion.stderr | 14 ++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/ui/macros/macro-rules-attr-infinite-recursion.rs create mode 100644 tests/ui/macros/macro-rules-attr-infinite-recursion.stderr diff --git a/tests/ui/macros/macro-rules-attr-infinite-recursion.rs b/tests/ui/macros/macro-rules-attr-infinite-recursion.rs new file mode 100644 index 0000000000000..dc54c32cad364 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-infinite-recursion.rs @@ -0,0 +1,12 @@ +#![crate_type = "lib"] +#![feature(macro_attr)] + +macro_rules! attr { + attr() { $($body:tt)* } => { + #[attr] $($body)* + }; + //~^^ ERROR: recursion limit reached +} + +#[attr] +struct S; diff --git a/tests/ui/macros/macro-rules-attr-infinite-recursion.stderr b/tests/ui/macros/macro-rules-attr-infinite-recursion.stderr new file mode 100644 index 0000000000000..7d9a94338f51f --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-infinite-recursion.stderr @@ -0,0 +1,14 @@ +error: recursion limit reached while expanding `#[attr]` + --> $DIR/macro-rules-attr-infinite-recursion.rs:6:9 + | +LL | #[attr] $($body)* + | ^^^^^^^ +... +LL | #[attr] + | ------- in this attribute macro expansion + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`macro_rules_attr_infinite_recursion`) + = note: this error originates in the attribute macro `attr` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error +