about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-08-18 15:31:10 +1000
committerGitHub <noreply@github.com>2025-08-18 15:31:10 +1000
commit152b43c7cb9705b071394e4deb40d82e20c12552 (patch)
tree370ec48bb030f5320f6fde9392bd12202f661d53
parentd92e1fe8d7c6d08e2921c40cfa38b625cb00a76d (diff)
parent354fcf2b52119d938b3181bd6cbc3be1929138df (diff)
downloadrust-152b43c7cb9705b071394e4deb40d82e20c12552.tar.gz
rust-152b43c7cb9705b071394e4deb40d82e20c12552.zip
Rollup merge of #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 https://github.com/rust-lang/rust/pull/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: https://github.com/rust-lang/rust/issues/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.
-rw-r--r--compiler/rustc_expand/messages.ftl2
-rw-r--r--compiler/rustc_expand/src/errors.rs1
-rw-r--r--compiler/rustc_expand/src/expand.rs31
-rw-r--r--compiler/rustc_expand/src/mbe/diagnostics.rs24
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs177
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_resolve/messages.ftl2
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--tests/ui/feature-gates/feature-gate-macro-derive.rs4
-rw-r--r--tests/ui/feature-gates/feature-gate-macro-derive.stderr13
-rw-r--r--tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr6
-rw-r--r--tests/ui/macros/macro-rules-derive-error.rs51
-rw-r--r--tests/ui/macros/macro-rules-derive-error.stderr75
-rw-r--r--tests/ui/macros/macro-rules-derive.rs71
-rw-r--r--tests/ui/macros/macro-rules-derive.run.stdout17
-rw-r--r--tests/ui/parser/macro/macro-attr-bad.rs4
-rw-r--r--tests/ui/parser/macro/macro-attr-bad.stderr4
-rw-r--r--tests/ui/parser/macro/macro-attr-recovery.rs2
-rw-r--r--tests/ui/parser/macro/macro-attr-recovery.stderr2
-rw-r--r--tests/ui/parser/macro/macro-derive-bad.rs43
-rw-r--r--tests/ui/parser/macro/macro-derive-bad.stderr90
21 files changed, 593 insertions, 29 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/expand.rs b/compiler/rustc_expand/src/expand.rs
index 670f5c91bb9..1f7f4c7d856 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -16,6 +16,7 @@ use rustc_attr_parsing::{EvalConfigResult, ShouldEmit};
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
 use rustc_errors::PResult;
 use rustc_feature::Features;
+use rustc_hir::def::MacroKinds;
 use rustc_parse::parser::{
     AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma,
     token_descr,
@@ -565,6 +566,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                                 .map(|DeriveResolution { path, item, exts: _, is_const }| {
                                     // FIXME: Consider using the derive resolutions (`_exts`)
                                     // instead of enqueuing the derives to be resolved again later.
+                                    // Note that this can result in duplicate diagnostics.
                                     let expn_id = LocalExpnId::fresh_empty();
                                     derive_invocations.push((
                                         Invocation {
@@ -922,6 +924,35 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                     }
                     fragment
                 }
+                SyntaxExtensionKind::MacroRules(expander)
+                    if expander.kinds().contains(MacroKinds::DERIVE) =>
+                {
+                    if is_const {
+                        let guar = self
+                            .cx
+                            .dcx()
+                            .span_err(span, "macro `derive` does not support const derives");
+                        return ExpandResult::Ready(fragment_kind.dummy(span, guar));
+                    }
+                    let body = item.to_tokens();
+                    match expander.expand_derive(self.cx, span, &body) {
+                        Ok(tok_result) => {
+                            let fragment =
+                                self.parse_ast_fragment(tok_result, fragment_kind, &path, span);
+                            if macro_stats {
+                                update_derive_macro_stats(
+                                    self.cx,
+                                    fragment_kind,
+                                    span,
+                                    &path,
+                                    &fragment,
+                                );
+                            }
+                            fragment
+                        }
+                        Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
+                    }
+                }
                 _ => unreachable!(),
             },
             InvocationKind::GlobDelegation { item, of_trait } => {
diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs
index 80433b7be91..f5edaf50edd 100644
--- a/compiler/rustc_expand/src/mbe/diagnostics.rs
+++ b/compiler/rustc_expand/src/mbe/diagnostics.rs
@@ -14,14 +14,22 @@ use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
 use crate::expand::{AstFragmentKind, parse_ast_fragment};
 use crate::mbe::macro_parser::ParseResult::*;
 use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
-use crate::mbe::macro_rules::{Tracker, try_match_macro, try_match_macro_attr};
+use crate::mbe::macro_rules::{
+    Tracker, try_match_macro, try_match_macro_attr, try_match_macro_derive,
+};
+
+pub(super) enum FailedMacro<'a> {
+    Func,
+    Attr(&'a TokenStream),
+    Derive,
+}
 
 pub(super) fn failed_to_match_macro(
     psess: &ParseSess,
     sp: Span,
     def_span: Span,
     name: Ident,
-    attr_args: Option<&TokenStream>,
+    args: FailedMacro<'_>,
     body: &TokenStream,
     rules: &[MacroRule],
 ) -> (Span, ErrorGuaranteed) {
@@ -36,10 +44,12 @@ pub(super) fn failed_to_match_macro(
     // diagnostics.
     let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
 
-    let try_success_result = if let Some(attr_args) = attr_args {
-        try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
-    } else {
-        try_match_macro(psess, name, body, rules, &mut tracker)
+    let try_success_result = match args {
+        FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker),
+        FailedMacro::Attr(attr_args) => {
+            try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker)
+        }
+        FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker),
     };
 
     if try_success_result.is_ok() {
@@ -90,7 +100,7 @@ pub(super) fn failed_to_match_macro(
     }
 
     // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
-    if attr_args.is_none()
+    if let FailedMacro::Func = args
         && let Some((body, comma_span)) = body.add_comma()
     {
         for rule in rules {
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 334f57f9d62..bced02ae4da 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -27,10 +27,10 @@ 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;
+use super::diagnostics::{FailedMacro, failed_to_match_macro};
 use super::macro_parser::{NamedMatches, NamedParseResult};
 use super::{SequenceRepetition, diagnostics};
 use crate::base::{
@@ -138,6 +138,8 @@ pub(super) enum MacroRule {
         body_span: Span,
         rhs: mbe::TokenTree,
     },
+    /// A derive rule, for use with `#[m]`
+    Derive { body: Vec<MatcherLoc>, body_span: Span, rhs: mbe::TokenTree },
 }
 
 pub struct MacroRulesMacroExpander {
@@ -157,6 +159,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)) }
     }
@@ -164,6 +167,63 @@ impl MacroRulesMacroExpander {
     pub fn kinds(&self) -> MacroKinds {
         self.kinds
     }
+
+    pub fn expand_derive(
+        &self,
+        cx: &mut ExtCtxt<'_>,
+        sp: Span,
+        body: &TokenStream,
+    ) -> Result<TokenStream, ErrorGuaranteed> {
+        // This is similar to `expand_macro`, but they have very different signatures, and will
+        // diverge further once derives support arguments.
+        let Self { name, ref rules, node_id, .. } = *self;
+        let psess = &cx.sess.psess;
+
+        if cx.trace_macros() {
+            let msg = format!("expanding `#[derive({name})] {}`", pprust::tts_to_string(body));
+            trace_macros_note(&mut cx.expansions, sp, msg);
+        }
+
+        match try_match_macro_derive(psess, name, body, rules, &mut NoopTracker) {
+            Ok((rule_index, rule, named_matches)) => {
+                let MacroRule::Derive { rhs, .. } = rule else {
+                    panic!("try_match_macro_derive returned non-derive rule");
+                };
+                let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else {
+                    cx.dcx().span_bug(sp, "malformed macro derive rhs");
+                };
+
+                let id = cx.current_expansion.id;
+                let tts = transcribe(psess, &named_matches, rhs, *rhs_span, self.transparency, id)
+                    .map_err(|e| e.emit())?;
+
+                if cx.trace_macros() {
+                    let msg = format!("to `{}`", pprust::tts_to_string(&tts));
+                    trace_macros_note(&mut cx.expansions, sp, msg);
+                }
+
+                if is_defined_in_current_crate(node_id) {
+                    cx.resolver.record_macro_rule_usage(node_id, rule_index);
+                }
+
+                Ok(tts)
+            }
+            Err(CanRetry::No(guar)) => Err(guar),
+            Err(CanRetry::Yes) => {
+                let (_, guar) = failed_to_match_macro(
+                    cx.psess(),
+                    sp,
+                    self.span,
+                    name,
+                    FailedMacro::Derive,
+                    body,
+                    rules,
+                );
+                cx.macro_error_and_trace_macros_diag();
+                Err(guar)
+            }
+        }
+    }
 }
 
 impl TTMacroExpander for MacroRulesMacroExpander {
@@ -325,8 +385,15 @@ fn expand_macro<'cx>(
         }
         Err(CanRetry::Yes) => {
             // Retry and emit a better error.
-            let (span, guar) =
-                failed_to_match_macro(cx.psess(), sp, def_span, name, None, &arg, rules);
+            let (span, guar) = failed_to_match_macro(
+                cx.psess(),
+                sp,
+                def_span,
+                name,
+                FailedMacro::Func,
+                &arg,
+                rules,
+            );
             cx.macro_error_and_trace_macros_diag();
             DummyResult::any(span, guar)
         }
@@ -388,8 +455,15 @@ fn expand_macro_attr(
         Err(CanRetry::No(guar)) => Err(guar),
         Err(CanRetry::Yes) => {
             // Retry and emit a better error.
-            let (_, guar) =
-                failed_to_match_macro(cx.psess(), sp, def_span, name, Some(&args), &body, rules);
+            let (_, guar) = failed_to_match_macro(
+                cx.psess(),
+                sp,
+                def_span,
+                name,
+                FailedMacro::Attr(&args),
+                &body,
+                rules,
+            );
             cx.trace_macros_diag();
             Err(guar)
         }
@@ -536,6 +610,44 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>(
     Err(CanRetry::Yes)
 }
 
+/// Try expanding the macro derive. Returns the index of the successful arm and its
+/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job
+/// to use `track` accordingly to record all errors correctly.
+#[instrument(level = "debug", skip(psess, body, rules, track), fields(tracking = %T::description()))]
+pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>(
+    psess: &ParseSess,
+    name: Ident,
+    body: &TokenStream,
+    rules: &'matcher [MacroRule],
+    track: &mut T,
+) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> {
+    // This uses the same strategy as `try_match_macro`
+    let body_parser = parser_from_cx(psess, body.clone(), T::recovery());
+    let mut tt_parser = TtParser::new(name);
+    for (i, rule) in rules.iter().enumerate() {
+        let MacroRule::Derive { body, .. } = rule else { continue };
+
+        let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut());
+
+        let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track);
+        track.after_arm(true, &result);
+
+        match result {
+            Success(named_matches) => {
+                psess.gated_spans.merge(gated_spans_snapshot);
+                return Ok((i, rule, named_matches));
+            }
+            Failure(_) => {
+                mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut())
+            }
+            Error(_, _) => return Err(CanRetry::Yes),
+            ErrorReported(guar) => return Err(CanRetry::No(guar)),
+        }
+    }
+
+    Err(CanRetry::Yes)
+}
+
 /// Converts a macro item into a syntax extension.
 pub fn compile_declarative_macro(
     sess: &Session,
@@ -569,7 +681,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 +691,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 +761,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 +809,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 +817,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));
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 07f928b8c88..f2b9121ffa7 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -556,6 +556,8 @@ declare_features! (
     (incomplete, loop_match, "1.90.0", Some(132306)),
     /// Allow `macro_rules!` attribute rules
     (unstable, macro_attr, "CURRENT_RUSTC_VERSION", Some(83527)),
+    /// Allow `macro_rules!` derive rules
+    (unstable, macro_derive, "CURRENT_RUSTC_VERSION", Some(143549)),
     /// Give access to additional metadata about declarative macro meta-variables.
     (unstable, macro_metavar_expr, "1.61.0", Some(83527)),
     /// Provides a way to concatenate identifiers using metavariable expressions.
diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl
index d5ff8a4b609..05b5abc3dc6 100644
--- a/compiler/rustc_resolve/messages.ftl
+++ b/compiler/rustc_resolve/messages.ftl
@@ -249,7 +249,7 @@ resolve_macro_cannot_use_as_attr =
     `{$ident}` exists, but has no `attr` rules
 
 resolve_macro_cannot_use_as_derive =
-     `{$ident}` exists, but a declarative macro cannot be used as a derive macro
+     `{$ident}` exists, but has no `derive` rules
 
 resolve_macro_defined_later =
     a macro with the same name exists, but it appears later
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 42ca2cb4175..3ed483db3c4 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1329,6 +1329,7 @@ symbols! {
         macro_attr,
         macro_attributes_in_derive_output,
         macro_concat,
+        macro_derive,
         macro_escape,
         macro_export,
         macro_lifetime_matcher,
diff --git a/tests/ui/feature-gates/feature-gate-macro-derive.rs b/tests/ui/feature-gates/feature-gate-macro-derive.rs
new file mode 100644
index 00000000000..b9d63423061
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-macro-derive.rs
@@ -0,0 +1,4 @@
+#![crate_type = "lib"]
+
+macro_rules! MyDerive { derive() {} => {} }
+//~^ ERROR `macro_rules!` derives are unstable
diff --git a/tests/ui/feature-gates/feature-gate-macro-derive.stderr b/tests/ui/feature-gates/feature-gate-macro-derive.stderr
new file mode 100644
index 00000000000..b7ca6717bd5
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-macro-derive.stderr
@@ -0,0 +1,13 @@
+error[E0658]: `macro_rules!` derives are unstable
+  --> $DIR/feature-gate-macro-derive.rs:3:1
+   |
+LL | macro_rules! MyDerive { derive() {} => {} }
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #83527 <https://github.com/rust-lang/rust/issues/83527> for more information
+   = help: add `#![feature(macro_attr)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr b/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr
index 77f8bef83a4..aad4a844ec1 100644
--- a/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr
+++ b/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr
@@ -2,7 +2,7 @@ error: cannot find derive macro `sample` in this scope
   --> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:6:10
    |
 LL | macro_rules! sample { () => {} }
-   |              ------ `sample` exists, but a declarative macro cannot be used as a derive macro
+   |              ------ `sample` exists, but has no `derive` rules
 ...
 LL | #[derive(sample)]
    |          ^^^^^^
@@ -20,7 +20,7 @@ error: cannot find derive macro `sample` in this scope
   --> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:6:10
    |
 LL | macro_rules! sample { () => {} }
-   |              ------ `sample` exists, but a declarative macro cannot be used as a derive macro
+   |              ------ `sample` exists, but has no `derive` rules
 ...
 LL | #[derive(sample)]
    |          ^^^^^^
@@ -31,7 +31,7 @@ error: cannot find derive macro `sample` in this scope
   --> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:6:10
    |
 LL | macro_rules! sample { () => {} }
-   |              ------ `sample` exists, but a declarative macro cannot be used as a derive macro
+   |              ------ `sample` exists, but has no `derive` rules
 ...
 LL | #[derive(sample)]
    |          ^^^^^^
diff --git a/tests/ui/macros/macro-rules-derive-error.rs b/tests/ui/macros/macro-rules-derive-error.rs
new file mode 100644
index 00000000000..3ef0236c528
--- /dev/null
+++ b/tests/ui/macros/macro-rules-derive-error.rs
@@ -0,0 +1,51 @@
+#![feature(macro_derive)]
+
+macro_rules! MyDerive {
+    derive() { $($body:tt)* } => {
+        compile_error!(concat!("MyDerive: ", stringify!($($body)*)));
+    };
+    //~^^ ERROR: MyDerive
+}
+
+macro_rules! fn_only {
+//~^ NOTE: `fn_only` exists, but has no `derive` rules
+//~| NOTE: `fn_only` exists, but has no `derive` rules
+    {} => {}
+}
+
+//~v NOTE: `DeriveOnly` exists, but has no rules for function-like invocation
+macro_rules! DeriveOnly {
+    derive() {} => {}
+}
+
+fn main() {
+    //~v NOTE: in this expansion of #[derive(MyDerive)]
+    #[derive(MyDerive)]
+    struct S1;
+
+    //~vv ERROR: cannot find macro `MyDerive` in this scope
+    //~| NOTE: `MyDerive` is in scope, but it is a derive
+    MyDerive!(arg);
+
+    #[derive(fn_only)]
+    struct S2;
+    //~^^ ERROR: cannot find derive macro `fn_only` in this scope
+    //~| ERROR: cannot find derive macro `fn_only` in this scope
+    //~| NOTE: duplicate diagnostic emitted
+
+    DeriveOnly!(); //~ ERROR: cannot find macro `DeriveOnly` in this scope
+}
+
+#[derive(ForwardReferencedDerive)]
+struct S;
+//~^^ ERROR: cannot find derive macro `ForwardReferencedDerive` in this scope
+//~| NOTE: consider moving the definition of `ForwardReferencedDerive` before this call
+//~| ERROR: cannot find derive macro `ForwardReferencedDerive` in this scope
+//~| NOTE: consider moving the definition of `ForwardReferencedDerive` before this call
+//~| NOTE: duplicate diagnostic emitted
+
+macro_rules! ForwardReferencedDerive {
+//~^ NOTE: a macro with the same name exists, but it appears later
+//~| NOTE: a macro with the same name exists, but it appears later
+    derive() {} => {}
+}
diff --git a/tests/ui/macros/macro-rules-derive-error.stderr b/tests/ui/macros/macro-rules-derive-error.stderr
new file mode 100644
index 00000000000..bf6f58a3686
--- /dev/null
+++ b/tests/ui/macros/macro-rules-derive-error.stderr
@@ -0,0 +1,75 @@
+error: MyDerive: struct S1;
+  --> $DIR/macro-rules-derive-error.rs:5:9
+   |
+LL |         compile_error!(concat!("MyDerive: ", stringify!($($body)*)));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL |     #[derive(MyDerive)]
+   |              -------- in this derive macro expansion
+   |
+   = note: this error originates in the derive macro `MyDerive` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: cannot find macro `MyDerive` in this scope
+  --> $DIR/macro-rules-derive-error.rs:28:5
+   |
+LL |     MyDerive!(arg);
+   |     ^^^^^^^^
+   |
+   = note: `MyDerive` is in scope, but it is a derive macro: `#[derive(MyDerive)]`
+
+error: cannot find derive macro `fn_only` in this scope
+  --> $DIR/macro-rules-derive-error.rs:30:14
+   |
+LL | macro_rules! fn_only {
+   |              ------- `fn_only` exists, but has no `derive` rules
+...
+LL |     #[derive(fn_only)]
+   |              ^^^^^^^
+
+error: cannot find derive macro `fn_only` in this scope
+  --> $DIR/macro-rules-derive-error.rs:30:14
+   |
+LL | macro_rules! fn_only {
+   |              ------- `fn_only` exists, but has no `derive` rules
+...
+LL |     #[derive(fn_only)]
+   |              ^^^^^^^
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: cannot find macro `DeriveOnly` in this scope
+  --> $DIR/macro-rules-derive-error.rs:36:5
+   |
+LL | macro_rules! DeriveOnly {
+   |              ---------- `DeriveOnly` exists, but has no rules for function-like invocation
+...
+LL |     DeriveOnly!();
+   |     ^^^^^^^^^^
+
+error: cannot find derive macro `ForwardReferencedDerive` in this scope
+  --> $DIR/macro-rules-derive-error.rs:39:10
+   |
+LL | #[derive(ForwardReferencedDerive)]
+   |          ^^^^^^^^^^^^^^^^^^^^^^^ consider moving the definition of `ForwardReferencedDerive` before this call
+   |
+note: a macro with the same name exists, but it appears later
+  --> $DIR/macro-rules-derive-error.rs:47:14
+   |
+LL | macro_rules! ForwardReferencedDerive {
+   |              ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: cannot find derive macro `ForwardReferencedDerive` in this scope
+  --> $DIR/macro-rules-derive-error.rs:39:10
+   |
+LL | #[derive(ForwardReferencedDerive)]
+   |          ^^^^^^^^^^^^^^^^^^^^^^^ consider moving the definition of `ForwardReferencedDerive` before this call
+   |
+note: a macro with the same name exists, but it appears later
+  --> $DIR/macro-rules-derive-error.rs:47:14
+   |
+LL | macro_rules! ForwardReferencedDerive {
+   |              ^^^^^^^^^^^^^^^^^^^^^^^
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 7 previous errors
+
diff --git a/tests/ui/macros/macro-rules-derive.rs b/tests/ui/macros/macro-rules-derive.rs
new file mode 100644
index 00000000000..d5294330fbf
--- /dev/null
+++ b/tests/ui/macros/macro-rules-derive.rs
@@ -0,0 +1,71 @@
+//@ run-pass
+//@ check-run-results
+#![feature(macro_derive)]
+
+#[macro_export]
+macro_rules! MyExportedDerive {
+    derive() { $($body:tt)* } => {
+        println!("MyExportedDerive: body={:?}", stringify!($($body)*));
+    };
+    { $($args:tt)* } => {
+        println!("MyExportedDerive!({:?})", stringify!($($args)*));
+    };
+}
+
+macro_rules! MyLocalDerive {
+    derive() { $($body:tt)* } => {
+        println!("MyLocalDerive: body={:?}", stringify!($($body)*));
+    };
+    { $($args:tt)* } => {
+        println!("MyLocalDerive!({:?})", stringify!($($args)*));
+    };
+}
+
+trait MyTrait {
+    fn name() -> &'static str;
+}
+
+macro_rules! MyTrait {
+    derive() { struct $name:ident; } => {
+        impl MyTrait for $name {
+            fn name() -> &'static str {
+                stringify!($name)
+            }
+        }
+    };
+}
+
+#[derive(MyTrait)]
+struct MyGlobalType;
+
+fn main() {
+    #[derive(crate::MyExportedDerive)]
+    struct _S1;
+    #[derive(crate::MyExportedDerive, crate::MyExportedDerive)]
+    struct _Twice1;
+
+    crate::MyExportedDerive!();
+    crate::MyExportedDerive!(invoked, arguments);
+
+    #[derive(MyExportedDerive)]
+    struct _S2;
+    #[derive(MyExportedDerive, MyExportedDerive)]
+    struct _Twice2;
+
+    MyExportedDerive!();
+    MyExportedDerive!(invoked, arguments);
+
+    #[derive(MyLocalDerive)]
+    struct _S3;
+    #[derive(MyLocalDerive, MyLocalDerive)]
+    struct _Twice3;
+
+    MyLocalDerive!();
+    MyLocalDerive!(invoked, arguments);
+
+    #[derive(MyTrait)]
+    struct MyLocalType;
+
+    println!("MyGlobalType::name(): {}", MyGlobalType::name());
+    println!("MyLocalType::name(): {}", MyLocalType::name());
+}
diff --git a/tests/ui/macros/macro-rules-derive.run.stdout b/tests/ui/macros/macro-rules-derive.run.stdout
new file mode 100644
index 00000000000..ee492873302
--- /dev/null
+++ b/tests/ui/macros/macro-rules-derive.run.stdout
@@ -0,0 +1,17 @@
+MyExportedDerive: body="struct _S1;"
+MyExportedDerive: body="struct _Twice1;"
+MyExportedDerive: body="struct _Twice1;"
+MyExportedDerive!("")
+MyExportedDerive!("invoked, arguments")
+MyExportedDerive: body="struct _S2;"
+MyExportedDerive: body="struct _Twice2;"
+MyExportedDerive: body="struct _Twice2;"
+MyExportedDerive!("")
+MyExportedDerive!("invoked, arguments")
+MyLocalDerive: body="struct _S3;"
+MyLocalDerive: body="struct _Twice3;"
+MyLocalDerive: body="struct _Twice3;"
+MyLocalDerive!("")
+MyLocalDerive!("invoked, arguments")
+MyGlobalType::name(): MyGlobalType
+MyLocalType::name(): MyLocalType
diff --git a/tests/ui/parser/macro/macro-attr-bad.rs b/tests/ui/parser/macro/macro-attr-bad.rs
index 4313a4d04ab..9f50b057a7a 100644
--- a/tests/ui/parser/macro/macro-attr-bad.rs
+++ b/tests/ui/parser/macro/macro-attr-bad.rs
@@ -14,10 +14,10 @@ macro_rules! attr_incomplete_4 { attr() {} => }
 //~^ ERROR macro definition ended unexpectedly
 
 macro_rules! attr_noparens_1 { attr{} {} => {} }
-//~^ ERROR macro attribute argument matchers require parentheses
+//~^ ERROR `attr` rule argument matchers require parentheses
 
 macro_rules! attr_noparens_2 { attr[] {} => {} }
-//~^ ERROR macro attribute argument matchers require parentheses
+//~^ ERROR `attr` rule argument matchers require parentheses
 
 macro_rules! attr_noparens_3 { attr _ {} => {} }
 //~^ ERROR invalid macro matcher
diff --git a/tests/ui/parser/macro/macro-attr-bad.stderr b/tests/ui/parser/macro/macro-attr-bad.stderr
index 4d286b66649..bf0ed13cd55 100644
--- a/tests/ui/parser/macro/macro-attr-bad.stderr
+++ b/tests/ui/parser/macro/macro-attr-bad.stderr
@@ -22,7 +22,7 @@ error: macro definition ended unexpectedly
 LL | macro_rules! attr_incomplete_4 { attr() {} => }
    |                                              ^ expected right-hand side of macro rule
 
-error: macro attribute argument matchers require parentheses
+error: `attr` rule argument matchers require parentheses
   --> $DIR/macro-attr-bad.rs:16:36
    |
 LL | macro_rules! attr_noparens_1 { attr{} {} => {} }
@@ -34,7 +34,7 @@ LL - macro_rules! attr_noparens_1 { attr{} {} => {} }
 LL + macro_rules! attr_noparens_1 { attr() {} => {} }
    |
 
-error: macro attribute argument matchers require parentheses
+error: `attr` rule argument matchers require parentheses
   --> $DIR/macro-attr-bad.rs:19:36
    |
 LL | macro_rules! attr_noparens_2 { attr[] {} => {} }
diff --git a/tests/ui/parser/macro/macro-attr-recovery.rs b/tests/ui/parser/macro/macro-attr-recovery.rs
index dbb795f57aa..3a942973e5e 100644
--- a/tests/ui/parser/macro/macro-attr-recovery.rs
+++ b/tests/ui/parser/macro/macro-attr-recovery.rs
@@ -3,7 +3,7 @@
 
 macro_rules! attr {
     attr[$($args:tt)*] { $($body:tt)* } => {
-        //~^ ERROR: macro attribute argument matchers require parentheses
+        //~^ ERROR: `attr` rule argument matchers require parentheses
         //~v ERROR: attr:
         compile_error!(concat!(
             "attr: args=\"",
diff --git a/tests/ui/parser/macro/macro-attr-recovery.stderr b/tests/ui/parser/macro/macro-attr-recovery.stderr
index ab3a0b7c607..e1f8dccf1b8 100644
--- a/tests/ui/parser/macro/macro-attr-recovery.stderr
+++ b/tests/ui/parser/macro/macro-attr-recovery.stderr
@@ -1,4 +1,4 @@
-error: macro attribute argument matchers require parentheses
+error: `attr` rule argument matchers require parentheses
   --> $DIR/macro-attr-recovery.rs:5:9
    |
 LL |     attr[$($args:tt)*] { $($body:tt)* } => {
diff --git a/tests/ui/parser/macro/macro-derive-bad.rs b/tests/ui/parser/macro/macro-derive-bad.rs
new file mode 100644
index 00000000000..79b9eb8c113
--- /dev/null
+++ b/tests/ui/parser/macro/macro-derive-bad.rs
@@ -0,0 +1,43 @@
+#![crate_type = "lib"]
+#![feature(macro_derive)]
+
+macro_rules! derive_incomplete_1 { derive }
+//~^ ERROR macro definition ended unexpectedly
+//~| NOTE expected `()` after `derive`
+
+macro_rules! derive_incomplete_2 { derive() }
+//~^ ERROR macro definition ended unexpectedly
+//~| NOTE expected macro derive body
+
+macro_rules! derive_incomplete_3 { derive() {} }
+//~^ ERROR expected `=>`
+//~| NOTE expected `=>`
+
+macro_rules! derive_incomplete_4 { derive() {} => }
+//~^ ERROR macro definition ended unexpectedly
+//~| NOTE expected right-hand side of macro rule
+
+macro_rules! derive_noparens_1 { derive{} {} => {} }
+//~^ ERROR `derive` rule argument matchers require parentheses
+
+macro_rules! derive_noparens_2 { derive[] {} => {} }
+//~^ ERROR `derive` rule argument matchers require parentheses
+
+macro_rules! derive_noparens_3 { derive _ {} => {} }
+//~^ ERROR `derive` must be followed by `()`
+
+macro_rules! derive_args_1 { derive($x:ident) ($y:ident) => {} }
+//~^ ERROR `derive` rules do not accept arguments
+
+macro_rules! derive_args_2 { derive() => {} }
+//~^ ERROR expected macro derive body, got `=>`
+
+macro_rules! derive_args_3 { derive($x:ident) => {} }
+//~^ ERROR `derive` rules do not accept arguments
+//~| ERROR expected macro derive body, got `=>`
+//~| NOTE need `()` after this `derive`
+
+macro_rules! derive_dup_matcher { derive() {$x:ident $x:ident} => {} }
+//~^ ERROR duplicate matcher binding
+//~| NOTE duplicate binding
+//~| NOTE previous binding
diff --git a/tests/ui/parser/macro/macro-derive-bad.stderr b/tests/ui/parser/macro/macro-derive-bad.stderr
new file mode 100644
index 00000000000..ec750c9ac82
--- /dev/null
+++ b/tests/ui/parser/macro/macro-derive-bad.stderr
@@ -0,0 +1,90 @@
+error: macro definition ended unexpectedly
+  --> $DIR/macro-derive-bad.rs:4:42
+   |
+LL | macro_rules! derive_incomplete_1 { derive }
+   |                                          ^ expected `()` after `derive`
+
+error: macro definition ended unexpectedly
+  --> $DIR/macro-derive-bad.rs:8:44
+   |
+LL | macro_rules! derive_incomplete_2 { derive() }
+   |                                            ^ expected macro derive body
+
+error: expected `=>`, found end of macro arguments
+  --> $DIR/macro-derive-bad.rs:12:47
+   |
+LL | macro_rules! derive_incomplete_3 { derive() {} }
+   |                                               ^ expected `=>`
+
+error: macro definition ended unexpectedly
+  --> $DIR/macro-derive-bad.rs:16:50
+   |
+LL | macro_rules! derive_incomplete_4 { derive() {} => }
+   |                                                  ^ expected right-hand side of macro rule
+
+error: `derive` rule argument matchers require parentheses
+  --> $DIR/macro-derive-bad.rs:20:40
+   |
+LL | macro_rules! derive_noparens_1 { derive{} {} => {} }
+   |                                        ^^
+   |
+help: the delimiters should be `(` and `)`
+   |
+LL - macro_rules! derive_noparens_1 { derive{} {} => {} }
+LL + macro_rules! derive_noparens_1 { derive() {} => {} }
+   |
+
+error: `derive` rule argument matchers require parentheses
+  --> $DIR/macro-derive-bad.rs:23:40
+   |
+LL | macro_rules! derive_noparens_2 { derive[] {} => {} }
+   |                                        ^^
+   |
+help: the delimiters should be `(` and `)`
+   |
+LL - macro_rules! derive_noparens_2 { derive[] {} => {} }
+LL + macro_rules! derive_noparens_2 { derive() {} => {} }
+   |
+
+error: `derive` rules do not accept arguments; `derive` must be followed by `()`
+  --> $DIR/macro-derive-bad.rs:26:41
+   |
+LL | macro_rules! derive_noparens_3 { derive _ {} => {} }
+   |                                         ^
+
+error: `derive` rules do not accept arguments; `derive` must be followed by `()`
+  --> $DIR/macro-derive-bad.rs:29:36
+   |
+LL | macro_rules! derive_args_1 { derive($x:ident) ($y:ident) => {} }
+   |                                    ^^^^^^^^^^
+
+error: expected macro derive body, got `=>`
+  --> $DIR/macro-derive-bad.rs:32:39
+   |
+LL | macro_rules! derive_args_2 { derive() => {} }
+   |                                       ^^
+
+error: `derive` rules do not accept arguments; `derive` must be followed by `()`
+  --> $DIR/macro-derive-bad.rs:35:36
+   |
+LL | macro_rules! derive_args_3 { derive($x:ident) => {} }
+   |                                    ^^^^^^^^^^
+
+error: expected macro derive body, got `=>`
+  --> $DIR/macro-derive-bad.rs:35:47
+   |
+LL | macro_rules! derive_args_3 { derive($x:ident) => {} }
+   |                              ------           ^^
+   |                              |
+   |                              need `()` after this `derive`
+
+error: duplicate matcher binding
+  --> $DIR/macro-derive-bad.rs:40:54
+   |
+LL | macro_rules! derive_dup_matcher { derive() {$x:ident $x:ident} => {} }
+   |                                             -------- ^^^^^^^^ duplicate binding
+   |                                             |
+   |                                             previous binding
+
+error: aborting due to 12 previous errors
+