Skip to content

Commit 1bbfd03

Browse files
authored
Rollup merge of rust-lang#145208 - joshtriplett:mbe-derive, r=petrochenkov
Implement declarative (`macro_rules!`) derive macros (RFC 3698) This is a draft for review, and should not be merged yet. This is layered atop rust-lang#145153 , and has only two additional commits atop that. The first handles parsing and provides a test for various parse errors. The second implements expansion and handles application. This implements RFC 3698, "Declarative (`macro_rules!`) derive macros". Tracking issue: rust-lang#143549 This has one remaining issue, which I could use some help debugging: in `tests/ui/macros/macro-rules-derive-error.rs`, the diagnostics for `derive(fn_only)` (for a `fn_only` with no `derive` rules) and `derive(ForwardReferencedDerive)` both get emitted twice, as a duplicate diagnostic. From what I can tell via adding some debugging code, `unresolved_macro_suggestions` is getting called twice from `finalize_macro_resolutions` for each of them, because `self.single_segment_macro_resolutions` has two entries for the macro, with two different `parent_scope` values. I'm not clear on why that happened; it doesn't happen with the equivalent code using attrs. I'd welcome any suggestions for fixing this.
2 parents 3286569 + 354fcf2 commit 1bbfd03

21 files changed

+593
-29
lines changed

compiler/rustc_expand/messages.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ 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
73+
expand_macro_args_bad_delim = `{$rule_kw}` rule argument matchers require parentheses
7474
expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)`
7575
7676
expand_macro_body_stability =

compiler/rustc_expand/src/errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ pub(crate) struct MacroArgsBadDelim {
490490
pub span: Span,
491491
#[subdiagnostic]
492492
pub sugg: MacroArgsBadDelimSugg,
493+
pub rule_kw: Symbol,
493494
}
494495

495496
#[derive(Subdiagnostic)]

compiler/rustc_expand/src/expand.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc_attr_parsing::{EvalConfigResult, ShouldEmit};
1616
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
1717
use rustc_errors::PResult;
1818
use rustc_feature::Features;
19+
use rustc_hir::def::MacroKinds;
1920
use rustc_parse::parser::{
2021
AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma,
2122
token_descr,
@@ -565,6 +566,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
565566
.map(|DeriveResolution { path, item, exts: _, is_const }| {
566567
// FIXME: Consider using the derive resolutions (`_exts`)
567568
// instead of enqueuing the derives to be resolved again later.
569+
// Note that this can result in duplicate diagnostics.
568570
let expn_id = LocalExpnId::fresh_empty();
569571
derive_invocations.push((
570572
Invocation {
@@ -922,6 +924,35 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
922924
}
923925
fragment
924926
}
927+
SyntaxExtensionKind::MacroRules(expander)
928+
if expander.kinds().contains(MacroKinds::DERIVE) =>
929+
{
930+
if is_const {
931+
let guar = self
932+
.cx
933+
.dcx()
934+
.span_err(span, "macro `derive` does not support const derives");
935+
return ExpandResult::Ready(fragment_kind.dummy(span, guar));
936+
}
937+
let body = item.to_tokens();
938+
match expander.expand_derive(self.cx, span, &body) {
939+
Ok(tok_result) => {
940+
let fragment =
941+
self.parse_ast_fragment(tok_result, fragment_kind, &path, span);
942+
if macro_stats {
943+
update_derive_macro_stats(
944+
self.cx,
945+
fragment_kind,
946+
span,
947+
&path,
948+
&fragment,
949+
);
950+
}
951+
fragment
952+
}
953+
Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
954+
}
955+
}
925956
_ => unreachable!(),
926957
},
927958
InvocationKind::GlobDelegation { item, of_trait } => {

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ 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, try_match_macro_attr};
17+
use crate::mbe::macro_rules::{
18+
Tracker, try_match_macro, try_match_macro_attr, try_match_macro_derive,
19+
};
20+
21+
pub(super) enum FailedMacro<'a> {
22+
Func,
23+
Attr(&'a TokenStream),
24+
Derive,
25+
}
1826

1927
pub(super) fn failed_to_match_macro(
2028
psess: &ParseSess,
2129
sp: Span,
2230
def_span: Span,
2331
name: Ident,
24-
attr_args: Option<&TokenStream>,
32+
args: FailedMacro<'_>,
2533
body: &TokenStream,
2634
rules: &[MacroRule],
2735
) -> (Span, ErrorGuaranteed) {
@@ -36,10 +44,12 @@ pub(super) fn failed_to_match_macro(
3644
// diagnostics.
3745
let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
3846

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)
47+
let try_success_result = match args {
48+
FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker),
49+
FailedMacro::Attr(attr_args) => {
50+
try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
51+
}
52+
FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker),
4353
};
4454

4555
if try_success_result.is_ok() {
@@ -90,7 +100,7 @@ pub(super) fn failed_to_match_macro(
90100
}
91101

92102
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
93-
if attr_args.is_none()
103+
if let FailedMacro::Func = args
94104
&& let Some((body, comma_span)) = body.add_comma()
95105
{
96106
for rule in rules {

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 166 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ use rustc_session::Session;
2727
use rustc_session::parse::{ParseSess, feature_err};
2828
use rustc_span::edition::Edition;
2929
use rustc_span::hygiene::Transparency;
30-
use rustc_span::{Ident, Span, kw, sym};
30+
use rustc_span::{Ident, Span, Symbol, kw, sym};
3131
use tracing::{debug, instrument, trace, trace_span};
3232

33-
use super::diagnostics::failed_to_match_macro;
33+
use super::diagnostics::{FailedMacro, failed_to_match_macro};
3434
use super::macro_parser::{NamedMatches, NamedParseResult};
3535
use super::{SequenceRepetition, diagnostics};
3636
use crate::base::{
@@ -138,6 +138,8 @@ pub(super) enum MacroRule {
138138
body_span: Span,
139139
rhs: mbe::TokenTree,
140140
},
141+
/// A derive rule, for use with `#[m]`
142+
Derive { body: Vec<MatcherLoc>, body_span: Span, rhs: mbe::TokenTree },
141143
}
142144

143145
pub struct MacroRulesMacroExpander {
@@ -157,13 +159,71 @@ impl MacroRulesMacroExpander {
157159
MacroRule::Attr { args_span, body_span, ref rhs, .. } => {
158160
(MultiSpan::from_spans(vec![args_span, body_span]), rhs)
159161
}
162+
MacroRule::Derive { body_span, ref rhs, .. } => (MultiSpan::from_span(body_span), rhs),
160163
};
161164
if has_compile_error_macro(rhs) { None } else { Some((&self.name, span)) }
162165
}
163166

164167
pub fn kinds(&self) -> MacroKinds {
165168
self.kinds
166169
}
170+
171+
pub fn expand_derive(
172+
&self,
173+
cx: &mut ExtCtxt<'_>,
174+
sp: Span,
175+
body: &TokenStream,
176+
) -> Result<TokenStream, ErrorGuaranteed> {
177+
// This is similar to `expand_macro`, but they have very different signatures, and will
178+
// diverge further once derives support arguments.
179+
let Self { name, ref rules, node_id, .. } = *self;
180+
let psess = &cx.sess.psess;
181+
182+
if cx.trace_macros() {
183+
let msg = format!("expanding `#[derive({name})] {}`", pprust::tts_to_string(body));
184+
trace_macros_note(&mut cx.expansions, sp, msg);
185+
}
186+
187+
match try_match_macro_derive(psess, name, body, rules, &mut NoopTracker) {
188+
Ok((rule_index, rule, named_matches)) => {
189+
let MacroRule::Derive { rhs, .. } = rule else {
190+
panic!("try_match_macro_derive returned non-derive rule");
191+
};
192+
let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
193+
cx.dcx().span_bug(sp, "malformed macro derive rhs");
194+
};
195+
196+
let id = cx.current_expansion.id;
197+
let tts = transcribe(psess, &named_matches, rhs, *rhs_span, self.transparency, id)
198+
.map_err(|e| e.emit())?;
199+
200+
if cx.trace_macros() {
201+
let msg = format!("to `{}`", pprust::tts_to_string(&tts));
202+
trace_macros_note(&mut cx.expansions, sp, msg);
203+
}
204+
205+
if is_defined_in_current_crate(node_id) {
206+
cx.resolver.record_macro_rule_usage(node_id, rule_index);
207+
}
208+
209+
Ok(tts)
210+
}
211+
Err(CanRetry::No(guar)) => Err(guar),
212+
Err(CanRetry::Yes) => {
213+
let (_, guar) = failed_to_match_macro(
214+
cx.psess(),
215+
sp,
216+
self.span,
217+
name,
218+
FailedMacro::Derive,
219+
body,
220+
rules,
221+
);
222+
cx.macro_error_and_trace_macros_diag();
223+
Err(guar)
224+
}
225+
}
226+
}
167227
}
168228

169229
impl TTMacroExpander for MacroRulesMacroExpander {
@@ -325,8 +385,15 @@ fn expand_macro<'cx>(
325385
}
326386
Err(CanRetry::Yes) => {
327387
// Retry and emit a better error.
328-
let (span, guar) =
329-
failed_to_match_macro(cx.psess(), sp, def_span, name, None, &arg, rules);
388+
let (span, guar) = failed_to_match_macro(
389+
cx.psess(),
390+
sp,
391+
def_span,
392+
name,
393+
FailedMacro::Func,
394+
&arg,
395+
rules,
396+
);
330397
cx.macro_error_and_trace_macros_diag();
331398
DummyResult::any(span, guar)
332399
}
@@ -388,8 +455,15 @@ fn expand_macro_attr(
388455
Err(CanRetry::No(guar)) => Err(guar),
389456
Err(CanRetry::Yes) => {
390457
// Retry and emit a better error.
391-
let (_, guar) =
392-
failed_to_match_macro(cx.psess(), sp, def_span, name, Some(&args), &body, rules);
458+
let (_, guar) = failed_to_match_macro(
459+
cx.psess(),
460+
sp,
461+
def_span,
462+
name,
463+
FailedMacro::Attr(&args),
464+
&body,
465+
rules,
466+
);
393467
cx.trace_macros_diag();
394468
Err(guar)
395469
}
@@ -536,6 +610,44 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>(
536610
Err(CanRetry::Yes)
537611
}
538612

613+
/// Try expanding the macro derive. Returns the index of the successful arm and its
614+
/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job
615+
/// to use `track` accordingly to record all errors correctly.
616+
#[instrument(level = "debug", skip(psess, body, rules, track), fields(tracking = %T::description()))]
617+
pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>(
618+
psess: &ParseSess,
619+
name: Ident,
620+
body: &TokenStream,
621+
rules: &'matcher [MacroRule],
622+
track: &mut T,
623+
) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> {
624+
// This uses the same strategy as `try_match_macro`
625+
let body_parser = parser_from_cx(psess, body.clone(), T::recovery());
626+
let mut tt_parser = TtParser::new(name);
627+
for (i, rule) in rules.iter().enumerate() {
628+
let MacroRule::Derive { body, .. } = rule else { continue };
629+
630+
let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut());
631+
632+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track);
633+
track.after_arm(true, &result);
634+
635+
match result {
636+
Success(named_matches) => {
637+
psess.gated_spans.merge(gated_spans_snapshot);
638+
return Ok((i, rule, named_matches));
639+
}
640+
Failure(_) => {
641+
mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut())
642+
}
643+
Error(_, _) => return Err(CanRetry::Yes),
644+
ErrorReported(guar) => return Err(CanRetry::No(guar)),
645+
}
646+
}
647+
648+
Err(CanRetry::Yes)
649+
}
650+
539651
/// Converts a macro item into a syntax extension.
540652
pub fn compile_declarative_macro(
541653
sess: &Session,
@@ -569,7 +681,7 @@ pub fn compile_declarative_macro(
569681
let mut rules = Vec::new();
570682

571683
while p.token != token::Eof {
572-
let args = if p.eat_keyword_noexpect(sym::attr) {
684+
let (args, is_derive) = if p.eat_keyword_noexpect(sym::attr) {
573685
kinds |= MacroKinds::ATTR;
574686
if !features.macro_attr() {
575687
feature_err(sess, sym::macro_attr, span, "`macro_rules!` attributes are unstable")
@@ -579,16 +691,46 @@ pub fn compile_declarative_macro(
579691
return dummy_syn_ext(guar);
580692
}
581693
let args = p.parse_token_tree();
582-
check_args_parens(sess, &args);
694+
check_args_parens(sess, sym::attr, &args);
583695
let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition);
584696
check_emission(check_lhs(sess, node_id, &args));
585697
if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") {
586698
return dummy_syn_ext(guar);
587699
}
588-
Some(args)
700+
(Some(args), false)
701+
} else if p.eat_keyword_noexpect(sym::derive) {
702+
kinds |= MacroKinds::DERIVE;
703+
let derive_keyword_span = p.prev_token.span;
704+
if !features.macro_derive() {
705+
feature_err(sess, sym::macro_attr, span, "`macro_rules!` derives are unstable")
706+
.emit();
707+
}
708+
if let Some(guar) = check_no_eof(sess, &p, "expected `()` after `derive`") {
709+
return dummy_syn_ext(guar);
710+
}
711+
let args = p.parse_token_tree();
712+
check_args_parens(sess, sym::derive, &args);
713+
let args_empty_result = check_args_empty(sess, &args);
714+
let args_not_empty = args_empty_result.is_err();
715+
check_emission(args_empty_result);
716+
if let Some(guar) = check_no_eof(sess, &p, "expected macro derive body") {
717+
return dummy_syn_ext(guar);
718+
}
719+
// If the user has `=>` right after the `()`, they might have forgotten the empty
720+
// parentheses.
721+
if p.token == token::FatArrow {
722+
let mut err = sess
723+
.dcx()
724+
.struct_span_err(p.token.span, "expected macro derive body, got `=>`");
725+
if args_not_empty {
726+
err.span_label(derive_keyword_span, "need `()` after this `derive`");
727+
}
728+
return dummy_syn_ext(err.emit());
729+
}
730+
(None, true)
589731
} else {
590732
kinds |= MacroKinds::BANG;
591-
None
733+
(None, false)
592734
};
593735
let lhs_tt = p.parse_token_tree();
594736
let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition);
@@ -619,6 +761,8 @@ pub fn compile_declarative_macro(
619761
let args = mbe::macro_parser::compute_locs(&delimited.tts);
620762
let body_span = lhs_span;
621763
rules.push(MacroRule::Attr { args, args_span, body: lhs, body_span, rhs: rhs_tt });
764+
} else if is_derive {
765+
rules.push(MacroRule::Derive { body: lhs, body_span: lhs_span, rhs: rhs_tt });
622766
} else {
623767
rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt });
624768
}
@@ -665,18 +809,29 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option<Err
665809
None
666810
}
667811

668-
fn check_args_parens(sess: &Session, args: &tokenstream::TokenTree) {
812+
fn check_args_parens(sess: &Session, rule_kw: Symbol, args: &tokenstream::TokenTree) {
669813
// This does not handle the non-delimited case; that gets handled separately by `check_lhs`.
670814
if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args
671815
&& *delim != Delimiter::Parenthesis
672816
{
673817
sess.dcx().emit_err(errors::MacroArgsBadDelim {
674818
span: dspan.entire(),
675819
sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close },
820+
rule_kw,
676821
});
677822
}
678823
}
679824

825+
fn check_args_empty(sess: &Session, args: &tokenstream::TokenTree) -> Result<(), ErrorGuaranteed> {
826+
match args {
827+
tokenstream::TokenTree::Delimited(.., delimited) if delimited.is_empty() => Ok(()),
828+
_ => {
829+
let msg = "`derive` rules do not accept arguments; `derive` must be followed by `()`";
830+
Err(sess.dcx().span_err(args.span(), msg))
831+
}
832+
}
833+
}
834+
680835
fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
681836
let e1 = check_lhs_nt_follows(sess, node_id, lhs);
682837
let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs));

0 commit comments

Comments
 (0)