Skip to content

Commit e2c9de3

Browse files
committed
mbe: Parse macro attribute rules
This handles various kinds of errors, but does not allow applying the attributes yet.
1 parent cef3940 commit e2c9de3

File tree

9 files changed

+224
-18
lines changed

9 files changed

+224
-18
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 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: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ 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};
@@ -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,12 @@ 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 { args: Vec<MatcherLoc>, body: Vec<MatcherLoc>, lhs_span: Span, rhs: mbe::TokenTree },
130134
}
131135

132136
pub struct MacroRulesMacroExpander {
@@ -140,8 +144,9 @@ pub struct MacroRulesMacroExpander {
140144
impl MacroRulesMacroExpander {
141145
pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> {
142146
// 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)) }
147+
let (MacroRule::Func { lhs_span, ref rhs, .. } | MacroRule::Attr { lhs_span, ref rhs, .. }) =
148+
self.rules[rule_i];
149+
if has_compile_error_macro(rhs) { None } else { Some((&self.name, lhs_span)) }
145150
}
146151
}
147152

@@ -245,14 +250,17 @@ fn expand_macro<'cx>(
245250

246251
match try_success_result {
247252
Ok((rule_index, rule, named_matches)) => {
248-
let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else {
253+
let MacroRule::Func { rhs, .. } = rule else {
254+
panic!("try_match_macro returned non-func rule");
255+
};
256+
let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
249257
cx.dcx().span_bug(sp, "malformed macro rhs");
250258
};
251-
let arm_span = rule.rhs.span();
259+
let arm_span = rhs_span.entire();
252260

253261
// rhs has holes ( `$id` and `$(...)` that need filled)
254262
let id = cx.current_expansion.id;
255-
let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) {
263+
let tts = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) {
256264
Ok(tts) => tts,
257265
Err(err) => {
258266
let guar = err.emit();
@@ -327,6 +335,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
327335
// Try each arm's matchers.
328336
let mut tt_parser = TtParser::new(name);
329337
for (i, rule) in rules.iter().enumerate() {
338+
let MacroRule::Func { lhs, .. } = rule else { continue };
330339
let _tracing_span = trace_span!("Matching arm", %i);
331340

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

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

340349
track.after_arm(&result);
341350

@@ -404,6 +413,26 @@ pub fn compile_declarative_macro(
404413
let mut rules = Vec::new();
405414

406415
while p.token != token::Eof {
416+
let args = if p.eat_keyword_noexpect(sym::attr) {
417+
if !features.macro_attr() {
418+
let msg = "`macro_rules!` attributes are unstable";
419+
let e = feature_err(sess, sym::macro_attr, span, msg);
420+
return dummy_syn_ext(e.emit());
421+
}
422+
if let Some(guar) = check_no_eof(sess, &p, "expected macro attr args") {
423+
return dummy_syn_ext(guar);
424+
}
425+
let args = p.parse_token_tree();
426+
check_emission(check_args_parens(sess, &args));
427+
let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition);
428+
check_emission(check_lhs(sess, node_id, &args));
429+
if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") {
430+
return dummy_syn_ext(guar);
431+
}
432+
Some(args)
433+
} else {
434+
None
435+
};
407436
let lhs_tt = p.parse_token_tree();
408437
let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition);
409438
check_emission(check_lhs(sess, node_id, &lhs_tt));
@@ -416,7 +445,7 @@ pub fn compile_declarative_macro(
416445
let rhs_tt = p.parse_token_tree();
417446
let rhs_tt = parse_one_tt(rhs_tt, RulePart::Body, sess, node_id, features, edition);
418447
check_emission(check_rhs(sess, &rhs_tt));
419-
check_emission(macro_check::check_meta_variables(&sess.psess, node_id, &lhs_tt, &rhs_tt));
448+
check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs_tt));
420449
let lhs_span = lhs_tt.span();
421450
// Convert the lhs into `MatcherLoc` form, which is better for doing the
422451
// actual matching.
@@ -425,7 +454,16 @@ pub fn compile_declarative_macro(
425454
} else {
426455
return dummy_syn_ext(guar.unwrap());
427456
};
428-
rules.push(MacroRule { lhs, lhs_span, rhs: rhs_tt });
457+
if let Some(args) = args {
458+
let lhs_span = args.span().to(lhs_span);
459+
let mbe::TokenTree::Delimited(.., delimited) = args else {
460+
return dummy_syn_ext(guar.unwrap());
461+
};
462+
let args = mbe::macro_parser::compute_locs(&delimited.tts);
463+
rules.push(MacroRule::Attr { args, body: lhs, lhs_span, rhs: rhs_tt });
464+
} else {
465+
rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt });
466+
}
429467
if p.token == token::Eof {
430468
break;
431469
}
@@ -469,6 +507,19 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option<Err
469507
None
470508
}
471509

510+
fn check_args_parens(sess: &Session, args: &tokenstream::TokenTree) -> Result<(), ErrorGuaranteed> {
511+
// This does not handle the non-delimited case; that gets handled separately by `check_lhs`.
512+
if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args
513+
&& *delim != Delimiter::Parenthesis
514+
{
515+
return Err(sess.dcx().emit_err(errors::MacroArgsBadDelim {
516+
span: dspan.entire(),
517+
sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close },
518+
}));
519+
}
520+
Ok(())
521+
}
522+
472523
fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
473524
let e1 = check_lhs_nt_follows(sess, node_id, lhs);
474525
let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs));
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 argument matchers require parentheses
18+
19+
macro_rules! attr_noparens_2 { attr[] {} => {} }
20+
//~^ ERROR macro 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
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
error: macro definition ended unexpectedly
2+
--> $DIR/macro-attr-bad.rs:4:38
3+
|
4+
LL | macro_rules! attr_incomplete_1 { attr }
5+
| ^ expected macro attr args
6+
7+
error: macro definition ended unexpectedly
8+
--> $DIR/macro-attr-bad.rs:7:40
9+
|
10+
LL | macro_rules! attr_incomplete_2 { attr() }
11+
| ^ expected macro attr body
12+
13+
error: expected `=>`, found end of macro arguments
14+
--> $DIR/macro-attr-bad.rs:10:43
15+
|
16+
LL | macro_rules! attr_incomplete_3 { attr() {} }
17+
| ^ expected `=>`
18+
19+
error: macro definition ended unexpectedly
20+
--> $DIR/macro-attr-bad.rs:13:46
21+
|
22+
LL | macro_rules! attr_incomplete_4 { attr() {} => }
23+
| ^ expected right-hand side of macro rule
24+
25+
error: macro argument matchers require parentheses
26+
--> $DIR/macro-attr-bad.rs:16:36
27+
|
28+
LL | macro_rules! attr_noparens_1 { attr{} {} => {} }
29+
| ^^
30+
|
31+
help: the delimiters should be `(` and `)`
32+
|
33+
LL - macro_rules! attr_noparens_1 { attr{} {} => {} }
34+
LL + macro_rules! attr_noparens_1 { attr() {} => {} }
35+
|
36+
37+
error: macro argument matchers require parentheses
38+
--> $DIR/macro-attr-bad.rs:19:36
39+
|
40+
LL | macro_rules! attr_noparens_2 { attr[] {} => {} }
41+
| ^^
42+
|
43+
help: the delimiters should be `(` and `)`
44+
|
45+
LL - macro_rules! attr_noparens_2 { attr[] {} => {} }
46+
LL + macro_rules! attr_noparens_2 { attr() {} => {} }
47+
|
48+
49+
error: invalid macro matcher; matchers must be contained in balanced delimiters
50+
--> $DIR/macro-attr-bad.rs:22:37
51+
|
52+
LL | macro_rules! attr_noparens_3 { attr _ {} => {} }
53+
| ^
54+
55+
error: duplicate matcher binding
56+
--> $DIR/macro-attr-bad.rs:25:52
57+
|
58+
LL | macro_rules! attr_dup_matcher_1 { attr() {$x:ident $x:ident} => {} }
59+
| -------- ^^^^^^^^ duplicate binding
60+
| |
61+
| previous binding
62+
63+
error: duplicate matcher binding
64+
--> $DIR/macro-attr-bad.rs:28:49
65+
|
66+
LL | macro_rules! attr_dup_matcher_2 { attr($x:ident $x:ident) {} => {} }
67+
| -------- ^^^^^^^^ duplicate binding
68+
| |
69+
| previous binding
70+
71+
error: duplicate matcher binding
72+
--> $DIR/macro-attr-bad.rs:31:51
73+
|
74+
LL | macro_rules! attr_dup_matcher_3 { attr($x:ident) {$x:ident} => {} }
75+
| -------- ^^^^^^^^ duplicate binding
76+
| |
77+
| previous binding
78+
79+
error: aborting due to 10 previous errors
80+

0 commit comments

Comments
 (0)