about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2024-12-28 02:19:35 +0000
committerEsteban Küber <esteban@kuber.com.ar>2025-06-12 21:28:49 +0000
commit92a798dac0e2e13380d053b532ef36f1eddf9acf (patch)
tree430fd3c78aed15d8d3ab6f8fc813f09af35fcedd
parent6c8138de8f1c96b2f66adbbc0e37c73525444750 (diff)
downloadrust-92a798dac0e2e13380d053b532ef36f1eddf9acf.tar.gz
rust-92a798dac0e2e13380d053b532ef36f1eddf9acf.zip
Detect when attribute is provided by missing `derive` macro
```
error: cannot find attribute `empty_helper` in this scope
  --> $DIR/derive-helper-legacy-limits.rs:17:3
   |
LL | #[empty_helper]
   |   ^^^^^^^^^^^^
   |
help: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
   |
LL + #[derive(Empty)]
LL | struct S2;
   |
```

Look at proc-macro attributes when encountering unknown attribute

```
error: cannot find attribute `sede` in this scope
  --> src/main.rs:18:7
   |
18 |     #[sede(untagged)]
   |       ^^^^
   |
help: the derive macros `Serialize` and `Deserialize` accept the similarly named `serde` attribute
   |
18 |     #[serde(untagged)]
   |       ~~~~~

error: cannot find attribute `serde` in this scope
  --> src/main.rs:12:7
   |
12 |     #[serde(untagged)]
   |       ^^^^^
   |
   = note: `serde` is in scope, but it is a crate, not an attribute
help: `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`, you might be missing a `derive` attribute
   |
10 | #[derive(Serialize, Deserialize)]
   |
```
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs134
-rw-r--r--compiler/rustc_resolve/src/ident.rs1
-rw-r--r--compiler/rustc_resolve/src/late.rs2
-rw-r--r--compiler/rustc_resolve/src/lib.rs2
-rw-r--r--compiler/rustc_resolve/src/macros.rs30
-rw-r--r--tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr14
-rw-r--r--tests/ui/macros/auxiliary/serde.rs19
-rw-r--r--tests/ui/macros/missing-derive-1.rs33
-rw-r--r--tests/ui/macros/missing-derive-1.stderr47
-rw-r--r--tests/ui/macros/missing-derive-2.rs26
-rw-r--r--tests/ui/macros/missing-derive-2.stderr47
-rw-r--r--tests/ui/macros/missing-derive-3.rs24
-rw-r--r--tests/ui/macros/missing-derive-3.stderr32
-rw-r--r--tests/ui/proc-macro/derive-helper-legacy-limits.stderr6
-rw-r--r--tests/ui/proc-macro/derive-helper-shadowing.stderr2
-rw-r--r--tests/ui/proc-macro/disappearing-resolution.stderr6
16 files changed, 418 insertions, 7 deletions
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 201b1c0a493..9149974a617 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -7,7 +7,7 @@ use rustc_ast::{
 use rustc_ast_pretty::pprust;
 use rustc_attr_data_structures::{self as attr, Stability};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_data_structures::unord::UnordSet;
+use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_errors::codes::*;
 use rustc_errors::{
     Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, MultiSpan, SuggestionStyle,
@@ -1054,6 +1054,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                                 false,
                                 false,
                                 None,
+                                None,
                             ) else {
                                 continue;
                             };
@@ -1482,7 +1483,35 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         parent_scope: &ParentScope<'ra>,
         ident: Ident,
         krate: &Crate,
+        sugg_span: Option<Span>,
     ) {
+        // Bring imported but unused `derive` macros into `macro_map` so we ensure they can be used
+        // for suggestions.
+        self.visit_scopes(
+            ScopeSet::Macro(MacroKind::Derive),
+            &parent_scope,
+            ident.span.ctxt(),
+            |this, scope, _use_prelude, _ctxt| {
+                let Scope::Module(m, _) = scope else {
+                    return None;
+                };
+                for (_, resolution) in this.resolutions(m).borrow().iter() {
+                    let Some(binding) = resolution.borrow().binding else {
+                        continue;
+                    };
+                    let Res::Def(DefKind::Macro(MacroKind::Derive | MacroKind::Attr), def_id) =
+                        binding.res()
+                    else {
+                        continue;
+                    };
+                    // By doing this all *imported* macros get added to the `macro_map` even if they
+                    // are *unused*, which makes the later suggestions find them and work.
+                    let _ = this.get_macro_by_def_id(def_id);
+                }
+                None::<()>
+            },
+        );
+
         let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind);
         let suggestion = self.early_lookup_typo_candidate(
             ScopeSet::Macro(macro_kind),
@@ -1490,7 +1519,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             ident,
             is_expected,
         );
-        self.add_typo_suggestion(err, suggestion, ident.span);
+        if !self.add_typo_suggestion(err, suggestion, ident.span) {
+            self.detect_derive_attribute(err, ident, parent_scope, sugg_span);
+        }
 
         let import_suggestions =
             self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
@@ -1623,6 +1654,105 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         }
     }
 
+    /// Given an attribute macro that failed to be resolved, look for `derive` macros that could
+    /// provide it, either as-is or with small typos.
+    fn detect_derive_attribute(
+        &self,
+        err: &mut Diag<'_>,
+        ident: Ident,
+        parent_scope: &ParentScope<'ra>,
+        sugg_span: Option<Span>,
+    ) {
+        // Find all of the `derive`s in scope and collect their corresponding declared
+        // attributes.
+        // FIXME: this only works if the crate that owns the macro that has the helper_attr
+        // has already been imported.
+        let mut derives = vec![];
+        let mut all_attrs: UnordMap<Symbol, Vec<_>> = UnordMap::default();
+        // We're collecting these in a hashmap, and handle ordering the output further down.
+        #[allow(rustc::potential_query_instability)]
+        for (def_id, data) in &self.macro_map {
+            for helper_attr in &data.ext.helper_attrs {
+                let item_name = self.tcx.item_name(*def_id);
+                all_attrs.entry(*helper_attr).or_default().push(item_name);
+                if helper_attr == &ident.name {
+                    derives.push(item_name);
+                }
+            }
+        }
+        let kind = MacroKind::Derive.descr();
+        if !derives.is_empty() {
+            // We found an exact match for the missing attribute in a `derive` macro. Suggest it.
+            let mut derives: Vec<String> = derives.into_iter().map(|d| d.to_string()).collect();
+            derives.sort();
+            derives.dedup();
+            let msg = match &derives[..] {
+                [derive] => format!(" `{derive}`"),
+                [start @ .., last] => format!(
+                    "s {} and `{last}`",
+                    start.iter().map(|d| format!("`{d}`")).collect::<Vec<_>>().join(", ")
+                ),
+                [] => unreachable!("we checked for this to be non-empty 10 lines above!?"),
+            };
+            let msg = format!(
+                "`{}` is an attribute that can be used by the {kind}{msg}, you might be \
+                     missing a `derive` attribute",
+                ident.name,
+            );
+            let sugg_span = if let ModuleKind::Def(DefKind::Enum, id, _) = parent_scope.module.kind
+            {
+                let span = self.def_span(id);
+                if span.from_expansion() {
+                    None
+                } else {
+                    // For enum variants sugg_span is empty but we can get the enum's Span.
+                    Some(span.shrink_to_lo())
+                }
+            } else {
+                // For items this `Span` will be populated, everything else it'll be None.
+                sugg_span
+            };
+            match sugg_span {
+                Some(span) => {
+                    err.span_suggestion_verbose(
+                        span,
+                        msg,
+                        format!("#[derive({})]\n", derives.join(", ")),
+                        Applicability::MaybeIncorrect,
+                    );
+                }
+                None => {
+                    err.note(msg);
+                }
+            }
+        } else {
+            // We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
+            let all_attr_names = all_attrs.keys().map(|s| *s).into_sorted_stable_ord();
+            if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None)
+                && let Some(macros) = all_attrs.get(&best_match)
+            {
+                let mut macros: Vec<String> = macros.into_iter().map(|d| d.to_string()).collect();
+                macros.sort();
+                macros.dedup();
+                let msg = match &macros[..] {
+                    [] => return,
+                    [name] => format!(" `{name}` accepts"),
+                    [start @ .., end] => format!(
+                        "s {} and `{end}` accept",
+                        start.iter().map(|m| format!("`{m}`")).collect::<Vec<_>>().join(", "),
+                    ),
+                };
+                let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute");
+                err.span_suggestion_verbose(
+                    ident.span,
+                    msg,
+                    best_match,
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        }
+    }
+
     pub(crate) fn add_typo_suggestion(
         &self,
         err: &mut Diag<'_>,
diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs
index 180d6af219d..68fbe48ebcb 100644
--- a/compiler/rustc_resolve/src/ident.rs
+++ b/compiler/rustc_resolve/src/ident.rs
@@ -460,6 +460,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                                 true,
                                 force,
                                 ignore_import,
+                                None,
                             ) {
                                 Ok((Some(ext), _)) => {
                                     if ext.helper_attrs.contains(&ident.name) {
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 3dc285fdab6..fa4b024c422 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -4509,7 +4509,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
             let path_seg = |seg: &Segment| PathSegment::from_ident(seg.ident);
             let path = Path { segments: path.iter().map(path_seg).collect(), span, tokens: None };
             if let Ok((_, res)) =
-                self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None)
+                self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None, None)
             {
                 return Ok(Some(PartialRes::new(res)));
             }
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 9ba70abd4d9..2ff48c503c5 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -1139,7 +1139,7 @@ pub struct Resolver<'ra, 'tcx> {
     proc_macro_stubs: FxHashSet<LocalDefId>,
     /// Traces collected during macro resolution and validated when it's complete.
     single_segment_macro_resolutions:
-        Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>)>,
+        Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>, Option<Span>)>,
     multi_segment_macro_resolutions:
         Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'ra>, Option<Res>, Namespace)>,
     builtin_attrs: Vec<(Ident, ParentScope<'ra>)>,
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index ee905065b96..e80d4422e37 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -12,7 +12,8 @@ use rustc_attr_data_structures::StabilityLevel;
 use rustc_data_structures::intern::Interned;
 use rustc_errors::{Applicability, DiagCtxtHandle, StashKey};
 use rustc_expand::base::{
-    DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind,
+    Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension,
+    SyntaxExtensionKind,
 };
 use rustc_expand::compile_declarative_macro;
 use rustc_expand::expand::{
@@ -294,6 +295,14 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
                     && self.tcx.def_kind(mod_def_id) == DefKind::Mod
             })
             .map(|&InvocationParent { parent_def: mod_def_id, .. }| mod_def_id);
+        let sugg_span = match &invoc.kind {
+            InvocationKind::Attr { item: Annotatable::Item(item), .. }
+                if !item.span.from_expansion() =>
+            {
+                Some(item.span.shrink_to_lo())
+            }
+            _ => None,
+        };
         let (ext, res) = self.smart_resolve_macro_path(
             path,
             kind,
@@ -304,6 +313,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
             force,
             deleg_impl,
             looks_like_invoc_in_mod_inert_attr,
+            sugg_span,
         )?;
 
         let span = invoc.span();
@@ -386,6 +396,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
                         true,
                         force,
                         None,
+                        None,
                     ) {
                         Ok((Some(ext), _)) => {
                             if !ext.helper_attrs.is_empty() {
@@ -528,6 +539,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         force: bool,
         deleg_impl: Option<LocalDefId>,
         invoc_in_mod_inert_attr: Option<LocalDefId>,
+        suggestion_span: Option<Span>,
     ) -> Result<(Arc<SyntaxExtension>, Res), Indeterminate> {
         let (ext, res) = match self.resolve_macro_or_delegation_path(
             path,
@@ -538,6 +550,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             deleg_impl,
             invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)),
             None,
+            suggestion_span,
         ) {
             Ok((Some(ext), res)) => (ext, res),
             Ok((None, res)) => (self.dummy_ext(kind), res),
@@ -681,6 +694,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         trace: bool,
         force: bool,
         ignore_import: Option<Import<'ra>>,
+        suggestion_span: Option<Span>,
     ) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
         self.resolve_macro_or_delegation_path(
             path,
@@ -691,6 +705,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             None,
             None,
             ignore_import,
+            suggestion_span,
         )
     }
 
@@ -704,6 +719,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         deleg_impl: Option<LocalDefId>,
         invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>,
         ignore_import: Option<Import<'ra>>,
+        suggestion_span: Option<Span>,
     ) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
         let path_span = ast_path.span;
         let mut path = Segment::from_path(ast_path);
@@ -768,6 +784,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                     kind,
                     *parent_scope,
                     binding.ok(),
+                    suggestion_span,
                 ));
             }
 
@@ -905,7 +922,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         }
 
         let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
-        for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
+        for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
             match self.early_resolve_ident_in_lexical_scope(
                 ident,
                 ScopeSet::Macro(kind),
@@ -946,7 +963,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                         expected,
                         ident,
                     });
-                    self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate);
+                    self.unresolved_macro_suggestions(
+                        &mut err,
+                        kind,
+                        &parent_scope,
+                        ident,
+                        krate,
+                        sugg_span,
+                    );
                     err.emit();
                 }
             }
diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index 03fca17aa55..001699b2bc7 100644
--- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -583,18 +583,32 @@ error: cannot find attribute `multipart_suggestion` in this scope
    |
 LL | #[multipart_suggestion(no_crate_suggestion)]
    |   ^^^^^^^^^^^^^^^^^^^^
+   |
+help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Subdiagnostic)]
+LL | struct MultipartSuggestion {
+   |
 
 error: cannot find attribute `multipart_suggestion` in this scope
   --> $DIR/diagnostic-derive.rs:647:3
    |
 LL | #[multipart_suggestion()]
    |   ^^^^^^^^^^^^^^^^^^^^
+   |
+help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Subdiagnostic)]
+LL | struct MultipartSuggestion {
+   |
 
 error: cannot find attribute `multipart_suggestion` in this scope
   --> $DIR/diagnostic-derive.rs:651:7
    |
 LL |     #[multipart_suggestion(no_crate_suggestion)]
    |       ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
 
 error[E0425]: cannot find value `nonsense` in module `crate::fluent_generated`
   --> $DIR/diagnostic-derive.rs:75:8
diff --git a/tests/ui/macros/auxiliary/serde.rs b/tests/ui/macros/auxiliary/serde.rs
new file mode 100644
index 00000000000..355b650bcf3
--- /dev/null
+++ b/tests/ui/macros/auxiliary/serde.rs
@@ -0,0 +1,19 @@
+//@ force-host
+//@ no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+#![feature(proc_macro_quote)]
+
+extern crate proc_macro;
+
+use proc_macro::*;
+
+#[proc_macro_derive(Serialize, attributes(serde))]
+pub fn serialize(ts: TokenStream) -> TokenStream {
+    quote!{}
+}
+
+#[proc_macro_derive(Deserialize, attributes(serde))]
+pub fn deserialize(ts: TokenStream) -> TokenStream {
+    quote!{}
+}
diff --git a/tests/ui/macros/missing-derive-1.rs b/tests/ui/macros/missing-derive-1.rs
new file mode 100644
index 00000000000..e23ef7bf07b
--- /dev/null
+++ b/tests/ui/macros/missing-derive-1.rs
@@ -0,0 +1,33 @@
+//@aux-build:serde.rs
+
+// derive macros imported and used
+
+extern crate serde;
+use serde::{Serialize, Deserialize};
+
+#[serde(untagged)] //~ ERROR cannot find attribute `serde`
+enum A { //~ HELP `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`
+    A,
+    B,
+}
+
+enum B { //~ HELP `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`
+    A,
+    #[serde(untagged)] //~ ERROR cannot find attribute `serde`
+    B,
+}
+
+enum C {
+    A,
+    #[sede(untagged)] //~ ERROR cannot find attribute `sede`
+    B, //~^ HELP the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(untagged)]
+enum D {
+    A,
+    B,
+}
+
+fn main() {}
diff --git a/tests/ui/macros/missing-derive-1.stderr b/tests/ui/macros/missing-derive-1.stderr
new file mode 100644
index 00000000000..15584100ffa
--- /dev/null
+++ b/tests/ui/macros/missing-derive-1.stderr
@@ -0,0 +1,47 @@
+error: cannot find attribute `serde` in this scope
+  --> $DIR/missing-derive-1.rs:8:3
+   |
+LL | #[serde(untagged)]
+   |   ^^^^^
+   |
+note: `serde` is imported here, but it is a crate, not an attribute
+  --> $DIR/missing-derive-1.rs:5:1
+   |
+LL | extern crate serde;
+   | ^^^^^^^^^^^^^^^^^^^
+help: `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Deserialize, Serialize)]
+LL | enum A {
+   |
+
+error: cannot find attribute `serde` in this scope
+  --> $DIR/missing-derive-1.rs:16:7
+   |
+LL |     #[serde(untagged)]
+   |       ^^^^^
+   |
+note: `serde` is imported here, but it is a crate, not an attribute
+  --> $DIR/missing-derive-1.rs:5:1
+   |
+LL | extern crate serde;
+   | ^^^^^^^^^^^^^^^^^^^
+help: `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Deserialize, Serialize)]
+LL | enum B {
+   |
+
+error: cannot find attribute `sede` in this scope
+  --> $DIR/missing-derive-1.rs:22:7
+   |
+LL |     #[sede(untagged)]
+   |       ^^^^
+   |
+help: the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
+   |
+LL |     #[serde(untagged)]
+   |         +
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/macros/missing-derive-2.rs b/tests/ui/macros/missing-derive-2.rs
new file mode 100644
index 00000000000..027d465d8b3
--- /dev/null
+++ b/tests/ui/macros/missing-derive-2.rs
@@ -0,0 +1,26 @@
+//@aux-build:serde.rs
+
+// derive macros imported but unused
+
+extern crate serde;
+use serde::{Serialize, Deserialize};
+
+#[serde(untagged)] //~ ERROR cannot find attribute `serde`
+enum A { //~ HELP `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`
+    A,
+    B,
+}
+
+enum B { //~ HELP `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`
+    A,
+    #[serde(untagged)] //~ ERROR cannot find attribute `serde`
+    B,
+}
+
+enum C {
+    A,
+    #[sede(untagged)] //~ ERROR cannot find attribute `sede`
+    B, //~^ HELP the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
+}
+
+fn main() {}
diff --git a/tests/ui/macros/missing-derive-2.stderr b/tests/ui/macros/missing-derive-2.stderr
new file mode 100644
index 00000000000..6c8e9e12019
--- /dev/null
+++ b/tests/ui/macros/missing-derive-2.stderr
@@ -0,0 +1,47 @@
+error: cannot find attribute `sede` in this scope
+  --> $DIR/missing-derive-2.rs:22:7
+   |
+LL |     #[sede(untagged)]
+   |       ^^^^
+   |
+help: the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
+   |
+LL |     #[serde(untagged)]
+   |         +
+
+error: cannot find attribute `serde` in this scope
+  --> $DIR/missing-derive-2.rs:16:7
+   |
+LL |     #[serde(untagged)]
+   |       ^^^^^
+   |
+note: `serde` is imported here, but it is a crate, not an attribute
+  --> $DIR/missing-derive-2.rs:5:1
+   |
+LL | extern crate serde;
+   | ^^^^^^^^^^^^^^^^^^^
+help: `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Deserialize, Serialize)]
+LL | enum B {
+   |
+
+error: cannot find attribute `serde` in this scope
+  --> $DIR/missing-derive-2.rs:8:3
+   |
+LL | #[serde(untagged)]
+   |   ^^^^^
+   |
+note: `serde` is imported here, but it is a crate, not an attribute
+  --> $DIR/missing-derive-2.rs:5:1
+   |
+LL | extern crate serde;
+   | ^^^^^^^^^^^^^^^^^^^
+help: `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Deserialize, Serialize)]
+LL | enum A {
+   |
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/macros/missing-derive-3.rs b/tests/ui/macros/missing-derive-3.rs
new file mode 100644
index 00000000000..8add8198890
--- /dev/null
+++ b/tests/ui/macros/missing-derive-3.rs
@@ -0,0 +1,24 @@
+//@aux-build:serde.rs
+
+// derive macros not imported, but namespace imported. Not yet handled.
+extern crate serde;
+
+#[serde(untagged)] //~ ERROR cannot find attribute `serde`
+enum A {
+    A,
+    B,
+}
+
+enum B {
+    A,
+    #[serde(untagged)] //~ ERROR cannot find attribute `serde`
+    B,
+}
+
+enum C {
+    A,
+    #[sede(untagged)] //~ ERROR cannot find attribute `sede`
+    B,
+}
+
+fn main() {}
diff --git a/tests/ui/macros/missing-derive-3.stderr b/tests/ui/macros/missing-derive-3.stderr
new file mode 100644
index 00000000000..0a7ed8d0876
--- /dev/null
+++ b/tests/ui/macros/missing-derive-3.stderr
@@ -0,0 +1,32 @@
+error: cannot find attribute `sede` in this scope
+  --> $DIR/missing-derive-3.rs:20:7
+   |
+LL |     #[sede(untagged)]
+   |       ^^^^
+
+error: cannot find attribute `serde` in this scope
+  --> $DIR/missing-derive-3.rs:14:7
+   |
+LL |     #[serde(untagged)]
+   |       ^^^^^
+   |
+note: `serde` is imported here, but it is a crate, not an attribute
+  --> $DIR/missing-derive-3.rs:4:1
+   |
+LL | extern crate serde;
+   | ^^^^^^^^^^^^^^^^^^^
+
+error: cannot find attribute `serde` in this scope
+  --> $DIR/missing-derive-3.rs:6:3
+   |
+LL | #[serde(untagged)]
+   |   ^^^^^
+   |
+note: `serde` is imported here, but it is a crate, not an attribute
+  --> $DIR/missing-derive-3.rs:4:1
+   |
+LL | extern crate serde;
+   | ^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/proc-macro/derive-helper-legacy-limits.stderr b/tests/ui/proc-macro/derive-helper-legacy-limits.stderr
index f5cd455435d..df3c5c47ddb 100644
--- a/tests/ui/proc-macro/derive-helper-legacy-limits.stderr
+++ b/tests/ui/proc-macro/derive-helper-legacy-limits.stderr
@@ -3,6 +3,12 @@ error: cannot find attribute `empty_helper` in this scope
    |
 LL | #[empty_helper]
    |   ^^^^^^^^^^^^
+   |
+help: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Empty)]
+LL | struct S2;
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/proc-macro/derive-helper-shadowing.stderr b/tests/ui/proc-macro/derive-helper-shadowing.stderr
index f284b1c54dd..1206778bb23 100644
--- a/tests/ui/proc-macro/derive-helper-shadowing.stderr
+++ b/tests/ui/proc-macro/derive-helper-shadowing.stderr
@@ -16,6 +16,7 @@ error: cannot find attribute `empty_helper` in this scope
 LL |             #[derive(GenHelperUse)]
    |                      ^^^^^^^^^^^^
    |
+   = note: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
    = note: this error originates in the derive macro `GenHelperUse` (in Nightly builds, run with -Z macro-backtrace for more info)
 help: consider importing this attribute macro through its public re-export
    |
@@ -31,6 +32,7 @@ LL |         #[empty_helper]
 LL |             gen_helper_use!();
    |             ----------------- in this macro invocation
    |
+   = note: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
    = note: this error originates in the macro `gen_helper_use` (in Nightly builds, run with -Z macro-backtrace for more info)
 help: consider importing this attribute macro through its public re-export
    |
diff --git a/tests/ui/proc-macro/disappearing-resolution.stderr b/tests/ui/proc-macro/disappearing-resolution.stderr
index 734e0cd2ab6..6c0f1a52915 100644
--- a/tests/ui/proc-macro/disappearing-resolution.stderr
+++ b/tests/ui/proc-macro/disappearing-resolution.stderr
@@ -3,6 +3,12 @@ error: cannot find attribute `empty_helper` in this scope
    |
 LL | #[empty_helper]
    |   ^^^^^^^^^^^^
+   |
+help: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
+   |
+LL + #[derive(Empty)]
+LL | struct S;
+   |
 
 error[E0603]: derive macro import `Empty` is private
   --> $DIR/disappearing-resolution.rs:11:8