about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorVadim Petrochenkov <vadim.petrochenkov@gmail.com>2020-11-14 14:47:14 +0300
committerVadim Petrochenkov <vadim.petrochenkov@gmail.com>2021-02-07 20:08:45 +0300
commitdbdbd30bf2cb0d48c8bbce83c2458592664dbb18 (patch)
tree92877e16f45e3cf927ffc4296e0f90c48ec036f5 /compiler
parentae00b62ceb7eaf1f02f5289ab233bf7e0e8060d5 (diff)
downloadrust-dbdbd30bf2cb0d48c8bbce83c2458592664dbb18.tar.gz
rust-dbdbd30bf2cb0d48c8bbce83c2458592664dbb18.zip
expand/resolve: Turn `#[derive]` into a regular macro attribute
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_builtin_macros/Cargo.toml1
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs132
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs2
-rw-r--r--compiler/rustc_expand/src/base.rs44
-rw-r--r--compiler/rustc_expand/src/expand.rs339
-rw-r--r--compiler/rustc_expand/src/proc_macro.rs94
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs1
-rw-r--r--compiler/rustc_lint/src/context.rs3
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs50
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs1
-rw-r--r--compiler/rustc_parse/src/parser/attr.rs6
-rw-r--r--compiler/rustc_resolve/src/lib.rs3
-rw-r--r--compiler/rustc_resolve/src/macros.rs145
13 files changed, 407 insertions, 414 deletions
diff --git a/compiler/rustc_builtin_macros/Cargo.toml b/compiler/rustc_builtin_macros/Cargo.toml
index c397a854126..eb022b5b2b1 100644
--- a/compiler/rustc_builtin_macros/Cargo.toml
+++ b/compiler/rustc_builtin_macros/Cargo.toml
@@ -15,6 +15,7 @@ rustc_attr = { path = "../rustc_attr" }
 rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_errors = { path = "../rustc_errors" }
 rustc_feature = { path = "../rustc_feature" }
+rustc_lexer = { path = "../rustc_lexer" }
 rustc_parse = { path = "../rustc_parse" }
 rustc_target = { path = "../rustc_target" }
 rustc_session = { path = "../rustc_session" }
diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs
new file mode 100644
index 00000000000..fad64858ce3
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -0,0 +1,132 @@
+use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
+use rustc_expand::config::StripUnconfigured;
+use rustc_feature::AttributeTemplate;
+use rustc_parse::validate_attr;
+use rustc_session::Session;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+crate struct Expander;
+
+impl MultiItemModifier for Expander {
+    fn expand(
+        &self,
+        ecx: &mut ExtCtxt<'_>,
+        span: Span,
+        meta_item: &ast::MetaItem,
+        item: Annotatable,
+    ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+        let sess = ecx.sess;
+        if report_bad_target(sess, &item, span) {
+            // We don't want to pass inappropriate targets to derive macros to avoid
+            // follow up errors, all other errors below are recoverable.
+            return ExpandResult::Ready(vec![item]);
+        }
+
+        let template =
+            AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
+        let attr = ecx.attribute(meta_item.clone());
+        validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template);
+
+        let derives: Vec<_> = attr
+            .meta_item_list()
+            .unwrap_or_default()
+            .into_iter()
+            .filter_map(|nested_meta| match nested_meta {
+                NestedMetaItem::MetaItem(meta) => Some(meta),
+                NestedMetaItem::Literal(lit) => {
+                    // Reject `#[derive("Debug")]`.
+                    report_unexpected_literal(sess, &lit);
+                    None
+                }
+            })
+            .map(|meta| {
+                // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
+                report_path_args(sess, &meta);
+                meta.path
+            })
+            .collect();
+
+        // FIXME: Try to cache intermediate results to avoid collecting same paths multiple times.
+        match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) {
+            Ok(()) => {
+                let mut visitor =
+                    StripUnconfigured { sess, features: ecx.ecfg.features, modified: false };
+                let mut item = visitor.fully_configure(item);
+                if visitor.modified {
+                    // Erase the tokens if cfg-stripping modified the item
+                    // This will cause us to synthesize fake tokens
+                    // when `nt_to_tokenstream` is called on this item.
+                    match &mut item {
+                        Annotatable::Item(item) => item,
+                        Annotatable::Stmt(stmt) => match &mut stmt.kind {
+                            StmtKind::Item(item) => item,
+                            _ => unreachable!(),
+                        },
+                        _ => unreachable!(),
+                    }
+                    .tokens = None;
+                }
+                ExpandResult::Ready(vec![item])
+            }
+            Err(Indeterminate) => ExpandResult::Retry(item),
+        }
+    }
+}
+
+fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool {
+    let item_kind = match item {
+        Annotatable::Item(item) => Some(&item.kind),
+        Annotatable::Stmt(stmt) => match &stmt.kind {
+            StmtKind::Item(item) => Some(&item.kind),
+            _ => None,
+        },
+        _ => None,
+    };
+
+    let bad_target =
+        !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
+    if bad_target {
+        struct_span_err!(
+            sess,
+            span,
+            E0774,
+            "`derive` may only be applied to structs, enums and unions",
+        )
+        .emit();
+    }
+    bad_target
+}
+
+fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) {
+    let help_msg = match lit.token.kind {
+        token::Str if rustc_lexer::is_ident(&lit.token.symbol.as_str()) => {
+            format!("try using `#[derive({})]`", lit.token.symbol)
+        }
+        _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(),
+    };
+    struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",)
+        .help(&help_msg)
+        .emit();
+}
+
+fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
+    let report_error = |title, action| {
+        let span = meta.span.with_lo(meta.path.span.hi());
+        sess.struct_span_err(span, title)
+            .span_suggestion(span, action, String::new(), Applicability::MachineApplicable)
+            .emit();
+    };
+    match meta.kind {
+        MetaItemKind::Word => {}
+        MetaItemKind::List(..) => report_error(
+            "traits in `#[derive(...)]` don't accept arguments",
+            "remove the arguments",
+        ),
+        MetaItemKind::NameValue(..) => {
+            report_error("traits in `#[derive(...)]` don't accept values", "remove the value")
+        }
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index 59844b6c692..9a3c914337c 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -27,6 +27,7 @@ mod cfg_accessible;
 mod compile_error;
 mod concat;
 mod concat_idents;
+mod derive;
 mod deriving;
 mod env;
 mod format;
@@ -88,6 +89,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
     register_attr! {
         bench: test::expand_bench,
         cfg_accessible: cfg_accessible::Expander,
+        derive: derive::Expander,
         global_allocator: global_allocator::expand,
         test: test::expand_test,
         test_case: test::expand_test_case,
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 08543d1622a..35ddb1fb9bc 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -141,7 +141,7 @@ impl Annotatable {
     }
 
     crate fn into_tokens(self, sess: &ParseSess) -> TokenStream {
-        nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::No)
+        nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::Yes)
     }
 
     pub fn expect_item(self) -> P<ast::Item> {
@@ -234,25 +234,6 @@ impl Annotatable {
             _ => panic!("expected variant"),
         }
     }
-
-    pub fn derive_allowed(&self) -> bool {
-        match *self {
-            Annotatable::Stmt(ref stmt) => match stmt.kind {
-                ast::StmtKind::Item(ref item) => matches!(
-                    item.kind,
-                    ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..)
-                ),
-                _ => false,
-            },
-            Annotatable::Item(ref item) => match item.kind {
-                ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..) => {
-                    true
-                }
-                _ => false,
-            },
-            _ => false,
-        }
-    }
 }
 
 /// Result of an expansion that may need to be retried.
@@ -854,12 +835,6 @@ impl SyntaxExtension {
     }
 }
 
-/// Result of resolving a macro invocation.
-pub enum InvocationRes {
-    Single(Lrc<SyntaxExtension>),
-    DeriveContainer(Vec<Lrc<SyntaxExtension>>),
-}
-
 /// Error type that denotes indeterminacy.
 pub struct Indeterminate;
 
@@ -885,16 +860,29 @@ pub trait ResolverExpand {
         invoc: &Invocation,
         eager_expansion_root: ExpnId,
         force: bool,
-    ) -> Result<InvocationRes, Indeterminate>;
+    ) -> Result<Lrc<SyntaxExtension>, Indeterminate>;
 
     fn check_unused_macros(&mut self);
 
     /// Some parent node that is close enough to the given macro call.
-    fn lint_node_id(&mut self, expn_id: ExpnId) -> NodeId;
+    fn lint_node_id(&self, expn_id: ExpnId) -> NodeId;
 
     // Resolver interfaces for specific built-in macros.
     /// Does `#[derive(...)]` attribute with the given `ExpnId` have built-in `Copy` inside it?
     fn has_derive_copy(&self, expn_id: ExpnId) -> bool;
+    /// Resolve paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
+    fn resolve_derives(
+        &mut self,
+        expn_id: ExpnId,
+        derives: Vec<ast::Path>,
+        force: bool,
+    ) -> Result<(), Indeterminate>;
+    /// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
+    /// back from resolver.
+    fn take_derive_resolutions(
+        &mut self,
+        expn_id: ExpnId,
+    ) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>>;
     /// Path resolution logic for `#[cfg_accessible(path)]`.
     fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
 }
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 5fdb7fc5915..870b5c92d89 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1,24 +1,26 @@
 use crate::base::*;
 use crate::config::StripUnconfigured;
 use crate::configure;
-use crate::hygiene::{ExpnData, ExpnKind, SyntaxContext};
+use crate::hygiene::SyntaxContext;
 use crate::mbe::macro_rules::annotate_err_with_kind;
 use crate::module::{parse_external_mod, push_directory, Directory, DirectoryOwnership};
 use crate::placeholders::{placeholder, PlaceholderExpander};
-use crate::proc_macro::collect_derives;
 
+use rustc_ast as ast;
 use rustc_ast::mut_visit::*;
 use rustc_ast::ptr::P;
 use rustc_ast::token;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::visit::{self, AssocCtxt, Visitor};
-use rustc_ast::{self as ast, AttrItem, AttrStyle, Block, LitKind, NodeId, PatKind, Path};
-use rustc_ast::{ItemKind, MacArgs, MacCallStmt, MacStmtStyle, StmtKind, Unsafe};
+use rustc_ast::{AttrItem, AttrStyle, Block, ItemKind, LitKind, MacArgs};
+use rustc_ast::{MacCallStmt, MacStmtStyle, MetaItemKind, NestedMetaItem};
+use rustc_ast::{NodeId, PatKind, Path, StmtKind, Unsafe};
 use rustc_ast_pretty::pprust;
 use rustc_attr::{self as attr, is_builtin_attr, HasAttrs};
 use rustc_data_structures::map_in_place::MapInPlace;
 use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_errors::{struct_span_err, Applicability, PResult};
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::{Applicability, PResult};
 use rustc_feature::Features;
 use rustc_parse::parser::{AttemptLocalParseRecovery, ForceCollect, Parser};
 use rustc_parse::validate_attr;
@@ -302,20 +304,11 @@ pub enum InvocationKind {
         item: Annotatable,
         // Required for resolving derive helper attributes.
         derives: Vec<Path>,
-        // We temporarily report errors for attribute macros placed after derives
-        after_derive: bool,
     },
     Derive {
         path: Path,
         item: Annotatable,
     },
-    /// "Invocation" that contains all derives from an item,
-    /// broken into multiple `Derive` invocations when expanded.
-    /// FIXME: Find a way to remove it.
-    DeriveContainer {
-        derives: Vec<Path>,
-        item: Annotatable,
-    },
 }
 
 impl InvocationKind {
@@ -328,7 +321,6 @@ impl InvocationKind {
         match self {
             InvocationKind::Attr { item: Annotatable::StructField(field), .. }
             | InvocationKind::Derive { item: Annotatable::StructField(field), .. }
-            | InvocationKind::DeriveContainer { item: Annotatable::StructField(field), .. }
                 if field.ident.is_none() =>
             {
                 Some(field.vis.clone())
@@ -344,7 +336,6 @@ impl Invocation {
             InvocationKind::Bang { span, .. } => *span,
             InvocationKind::Attr { attr, .. } => attr.span,
             InvocationKind::Derive { path, .. } => path.span,
-            InvocationKind::DeriveContainer { item, .. } => item.span(),
         }
     }
 }
@@ -446,7 +437,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         let mut undetermined_invocations = Vec::new();
         let (mut progress, mut force) = (false, !self.monotonic);
         loop {
-            let (invoc, res) = if let Some(invoc) = invocations.pop() {
+            let (invoc, ext) = if let Some(invoc) = invocations.pop() {
                 invoc
             } else {
                 self.resolve_imports();
@@ -464,8 +455,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 continue;
             };
 
-            let res = match res {
-                Some(res) => res,
+            let ext = match ext {
+                Some(ext) => ext,
                 None => {
                     let eager_expansion_root = if self.monotonic {
                         invoc.expansion_data.id
@@ -477,7 +468,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                         eager_expansion_root,
                         force,
                     ) {
-                        Ok(res) => res,
+                        Ok(ext) => ext,
                         Err(Indeterminate) => {
                             // Cannot resolve, will retry this invocation later.
                             undetermined_invocations.push((invoc, None));
@@ -491,86 +482,78 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             self.cx.current_expansion = invoc.expansion_data.clone();
             self.cx.force_mode = force;
 
-            // FIXME(jseyfried): Refactor out the following logic
             let fragment_kind = invoc.fragment_kind;
-            let (expanded_fragment, new_invocations) = match res {
-                InvocationRes::Single(ext) => match self.expand_invoc(invoc, &ext.kind) {
-                    ExpandResult::Ready(fragment) => self.collect_invocations(fragment, &[]),
-                    ExpandResult::Retry(invoc) => {
-                        if force {
-                            self.cx.span_bug(
-                                invoc.span(),
-                                "expansion entered force mode but is still stuck",
-                            );
-                        } else {
-                            // Cannot expand, will retry this invocation later.
-                            undetermined_invocations
-                                .push((invoc, Some(InvocationRes::Single(ext))));
-                            continue;
-                        }
-                    }
-                },
-                InvocationRes::DeriveContainer(_exts) => {
-                    // FIXME: Consider using the derive resolutions (`_exts`) immediately,
-                    // instead of enqueuing the derives to be resolved again later.
-                    let (derives, mut item) = match invoc.kind {
-                        InvocationKind::DeriveContainer { derives, item } => (derives, item),
-                        _ => unreachable!(),
-                    };
-                    let (item, derive_placeholders) = if !item.derive_allowed() {
-                        self.error_derive_forbidden_on_non_adt(&derives, &item);
-                        item.visit_attrs(|attrs| attrs.retain(|a| !a.has_name(sym::derive)));
-                        (item, Vec::new())
-                    } else {
-                        let mut visitor = StripUnconfigured {
-                            sess: self.cx.sess,
-                            features: self.cx.ecfg.features,
-                            modified: false,
-                        };
-                        let mut item = visitor.fully_configure(item);
-                        item.visit_attrs(|attrs| attrs.retain(|a| !a.has_name(sym::derive)));
-                        if visitor.modified && !derives.is_empty() {
-                            // Erase the tokens if cfg-stripping modified the item
-                            // This will cause us to synthesize fake tokens
-                            // when `nt_to_tokenstream` is called on this item.
-                            match &mut item {
-                                Annotatable::Item(item) => item.tokens = None,
-                                Annotatable::Stmt(stmt) => {
-                                    if let StmtKind::Item(item) = &mut stmt.kind {
-                                        item.tokens = None
-                                    } else {
-                                        panic!("Unexpected stmt {:?}", stmt);
-                                    }
-                                }
-                                _ => panic!("Unexpected annotatable {:?}", item),
+            let (expanded_fragment, new_invocations) = match self.expand_invoc(invoc, &ext.kind) {
+                ExpandResult::Ready(fragment) => {
+                    let derive_placeholders = self
+                        .cx
+                        .resolver
+                        .take_derive_resolutions(expn_id)
+                        .map(|derives| {
+                            enum AnnotatableRef<'a> {
+                                Item(&'a P<ast::Item>),
+                                Stmt(&'a ast::Stmt),
                             }
-                        }
+                            let item = match &fragment {
+                                AstFragment::Items(items) => match &items[..] {
+                                    [item] => AnnotatableRef::Item(item),
+                                    _ => unreachable!(),
+                                },
+                                AstFragment::Stmts(stmts) => match &stmts[..] {
+                                    [stmt] => AnnotatableRef::Stmt(stmt),
+                                    _ => unreachable!(),
+                                },
+                                _ => unreachable!(),
+                            };
 
-                        invocations.reserve(derives.len());
-                        let derive_placeholders = derives
-                            .into_iter()
-                            .map(|path| {
-                                let expn_id = ExpnId::fresh(None);
-                                invocations.push((
-                                    Invocation {
-                                        kind: InvocationKind::Derive { path, item: item.clone() },
-                                        fragment_kind,
-                                        expansion_data: ExpansionData {
-                                            id: expn_id,
-                                            ..self.cx.current_expansion.clone()
+                            invocations.reserve(derives.len());
+                            derives
+                                .into_iter()
+                                .map(|(_exts, path)| {
+                                    // FIXME: Consider using the derive resolutions (`_exts`)
+                                    // instead of enqueuing the derives to be resolved again later.
+                                    let expn_id = ExpnId::fresh(None);
+                                    invocations.push((
+                                        Invocation {
+                                            kind: InvocationKind::Derive {
+                                                path,
+                                                item: match item {
+                                                    AnnotatableRef::Item(item) => {
+                                                        Annotatable::Item(item.clone())
+                                                    }
+                                                    AnnotatableRef::Stmt(stmt) => {
+                                                        Annotatable::Stmt(P(stmt.clone()))
+                                                    }
+                                                },
+                                            },
+                                            fragment_kind,
+                                            expansion_data: ExpansionData {
+                                                id: expn_id,
+                                                ..self.cx.current_expansion.clone()
+                                            },
                                         },
-                                    },
-                                    None,
-                                ));
-                                NodeId::placeholder_from_expn_id(expn_id)
-                            })
-                            .collect::<Vec<_>>();
-                        (item, derive_placeholders)
-                    };
+                                        None,
+                                    ));
+                                    NodeId::placeholder_from_expn_id(expn_id)
+                                })
+                                .collect::<Vec<_>>()
+                        })
+                        .unwrap_or_default();
 
-                    let fragment = fragment_kind.expect_from_annotatables(::std::iter::once(item));
                     self.collect_invocations(fragment, &derive_placeholders)
                 }
+                ExpandResult::Retry(invoc) => {
+                    if force {
+                        self.cx.span_bug(
+                            invoc.span(),
+                            "expansion entered force mode but is still stuck",
+                        );
+                    } else {
+                        // Cannot expand, will retry this invocation later.
+                        undetermined_invocations.push((invoc, Some(ext)));
+                        continue;
+                    }
+                }
             };
 
             progress = true;
@@ -596,29 +579,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         fragment_with_placeholders
     }
 
-    fn error_derive_forbidden_on_non_adt(&self, derives: &[Path], item: &Annotatable) {
-        let attr = self.cx.sess.find_by_name(item.attrs(), sym::derive);
-        let span = attr.map_or(item.span(), |attr| attr.span);
-        let mut err = struct_span_err!(
-            self.cx.sess,
-            span,
-            E0774,
-            "`derive` may only be applied to structs, enums and unions",
-        );
-        if let Some(ast::Attribute { style: ast::AttrStyle::Inner, .. }) = attr {
-            let trait_list = derives.iter().map(|t| pprust::path_to_string(t)).collect::<Vec<_>>();
-            let suggestion = format!("#[derive({})]", trait_list.join(", "));
-            err.span_suggestion(
-                span,
-                "try an outer attribute",
-                suggestion,
-                // We don't 𝑘𝑛𝑜𝑤 that the following item is an ADT
-                Applicability::MaybeIncorrect,
-            );
-        }
-        err.emit();
-    }
-
     fn resolve_imports(&mut self) {
         if self.monotonic {
             self.cx.resolver.resolve_imports();
@@ -633,7 +593,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         &mut self,
         mut fragment: AstFragment,
         extra_placeholders: &[NodeId],
-    ) -> (AstFragment, Vec<(Invocation, Option<InvocationRes>)>) {
+    ) -> (AstFragment, Vec<(Invocation, Option<Lrc<SyntaxExtension>>)>) {
         // Resolve `$crate`s in the fragment for pretty-printing.
         self.cx.resolver.resolve_dollar_crates();
 
@@ -733,7 +693,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 }
                 _ => unreachable!(),
             },
-            InvocationKind::Attr { attr, mut item, derives, after_derive } => match ext {
+            InvocationKind::Attr { attr, mut item, derives } => match ext {
                 SyntaxExtensionKind::Attr(expander) => {
                     self.gate_proc_macro_input(&item);
                     self.gate_proc_macro_attr_item(span, &item);
@@ -764,12 +724,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                                 ExpandResult::Retry(item) => {
                                     // Reassemble the original invocation for retrying.
                                     return ExpandResult::Retry(Invocation {
-                                        kind: InvocationKind::Attr {
-                                            attr,
-                                            item,
-                                            derives,
-                                            after_derive,
-                                        },
+                                        kind: InvocationKind::Attr { attr, item, derives },
                                         ..invoc
                                     });
                                 }
@@ -813,7 +768,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 }
                 _ => unreachable!(),
             },
-            InvocationKind::DeriveContainer { .. } => unreachable!(),
         })
     }
 
@@ -1011,29 +965,13 @@ pub fn ensure_complete_parse<'a>(
 struct InvocationCollector<'a, 'b> {
     cx: &'a mut ExtCtxt<'b>,
     cfg: StripUnconfigured<'a>,
-    invocations: Vec<(Invocation, Option<InvocationRes>)>,
+    invocations: Vec<(Invocation, Option<Lrc<SyntaxExtension>>)>,
     monotonic: bool,
 }
 
 impl<'a, 'b> InvocationCollector<'a, 'b> {
     fn collect(&mut self, fragment_kind: AstFragmentKind, kind: InvocationKind) -> AstFragment {
-        // Expansion data for all the collected invocations is set upon their resolution,
-        // with exception of the derive container case which is not resolved and can get
-        // its expansion data immediately.
-        let expn_data = match &kind {
-            InvocationKind::DeriveContainer { item, .. } => {
-                let mut expn_data = ExpnData::default(
-                    ExpnKind::Macro(MacroKind::Attr, sym::derive),
-                    item.span(),
-                    self.cx.sess.parse_sess.edition,
-                    None,
-                );
-                expn_data.parent = self.cx.current_expansion.id;
-                Some(expn_data)
-            }
-            _ => None,
-        };
-        let expn_id = ExpnId::fresh(expn_data);
+        let expn_id = ExpnId::fresh(None);
         let vis = kind.placeholder_visibility();
         self.invocations.push((
             Invocation {
@@ -1061,64 +999,44 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
 
     fn collect_attr(
         &mut self,
-        (attr, derives, after_derive): (Option<ast::Attribute>, Vec<Path>, bool),
+        (attr, derives): (ast::Attribute, Vec<Path>),
         item: Annotatable,
         kind: AstFragmentKind,
     ) -> AstFragment {
-        self.collect(
-            kind,
-            match attr {
-                Some(attr) => InvocationKind::Attr { attr, item, derives, after_derive },
-                None => InvocationKind::DeriveContainer { derives, item },
-            },
-        )
-    }
-
-    fn find_attr_invoc(
-        &self,
-        attrs: &mut Vec<ast::Attribute>,
-        after_derive: &mut bool,
-    ) -> Option<ast::Attribute> {
-        attrs
-            .iter()
-            .position(|a| {
-                if a.has_name(sym::derive) {
-                    *after_derive = true;
-                }
-                !self.cx.sess.is_attr_known(a) && !is_builtin_attr(a)
-            })
-            .map(|i| attrs.remove(i))
-    }
-
-    /// If `item` is an attr invocation, remove and return the macro attribute and derive traits.
-    fn take_first_attr(
-        &mut self,
-        item: &mut impl HasAttrs,
-    ) -> Option<(Option<ast::Attribute>, Vec<Path>, /* after_derive */ bool)> {
-        let (mut attr, mut traits, mut after_derive) = (None, Vec::new(), false);
-
-        item.visit_attrs(|mut attrs| {
-            attr = self.find_attr_invoc(&mut attrs, &mut after_derive);
-            traits = collect_derives(&mut self.cx, &mut attrs);
-        });
-
-        if attr.is_some() || !traits.is_empty() { Some((attr, traits, after_derive)) } else { None }
+        self.collect(kind, InvocationKind::Attr { attr, item, derives })
     }
 
-    /// Alternative to `take_first_attr()` that ignores `#[derive]` so invocations fallthrough
-    /// to the unused-attributes lint (making it an error on statements and expressions
-    /// is a breaking change)
-    fn take_first_attr_no_derive(
-        &mut self,
-        nonitem: &mut impl HasAttrs,
-    ) -> Option<(Option<ast::Attribute>, Vec<Path>, /* after_derive */ bool)> {
-        let (mut attr, mut after_derive) = (None, false);
-
-        nonitem.visit_attrs(|mut attrs| {
-            attr = self.find_attr_invoc(&mut attrs, &mut after_derive);
+    /// If `item` is an attribute invocation, remove the attribute and return it together with
+    /// derives following it. We have to collect the derives in order to resolve legacy derive
+    /// helpers (helpers written before derives that introduce them).
+    fn take_first_attr(&mut self, item: &mut impl HasAttrs) -> Option<(ast::Attribute, Vec<Path>)> {
+        let mut attr = None;
+
+        item.visit_attrs(|attrs| {
+            attr = attrs
+                .iter()
+                .position(|a| !self.cx.sess.is_attr_known(a) && !is_builtin_attr(a))
+                .map(|attr_pos| {
+                    let attr = attrs.remove(attr_pos);
+                    let following_derives = attrs[attr_pos..]
+                        .iter()
+                        .filter(|a| a.has_name(sym::derive))
+                        .flat_map(|a| a.meta_item_list().unwrap_or_default())
+                        .filter_map(|nested_meta| match nested_meta {
+                            NestedMetaItem::MetaItem(ast::MetaItem {
+                                kind: MetaItemKind::Word,
+                                path,
+                                ..
+                            }) => Some(path),
+                            _ => None,
+                        })
+                        .collect();
+
+                    (attr, following_derives)
+                })
         });
 
-        attr.map(|attr| (Some(attr), Vec::new(), after_derive))
+        attr
     }
 
     fn configure<T: HasAttrs>(&mut self, node: T) -> Option<T> {
@@ -1132,17 +1050,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
         for attr in attrs.iter() {
             rustc_ast_passes::feature_gate::check_attribute(attr, self.cx.sess, features);
             validate_attr::check_meta(&self.cx.sess.parse_sess, attr);
-
-            // macros are expanded before any lint passes so this warning has to be hardcoded
-            if attr.has_name(sym::derive) {
-                self.cx
-                    .parse_sess()
-                    .span_diagnostic
-                    .struct_span_warn(attr.span, "`#[derive]` does nothing on macro invocations")
-                    .note("this may become a hard error in a future release")
-                    .emit();
-            }
-
             if attr.doc_str().is_some() {
                 self.cx.sess.parse_sess.buffer_lint_with_diagnostic(
                     &UNUSED_DOC_COMMENTS,
@@ -1162,12 +1069,10 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
         visit_clobber(expr.deref_mut(), |mut expr| {
             self.cfg.configure_expr_kind(&mut expr.kind);
 
-            if let Some(attr) = self.take_first_attr_no_derive(&mut expr) {
+            if let Some(attr) = self.take_first_attr(&mut expr) {
                 // Collect the invoc regardless of whether or not attributes are permitted here
                 // expansion will eat the attribute so it won't error later.
-                if let Some(attr) = attr.0.as_ref() {
-                    self.cfg.maybe_emit_expr_attr_err(attr)
-                }
+                self.cfg.maybe_emit_expr_attr_err(&attr.0);
 
                 // AstFragmentKind::Expr requires the macro to emit an expression.
                 return self
@@ -1263,10 +1168,8 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
         expr.filter_map(|mut expr| {
             self.cfg.configure_expr_kind(&mut expr.kind);
 
-            if let Some(attr) = self.take_first_attr_no_derive(&mut expr) {
-                if let Some(attr) = attr.0.as_ref() {
-                    self.cfg.maybe_emit_expr_attr_err(attr)
-                }
+            if let Some(attr) = self.take_first_attr(&mut expr) {
+                self.cfg.maybe_emit_expr_attr_err(&attr.0);
 
                 return self
                     .collect_attr(attr, Annotatable::Expr(P(expr)), AstFragmentKind::OptExpr)
@@ -1308,15 +1211,7 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
 
         // we'll expand attributes on expressions separately
         if !stmt.is_expr() {
-            let attr = if stmt.is_item() {
-                self.take_first_attr(&mut stmt)
-            } else {
-                // Ignore derives on non-item statements for backwards compatibility.
-                // This will result in a unused attribute warning
-                self.take_first_attr_no_derive(&mut stmt)
-            };
-
-            if let Some(attr) = attr {
+            if let Some(attr) = self.take_first_attr(&mut stmt) {
                 return self
                     .collect_attr(attr, Annotatable::Stmt(P(stmt)), AstFragmentKind::Stmts)
                     .make_stmts();
diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs
index 6779734cfc1..8cbaa7c945a 100644
--- a/compiler/rustc_expand/src/proc_macro.rs
+++ b/compiler/rustc_expand/src/proc_macro.rs
@@ -1,16 +1,14 @@
 use crate::base::{self, *};
 use crate::proc_macro_server;
 
+use rustc_ast as ast;
 use rustc_ast::ptr::P;
 use rustc_ast::token;
 use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, TokenStream, TokenTree};
-use rustc_ast::{self as ast, *};
 use rustc_data_structures::sync::Lrc;
-use rustc_errors::{struct_span_err, Applicability, ErrorReported};
-use rustc_lexer::is_ident;
+use rustc_errors::ErrorReported;
 use rustc_parse::nt_to_tokenstream;
 use rustc_parse::parser::ForceCollect;
-use rustc_span::symbol::sym;
 use rustc_span::{Span, DUMMY_SP};
 
 const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;
@@ -142,91 +140,3 @@ impl MultiItemModifier for ProcMacroDerive {
         ExpandResult::Ready(items)
     }
 }
-
-crate fn collect_derives(cx: &mut ExtCtxt<'_>, attrs: &mut Vec<ast::Attribute>) -> Vec<ast::Path> {
-    let mut result = Vec::new();
-    attrs.retain(|attr| {
-        if !attr.has_name(sym::derive) {
-            return true;
-        }
-
-        // 1) First let's ensure that it's a meta item.
-        let nmis = match attr.meta_item_list() {
-            None => {
-                cx.struct_span_err(attr.span, "malformed `derive` attribute input")
-                    .span_suggestion(
-                        attr.span,
-                        "missing traits to be derived",
-                        "#[derive(Trait1, Trait2, ...)]".to_owned(),
-                        Applicability::HasPlaceholders,
-                    )
-                    .emit();
-                return false;
-            }
-            Some(x) => x,
-        };
-
-        let mut error_reported_filter_map = false;
-        let mut error_reported_map = false;
-        let traits = nmis
-            .into_iter()
-            // 2) Moreover, let's ensure we have a path and not `#[derive("foo")]`.
-            .filter_map(|nmi| match nmi {
-                NestedMetaItem::Literal(lit) => {
-                    error_reported_filter_map = true;
-                    let mut err = struct_span_err!(
-                        cx.sess,
-                        lit.span,
-                        E0777,
-                        "expected path to a trait, found literal",
-                    );
-                    let token = lit.token.to_string();
-                    if token.starts_with('"')
-                        && token.len() > 2
-                        && is_ident(&token[1..token.len() - 1])
-                    {
-                        err.help(&format!("try using `#[derive({})]`", &token[1..token.len() - 1]));
-                    } else {
-                        err.help("for example, write `#[derive(Debug)]` for `Debug`");
-                    }
-                    err.emit();
-                    None
-                }
-                NestedMetaItem::MetaItem(mi) => Some(mi),
-            })
-            // 3) Finally, we only accept `#[derive($path_0, $path_1, ..)]`
-            // but not e.g. `#[derive($path_0 = "value", $path_1(abc))]`.
-            // In this case we can still at least determine that the user
-            // wanted this trait to be derived, so let's keep it.
-            .map(|mi| {
-                let mut traits_dont_accept = |title, action| {
-                    error_reported_map = true;
-                    let sp = mi.span.with_lo(mi.path.span.hi());
-                    cx.struct_span_err(sp, title)
-                        .span_suggestion(
-                            sp,
-                            action,
-                            String::new(),
-                            Applicability::MachineApplicable,
-                        )
-                        .emit();
-                };
-                match &mi.kind {
-                    MetaItemKind::List(..) => traits_dont_accept(
-                        "traits in `#[derive(...)]` don't accept arguments",
-                        "remove the arguments",
-                    ),
-                    MetaItemKind::NameValue(..) => traits_dont_accept(
-                        "traits in `#[derive(...)]` don't accept values",
-                        "remove the value",
-                    ),
-                    MetaItemKind::Word => {}
-                }
-                mi.path
-            });
-
-        result.extend(traits);
-        !error_reported_filter_map && !error_reported_map
-    });
-    result
-}
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 3ed5320da73..ac50703b544 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -188,7 +188,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")),
 
     // Macros:
-    ungated!(derive, Normal, template!(List: "Trait1, Trait2, ...")),
     ungated!(automatically_derived, Normal, template!(Word)),
     // FIXME(#14407)
     ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")),
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 58a9064b919..254220839aa 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -636,6 +636,9 @@ pub trait LintContext: Sized {
                     db.span_label(span, "ABI should be specified here");
                     db.help(&format!("the default ABI is {}", default_abi.name()));
                 }
+                BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
+                    db.span_label(span, "the attribute is introduced here");
+                }
             }
             // Rewrap `db`, and pass control to the user.
             decorate(LintDiagnosticBuilder::new(db));
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index da62ad3a6b1..f0a5ea150b7 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -1,12 +1,11 @@
 // ignore-tidy-filelength
+
 //! Some lints that are built in to the compiler.
 //!
 //! These are the built-in lints that are emitted direct in the main
 //! compiler code, rather than using their own custom pass. Those
 //! lints are all available in `rustc_lint::builtin`.
 
-// ignore-tidy-filelength
-
 use crate::{declare_lint, declare_lint_pass};
 use rustc_span::edition::Edition;
 use rustc_span::symbol::sym;
@@ -2922,6 +2921,52 @@ declare_lint! {
     };
 }
 
+declare_lint! {
+    /// The `legacy_derive_helpers` lint detects derive helper attributes
+    /// that are used before they are introduced.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,ignore (needs extern crate)
+    /// #[serde(rename_all = "camelCase")]
+    /// #[derive(Deserialize)]
+    /// struct S { /* fields */ }
+    /// ```
+    ///
+    /// produces:
+    ///
+    /// ```text
+    /// warning: derive helper attribute is used before it is introduced
+    ///   --> $DIR/legacy-derive-helpers.rs:1:3
+    ///    |
+    ///  1 | #[serde(rename_all = "camelCase")]
+    ///    |   ^^^^^
+    /// ...
+    ///  2 | #[derive(Deserialize)]
+    ///    |          ----------- the attribute is introduced here
+    /// ```
+    ///
+    /// ### Explanation
+    ///
+    /// Attributes like this work for historical reasons, but attribute expansion works in
+    /// left-to-right order in general, so, to resolve `#[serde]`, compiler has to try to "look
+    /// into the future" at not yet expanded part of the item , but such attempts are not always
+    /// reliable.
+    ///
+    /// To fix the warning place the helper attribute after its corresponding derive.
+    /// ```rust,ignore (needs extern crate)
+    /// #[derive(Deserialize)]
+    /// #[serde(rename_all = "camelCase")]
+    /// struct S { /* fields */ }
+    /// ```
+    pub LEGACY_DERIVE_HELPERS,
+    Warn,
+    "detects derive helper attributes that are used before they are introduced",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #79202 <https://github.com/rust-lang/rust/issues/79202>",
+    };
+}
+
 declare_lint_pass! {
     /// Does nothing as a lint pass, but registers some `Lint`s
     /// that are used by other parts of the compiler.
@@ -3012,6 +3057,7 @@ declare_lint_pass! {
         MISSING_ABI,
         SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
         DISJOINT_CAPTURE_DROP_REORDER,
+        LEGACY_DERIVE_HELPERS,
     ]
 }
 
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 9d60a51a0af..594e2cbd3ae 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -256,6 +256,7 @@ pub enum BuiltinLintDiagnostics {
     MissingAbi(Span, Abi),
     UnusedDocComment(Span),
     PatternsInFnsWithoutBody(Span, Ident),
+    LegacyDeriveHelpers(Span),
 }
 
 /// Lints that are buffered up early on in the `Session` before the
diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs
index 1b26fb33370..523eb9dba35 100644
--- a/compiler/rustc_parse/src/parser/attr.rs
+++ b/compiler/rustc_parse/src/parser/attr.rs
@@ -306,13 +306,11 @@ impl<'a> Parser<'a> {
 }
 
 pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool {
-    // One of the attributes may either itself be a macro, or apply derive macros (`derive`),
+    // One of the attributes may either itself be a macro,
     // or expand to macro attributes (`cfg_attr`).
     attrs.iter().any(|attr| {
         attr.ident().map_or(true, |ident| {
-            ident.name == sym::derive
-                || ident.name == sym::cfg_attr
-                || !rustc_feature::is_builtin_attr_name(ident.name)
+            ident.name == sym::cfg_attr || !rustc_feature::is_builtin_attr_name(ident.name)
         })
     })
 }
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 2be669a1726..819fabdd1f1 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -967,6 +967,8 @@ pub struct Resolver<'a> {
     output_macro_rules_scopes: FxHashMap<ExpnId, MacroRulesScopeRef<'a>>,
     /// Helper attributes that are in scope for the given expansion.
     helper_attrs: FxHashMap<ExpnId, Vec<Ident>>,
+    /// Resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
+    derive_resolutions: FxHashMap<ExpnId, Vec<(Lrc<SyntaxExtension>, ast::Path)>>,
 
     /// Avoid duplicated errors for "name already defined".
     name_already_seen: FxHashMap<Symbol, Span>,
@@ -1295,6 +1297,7 @@ impl<'a> Resolver<'a> {
             invocation_parent_scopes: Default::default(),
             output_macro_rules_scopes: Default::default(),
             helper_attrs: Default::default(),
+            derive_resolutions: Default::default(),
             local_macro_def_scopes: FxHashMap::default(),
             name_already_seen: FxHashMap::default(),
             potentially_unused_imports: Vec::new(),
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index d76db7c645a..6d3fde33f4d 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -14,8 +14,7 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::ptr_key::PtrKey;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::struct_span_err;
-use rustc_expand::base::{Indeterminate, InvocationRes, ResolverExpand};
-use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
+use rustc_expand::base::{Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind};
 use rustc_expand::compile_declarative_macro;
 use rustc_expand::expand::{AstFragment, Invocation, InvocationKind};
 use rustc_feature::is_builtin_attr_name;
@@ -24,7 +23,8 @@ use rustc_hir::def_id;
 use rustc_hir::PrimTy;
 use rustc_middle::middle::stability;
 use rustc_middle::ty;
-use rustc_session::lint::builtin::{SOFT_UNSTABLE, UNUSED_MACROS};
+use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNUSED_MACROS};
+use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_session::parse::feature_err;
 use rustc_session::Session;
 use rustc_span::edition::Edition;
@@ -227,7 +227,7 @@ impl<'a> ResolverExpand for Resolver<'a> {
         invoc: &Invocation,
         eager_expansion_root: ExpnId,
         force: bool,
-    ) -> Result<InvocationRes, Indeterminate> {
+    ) -> Result<Lrc<SyntaxExtension>, Indeterminate> {
         let invoc_id = invoc.expansion_data.id;
         let parent_scope = match self.invocation_parent_scopes.get(&invoc_id) {
             Some(parent_scope) => *parent_scope,
@@ -244,65 +244,15 @@ impl<'a> ResolverExpand for Resolver<'a> {
             }
         };
 
-        let (path, kind, inner_attr, derives, after_derive) = match invoc.kind {
-            InvocationKind::Attr { ref attr, ref derives, after_derive, .. } => (
+        let (path, kind, inner_attr, derives) = match invoc.kind {
+            InvocationKind::Attr { ref attr, ref derives, .. } => (
                 &attr.get_normal_item().path,
                 MacroKind::Attr,
                 attr.style == ast::AttrStyle::Inner,
                 self.arenas.alloc_ast_paths(derives),
-                after_derive,
             ),
-            InvocationKind::Bang { ref mac, .. } => {
-                (&mac.path, MacroKind::Bang, false, &[][..], false)
-            }
-            InvocationKind::Derive { ref path, .. } => {
-                (path, MacroKind::Derive, false, &[][..], false)
-            }
-            InvocationKind::DeriveContainer { ref derives, .. } => {
-                // Block expansion of the container until we resolve all derives in it.
-                // This is required for two reasons:
-                // - Derive helper attributes are in scope for the item to which the `#[derive]`
-                //   is applied, so they have to be produced by the container's expansion rather
-                //   than by individual derives.
-                // - Derives in the container need to know whether one of them is a built-in `Copy`.
-                // FIXME: Try to avoid repeated resolutions for derives here and in expansion.
-                let mut exts = Vec::new();
-                let mut helper_attrs = Vec::new();
-                for path in derives {
-                    exts.push(
-                        match self.resolve_macro_path(
-                            path,
-                            Some(MacroKind::Derive),
-                            &parent_scope,
-                            true,
-                            force,
-                        ) {
-                            Ok((Some(ext), _)) => {
-                                let span = path
-                                    .segments
-                                    .last()
-                                    .unwrap()
-                                    .ident
-                                    .span
-                                    .normalize_to_macros_2_0();
-                                helper_attrs.extend(
-                                    ext.helper_attrs.iter().map(|name| Ident::new(*name, span)),
-                                );
-                                if ext.builtin_name == Some(sym::Copy) {
-                                    self.containers_deriving_copy.insert(invoc_id);
-                                }
-                                ext
-                            }
-                            Ok(_) | Err(Determinacy::Determined) => {
-                                self.dummy_ext(MacroKind::Derive)
-                            }
-                            Err(Determinacy::Undetermined) => return Err(Indeterminate),
-                        },
-                    )
-                }
-                self.helper_attrs.insert(invoc_id, helper_attrs);
-                return Ok(InvocationRes::DeriveContainer(exts));
-            }
+            InvocationKind::Bang { ref mac, .. } => (&mac.path, MacroKind::Bang, false, &[][..]),
+            InvocationKind::Derive { ref path, .. } => (path, MacroKind::Derive, false, &[][..]),
         };
 
         // Derives are not included when `invocations` are collected, so we have to add them here.
@@ -328,14 +278,11 @@ impl<'a> ResolverExpand for Resolver<'a> {
         ));
 
         if let Res::Def(_, _) = res {
-            if after_derive {
-                self.session.span_err(span, "macro attributes must be placed before `#[derive]`");
-            }
             let normal_module_def_id = self.macro_def_scope(invoc_id).nearest_parent_mod;
             self.definitions.add_parent_module_of_macro_def(invoc_id, normal_module_def_id);
         }
 
-        Ok(InvocationRes::Single(ext))
+        Ok(ext)
     }
 
     fn check_unused_macros(&mut self) {
@@ -344,7 +291,7 @@ impl<'a> ResolverExpand for Resolver<'a> {
         }
     }
 
-    fn lint_node_id(&mut self, expn_id: ExpnId) -> NodeId {
+    fn lint_node_id(&self, expn_id: ExpnId) -> NodeId {
         // FIXME - make this more precise. This currently returns the NodeId of the
         // nearest closing item - we should try to return the closest parent of the ExpnId
         self.invocation_parents
@@ -356,6 +303,63 @@ impl<'a> ResolverExpand for Resolver<'a> {
         self.containers_deriving_copy.contains(&expn_id)
     }
 
+    fn resolve_derives(
+        &mut self,
+        expn_id: ExpnId,
+        derives: Vec<ast::Path>,
+        force: bool,
+    ) -> Result<(), Indeterminate> {
+        // Block expansion of the container until we resolve all derives in it.
+        // This is required for two reasons:
+        // - Derive helper attributes are in scope for the item to which the `#[derive]`
+        //   is applied, so they have to be produced by the container's expansion rather
+        //   than by individual derives.
+        // - Derives in the container need to know whether one of them is a built-in `Copy`.
+        // FIXME: Try to cache intermediate results to avoid resolving same derives multiple times.
+        let parent_scope = self.invocation_parent_scopes[&expn_id];
+        let mut exts = Vec::new();
+        let mut helper_attrs = Vec::new();
+        let mut has_derive_copy = false;
+        for path in derives {
+            exts.push((
+                match self.resolve_macro_path(
+                    &path,
+                    Some(MacroKind::Derive),
+                    &parent_scope,
+                    true,
+                    force,
+                ) {
+                    Ok((Some(ext), _)) => {
+                        let span =
+                            path.segments.last().unwrap().ident.span.normalize_to_macros_2_0();
+                        helper_attrs
+                            .extend(ext.helper_attrs.iter().map(|name| Ident::new(*name, span)));
+                        has_derive_copy |= ext.builtin_name == Some(sym::Copy);
+                        ext
+                    }
+                    Ok(_) | Err(Determinacy::Determined) => self.dummy_ext(MacroKind::Derive),
+                    Err(Determinacy::Undetermined) => return Err(Indeterminate),
+                },
+                path,
+            ))
+        }
+        self.derive_resolutions.insert(expn_id, exts);
+        self.helper_attrs.insert(expn_id, helper_attrs);
+        // Mark this derive as having `Copy` either if it has `Copy` itself or if its parent derive
+        // has `Copy`, to support cases like `#[derive(Clone, Copy)] #[derive(Debug)]`.
+        if has_derive_copy || self.has_derive_copy(parent_scope.expansion) {
+            self.containers_deriving_copy.insert(expn_id);
+        }
+        Ok(())
+    }
+
+    fn take_derive_resolutions(
+        &mut self,
+        expn_id: ExpnId,
+    ) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>> {
+        self.derive_resolutions.remove(&expn_id)
+    }
+
     // The function that implements the resolution logic of `#[cfg_accessible(path)]`.
     // Returns true if the path can certainly be resolved in one of three namespaces,
     // returns false if the path certainly cannot be resolved in any of the three namespaces.
@@ -818,6 +822,8 @@ impl<'a> Resolver<'a> {
                                 let is_builtin = |res| {
                                     matches!(res, Res::NonMacroAttr(NonMacroAttrKind::Builtin(..)))
                                 };
+                                let derive_helper =
+                                    Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper);
                                 let derive_helper_compat =
                                     Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat);
 
@@ -826,7 +832,7 @@ impl<'a> Resolver<'a> {
                                 } else if is_builtin(innermost_res) || is_builtin(res) {
                                     Some(AmbiguityKind::BuiltinAttr)
                                 } else if innermost_res == derive_helper_compat
-                                    || res == derive_helper_compat
+                                    || res == derive_helper_compat && innermost_res != derive_helper
                                 {
                                     Some(AmbiguityKind::DeriveHelper)
                                 } else if innermost_flags.contains(Flags::MACRO_RULES)
@@ -992,6 +998,15 @@ impl<'a> Resolver<'a> {
                     let res = binding.res();
                     let seg = Segment::from_ident(ident);
                     check_consistency(self, &[seg], ident.span, kind, initial_res, res);
+                    if res == Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat) {
+                        self.lint_buffer.buffer_lint_with_diagnostic(
+                            LEGACY_DERIVE_HELPERS,
+                            self.lint_node_id(parent_scope.expansion),
+                            ident.span,
+                            "derive helper attribute is used before it is introduced",
+                            BuiltinLintDiagnostics::LegacyDeriveHelpers(binding.span),
+                        );
+                    }
                 }
                 Err(..) => {
                     let expected = kind.descr_expected();
@@ -1078,7 +1093,7 @@ impl<'a> Resolver<'a> {
     crate fn check_reserved_macro_name(&mut self, ident: Ident, res: Res) {
         // Reserve some names that are not quite covered by the general check
         // performed on `Resolver::builtin_attrs`.
-        if ident.name == sym::cfg || ident.name == sym::cfg_attr || ident.name == sym::derive {
+        if ident.name == sym::cfg || ident.name == sym::cfg_attr {
             let macro_kind = self.get_macro(res).map(|ext| ext.macro_kind());
             if macro_kind.is_some() && sub_namespace_match(macro_kind, Some(MacroKind::Attr)) {
                 self.session.span_err(