about summary refs log tree commit diff
path: root/compiler/rustc_expand
diff options
context:
space:
mode:
authorJosh Triplett <josh@joshtriplett.org>2025-08-09 15:16:19 -0700
committerJosh Triplett <josh@joshtriplett.org>2025-08-14 13:53:57 -0700
commit8fb98ef36846f74c8a642e856808ad5600ae4110 (patch)
treeb99fb21a9f7d1a9be132370d3526e40c7416f4fe /compiler/rustc_expand
parent2c1ac85679678dfe5cce7ea8037735b0349ceaf3 (diff)
downloadrust-8fb98ef36846f74c8a642e856808ad5600ae4110.tar.gz
rust-8fb98ef36846f74c8a642e856808ad5600ae4110.zip
mbe: Parse macro `derive` rules
This handles various kinds of errors, but does not allow applying the
derive yet.

This adds the feature gate `macro_derive`.
Diffstat (limited to 'compiler/rustc_expand')
-rw-r--r--compiler/rustc_expand/messages.ftl2
-rw-r--r--compiler/rustc_expand/src/errors.rs1
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs59
3 files changed, 55 insertions, 7 deletions
diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl
index 1f8f3be6809..61ba716d082 100644
--- a/compiler/rustc_expand/messages.ftl
+++ b/compiler/rustc_expand/messages.ftl
@@ -70,7 +70,7 @@ expand_invalid_fragment_specifier =
     invalid fragment specifier `{$fragment}`
     .help = {$help}
 
-expand_macro_args_bad_delim = macro attribute argument matchers require parentheses
+expand_macro_args_bad_delim = `{$rule_kw}` rule argument matchers require parentheses
 expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)`
 
 expand_macro_body_stability =
diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs
index e58269991fc..ba9d76970f0 100644
--- a/compiler/rustc_expand/src/errors.rs
+++ b/compiler/rustc_expand/src/errors.rs
@@ -490,6 +490,7 @@ pub(crate) struct MacroArgsBadDelim {
     pub span: Span,
     #[subdiagnostic]
     pub sugg: MacroArgsBadDelimSugg,
+    pub rule_kw: Symbol,
 }
 
 #[derive(Subdiagnostic)]
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 334f57f9d62..ae2c96d3840 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -27,7 +27,7 @@ use rustc_session::Session;
 use rustc_session::parse::{ParseSess, feature_err};
 use rustc_span::edition::Edition;
 use rustc_span::hygiene::Transparency;
-use rustc_span::{Ident, Span, kw, sym};
+use rustc_span::{Ident, Span, Symbol, kw, sym};
 use tracing::{debug, instrument, trace, trace_span};
 
 use super::diagnostics::failed_to_match_macro;
@@ -138,6 +138,9 @@ pub(super) enum MacroRule {
         body_span: Span,
         rhs: mbe::TokenTree,
     },
+    /// A derive rule, for use with `#[m]`
+    #[expect(unused)]
+    Derive { body: Vec<MatcherLoc>, body_span: Span, rhs: mbe::TokenTree },
 }
 
 pub struct MacroRulesMacroExpander {
@@ -157,6 +160,7 @@ impl MacroRulesMacroExpander {
             MacroRule::Attr { args_span, body_span, ref rhs, .. } => {
                 (MultiSpan::from_spans(vec![args_span, body_span]), rhs)
             }
+            MacroRule::Derive { body_span, ref rhs, .. } => (MultiSpan::from_span(body_span), rhs),
         };
         if has_compile_error_macro(rhs) { None } else { Some((&self.name, span)) }
     }
@@ -569,7 +573,7 @@ pub fn compile_declarative_macro(
     let mut rules = Vec::new();
 
     while p.token != token::Eof {
-        let args = if p.eat_keyword_noexpect(sym::attr) {
+        let (args, is_derive) = if p.eat_keyword_noexpect(sym::attr) {
             kinds |= MacroKinds::ATTR;
             if !features.macro_attr() {
                 feature_err(sess, sym::macro_attr, span, "`macro_rules!` attributes are unstable")
@@ -579,16 +583,46 @@ pub fn compile_declarative_macro(
                 return dummy_syn_ext(guar);
             }
             let args = p.parse_token_tree();
-            check_args_parens(sess, &args);
+            check_args_parens(sess, sym::attr, &args);
             let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition);
             check_emission(check_lhs(sess, node_id, &args));
             if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") {
                 return dummy_syn_ext(guar);
             }
-            Some(args)
+            (Some(args), false)
+        } else if p.eat_keyword_noexpect(sym::derive) {
+            kinds |= MacroKinds::DERIVE;
+            let derive_keyword_span = p.prev_token.span;
+            if !features.macro_derive() {
+                feature_err(sess, sym::macro_attr, span, "`macro_rules!` derives are unstable")
+                    .emit();
+            }
+            if let Some(guar) = check_no_eof(sess, &p, "expected `()` after `derive`") {
+                return dummy_syn_ext(guar);
+            }
+            let args = p.parse_token_tree();
+            check_args_parens(sess, sym::derive, &args);
+            let args_empty_result = check_args_empty(sess, &args);
+            let args_not_empty = args_empty_result.is_err();
+            check_emission(args_empty_result);
+            if let Some(guar) = check_no_eof(sess, &p, "expected macro derive body") {
+                return dummy_syn_ext(guar);
+            }
+            // If the user has `=>` right after the `()`, they might have forgotten the empty
+            // parentheses.
+            if p.token == token::FatArrow {
+                let mut err = sess
+                    .dcx()
+                    .struct_span_err(p.token.span, "expected macro derive body, got `=>`");
+                if args_not_empty {
+                    err.span_label(derive_keyword_span, "need `()` after this `derive`");
+                }
+                return dummy_syn_ext(err.emit());
+            }
+            (None, true)
         } else {
             kinds |= MacroKinds::BANG;
-            None
+            (None, false)
         };
         let lhs_tt = p.parse_token_tree();
         let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition);
@@ -619,6 +653,8 @@ pub fn compile_declarative_macro(
             let args = mbe::macro_parser::compute_locs(&delimited.tts);
             let body_span = lhs_span;
             rules.push(MacroRule::Attr { args, args_span, body: lhs, body_span, rhs: rhs_tt });
+        } else if is_derive {
+            rules.push(MacroRule::Derive { body: lhs, body_span: lhs_span, rhs: rhs_tt });
         } else {
             rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt });
         }
@@ -665,7 +701,7 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option<Err
     None
 }
 
-fn check_args_parens(sess: &Session, args: &tokenstream::TokenTree) {
+fn check_args_parens(sess: &Session, rule_kw: Symbol, args: &tokenstream::TokenTree) {
     // This does not handle the non-delimited case; that gets handled separately by `check_lhs`.
     if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args
         && *delim != Delimiter::Parenthesis
@@ -673,10 +709,21 @@ fn check_args_parens(sess: &Session, args: &tokenstream::TokenTree) {
         sess.dcx().emit_err(errors::MacroArgsBadDelim {
             span: dspan.entire(),
             sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close },
+            rule_kw,
         });
     }
 }
 
+fn check_args_empty(sess: &Session, args: &tokenstream::TokenTree) -> Result<(), ErrorGuaranteed> {
+    match args {
+        tokenstream::TokenTree::Delimited(.., delimited) if delimited.is_empty() => Ok(()),
+        _ => {
+            let msg = "`derive` rules do not accept arguments; `derive` must be followed by `()`";
+            Err(sess.dcx().span_err(args.span(), msg))
+        }
+    }
+}
+
 fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
     let e1 = check_lhs_nt_follows(sess, node_id, lhs);
     let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs));