Skip to content

Commit 354fcf2

Browse files
committed
mbe: Handle applying macro_rules derives
Add infrastructure to apply a derive macro to arguments, consuming and returning a `TokenTree` only. Handle `SyntaxExtensionKind::MacroRules` when expanding a derive, if the macro's kinds support derive. Add tests covering various cases of `macro_rules` derives. Note that due to a pre-existing FIXME in `expand.rs`, derives are re-queued and some errors get emitted twice. Duplicate diagnostic suppression makes them not visible, but the FIXME should still get fixed.
1 parent 8fb98ef commit 354fcf2

File tree

9 files changed

+380
-17
lines changed

9 files changed

+380
-17
lines changed

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: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use rustc_span::hygiene::Transparency;
3030
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::{
@@ -139,7 +139,6 @@ pub(super) enum MacroRule {
139139
rhs: mbe::TokenTree,
140140
},
141141
/// A derive rule, for use with `#[m]`
142-
#[expect(unused)]
143142
Derive { body: Vec<MatcherLoc>, body_span: Span, rhs: mbe::TokenTree },
144143
}
145144

@@ -168,6 +167,63 @@ impl MacroRulesMacroExpander {
168167
pub fn kinds(&self) -> MacroKinds {
169168
self.kinds
170169
}
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+
}
171227
}
172228

173229
impl TTMacroExpander for MacroRulesMacroExpander {
@@ -329,8 +385,15 @@ fn expand_macro<'cx>(
329385
}
330386
Err(CanRetry::Yes) => {
331387
// Retry and emit a better error.
332-
let (span, guar) =
333-
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+
);
334397
cx.macro_error_and_trace_macros_diag();
335398
DummyResult::any(span, guar)
336399
}
@@ -392,8 +455,15 @@ fn expand_macro_attr(
392455
Err(CanRetry::No(guar)) => Err(guar),
393456
Err(CanRetry::Yes) => {
394457
// Retry and emit a better error.
395-
let (_, guar) =
396-
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+
);
397467
cx.trace_macros_diag();
398468
Err(guar)
399469
}
@@ -540,6 +610,44 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>(
540610
Err(CanRetry::Yes)
541611
}
542612

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+
543651
/// Converts a macro item into a syntax extension.
544652
pub fn compile_declarative_macro(
545653
sess: &Session,

compiler/rustc_resolve/messages.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ resolve_macro_cannot_use_as_attr =
249249
`{$ident}` exists, but has no `attr` rules
250250
251251
resolve_macro_cannot_use_as_derive =
252-
`{$ident}` exists, but a declarative macro cannot be used as a derive macro
252+
`{$ident}` exists, but has no `derive` rules
253253
254254
resolve_macro_defined_later =
255255
a macro with the same name exists, but it appears later

tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error: cannot find derive macro `sample` in this scope
22
--> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:6:10
33
|
44
LL | macro_rules! sample { () => {} }
5-
| ------ `sample` exists, but a declarative macro cannot be used as a derive macro
5+
| ------ `sample` exists, but has no `derive` rules
66
...
77
LL | #[derive(sample)]
88
| ^^^^^^
@@ -20,7 +20,7 @@ error: cannot find derive macro `sample` in this scope
2020
--> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:6:10
2121
|
2222
LL | macro_rules! sample { () => {} }
23-
| ------ `sample` exists, but a declarative macro cannot be used as a derive macro
23+
| ------ `sample` exists, but has no `derive` rules
2424
...
2525
LL | #[derive(sample)]
2626
| ^^^^^^
@@ -31,7 +31,7 @@ error: cannot find derive macro `sample` in this scope
3131
--> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:6:10
3232
|
3333
LL | macro_rules! sample { () => {} }
34-
| ------ `sample` exists, but a declarative macro cannot be used as a derive macro
34+
| ------ `sample` exists, but has no `derive` rules
3535
...
3636
LL | #[derive(sample)]
3737
| ^^^^^^
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#![feature(macro_derive)]
2+
3+
macro_rules! MyDerive {
4+
derive() { $($body:tt)* } => {
5+
compile_error!(concat!("MyDerive: ", stringify!($($body)*)));
6+
};
7+
//~^^ ERROR: MyDerive
8+
}
9+
10+
macro_rules! fn_only {
11+
//~^ NOTE: `fn_only` exists, but has no `derive` rules
12+
//~| NOTE: `fn_only` exists, but has no `derive` rules
13+
{} => {}
14+
}
15+
16+
//~v NOTE: `DeriveOnly` exists, but has no rules for function-like invocation
17+
macro_rules! DeriveOnly {
18+
derive() {} => {}
19+
}
20+
21+
fn main() {
22+
//~v NOTE: in this expansion of #[derive(MyDerive)]
23+
#[derive(MyDerive)]
24+
struct S1;
25+
26+
//~vv ERROR: cannot find macro `MyDerive` in this scope
27+
//~| NOTE: `MyDerive` is in scope, but it is a derive
28+
MyDerive!(arg);
29+
30+
#[derive(fn_only)]
31+
struct S2;
32+
//~^^ ERROR: cannot find derive macro `fn_only` in this scope
33+
//~| ERROR: cannot find derive macro `fn_only` in this scope
34+
//~| NOTE: duplicate diagnostic emitted
35+
36+
DeriveOnly!(); //~ ERROR: cannot find macro `DeriveOnly` in this scope
37+
}
38+
39+
#[derive(ForwardReferencedDerive)]
40+
struct S;
41+
//~^^ ERROR: cannot find derive macro `ForwardReferencedDerive` in this scope
42+
//~| NOTE: consider moving the definition of `ForwardReferencedDerive` before this call
43+
//~| ERROR: cannot find derive macro `ForwardReferencedDerive` in this scope
44+
//~| NOTE: consider moving the definition of `ForwardReferencedDerive` before this call
45+
//~| NOTE: duplicate diagnostic emitted
46+
47+
macro_rules! ForwardReferencedDerive {
48+
//~^ NOTE: a macro with the same name exists, but it appears later
49+
//~| NOTE: a macro with the same name exists, but it appears later
50+
derive() {} => {}
51+
}

0 commit comments

Comments
 (0)