Skip to content

Commit c651059

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

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_attr_data_structures::{AttributeKind, find_attr};
@@ -22,7 +22,7 @@ use rustc_lint_defs::builtin::{
2222
use rustc_parse::exp;
2323
use rustc_parse::parser::{Parser, Recovery};
2424
use rustc_session::Session;
25-
use rustc_session::parse::ParseSess;
25+
use rustc_session::parse::{ParseSess, feature_err};
2626
use rustc_span::edition::Edition;
2727
use rustc_span::hygiene::Transparency;
2828
use rustc_span::{Ident, Span, kw, sym};
@@ -34,11 +34,13 @@ use crate::base::{
3434
DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension,
3535
SyntaxExtensionKind, TTMacroExpander,
3636
};
37+
use crate::errors;
3738
use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment};
39+
use crate::mbe::macro_check::check_meta_variables;
3840
use crate::mbe::macro_parser::{Error, ErrorReported, Failure, MatcherLoc, Success, TtParser};
3941
use crate::mbe::quoted::{RulePart, parse_one_tt};
4042
use crate::mbe::transcribe::transcribe;
41-
use crate::mbe::{self, KleeneOp, macro_check};
43+
use crate::mbe::{self, KleeneOp};
4244

4345
pub(crate) struct ParserAnyMacro<'a> {
4446
parser: Parser<'a>,
@@ -122,10 +124,12 @@ impl<'a> ParserAnyMacro<'a> {
122124
}
123125
}
124126

125-
pub(super) struct MacroRule {
126-
pub(super) lhs: Vec<MatcherLoc>,
127-
lhs_span: Span,
128-
rhs: mbe::TokenTree,
127+
pub(super) enum MacroRule {
128+
/// A function-style rule, for use with `m!()`
129+
Func { lhs: Vec<MatcherLoc>, lhs_span: Span, rhs: mbe::TokenTree },
130+
/// An attr rule, for use with `#[m]`
131+
#[expect(unused)]
132+
Attr { args: Vec<MatcherLoc>, body: Vec<MatcherLoc>, lhs_span: Span, rhs: mbe::TokenTree },
129133
}
130134

131135
pub struct MacroRulesMacroExpander {
@@ -139,8 +143,9 @@ pub struct MacroRulesMacroExpander {
139143
impl MacroRulesMacroExpander {
140144
pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> {
141145
// If the rhs contains an invocation like `compile_error!`, don't report it as unused.
142-
let rule = &self.rules[rule_i];
143-
if has_compile_error_macro(&rule.rhs) { None } else { Some((&self.name, rule.lhs_span)) }
146+
let (MacroRule::Func { lhs_span, ref rhs, .. } | MacroRule::Attr { lhs_span, ref rhs, .. }) =
147+
self.rules[rule_i];
148+
if has_compile_error_macro(rhs) { None } else { Some((&self.name, lhs_span)) }
144149
}
145150
}
146151

@@ -244,14 +249,17 @@ fn expand_macro<'cx>(
244249

245250
match try_success_result {
246251
Ok((rule_index, rule, named_matches)) => {
247-
let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else {
252+
let MacroRule::Func { rhs, .. } = rule else {
253+
panic!("try_match_macro returned non-func rule");
254+
};
255+
let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
248256
cx.dcx().span_bug(sp, "malformed macro rhs");
249257
};
250-
let arm_span = rule.rhs.span();
258+
let arm_span = rhs_span.entire();
251259

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

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

337-
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, track);
346+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track);
338347

339348
track.after_arm(&result);
340349

@@ -403,6 +412,26 @@ pub fn compile_declarative_macro(
403412
let mut rules = Vec::new();
404413

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

509+
fn check_args_parens(sess: &Session, args: &tokenstream::TokenTree) -> Result<(), ErrorGuaranteed> {
510+
// This does not handle the non-delimited case; that gets handled separately by `check_lhs`.
511+
if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args
512+
&& *delim != Delimiter::Parenthesis
513+
{
514+
return Err(sess.dcx().emit_err(errors::MacroArgsBadDelim {
515+
span: dspan.entire(),
516+
sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close },
517+
}));
518+
}
519+
Ok(())
520+
}
521+
471522
fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
472523
let e1 = check_lhs_nt_follows(sess, node_id, lhs);
473524
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)