@@ -29,11 +29,12 @@ use rustc_span::hygiene::Transparency;
29
29
use rustc_span:: { Ident , Span , kw, sym} ;
30
30
use tracing:: { debug, instrument, trace, trace_span} ;
31
31
32
+ use super :: diagnostics:: failed_to_match_macro;
32
33
use super :: macro_parser:: { NamedMatches , NamedParseResult } ;
33
34
use super :: { SequenceRepetition , diagnostics} ;
34
35
use crate :: base:: {
35
- DummyResult , ExpandResult , ExtCtxt , MacResult , MacroExpanderResult , SyntaxExtension ,
36
- SyntaxExtensionKind , TTMacroExpander ,
36
+ AttrProcMacro , DummyResult , ExpandResult , ExtCtxt , MacResult , MacroExpanderResult ,
37
+ SyntaxExtension , SyntaxExtensionKind , TTMacroExpander ,
37
38
} ;
38
39
use crate :: errors;
39
40
use crate :: expand:: { AstFragment , AstFragmentKind , ensure_complete_parse, parse_ast_fragment} ;
@@ -129,7 +130,6 @@ pub(super) enum MacroRule {
129
130
/// A function-style rule, for use with `m!()`
130
131
Func { lhs : Vec < MatcherLoc > , lhs_span : Span , rhs : mbe:: TokenTree } ,
131
132
/// An attr rule, for use with `#[m]`
132
- #[ expect( unused) ]
133
133
Attr {
134
134
args : Vec < MatcherLoc > ,
135
135
args_span : Span ,
@@ -180,6 +180,28 @@ impl TTMacroExpander for MacroRulesMacroExpander {
180
180
}
181
181
}
182
182
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
+
183
205
struct DummyExpander ( ErrorGuaranteed ) ;
184
206
185
207
impl TTMacroExpander for DummyExpander {
@@ -212,7 +234,7 @@ pub(super) trait Tracker<'matcher> {
212
234
213
235
/// This is called after an arm has been parsed, either successfully or unsuccessfully. When
214
236
/// 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 > ) { }
216
238
217
239
/// For tracing.
218
240
fn description ( ) -> & ' static str ;
@@ -298,13 +320,76 @@ fn expand_macro<'cx>(
298
320
Err ( CanRetry :: Yes ) => {
299
321
// Retry and emit a better error.
300
322
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) ;
302
324
cx. macro_error_and_trace_macros_diag ( ) ;
303
325
DummyResult :: any ( span, guar)
304
326
}
305
327
}
306
328
}
307
329
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
+
308
393
pub ( super ) enum CanRetry {
309
394
Yes ,
310
395
/// 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>>(
356
441
357
442
let result = tt_parser. parse_tt ( & mut Cow :: Borrowed ( & parser) , lhs, track) ;
358
443
359
- track. after_arm ( & result) ;
444
+ track. after_arm ( true , & result) ;
360
445
361
446
match result {
362
447
Success ( named_matches) => {
@@ -391,6 +476,60 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
391
476
Err ( CanRetry :: Yes )
392
477
}
393
478
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
+
394
533
/// Converts a macro item into a syntax extension.
395
534
pub fn compile_declarative_macro (
396
535
sess : & Session ,
@@ -401,13 +540,13 @@ pub fn compile_declarative_macro(
401
540
span : Span ,
402
541
node_id : NodeId ,
403
542
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| {
407
545
let is_local = is_defined_in_current_crate ( node_id) ;
408
546
SyntaxExtension :: new ( sess, kind, span, Vec :: new ( ) , edition, ident. name , attrs, is_local)
409
547
} ;
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 ) ;
411
550
412
551
let macro_rules = macro_def. macro_rules ;
413
552
let exp_sep = if macro_rules { exp ! ( Semi ) } else { exp ! ( Comma ) } ;
@@ -420,10 +559,12 @@ pub fn compile_declarative_macro(
420
559
let mut guar = None ;
421
560
let mut check_emission = |ret : Result < ( ) , ErrorGuaranteed > | guar = guar. or ( ret. err ( ) ) ;
422
561
562
+ let mut has_attr_rules = false ;
423
563
let mut rules = Vec :: new ( ) ;
424
564
425
565
while p. token != token:: Eof {
426
566
let args = if p. eat_keyword_noexpect ( sym:: attr) {
567
+ has_attr_rules = true ;
427
568
if !features. macro_attr ( ) {
428
569
feature_err ( sess, sym:: macro_attr, span, "`macro_rules!` attributes are unstable" )
429
570
. emit ( ) ;
@@ -499,9 +640,12 @@ pub fn compile_declarative_macro(
499
640
// Return the number of rules for unused rule linting, if this is a local macro.
500
641
let nrules = if is_defined_in_current_crate ( node_id) { rules. len ( ) } else { 0 } ;
501
642
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)
505
649
}
506
650
507
651
fn check_no_eof ( sess : & Session , p : & Parser < ' _ > , msg : & ' static str ) -> Option < ErrorGuaranteed > {
0 commit comments