Skip to content

Commit d06c474

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 c651059 commit d06c474

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
@@ -28,11 +28,12 @@ use rustc_span::hygiene::Transparency;
2828
use rustc_span::{Ident, Span, kw, sym};
2929
use tracing::{debug, instrument, trace, trace_span};
3030

31+
use super::diagnostics::{failed_to_match_macro, failed_to_match_macro_attr};
3132
use super::macro_parser::{NamedMatches, NamedParseResult};
3233
use super::{SequenceRepetition, diagnostics};
3334
use crate::base::{
34-
DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension,
35-
SyntaxExtensionKind, TTMacroExpander,
35+
AttrProcMacro, DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult,
36+
SyntaxExtension, SyntaxExtensionKind, TTMacroExpander,
3637
};
3738
use crate::errors;
3839
use 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+
172194
struct DummyExpander(ErrorGuaranteed);
173195

174196
impl 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.macro_error_and_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+
297381
pub(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.
384522
pub 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

496638
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
@@ -1054,6 +1054,14 @@ pub(crate) struct NameReservedInAttributeNamespace {
10541054
pub(crate) ident: Ident,
10551055
}
10561056

1057+
#[derive(Diagnostic)]
1058+
#[diag(resolve_builtin_macro_with_non_invocation_rules)]
1059+
pub(crate) struct BuiltinMacroWithNonInvocationRules {
1060+
#[primary_span]
1061+
pub(crate) span: Span,
1062+
pub(crate) ident: Ident,
1063+
}
1064+
10571065
#[derive(Diagnostic)]
10581066
#[diag(resolve_cannot_find_builtin_macro_with_name)]
10591067
pub(crate) struct CannotFindBuiltinMacroWithName {

0 commit comments

Comments
 (0)