@@ -29,11 +29,12 @@ use rustc_span::hygiene::Transparency;
2929use rustc_span:: { Ident , Span , kw, sym} ;
3030use tracing:: { debug, instrument, trace, trace_span} ;
3131
32+ use super :: diagnostics:: failed_to_match_macro;
3233use super :: macro_parser:: { NamedMatches , NamedParseResult } ;
3334use super :: { SequenceRepetition , diagnostics} ;
3435use crate :: base:: {
35- DummyResult , ExpandResult , ExtCtxt , MacResult , MacroExpanderResult , SyntaxExtension ,
36- SyntaxExtensionKind , TTMacroExpander ,
36+ AttrProcMacro , DummyResult , ExpandResult , ExtCtxt , MacResult , MacroExpanderResult ,
37+ SyntaxExtension , SyntaxExtensionKind , TTMacroExpander ,
3738} ;
3839use crate :: errors;
3940use crate :: expand:: { AstFragment , AstFragmentKind , ensure_complete_parse, parse_ast_fragment} ;
@@ -129,7 +130,6 @@ pub(super) enum MacroRule {
129130 /// A function-style rule, for use with `m!()`
130131 Func { lhs : Vec < MatcherLoc > , lhs_span : Span , rhs : mbe:: TokenTree } ,
131132 /// An attr rule, for use with `#[m]`
132- #[ expect( unused) ]
133133 Attr {
134134 args : Vec < MatcherLoc > ,
135135 args_span : Span ,
@@ -180,6 +180,28 @@ impl TTMacroExpander for MacroRulesMacroExpander {
180180 }
181181}
182182
183+ impl AttrProcMacro for MacroRulesMacroExpander {
184+ fn expand (
185+ & self ,
186+ cx : & mut ExtCtxt < ' _ > ,
187+ sp : Span ,
188+ args : TokenStream ,
189+ body : TokenStream ,
190+ ) -> Result < TokenStream , ErrorGuaranteed > {
191+ expand_macro_attr (
192+ cx,
193+ sp,
194+ self . span ,
195+ self . node_id ,
196+ self . name ,
197+ self . transparency ,
198+ args,
199+ body,
200+ & self . rules ,
201+ )
202+ }
203+ }
204+
183205struct DummyExpander ( ErrorGuaranteed ) ;
184206
185207impl TTMacroExpander for DummyExpander {
@@ -212,7 +234,7 @@ pub(super) trait Tracker<'matcher> {
212234
213235 /// This is called after an arm has been parsed, either successfully or unsuccessfully. When
214236 /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
215- fn after_arm ( & mut self , _result : & NamedParseResult < Self :: Failure > ) { }
237+ fn after_arm ( & mut self , _in_body : bool , _result : & NamedParseResult < Self :: Failure > ) { }
216238
217239 /// For tracing.
218240 fn description ( ) -> & ' static str ;
@@ -298,13 +320,76 @@ fn expand_macro<'cx>(
298320 Err ( CanRetry :: Yes ) => {
299321 // Retry and emit a better error.
300322 let ( span, guar) =
301- diagnostics :: failed_to_match_macro ( cx. psess ( ) , sp, def_span, name, arg, rules) ;
323+ failed_to_match_macro ( cx. psess ( ) , sp, def_span, name, None , & arg, rules) ;
302324 cx. macro_error_and_trace_macros_diag ( ) ;
303325 DummyResult :: any ( span, guar)
304326 }
305327 }
306328}
307329
330+ /// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`.
331+ #[ instrument( skip( cx, transparency, args, body, rules) ) ]
332+ fn expand_macro_attr (
333+ cx : & mut ExtCtxt < ' _ > ,
334+ sp : Span ,
335+ def_span : Span ,
336+ node_id : NodeId ,
337+ name : Ident ,
338+ transparency : Transparency ,
339+ args : TokenStream ,
340+ body : TokenStream ,
341+ rules : & [ MacroRule ] ,
342+ ) -> Result < TokenStream , ErrorGuaranteed > {
343+ let psess = & cx. sess . psess ;
344+ // Macros defined in the current crate have a real node id,
345+ // whereas macros from an external crate have a dummy id.
346+ let is_local = node_id != DUMMY_NODE_ID ;
347+
348+ if cx. trace_macros ( ) {
349+ let msg = format ! (
350+ "expanding `$[{name}({})] {}`" ,
351+ pprust:: tts_to_string( & args) ,
352+ pprust:: tts_to_string( & body) ,
353+ ) ;
354+ trace_macros_note ( & mut cx. expansions , sp, msg) ;
355+ }
356+
357+ // Track nothing for the best performance.
358+ match try_match_macro_attr ( psess, name, & args, & body, rules, & mut NoopTracker ) {
359+ Ok ( ( i, rule, named_matches) ) => {
360+ let MacroRule :: Attr { rhs, .. } = rule else {
361+ panic ! ( "try_macro_match_attr returned non-attr rule" ) ;
362+ } ;
363+ let mbe:: TokenTree :: Delimited ( rhs_span, _, rhs) = rhs else {
364+ cx. dcx ( ) . span_bug ( sp, "malformed macro rhs" ) ;
365+ } ;
366+
367+ let id = cx. current_expansion . id ;
368+ let tts = transcribe ( psess, & named_matches, rhs, * rhs_span, transparency, id)
369+ . map_err ( |e| e. emit ( ) ) ?;
370+
371+ if cx. trace_macros ( ) {
372+ let msg = format ! ( "to `{}`" , pprust:: tts_to_string( & tts) ) ;
373+ trace_macros_note ( & mut cx. expansions , sp, msg) ;
374+ }
375+
376+ if is_local {
377+ cx. resolver . record_macro_rule_usage ( node_id, i) ;
378+ }
379+
380+ Ok ( tts)
381+ }
382+ Err ( CanRetry :: No ( guar) ) => Err ( guar) ,
383+ Err ( CanRetry :: Yes ) => {
384+ // Retry and emit a better error.
385+ let ( _, guar) =
386+ failed_to_match_macro ( cx. psess ( ) , sp, def_span, name, Some ( & args) , & body, rules) ;
387+ cx. trace_macros_diag ( ) ;
388+ Err ( guar)
389+ }
390+ }
391+ }
392+
308393pub ( super ) enum CanRetry {
309394 Yes ,
310395 /// We are not allowed to retry macro expansion as a fatal error has been emitted already.
@@ -356,7 +441,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
356441
357442 let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & parser) , lhs, track) ;
358443
359- track. after_arm ( & result) ;
444+ track. after_arm ( true , & result) ;
360445
361446 match result {
362447 Success ( named_matches) => {
@@ -391,6 +476,60 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
391476 Err ( CanRetry :: Yes )
392477}
393478
479+ /// Try expanding the macro attribute. Returns the index of the successful arm and its
480+ /// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job
481+ /// to use `track` accordingly to record all errors correctly.
482+ #[ instrument( level = "debug" , skip( psess, attr_args, attr_body, rules, track) , fields( tracking = %T :: description( ) ) ) ]
483+ pub ( super ) fn try_match_macro_attr < ' matcher , T : Tracker < ' matcher > > (
484+ psess : & ParseSess ,
485+ name : Ident ,
486+ attr_args : & TokenStream ,
487+ attr_body : & TokenStream ,
488+ rules : & ' matcher [ MacroRule ] ,
489+ track : & mut T ,
490+ ) -> Result < ( usize , & ' matcher MacroRule , NamedMatches ) , CanRetry > {
491+ // This uses the same strategy as `try_match_macro`
492+ let args_parser = parser_from_cx ( psess, attr_args. clone ( ) , T :: recovery ( ) ) ;
493+ let body_parser = parser_from_cx ( psess, attr_body. clone ( ) , T :: recovery ( ) ) ;
494+ let mut tt_parser = TtParser :: new ( name) ;
495+ for ( i, rule) in rules. iter ( ) . enumerate ( ) {
496+ let MacroRule :: Attr { args, body, .. } = rule else { continue } ;
497+
498+ let mut gated_spans_snapshot = mem:: take ( & mut * psess. gated_spans . spans . borrow_mut ( ) ) ;
499+
500+ let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & args_parser) , args, track) ;
501+ track. after_arm ( false , & result) ;
502+
503+ let mut named_matches = match result {
504+ Success ( named_matches) => named_matches,
505+ Failure ( _) => {
506+ mem:: swap ( & mut gated_spans_snapshot, & mut psess. gated_spans . spans . borrow_mut ( ) ) ;
507+ continue ;
508+ }
509+ Error ( _, _) => return Err ( CanRetry :: Yes ) ,
510+ ErrorReported ( guar) => return Err ( CanRetry :: No ( guar) ) ,
511+ } ;
512+
513+ let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & body_parser) , body, track) ;
514+ track. after_arm ( true , & result) ;
515+
516+ match result {
517+ Success ( body_named_matches) => {
518+ psess. gated_spans . merge ( gated_spans_snapshot) ;
519+ named_matches. extend ( body_named_matches) ;
520+ return Ok ( ( i, rule, named_matches) ) ;
521+ }
522+ Failure ( _) => {
523+ mem:: swap ( & mut gated_spans_snapshot, & mut psess. gated_spans . spans . borrow_mut ( ) )
524+ }
525+ Error ( _, _) => return Err ( CanRetry :: Yes ) ,
526+ ErrorReported ( guar) => return Err ( CanRetry :: No ( guar) ) ,
527+ }
528+ }
529+
530+ Err ( CanRetry :: Yes )
531+ }
532+
394533/// Converts a macro item into a syntax extension.
395534pub fn compile_declarative_macro (
396535 sess : & Session ,
@@ -401,13 +540,13 @@ pub fn compile_declarative_macro(
401540 span : Span ,
402541 node_id : NodeId ,
403542 edition : Edition ,
404- ) -> ( SyntaxExtension , usize ) {
405- let mk_syn_ext = |expander| {
406- let kind = SyntaxExtensionKind :: LegacyBang ( expander) ;
543+ ) -> ( SyntaxExtension , Option < Arc < SyntaxExtension > > , usize ) {
544+ let mk_syn_ext = |kind| {
407545 let is_local = is_defined_in_current_crate ( node_id) ;
408546 SyntaxExtension :: new ( sess, kind, span, Vec :: new ( ) , edition, ident. name , attrs, is_local)
409547 } ;
410- let dummy_syn_ext = |guar| ( mk_syn_ext ( Arc :: new ( DummyExpander ( guar) ) ) , 0 ) ;
548+ let mk_bang_ext = |expander| mk_syn_ext ( SyntaxExtensionKind :: LegacyBang ( expander) ) ;
549+ let dummy_syn_ext = |guar| ( mk_bang_ext ( Arc :: new ( DummyExpander ( guar) ) ) , None , 0 ) ;
411550
412551 let macro_rules = macro_def. macro_rules ;
413552 let exp_sep = if macro_rules { exp ! ( Semi ) } else { exp ! ( Comma ) } ;
@@ -420,10 +559,12 @@ pub fn compile_declarative_macro(
420559 let mut guar = None ;
421560 let mut check_emission = |ret : Result < ( ) , ErrorGuaranteed > | guar = guar. or ( ret. err ( ) ) ;
422561
562+ let mut has_attr_rules = false ;
423563 let mut rules = Vec :: new ( ) ;
424564
425565 while p. token != token:: Eof {
426566 let args = if p. eat_keyword_noexpect ( sym:: attr) {
567+ has_attr_rules = true ;
427568 if !features. macro_attr ( ) {
428569 feature_err ( sess, sym:: macro_attr, span, "`macro_rules!` attributes are unstable" )
429570 . emit ( ) ;
@@ -499,9 +640,12 @@ pub fn compile_declarative_macro(
499640 // Return the number of rules for unused rule linting, if this is a local macro.
500641 let nrules = if is_defined_in_current_crate ( node_id) { rules. len ( ) } else { 0 } ;
501642
502- let expander =
503- Arc :: new ( MacroRulesMacroExpander { name : ident, span, node_id, transparency, rules } ) ;
504- ( mk_syn_ext ( expander) , nrules)
643+ let exp = Arc :: new ( MacroRulesMacroExpander { name : ident, span, node_id, transparency, rules } ) ;
644+ let opt_attr_ext = has_attr_rules. then ( || {
645+ let exp = Arc :: clone ( & exp) ;
646+ Arc :: new ( mk_syn_ext ( SyntaxExtensionKind :: Attr ( exp) ) )
647+ } ) ;
648+ ( mk_bang_ext ( exp) , opt_attr_ext, nrules)
505649}
506650
507651fn check_no_eof ( sess : & Session , p : & Parser < ' _ > , msg : & ' static str ) -> Option < ErrorGuaranteed > {
0 commit comments