Skip to content

Commit bad0d45

Browse files
committed
mbe: Parse macro attribute rules
This handles various kinds of errors, but does not allow applying the attributes yet. This adds the feature gate `macro_attr`.
1 parent 2054a0c commit bad0d45

File tree

11 files changed

+238
-20
lines changed

11 files changed

+238
-20
lines changed

compiler/rustc_expand/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ expand_invalid_fragment_specifier =
7070
invalid fragment specifier `{$fragment}`
7171
.help = {$help}
7272
73+
expand_macro_args_bad_delim = macro attribute argument matchers require parentheses
74+
expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)`
75+
7376
expand_macro_body_stability =
7477
macros cannot have body stability attributes
7578
.label = invalid body stability attribute

compiler/rustc_expand/src/errors.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,21 @@ mod metavar_exprs {
482482
pub key: MacroRulesNormalizedIdent,
483483
}
484484
}
485+
486+
#[derive(Diagnostic)]
487+
#[diag(expand_macro_args_bad_delim)]
488+
pub(crate) struct MacroArgsBadDelim {
489+
#[primary_span]
490+
pub span: Span,
491+
#[subdiagnostic]
492+
pub sugg: MacroArgsBadDelimSugg,
493+
}
494+
495+
#[derive(Subdiagnostic)]
496+
#[multipart_suggestion(expand_macro_args_bad_delim_sugg, applicability = "machine-applicable")]
497+
pub(crate) struct MacroArgsBadDelimSugg {
498+
#[suggestion_part(code = "(")]
499+
pub open: Span,
500+
#[suggestion_part(code = ")")]
501+
pub close: Span,
502+
}

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,12 @@ pub(super) fn failed_to_match_macro(
8181
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
8282
if let Some((arg, comma_span)) = arg.add_comma() {
8383
for rule in rules {
84+
let MacroRule::Func { lhs, .. } = rule else { continue };
8485
let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
8586
let mut tt_parser = TtParser::new(name);
8687

8788
if let Success(_) =
88-
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, &mut NoopTracker)
89+
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
8990
{
9091
if comma_span.is_dummy() {
9192
err.note("you might be missing a comma");

compiler/rustc_expand/src/mbe/macro_check.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,19 @@ struct MacroState<'a> {
193193
/// Arguments:
194194
/// - `psess` is used to emit diagnostics and lints
195195
/// - `node_id` is used to emit lints
196-
/// - `lhs` and `rhs` represent the rule
196+
/// - `args`, `lhs`, and `rhs` represent the rule
197197
pub(super) fn check_meta_variables(
198198
psess: &ParseSess,
199199
node_id: NodeId,
200+
args: Option<&TokenTree>,
200201
lhs: &TokenTree,
201202
rhs: &TokenTree,
202203
) -> Result<(), ErrorGuaranteed> {
203204
let mut guar = None;
204205
let mut binders = Binders::default();
206+
if let Some(args) = args {
207+
check_binders(psess, node_id, args, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
208+
}
205209
check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
206210
check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar);
207211
guar.map_or(Ok(()), Err)

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use std::{mem, slice};
66
use ast::token::IdentIsRaw;
77
use rustc_ast::token::NtPatKind::*;
88
use rustc_ast::token::TokenKind::*;
9-
use rustc_ast::token::{self, NonterminalKind, Token, TokenKind};
10-
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
9+
use rustc_ast::token::{self, Delimiter, NonterminalKind, Token, TokenKind};
10+
use rustc_ast::tokenstream::{self, DelimSpan, TokenStream};
1111
use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId};
1212
use rustc_ast_pretty::pprust;
1313
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
14-
use rustc_errors::{Applicability, Diag, ErrorGuaranteed};
14+
use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan};
1515
use rustc_feature::Features;
1616
use rustc_hir as hir;
1717
use rustc_hir::attrs::AttributeKind;
@@ -23,7 +23,7 @@ use rustc_lint_defs::builtin::{
2323
use rustc_parse::exp;
2424
use rustc_parse::parser::{Parser, Recovery};
2525
use rustc_session::Session;
26-
use rustc_session::parse::ParseSess;
26+
use rustc_session::parse::{ParseSess, feature_err};
2727
use rustc_span::edition::Edition;
2828
use rustc_span::hygiene::Transparency;
2929
use rustc_span::{Ident, Span, kw, sym};
@@ -35,11 +35,13 @@ use crate::base::{
3535
DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension,
3636
SyntaxExtensionKind, TTMacroExpander,
3737
};
38+
use crate::errors;
3839
use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment};
40+
use crate::mbe::macro_check::check_meta_variables;
3941
use crate::mbe::macro_parser::{Error, ErrorReported, Failure, MatcherLoc, Success, TtParser};
4042
use crate::mbe::quoted::{RulePart, parse_one_tt};
4143
use crate::mbe::transcribe::transcribe;
42-
use crate::mbe::{self, KleeneOp, macro_check};
44+
use crate::mbe::{self, KleeneOp};
4345

4446
pub(crate) struct ParserAnyMacro<'a> {
4547
parser: Parser<'a>,
@@ -123,10 +125,18 @@ impl<'a> ParserAnyMacro<'a> {
123125
}
124126
}
125127

126-
pub(super) struct MacroRule {
127-
pub(super) lhs: Vec<MatcherLoc>,
128-
lhs_span: Span,
129-
rhs: mbe::TokenTree,
128+
pub(super) enum MacroRule {
129+
/// A function-style rule, for use with `m!()`
130+
Func { lhs: Vec<MatcherLoc>, lhs_span: Span, rhs: mbe::TokenTree },
131+
/// An attr rule, for use with `#[m]`
132+
#[expect(unused)]
133+
Attr {
134+
args: Vec<MatcherLoc>,
135+
args_span: Span,
136+
body: Vec<MatcherLoc>,
137+
body_span: Span,
138+
rhs: mbe::TokenTree,
139+
},
130140
}
131141

132142
pub struct MacroRulesMacroExpander {
@@ -138,10 +148,15 @@ pub struct MacroRulesMacroExpander {
138148
}
139149

140150
impl MacroRulesMacroExpander {
141-
pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> {
151+
pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, MultiSpan)> {
142152
// If the rhs contains an invocation like `compile_error!`, don't report it as unused.
143-
let rule = &self.rules[rule_i];
144-
if has_compile_error_macro(&rule.rhs) { None } else { Some((&self.name, rule.lhs_span)) }
153+
let (span, rhs) = match self.rules[rule_i] {
154+
MacroRule::Func { lhs_span, ref rhs, .. } => (MultiSpan::from_span(lhs_span), rhs),
155+
MacroRule::Attr { args_span, body_span, ref rhs, .. } => {
156+
(MultiSpan::from_spans(vec![args_span, body_span]), rhs)
157+
}
158+
};
159+
if has_compile_error_macro(rhs) { None } else { Some((&self.name, span)) }
145160
}
146161
}
147162

@@ -245,14 +260,17 @@ fn expand_macro<'cx>(
245260

246261
match try_success_result {
247262
Ok((rule_index, rule, named_matches)) => {
248-
let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else {
263+
let MacroRule::Func { rhs, .. } = rule else {
264+
panic!("try_match_macro returned non-func rule");
265+
};
266+
let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
249267
cx.dcx().span_bug(sp, "malformed macro rhs");
250268
};
251-
let arm_span = rule.rhs.span();
269+
let arm_span = rhs_span.entire();
252270

253271
// rhs has holes ( `$id` and `$(...)` that need filled)
254272
let id = cx.current_expansion.id;
255-
let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) {
273+
let tts = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) {
256274
Ok(tts) => tts,
257275
Err(err) => {
258276
let guar = err.emit();
@@ -327,6 +345,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
327345
// Try each arm's matchers.
328346
let mut tt_parser = TtParser::new(name);
329347
for (i, rule) in rules.iter().enumerate() {
348+
let MacroRule::Func { lhs, .. } = rule else { continue };
330349
let _tracing_span = trace_span!("Matching arm", %i);
331350

332351
// Take a snapshot of the state of pre-expansion gating at this point.
@@ -335,7 +354,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
335354
// are not recorded. On the first `Success(..)`ful matcher, the spans are merged.
336355
let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut());
337356

338-
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, track);
357+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track);
339358

340359
track.after_arm(&result);
341360

@@ -404,6 +423,25 @@ pub fn compile_declarative_macro(
404423
let mut rules = Vec::new();
405424

406425
while p.token != token::Eof {
426+
let args = if p.eat_keyword_noexpect(sym::attr) {
427+
if !features.macro_attr() {
428+
feature_err(sess, sym::macro_attr, span, "`macro_rules!` attributes are unstable")
429+
.emit();
430+
}
431+
if let Some(guar) = check_no_eof(sess, &p, "expected macro attr args") {
432+
return dummy_syn_ext(guar);
433+
}
434+
let args = p.parse_token_tree();
435+
check_args_parens(sess, &args);
436+
let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition);
437+
check_emission(check_lhs(sess, node_id, &args));
438+
if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") {
439+
return dummy_syn_ext(guar);
440+
}
441+
Some(args)
442+
} else {
443+
None
444+
};
407445
let lhs_tt = p.parse_token_tree();
408446
let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition);
409447
check_emission(check_lhs(sess, node_id, &lhs_tt));
@@ -416,7 +454,7 @@ pub fn compile_declarative_macro(
416454
let rhs_tt = p.parse_token_tree();
417455
let rhs_tt = parse_one_tt(rhs_tt, RulePart::Body, sess, node_id, features, edition);
418456
check_emission(check_rhs(sess, &rhs_tt));
419-
check_emission(macro_check::check_meta_variables(&sess.psess, node_id, &lhs_tt, &rhs_tt));
457+
check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs_tt));
420458
let lhs_span = lhs_tt.span();
421459
// Convert the lhs into `MatcherLoc` form, which is better for doing the
422460
// actual matching.
@@ -425,7 +463,17 @@ pub fn compile_declarative_macro(
425463
} else {
426464
return dummy_syn_ext(guar.unwrap());
427465
};
428-
rules.push(MacroRule { lhs, lhs_span, rhs: rhs_tt });
466+
if let Some(args) = args {
467+
let args_span = args.span();
468+
let mbe::TokenTree::Delimited(.., delimited) = args else {
469+
return dummy_syn_ext(guar.unwrap());
470+
};
471+
let args = mbe::macro_parser::compute_locs(&delimited.tts);
472+
let body_span = lhs_span;
473+
rules.push(MacroRule::Attr { args, args_span, body: lhs, body_span, rhs: rhs_tt });
474+
} else {
475+
rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt });
476+
}
429477
if p.token == token::Eof {
430478
break;
431479
}
@@ -469,6 +517,18 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option<Err
469517
None
470518
}
471519

520+
fn check_args_parens(sess: &Session, args: &tokenstream::TokenTree) {
521+
// This does not handle the non-delimited case; that gets handled separately by `check_lhs`.
522+
if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args
523+
&& *delim != Delimiter::Parenthesis
524+
{
525+
sess.dcx().emit_err(errors::MacroArgsBadDelim {
526+
span: dspan.entire(),
527+
sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close },
528+
});
529+
}
530+
}
531+
472532
fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
473533
let e1 = check_lhs_nt_follows(sess, node_id, lhs);
474534
let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs));

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,8 @@ declare_features! (
554554
(unstable, link_arg_attribute, "1.76.0", Some(99427)),
555555
/// Allows fused `loop`/`match` for direct intraprocedural jumps.
556556
(incomplete, loop_match, "1.90.0", Some(132306)),
557+
/// Allow `macro_rules!` attribute rules
558+
(unstable, macro_attr, "CURRENT_RUSTC_VERSION", Some(83527)),
557559
/// Give access to additional metadata about declarative macro meta-variables.
558560
(unstable, macro_metavar_expr, "1.61.0", Some(83527)),
559561
/// Provides a way to concatenate identifiers using metavariable expressions.

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,7 @@ symbols! {
13111311
lt,
13121312
m68k_target_feature,
13131313
macro_at_most_once_rep,
1314+
macro_attr,
13141315
macro_attributes_in_derive_output,
13151316
macro_concat,
13161317
macro_escape,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#![crate_type = "lib"]
2+
3+
macro_rules! myattr { attr() {} => {} }
4+
//~^ ERROR `macro_rules!` attributes are unstable
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0658]: `macro_rules!` attributes are unstable
2+
--> $DIR/feature-gate-macro-attr.rs:3:1
3+
|
4+
LL | macro_rules! myattr { attr() {} => {} }
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #83527 <https://github.com/rust-lang/rust/issues/83527> for more information
8+
= help: add `#![feature(macro_attr)]` to the crate attributes to enable
9+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10+
11+
error: aborting due to 1 previous error
12+
13+
For more information about this error, try `rustc --explain E0658`.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![crate_type = "lib"]
2+
#![feature(macro_attr)]
3+
4+
macro_rules! attr_incomplete_1 { attr }
5+
//~^ ERROR macro definition ended unexpectedly
6+
7+
macro_rules! attr_incomplete_2 { attr() }
8+
//~^ ERROR macro definition ended unexpectedly
9+
10+
macro_rules! attr_incomplete_3 { attr() {} }
11+
//~^ ERROR expected `=>`
12+
13+
macro_rules! attr_incomplete_4 { attr() {} => }
14+
//~^ ERROR macro definition ended unexpectedly
15+
16+
macro_rules! attr_noparens_1 { attr{} {} => {} }
17+
//~^ ERROR macro attribute argument matchers require parentheses
18+
19+
macro_rules! attr_noparens_2 { attr[] {} => {} }
20+
//~^ ERROR macro attribute argument matchers require parentheses
21+
22+
macro_rules! attr_noparens_3 { attr _ {} => {} }
23+
//~^ ERROR invalid macro matcher
24+
25+
macro_rules! attr_dup_matcher_1 { attr() {$x:ident $x:ident} => {} }
26+
//~^ ERROR duplicate matcher binding
27+
28+
macro_rules! attr_dup_matcher_2 { attr($x:ident $x:ident) {} => {} }
29+
//~^ ERROR duplicate matcher binding
30+
31+
macro_rules! attr_dup_matcher_3 { attr($x:ident) {$x:ident} => {} }
32+
//~^ ERROR duplicate matcher binding

0 commit comments

Comments
 (0)