Skip to content

Commit 804d1a1

Browse files
authored
Rollup merge of #144579 - joshtriplett:mbe-attr, r=petrochenkov
Implement declarative (`macro_rules!`) attribute macros (RFC 3697) This implements [RFC 3697](#143547), "Declarative (`macro_rules!`) attribute macros". I would suggest reading this commit-by-commit. This first introduces the feature gate, then adds parsing for attribute rules (doing nothing with them), then adds the ability to look up and apply `macro_rules!` attributes by path, then adds support for local attributes, then adds a test, and finally makes various improvements to errors.
2 parents 18abf3a + f88839d commit 804d1a1

39 files changed

+743
-72
lines changed

compiler/rustc_errors/src/emitter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ pub trait Emitter {
409409
if !redundant_span || always_backtrace {
410410
let msg: Cow<'static, _> = match trace.kind {
411411
ExpnKind::Macro(MacroKind::Attr, _) => {
412-
"this procedural macro expansion".into()
412+
"this attribute macro expansion".into()
413413
}
414414
ExpnKind::Macro(MacroKind::Derive, _) => {
415415
"this derive macro expansion".into()

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: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,40 @@ use rustc_macros::Subdiagnostic;
77
use rustc_parse::parser::{Parser, Recovery, token_descr};
88
use rustc_session::parse::ParseSess;
99
use rustc_span::source_map::SourceMap;
10-
use rustc_span::{ErrorGuaranteed, Ident, Span};
10+
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span};
1111
use tracing::debug;
1212

1313
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,
2121
sp: Span,
2222
def_span: Span,
2323
name: Ident,
24-
arg: TokenStream,
24+
attr_args: Option<&TokenStream>,
25+
body: &TokenStream,
2526
rules: &[MacroRule],
2627
) -> (Span, ErrorGuaranteed) {
2728
debug!("failed to match macro");
29+
let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
30+
psess.source_map().guess_head_span(def_span)
31+
} else {
32+
DUMMY_SP
33+
};
34+
2835
// An error occurred, try the expansion again, tracking the expansion closely for better
2936
// diagnostics.
3037
let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
3138

32-
let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker);
39+
let try_success_result = if let Some(attr_args) = attr_args {
40+
try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
41+
} else {
42+
try_match_macro(psess, name, body, rules, &mut tracker)
43+
};
3344

3445
if try_success_result.is_ok() {
3546
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -47,15 +58,27 @@ pub(super) fn failed_to_match_macro(
4758

4859
let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
4960
else {
61+
// FIXME: we should report this at macro resolution time, as we do for
62+
// `resolve_macro_cannot_use_as_attr`. We can do that once we track multiple macro kinds for a
63+
// Def.
64+
if attr_args.is_none() && !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) {
65+
let msg = format!("macro has no rules for function-like invocation `{name}!`");
66+
let mut err = psess.dcx().struct_span_err(sp, msg);
67+
if !def_head_span.is_dummy() {
68+
let msg = "this macro has no rules for function-like invocation";
69+
err.span_label(def_head_span, msg);
70+
}
71+
return (sp, err.emit());
72+
}
5073
return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro"));
5174
};
5275

5376
let span = token.span.substitute_dummy(sp);
5477

5578
let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None));
5679
err.span_label(span, label);
57-
if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
58-
err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro");
80+
if !def_head_span.is_dummy() {
81+
err.span_label(def_head_span, "when calling this macro");
5982
}
6083

6184
annotate_doc_comment(&mut err, psess.source_map(), span);
@@ -79,13 +102,16 @@ pub(super) fn failed_to_match_macro(
79102
}
80103

81104
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
82-
if let Some((arg, comma_span)) = arg.add_comma() {
105+
if attr_args.is_none()
106+
&& let Some((body, comma_span)) = body.add_comma()
107+
{
83108
for rule in rules {
84-
let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
109+
let MacroRule::Func { lhs, .. } = rule else { continue };
110+
let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed);
85111
let mut tt_parser = TtParser::new(name);
86112

87113
if let Success(_) =
88-
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, &mut NoopTracker)
114+
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
89115
{
90116
if comma_span.is_dummy() {
91117
err.note("you might be missing a comma");
@@ -116,13 +142,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> {
116142

117143
struct BestFailure {
118144
token: Token,
119-
position_in_tokenstream: u32,
145+
position_in_tokenstream: (bool, u32),
120146
msg: &'static str,
121147
remaining_matcher: MatcherLoc,
122148
}
123149

124150
impl BestFailure {
125-
fn is_better_position(&self, position: u32) -> bool {
151+
fn is_better_position(&self, position: (bool, u32)) -> bool {
126152
position > self.position_in_tokenstream
127153
}
128154
}
@@ -142,7 +168,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
142168
}
143169
}
144170

145-
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
171+
fn after_arm(&mut self, in_body: bool, result: &NamedParseResult<Self::Failure>) {
146172
match result {
147173
Success(_) => {
148174
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -155,14 +181,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match
155181
Failure((token, approx_position, msg)) => {
156182
debug!(?token, ?msg, "a new failure of an arm");
157183

184+
let position_in_tokenstream = (in_body, *approx_position);
158185
if self
159186
.best_failure
160187
.as_ref()
161-
.is_none_or(|failure| failure.is_better_position(*approx_position))
188+
.is_none_or(|failure| failure.is_better_position(position_in_tokenstream))
162189
{
163190
self.best_failure = Some(BestFailure {
164191
token: *token,
165-
position_in_tokenstream: *approx_position,
192+
position_in_tokenstream,
166193
msg,
167194
remaining_matcher: self
168195
.remaining_matcher

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)

0 commit comments

Comments
 (0)