Skip to content

Commit b773168

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 e2c9de3 commit b773168

File tree

6 files changed

+247
-22
lines changed

6 files changed

+247
-22
lines changed

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ 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,
@@ -105,6 +105,70 @@ pub(super) fn failed_to_match_macro(
105105
(sp, guar)
106106
}
107107

108+
pub(super) fn failed_to_match_macro_attr(
109+
psess: &ParseSess,
110+
sp: Span,
111+
def_span: Span,
112+
name: Ident,
113+
args: TokenStream,
114+
body: TokenStream,
115+
rules: &[MacroRule],
116+
) -> ErrorGuaranteed {
117+
// An error occurred, try the expansion again, tracking the expansion closely for better
118+
// diagnostics.
119+
let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
120+
121+
let result = try_match_macro_attr(psess, name, &args, &body, rules, &mut tracker);
122+
if result.is_ok() {
123+
// Nonterminal parser recovery might turn failed matches into successful ones,
124+
// but for that it must have emitted an error already
125+
assert!(
126+
tracker.dcx.has_errors().is_some(),
127+
"Macro matching returned a success on the second try"
128+
);
129+
}
130+
131+
if let Some((_, guar)) = tracker.result {
132+
// An irrecoverable error occurred and has been emitted.
133+
return guar;
134+
}
135+
136+
let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
137+
else {
138+
return psess.dcx().span_delayed_bug(sp, "failed to match a macro attr");
139+
};
140+
141+
let span = token.span.substitute_dummy(sp);
142+
143+
let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None));
144+
err.span_label(span, label);
145+
if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
146+
err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro");
147+
}
148+
149+
annotate_doc_comment(&mut err, psess.source_map(), span);
150+
151+
if let Some(span) = remaining_matcher.span() {
152+
err.span_note(span, format!("while trying to match {remaining_matcher}"));
153+
} else {
154+
err.note(format!("while trying to match {remaining_matcher}"));
155+
}
156+
157+
if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
158+
&& (matches!(expected_token.kind, token::OpenInvisible(_))
159+
|| matches!(token.kind, token::OpenInvisible(_)))
160+
{
161+
err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
162+
err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
163+
164+
if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
165+
err.help("try using `:tt` instead in the macro definition");
166+
}
167+
}
168+
169+
err.emit()
170+
}
171+
108172
/// The tracker used for the slow error path that collects useful info for diagnostics.
109173
struct CollectTrackerAndEmitter<'dcx, 'matcher> {
110174
dcx: DiagCtxtHandle<'dcx>,
@@ -117,13 +181,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> {
117181

118182
struct BestFailure {
119183
token: Token,
120-
position_in_tokenstream: u32,
184+
position_in_tokenstream: (bool, u32),
121185
msg: &'static str,
122186
remaining_matcher: MatcherLoc,
123187
}
124188

125189
impl BestFailure {
126-
fn is_better_position(&self, position: u32) -> bool {
190+
fn is_better_position(&self, position: (bool, u32)) -> bool {
127191
position > self.position_in_tokenstream
128192
}
129193
}
@@ -143,7 +207,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
143207
}
144208
}
145209

146-
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
210+
fn after_arm(&mut self, in_body: bool, result: &NamedParseResult<Self::Failure>) {
147211
match result {
148212
Success(_) => {
149213
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -156,14 +220,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
156220
Failure((token, approx_position, msg)) => {
157221
debug!(?token, ?msg, "a new failure of an arm");
158222

223+
let position_in_tokenstream = (in_body, *approx_position);
159224
if self
160225
.best_failure
161226
.as_ref()
162-
.is_none_or(|failure| failure.is_better_position(*approx_position))
227+
.is_none_or(|failure| failure.is_better_position(position_in_tokenstream))
163228
{
164229
self.best_failure = Some(BestFailure {
165230
token: *token,
166-
position_in_tokenstream: *approx_position,
231+
position_in_tokenstream,
167232
msg,
168233
remaining_matcher: self
169234
.remaining_matcher

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 154 additions & 12 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, failed_to_match_macro_attr};
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 { args: Vec<MatcherLoc>, body: Vec<MatcherLoc>, lhs_span: Span, rhs: mbe::TokenTree },
134134
}
135135

@@ -170,6 +170,28 @@ impl TTMacroExpander for MacroRulesMacroExpander {
170170
}
171171
}
172172

173+
impl AttrProcMacro for MacroRulesMacroExpander {
174+
fn expand(
175+
&self,
176+
cx: &mut ExtCtxt<'_>,
177+
sp: Span,
178+
args: TokenStream,
179+
body: TokenStream,
180+
) -> Result<TokenStream, ErrorGuaranteed> {
181+
expand_macro_attr(
182+
cx,
183+
sp,
184+
self.span,
185+
self.node_id,
186+
self.name,
187+
self.transparency,
188+
args,
189+
body,
190+
&self.rules,
191+
)
192+
}
193+
}
194+
173195
struct DummyExpander(ErrorGuaranteed);
174196

175197
impl TTMacroExpander for DummyExpander {
@@ -202,7 +224,7 @@ pub(super) trait Tracker<'matcher> {
202224

203225
/// This is called after an arm has been parsed, either successfully or unsuccessfully. When
204226
/// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
205-
fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {}
227+
fn after_arm(&mut self, _in_body: bool, _result: &NamedParseResult<Self::Failure>) {}
206228

207229
/// For tracing.
208230
fn description() -> &'static str;
@@ -287,14 +309,76 @@ fn expand_macro<'cx>(
287309
}
288310
Err(CanRetry::Yes) => {
289311
// Retry and emit a better error.
290-
let (span, guar) =
291-
diagnostics::failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules);
312+
let (span, guar) = failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules);
292313
cx.macro_error_and_trace_macros_diag();
293314
DummyResult::any(span, guar)
294315
}
295316
}
296317
}
297318

319+
/// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`.
320+
#[instrument(skip(cx, transparency, args, body, rules))]
321+
fn expand_macro_attr(
322+
cx: &mut ExtCtxt<'_>,
323+
sp: Span,
324+
def_span: Span,
325+
node_id: NodeId,
326+
name: Ident,
327+
transparency: Transparency,
328+
args: TokenStream,
329+
body: TokenStream,
330+
rules: &[MacroRule],
331+
) -> Result<TokenStream, ErrorGuaranteed> {
332+
let psess = &cx.sess.psess;
333+
// Macros defined in the current crate have a real node id,
334+
// whereas macros from an external crate have a dummy id.
335+
let is_local = node_id != DUMMY_NODE_ID;
336+
337+
if cx.trace_macros() {
338+
let msg = format!(
339+
"expanding `$[{name}({})] {}`",
340+
pprust::tts_to_string(&args),
341+
pprust::tts_to_string(&body),
342+
);
343+
trace_macros_note(&mut cx.expansions, sp, msg);
344+
}
345+
346+
// Track nothing for the best performance.
347+
match try_match_macro_attr(psess, name, &args, &body, rules, &mut NoopTracker) {
348+
Ok((i, rule, named_matches)) => {
349+
let MacroRule::Attr { rhs, .. } = rule else {
350+
panic!("try_macro_match_attr returned non-attr rule");
351+
};
352+
let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
353+
cx.dcx().span_bug(sp, "malformed macro rhs");
354+
};
355+
356+
let id = cx.current_expansion.id;
357+
let tts = transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id)
358+
.map_err(|e| e.emit())?;
359+
360+
if cx.trace_macros() {
361+
let msg = format!("to `{}`", pprust::tts_to_string(&tts));
362+
trace_macros_note(&mut cx.expansions, sp, msg);
363+
}
364+
365+
if is_local {
366+
cx.resolver.record_macro_rule_usage(node_id, i);
367+
}
368+
369+
Ok(tts)
370+
}
371+
Err(CanRetry::No(guar)) => Err(guar),
372+
Err(CanRetry::Yes) => {
373+
// Retry and emit a better error.
374+
let guar =
375+
failed_to_match_macro_attr(cx.psess(), sp, def_span, name, args, body, rules);
376+
cx.trace_macros_diag();
377+
Err(guar)
378+
}
379+
}
380+
}
381+
298382
pub(super) enum CanRetry {
299383
Yes,
300384
/// We are not allowed to retry macro expansion as a fatal error has been emitted already.
@@ -346,7 +430,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
346430

347431
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track);
348432

349-
track.after_arm(&result);
433+
track.after_arm(true, &result);
350434

351435
match result {
352436
Success(named_matches) => {
@@ -381,6 +465,60 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
381465
Err(CanRetry::Yes)
382466
}
383467

468+
/// Try expanding the macro attribute. Returns the index of the successful arm and its
469+
/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job
470+
/// to use `track` accordingly to record all errors correctly.
471+
#[instrument(level = "debug", skip(psess, attr_args, attr_body, rules, track), fields(tracking = %T::description()))]
472+
pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>(
473+
psess: &ParseSess,
474+
name: Ident,
475+
attr_args: &TokenStream,
476+
attr_body: &TokenStream,
477+
rules: &'matcher [MacroRule],
478+
track: &mut T,
479+
) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> {
480+
// This uses the same strategy as `try_match_macro`
481+
let args_parser = parser_from_cx(psess, attr_args.clone(), T::recovery());
482+
let body_parser = parser_from_cx(psess, attr_body.clone(), T::recovery());
483+
let mut tt_parser = TtParser::new(name);
484+
for (i, rule) in rules.iter().enumerate() {
485+
let MacroRule::Attr { args, body, .. } = rule else { continue };
486+
487+
let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut());
488+
489+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, track);
490+
track.after_arm(false, &result);
491+
492+
let mut named_matches = match result {
493+
Success(named_matches) => named_matches,
494+
Failure(_) => {
495+
mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut());
496+
continue;
497+
}
498+
Error(_, _) => return Err(CanRetry::Yes),
499+
ErrorReported(guar) => return Err(CanRetry::No(guar)),
500+
};
501+
502+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track);
503+
track.after_arm(true, &result);
504+
505+
match result {
506+
Success(body_named_matches) => {
507+
psess.gated_spans.merge(gated_spans_snapshot);
508+
named_matches.extend(body_named_matches);
509+
return Ok((i, rule, named_matches));
510+
}
511+
Failure(_) => {
512+
mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut())
513+
}
514+
Error(_, _) => return Err(CanRetry::Yes),
515+
ErrorReported(guar) => return Err(CanRetry::No(guar)),
516+
}
517+
}
518+
519+
Err(CanRetry::Yes)
520+
}
521+
384522
/// Converts a macro item into a syntax extension.
385523
pub fn compile_declarative_macro(
386524
sess: &Session,
@@ -391,13 +529,13 @@ pub fn compile_declarative_macro(
391529
span: Span,
392530
node_id: NodeId,
393531
edition: Edition,
394-
) -> (SyntaxExtension, usize) {
395-
let mk_syn_ext = |expander| {
396-
let kind = SyntaxExtensionKind::LegacyBang(expander);
532+
) -> (SyntaxExtension, Option<Arc<SyntaxExtension>>, usize) {
533+
let mk_syn_ext = |kind| {
397534
let is_local = is_defined_in_current_crate(node_id);
398535
SyntaxExtension::new(sess, kind, span, Vec::new(), edition, ident.name, attrs, is_local)
399536
};
400-
let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), 0);
537+
let mk_bang_ext = |expander| mk_syn_ext(SyntaxExtensionKind::LegacyBang(expander));
538+
let dummy_syn_ext = |guar| (mk_bang_ext(Arc::new(DummyExpander(guar))), None, 0);
401539

402540
let macro_rules = macro_def.macro_rules;
403541
let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) };
@@ -410,10 +548,12 @@ pub fn compile_declarative_macro(
410548
let mut guar = None;
411549
let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err());
412550

551+
let mut has_attr_rules = false;
413552
let mut rules = Vec::new();
414553

415554
while p.token != token::Eof {
416555
let args = if p.eat_keyword_noexpect(sym::attr) {
556+
has_attr_rules = true;
417557
if !features.macro_attr() {
418558
let msg = "`macro_rules!` attributes are unstable";
419559
let e = feature_err(sess, sym::macro_attr, span, msg);
@@ -491,7 +631,9 @@ pub fn compile_declarative_macro(
491631

492632
let expander =
493633
Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules });
494-
(mk_syn_ext(expander), nrules)
634+
let opt_attr_ext =
635+
has_attr_rules.then(|| Arc::new(mk_syn_ext(SyntaxExtensionKind::Attr(expander.clone()))));
636+
(mk_bang_ext(expander), opt_attr_ext, nrules)
495637
}
496638

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

compiler/rustc_resolve/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ resolve_binding_shadows_something_unacceptable =
5353
resolve_binding_shadows_something_unacceptable_suggestion =
5454
try specify the pattern arguments
5555
56+
resolve_builtin_macro_with_non_invocation_rules =
57+
built-in macro `{$ident}` has non-invocation rules
58+
5659
resolve_cannot_be_reexported_crate_public =
5760
`{$ident}` is only public within the crate, and cannot be re-exported outside
5861

compiler/rustc_resolve/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,14 @@ pub(crate) struct NameReservedInAttributeNamespace {
10571057
pub(crate) ident: Ident,
10581058
}
10591059

1060+
#[derive(Diagnostic)]
1061+
#[diag(resolve_builtin_macro_with_non_invocation_rules)]
1062+
pub(crate) struct BuiltinMacroWithNonInvocationRules {
1063+
#[primary_span]
1064+
pub(crate) span: Span,
1065+
pub(crate) ident: Ident,
1066+
}
1067+
10601068
#[derive(Diagnostic)]
10611069
#[diag(resolve_cannot_find_builtin_macro_with_name)]
10621070
pub(crate) struct CannotFindBuiltinMacroWithName {

0 commit comments

Comments
 (0)