Skip to content

Commit 34be8ab

Browse files
committed
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.
1 parent 0cc0b11 commit 34be8ab

File tree

4 files changed

+185
-28
lines changed

4 files changed

+185
-28
lines changed

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
1414
use crate::expand::{AstFragmentKind, parse_ast_fragment};
1515
use crate::mbe::macro_parser::ParseResult::*;
1616
use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
17-
use crate::mbe::macro_rules::{Tracker, try_match_macro};
17+
use crate::mbe::macro_rules::{Tracker, try_match_macro, try_match_macro_attr};
1818

1919
pub(super) fn failed_to_match_macro(
2020
psess: &ParseSess,
2121
sp: Span,
2222
def_span: Span,
2323
name: Ident,
24-
arg: TokenStream,
24+
attr_args: Option<&TokenStream>,
25+
body: &TokenStream,
2526
rules: &[MacroRule],
2627
) -> (Span, ErrorGuaranteed) {
2728
debug!("failed to match macro");
@@ -35,7 +36,11 @@ pub(super) fn failed_to_match_macro(
3536
// diagnostics.
3637
let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
3738

38-
let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker);
39+
let try_success_result = if let Some(attr_args) = attr_args {
40+
try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
41+
} else {
42+
try_match_macro(psess, name, body, rules, &mut tracker)
43+
};
3944

4045
if try_success_result.is_ok() {
4146
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -56,7 +61,7 @@ pub(super) fn failed_to_match_macro(
5661
// FIXME: we should report this at macro resolution time, as we do for
5762
// `resolve_macro_cannot_use_as_attr`. We can do that once we track multiple macro kinds for a
5863
// Def.
59-
if !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) {
64+
if attr_args.is_none() && !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) {
6065
let msg = format!("macro has no rules for function-like invocation `{name}!`");
6166
let mut err = psess.dcx().struct_span_err(sp, msg);
6267
if !def_head_span.is_dummy() {
@@ -97,10 +102,12 @@ pub(super) fn failed_to_match_macro(
97102
}
98103

99104
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
100-
if let Some((arg, comma_span)) = arg.add_comma() {
105+
if attr_args.is_none()
106+
&& let Some((body, comma_span)) = body.add_comma()
107+
{
101108
for rule in rules {
102109
let MacroRule::Func { lhs, .. } = rule else { continue };
103-
let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
110+
let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed);
104111
let mut tt_parser = TtParser::new(name);
105112

106113
if let Success(_) =
@@ -135,13 +142,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> {
135142

136143
struct BestFailure {
137144
token: Token,
138-
position_in_tokenstream: u32,
145+
position_in_tokenstream: (bool, u32),
139146
msg: &'static str,
140147
remaining_matcher: MatcherLoc,
141148
}
142149

143150
impl BestFailure {
144-
fn is_better_position(&self, position: u32) -> bool {
151+
fn is_better_position(&self, position: (bool, u32)) -> bool {
145152
position > self.position_in_tokenstream
146153
}
147154
}
@@ -161,7 +168,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
161168
}
162169
}
163170

164-
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
171+
fn after_arm(&mut self, in_body: bool, result: &NamedParseResult<Self::Failure>) {
165172
match result {
166173
Success(_) => {
167174
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -174,14 +181,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
174181
Failure((token, approx_position, msg)) => {
175182
debug!(?token, ?msg, "a new failure of an arm");
176183

184+
let position_in_tokenstream = (in_body, *approx_position);
177185
if self
178186
.best_failure
179187
.as_ref()
180-
.is_none_or(|failure| failure.is_better_position(*approx_position))
188+
.is_none_or(|failure| failure.is_better_position(position_in_tokenstream))
181189
{
182190
self.best_failure = Some(BestFailure {
183191
token: *token,
184-
position_in_tokenstream: *approx_position,
192+
position_in_tokenstream,
185193
msg,
186194
remaining_matcher: self
187195
.remaining_matcher

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 157 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ use rustc_span::hygiene::Transparency;
2929
use rustc_span::{Ident, Span, kw, sym};
3030
use tracing::{debug, instrument, trace, trace_span};
3131

32+
use super::diagnostics::failed_to_match_macro;
3233
use super::macro_parser::{NamedMatches, NamedParseResult};
3334
use super::{SequenceRepetition, diagnostics};
3435
use crate::base::{
35-
DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension,
36-
SyntaxExtensionKind, TTMacroExpander,
36+
AttrProcMacro, DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult,
37+
SyntaxExtension, SyntaxExtensionKind, TTMacroExpander,
3738
};
3839
use crate::errors;
3940
use 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+
183205
struct DummyExpander(ErrorGuaranteed);
184206

185207
impl 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+
308393
pub(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.
395534
pub 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

507651
fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option<ErrorGuaranteed> {

compiler/rustc_resolve/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1028,13 +1028,14 @@ struct DeriveData {
10281028

10291029
struct MacroData {
10301030
ext: Arc<SyntaxExtension>,
1031+
attr_ext: Option<Arc<SyntaxExtension>>,
10311032
nrules: usize,
10321033
macro_rules: bool,
10331034
}
10341035

10351036
impl MacroData {
10361037
fn new(ext: Arc<SyntaxExtension>) -> MacroData {
1037-
MacroData { ext, nrules: 0, macro_rules: false }
1038+
MacroData { ext, attr_ext: None, nrules: 0, macro_rules: false }
10381039
}
10391040
}
10401041

0 commit comments

Comments
 (0)