Skip to content

Commit 52710b2

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.
1 parent c984190 commit 52710b2

File tree

9 files changed

+348
-18
lines changed

9 files changed

+348
-18
lines changed

compiler/rustc_expand/src/expand.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use rustc_session::lint::BuiltinLintDiag;
2626
use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS};
2727
use rustc_session::parse::feature_err;
2828
use rustc_session::{Limit, Session};
29-
use rustc_span::hygiene::SyntaxContext;
29+
use rustc_span::hygiene::{MacroKinds, SyntaxContext};
3030
use rustc_span::{ErrorGuaranteed, FileName, Ident, LocalExpnId, Span, Symbol, sym};
3131
use smallvec::SmallVec;
3232

@@ -923,6 +923,35 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
923923
}
924924
fragment
925925
}
926+
SyntaxExtensionKind::MacroRules(expander)
927+
if expander.kinds().contains(MacroKinds::DERIVE) =>
928+
{
929+
if is_const {
930+
let guar = self
931+
.cx
932+
.dcx()
933+
.span_err(span, "macro `derive` does not support const derives");
934+
return ExpandResult::Ready(fragment_kind.dummy(span, guar));
935+
}
936+
let body = item.to_tokens();
937+
match expander.expand_derive(self.cx, span, &body) {
938+
Ok(tok_result) => {
939+
let fragment =
940+
self.parse_ast_fragment(tok_result, fragment_kind, &path, span);
941+
if macro_stats {
942+
update_derive_macro_stats(
943+
self.cx,
944+
fragment_kind,
945+
span,
946+
&path,
947+
&fragment,
948+
);
949+
}
950+
fragment
951+
}
952+
Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
953+
}
954+
}
926955
_ => unreachable!(),
927956
},
928957
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
@@ -29,7 +29,7 @@ use rustc_span::hygiene::{MacroKinds, Transparency};
2929
use rustc_span::{Ident, Span, Symbol, kw, sym};
3030
use tracing::{debug, instrument, trace, trace_span};
3131

32-
use super::diagnostics::failed_to_match_macro;
32+
use super::diagnostics::{FailedMacro, failed_to_match_macro};
3333
use super::macro_parser::{NamedMatches, NamedParseResult};
3434
use super::{SequenceRepetition, diagnostics};
3535
use crate::base::{
@@ -138,7 +138,6 @@ pub(super) enum MacroRule {
138138
rhs: mbe::TokenTree,
139139
},
140140
/// A derive rule, for use with `#[m]`
141-
#[expect(unused)]
142141
Derive { body: Vec<MatcherLoc>, body_span: Span, rhs: mbe::TokenTree },
143142
}
144143

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

172228
impl TTMacroExpander for MacroRulesMacroExpander {
@@ -328,8 +384,15 @@ fn expand_macro<'cx>(
328384
}
329385
Err(CanRetry::Yes) => {
330386
// Retry and emit a better error.
331-
let (span, guar) =
332-
failed_to_match_macro(cx.psess(), sp, def_span, name, None, &arg, rules);
387+
let (span, guar) = failed_to_match_macro(
388+
cx.psess(),
389+
sp,
390+
def_span,
391+
name,
392+
FailedMacro::Func,
393+
&arg,
394+
rules,
395+
);
333396
cx.macro_error_and_trace_macros_diag();
334397
DummyResult::any(span, guar)
335398
}
@@ -391,8 +454,15 @@ fn expand_macro_attr(
391454
Err(CanRetry::No(guar)) => Err(guar),
392455
Err(CanRetry::Yes) => {
393456
// Retry and emit a better error.
394-
let (_, guar) =
395-
failed_to_match_macro(cx.psess(), sp, def_span, name, Some(&args), &body, rules);
457+
let (_, guar) = failed_to_match_macro(
458+
cx.psess(),
459+
sp,
460+
def_span,
461+
name,
462+
FailedMacro::Attr(&args),
463+
&body,
464+
rules,
465+
);
396466
cx.trace_macros_diag();
397467
Err(guar)
398468
}
@@ -539,6 +609,44 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>(
539609
Err(CanRetry::Yes)
540610
}
541611

612+
/// Try expanding the macro derive. Returns the index of the successful arm and its
613+
/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job
614+
/// to use `track` accordingly to record all errors correctly.
615+
#[instrument(level = "debug", skip(psess, body, rules, track), fields(tracking = %T::description()))]
616+
pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>(
617+
psess: &ParseSess,
618+
name: Ident,
619+
body: &TokenStream,
620+
rules: &'matcher [MacroRule],
621+
track: &mut T,
622+
) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> {
623+
// This uses the same strategy as `try_match_macro`
624+
let body_parser = parser_from_cx(psess, body.clone(), T::recovery());
625+
let mut tt_parser = TtParser::new(name);
626+
for (i, rule) in rules.iter().enumerate() {
627+
let MacroRule::Derive { body, .. } = rule else { continue };
628+
629+
let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut());
630+
631+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track);
632+
track.after_arm(true, &result);
633+
634+
match result {
635+
Success(named_matches) => {
636+
psess.gated_spans.merge(gated_spans_snapshot);
637+
return Ok((i, rule, named_matches));
638+
}
639+
Failure(_) => {
640+
mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut())
641+
}
642+
Error(_, _) => return Err(CanRetry::Yes),
643+
ErrorReported(guar) => return Err(CanRetry::No(guar)),
644+
}
645+
}
646+
647+
Err(CanRetry::Yes)
648+
}
649+
542650
/// Converts a macro item into a syntax extension.
543651
pub fn compile_declarative_macro(
544652
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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
//~v NOTE: `fn_only` exists, but has no `derive` rules
11+
macro_rules! fn_only {
12+
{} => {}
13+
}
14+
15+
//~v NOTE: `DeriveOnly` exists, but has no rules for function-like invocation
16+
macro_rules! DeriveOnly {
17+
derive() {} => {}
18+
}
19+
20+
fn main() {
21+
//~v NOTE: in this expansion of #[derive(MyDerive)]
22+
#[derive(MyDerive)]
23+
struct S1;
24+
25+
//~vv ERROR: cannot find macro `MyDerive` in this scope
26+
//~| NOTE: `MyDerive` is in scope, but it is a derive
27+
MyDerive!(arg);
28+
29+
//~v ERROR: cannot find derive macro `fn_only` in this scope
30+
#[derive(fn_only)]
31+
struct S2;
32+
33+
DeriveOnly!(); //~ ERROR: cannot find macro `DeriveOnly` in this scope
34+
}
35+
36+
//~vv ERROR: cannot find derive macro `ForwardReferencedDerive` in this scope
37+
//~| NOTE: consider moving the definition of `ForwardReferencedDerive` before this call
38+
#[derive(ForwardReferencedDerive)]
39+
struct S;
40+
41+
//~v NOTE: a macro with the same name exists, but it appears later
42+
macro_rules! ForwardReferencedDerive {
43+
derive() {} => {}
44+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
error: MyDerive: struct S1;
2+
--> $DIR/macro-rules-derive-error.rs:5:9
3+
|
4+
LL | compile_error!(concat!("MyDerive: ", stringify!($($body)*)));
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
...
7+
LL | #[derive(MyDerive)]
8+
| -------- in this derive macro expansion
9+
|
10+
= note: this error originates in the derive macro `MyDerive` (in Nightly builds, run with -Z macro-backtrace for more info)
11+
12+
error: cannot find macro `MyDerive` in this scope
13+
--> $DIR/macro-rules-derive-error.rs:27:5
14+
|
15+
LL | MyDerive!(arg);
16+
| ^^^^^^^^
17+
|
18+
= note: `MyDerive` is in scope, but it is a derive macro: `#[derive(MyDerive)]`
19+
20+
error: cannot find derive macro `fn_only` in this scope
21+
--> $DIR/macro-rules-derive-error.rs:30:14
22+
|
23+
LL | macro_rules! fn_only {
24+
| ------- `fn_only` exists, but has no `derive` rules
25+
...
26+
LL | #[derive(fn_only)]
27+
| ^^^^^^^
28+
29+
error: cannot find macro `DeriveOnly` in this scope
30+
--> $DIR/macro-rules-derive-error.rs:33:5
31+
|
32+
LL | macro_rules! DeriveOnly {
33+
| ---------- `DeriveOnly` exists, but has no rules for function-like invocation
34+
...
35+
LL | DeriveOnly!();
36+
| ^^^^^^^^^^
37+
38+
error: cannot find derive macro `ForwardReferencedDerive` in this scope
39+
--> $DIR/macro-rules-derive-error.rs:38:10
40+
|
41+
LL | #[derive(ForwardReferencedDerive)]
42+
| ^^^^^^^^^^^^^^^^^^^^^^^ consider moving the definition of `ForwardReferencedDerive` before this call
43+
|
44+
note: a macro with the same name exists, but it appears later
45+
--> $DIR/macro-rules-derive-error.rs:42:14
46+
|
47+
LL | macro_rules! ForwardReferencedDerive {
48+
| ^^^^^^^^^^^^^^^^^^^^^^^
49+
50+
error: aborting due to 5 previous errors
51+

0 commit comments

Comments
 (0)