@@ -28,11 +28,12 @@ use rustc_span::hygiene::Transparency;
2828use rustc_span:: { Ident , Span , kw, sym} ;
2929use tracing:: { debug, instrument, trace, trace_span} ;
3030
31+ use super :: diagnostics:: { failed_to_match_macro, failed_to_match_macro_attr} ;
3132use super :: macro_parser:: { NamedMatches , NamedParseResult } ;
3233use super :: { SequenceRepetition , diagnostics} ;
3334use crate :: base:: {
34- DummyResult , ExpandResult , ExtCtxt , MacResult , MacroExpanderResult , SyntaxExtension ,
35- SyntaxExtensionKind , TTMacroExpander ,
35+ AttrProcMacro , DummyResult , ExpandResult , ExtCtxt , MacResult , MacroExpanderResult ,
36+ SyntaxExtension , SyntaxExtensionKind , TTMacroExpander ,
3637} ;
3738use crate :: errors;
3839use crate :: expand:: { AstFragment , AstFragmentKind , ensure_complete_parse, parse_ast_fragment} ;
@@ -128,7 +129,6 @@ pub(super) enum MacroRule {
128129 /// A function-style rule, for use with `m!()`
129130 Func { lhs : Vec < MatcherLoc > , lhs_span : Span , rhs : mbe:: TokenTree } ,
130131 /// An attr rule, for use with `#[m]`
131- #[ expect( unused) ]
132132 Attr { args : Vec < MatcherLoc > , body : Vec < MatcherLoc > , lhs_span : Span , rhs : mbe:: TokenTree } ,
133133}
134134
@@ -169,6 +169,28 @@ impl TTMacroExpander for MacroRulesMacroExpander {
169169 }
170170}
171171
172+ impl AttrProcMacro for MacroRulesMacroExpander {
173+ fn expand (
174+ & self ,
175+ cx : & mut ExtCtxt < ' _ > ,
176+ sp : Span ,
177+ args : TokenStream ,
178+ body : TokenStream ,
179+ ) -> Result < TokenStream , ErrorGuaranteed > {
180+ expand_macro_attr (
181+ cx,
182+ sp,
183+ self . span ,
184+ self . node_id ,
185+ self . name ,
186+ self . transparency ,
187+ args,
188+ body,
189+ & self . rules ,
190+ )
191+ }
192+ }
193+
172194struct DummyExpander ( ErrorGuaranteed ) ;
173195
174196impl TTMacroExpander for DummyExpander {
@@ -201,7 +223,7 @@ pub(super) trait Tracker<'matcher> {
201223
202224 /// This is called after an arm has been parsed, either successfully or unsuccessfully. When
203225 /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
204- fn after_arm ( & mut self , _result : & NamedParseResult < Self :: Failure > ) { }
226+ fn after_arm ( & mut self , _in_body : bool , _result : & NamedParseResult < Self :: Failure > ) { }
205227
206228 /// For tracing.
207229 fn description ( ) -> & ' static str ;
@@ -286,14 +308,76 @@ fn expand_macro<'cx>(
286308 }
287309 Err ( CanRetry :: Yes ) => {
288310 // Retry and emit a better error.
289- let ( span, guar) =
290- diagnostics:: failed_to_match_macro ( cx. psess ( ) , sp, def_span, name, arg, rules) ;
311+ let ( span, guar) = failed_to_match_macro ( cx. psess ( ) , sp, def_span, name, arg, rules) ;
291312 cx. trace_macros_diag ( ) ;
292313 DummyResult :: any ( span, guar)
293314 }
294315 }
295316}
296317
318+ /// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`.
319+ #[ instrument( skip( cx, transparency, args, body, rules) ) ]
320+ fn expand_macro_attr (
321+ cx : & mut ExtCtxt < ' _ > ,
322+ sp : Span ,
323+ def_span : Span ,
324+ node_id : NodeId ,
325+ name : Ident ,
326+ transparency : Transparency ,
327+ args : TokenStream ,
328+ body : TokenStream ,
329+ rules : & [ MacroRule ] ,
330+ ) -> Result < TokenStream , ErrorGuaranteed > {
331+ let psess = & cx. sess . psess ;
332+ // Macros defined in the current crate have a real node id,
333+ // whereas macros from an external crate have a dummy id.
334+ let is_local = node_id != DUMMY_NODE_ID ;
335+
336+ if cx. trace_macros ( ) {
337+ let msg = format ! (
338+ "expanding `$[{name}({})] {}`" ,
339+ pprust:: tts_to_string( & args) ,
340+ pprust:: tts_to_string( & body) ,
341+ ) ;
342+ trace_macros_note ( & mut cx. expansions , sp, msg) ;
343+ }
344+
345+ // Track nothing for the best performance.
346+ match try_match_macro_attr ( psess, name, & args, & body, rules, & mut NoopTracker ) {
347+ Ok ( ( i, rule, named_matches) ) => {
348+ let MacroRule :: Attr { rhs, .. } = rule else {
349+ panic ! ( "try_macro_match_attr returned non-attr rule" ) ;
350+ } ;
351+ let mbe:: TokenTree :: Delimited ( rhs_span, _, rhs) = rhs else {
352+ cx. dcx ( ) . span_bug ( sp, "malformed macro rhs" ) ;
353+ } ;
354+
355+ let id = cx. current_expansion . id ;
356+ let tts = transcribe ( psess, & named_matches, rhs, * rhs_span, transparency, id)
357+ . map_err ( |e| e. emit ( ) ) ?;
358+
359+ if cx. trace_macros ( ) {
360+ let msg = format ! ( "to `{}`" , pprust:: tts_to_string( & tts) ) ;
361+ trace_macros_note ( & mut cx. expansions , sp, msg) ;
362+ }
363+
364+ if is_local {
365+ cx. resolver . record_macro_rule_usage ( node_id, i) ;
366+ }
367+
368+ Ok ( tts)
369+ }
370+ Err ( CanRetry :: No ( guar) ) => Err ( guar) ,
371+ Err ( CanRetry :: Yes ) => {
372+ // Retry and emit a better error.
373+ let guar =
374+ failed_to_match_macro_attr ( cx. psess ( ) , sp, def_span, name, args, body, rules) ;
375+ cx. trace_macros_diag ( ) ;
376+ Err ( guar)
377+ }
378+ }
379+ }
380+
297381pub ( super ) enum CanRetry {
298382 Yes ,
299383 /// 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>>(
345429
346430 let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & parser) , lhs, track) ;
347431
348- track. after_arm ( & result) ;
432+ track. after_arm ( true , & result) ;
349433
350434 match result {
351435 Success ( named_matches) => {
@@ -380,6 +464,60 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
380464 Err ( CanRetry :: Yes )
381465}
382466
467+ /// Try expanding the macro attribute. Returns the index of the successful arm and its
468+ /// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job
469+ /// to use `track` accordingly to record all errors correctly.
470+ #[ instrument( level = "debug" , skip( psess, attr_args, attr_body, rules, track) , fields( tracking = %T :: description( ) ) ) ]
471+ pub ( super ) fn try_match_macro_attr < ' matcher , T : Tracker < ' matcher > > (
472+ psess : & ParseSess ,
473+ name : Ident ,
474+ attr_args : & TokenStream ,
475+ attr_body : & TokenStream ,
476+ rules : & ' matcher [ MacroRule ] ,
477+ track : & mut T ,
478+ ) -> Result < ( usize , & ' matcher MacroRule , NamedMatches ) , CanRetry > {
479+ // This uses the same strategy as `try_match_macro`
480+ let args_parser = parser_from_cx ( psess, attr_args. clone ( ) , T :: recovery ( ) ) ;
481+ let body_parser = parser_from_cx ( psess, attr_body. clone ( ) , T :: recovery ( ) ) ;
482+ let mut tt_parser = TtParser :: new ( name) ;
483+ for ( i, rule) in rules. iter ( ) . enumerate ( ) {
484+ let MacroRule :: Attr { args, body, .. } = rule else { continue } ;
485+
486+ let mut gated_spans_snapshot = mem:: take ( & mut * psess. gated_spans . spans . borrow_mut ( ) ) ;
487+
488+ let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & args_parser) , args, track) ;
489+ track. after_arm ( false , & result) ;
490+
491+ let mut named_matches = match result {
492+ Success ( named_matches) => named_matches,
493+ Failure ( _) => {
494+ mem:: swap ( & mut gated_spans_snapshot, & mut psess. gated_spans . spans . borrow_mut ( ) ) ;
495+ continue ;
496+ }
497+ Error ( _, _) => return Err ( CanRetry :: Yes ) ,
498+ ErrorReported ( guar) => return Err ( CanRetry :: No ( guar) ) ,
499+ } ;
500+
501+ let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & body_parser) , body, track) ;
502+ track. after_arm ( true , & result) ;
503+
504+ match result {
505+ Success ( body_named_matches) => {
506+ psess. gated_spans . merge ( gated_spans_snapshot) ;
507+ named_matches. extend ( body_named_matches) ;
508+ return Ok ( ( i, rule, named_matches) ) ;
509+ }
510+ Failure ( _) => {
511+ mem:: swap ( & mut gated_spans_snapshot, & mut psess. gated_spans . spans . borrow_mut ( ) )
512+ }
513+ Error ( _, _) => return Err ( CanRetry :: Yes ) ,
514+ ErrorReported ( guar) => return Err ( CanRetry :: No ( guar) ) ,
515+ }
516+ }
517+
518+ Err ( CanRetry :: Yes )
519+ }
520+
383521/// Converts a macro item into a syntax extension.
384522pub fn compile_declarative_macro (
385523 sess : & Session ,
@@ -390,13 +528,13 @@ pub fn compile_declarative_macro(
390528 span : Span ,
391529 node_id : NodeId ,
392530 edition : Edition ,
393- ) -> ( SyntaxExtension , usize ) {
394- let mk_syn_ext = |expander| {
395- let kind = SyntaxExtensionKind :: LegacyBang ( expander) ;
531+ ) -> ( SyntaxExtension , Option < Arc < SyntaxExtension > > , usize ) {
532+ let mk_syn_ext = |kind| {
396533 let is_local = is_defined_in_current_crate ( node_id) ;
397534 SyntaxExtension :: new ( sess, kind, span, Vec :: new ( ) , edition, ident. name , attrs, is_local)
398535 } ;
399- let dummy_syn_ext = |guar| ( mk_syn_ext ( Arc :: new ( DummyExpander ( guar) ) ) , 0 ) ;
536+ let mk_bang_ext = |expander| mk_syn_ext ( SyntaxExtensionKind :: LegacyBang ( expander) ) ;
537+ let dummy_syn_ext = |guar| ( mk_bang_ext ( Arc :: new ( DummyExpander ( guar) ) ) , None , 0 ) ;
400538
401539 let macro_rules = macro_def. macro_rules ;
402540 let exp_sep = if macro_rules { exp ! ( Semi ) } else { exp ! ( Comma ) } ;
@@ -409,10 +547,12 @@ pub fn compile_declarative_macro(
409547 let mut guar = None ;
410548 let mut check_emission = |ret : Result < ( ) , ErrorGuaranteed > | guar = guar. or ( ret. err ( ) ) ;
411549
550+ let mut has_attr_rules = false ;
412551 let mut rules = Vec :: new ( ) ;
413552
414553 while p. token != token:: Eof {
415554 let args = if p. eat_keyword_noexpect ( sym:: attr) {
555+ has_attr_rules = true ;
416556 if !features. macro_attr ( ) {
417557 let msg = "`macro_rules!` attributes are unstable" ;
418558 let e = feature_err ( sess, sym:: macro_attr, span, msg) ;
@@ -490,7 +630,9 @@ pub fn compile_declarative_macro(
490630
491631 let expander =
492632 Arc :: new ( MacroRulesMacroExpander { name : ident, span, node_id, transparency, rules } ) ;
493- ( mk_syn_ext ( expander) , nrules)
633+ let opt_attr_ext =
634+ has_attr_rules. then ( || Arc :: new ( mk_syn_ext ( SyntaxExtensionKind :: Attr ( expander. clone ( ) ) ) ) ) ;
635+ ( mk_bang_ext ( expander) , opt_attr_ext, nrules)
494636}
495637
496638fn check_no_eof ( sess : & Session , p : & Parser < ' _ > , msg : & ' static str ) -> Option < ErrorGuaranteed > {
0 commit comments