@@ -1054,6 +1054,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
10541054 false,
10551055 false,
10561056 None,
1057+ None,
10571058 ) else {
10581059 continue;
10591060 };
@@ -1482,15 +1483,45 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14821483 parent_scope: &ParentScope<'ra>,
14831484 ident: Ident,
14841485 krate: &Crate,
1486+ sugg_span: Option<Span>,
14851487 ) {
1488+ // Bring imported but unused `derive` macros into `macro_map` so we ensure they can be used
1489+ // for suggestions.
1490+ self.visit_scopes(
1491+ ScopeSet::Macro(MacroKind::Derive),
1492+ &parent_scope,
1493+ ident.span.ctxt(),
1494+ |this, scope, _use_prelude, _ctxt| {
1495+ let Scope::Module(m, _) = scope else {
1496+ return None;
1497+ };
1498+ for (_, resolution) in this.resolutions(m).borrow().iter() {
1499+ let Some(binding) = resolution.borrow().binding else {
1500+ continue;
1501+ };
1502+ let Res::Def(DefKind::Macro(MacroKind::Derive | MacroKind::Attr), def_id) =
1503+ binding.res()
1504+ else {
1505+ continue;
1506+ };
1507+ // By doing this all *imported* macros get added to the `macro_map` even if they
1508+ // are *unused*, which makes the later suggestions find them and work.
1509+ let _ = this.get_macro_by_def_id(def_id);
1510+ }
1511+ None::<()>
1512+ },
1513+ );
1514+
14861515 let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind);
14871516 let suggestion = self.early_lookup_typo_candidate(
14881517 ScopeSet::Macro(macro_kind),
14891518 parent_scope,
14901519 ident,
14911520 is_expected,
14921521 );
1493- self . add_typo_suggestion ( err, suggestion, ident. span ) ;
1522+ if !self.add_typo_suggestion(err, suggestion, ident.span) {
1523+ self.detect_derive_attribute(err, ident, parent_scope, sugg_span);
1524+ }
14941525
14951526 let import_suggestions =
14961527 self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
@@ -1623,6 +1654,110 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
16231654 }
16241655 }
16251656
1657+ /// Given an attribute macro that failed to be resolved, look for `derive` macros that could
1658+ /// provide it, either as-is or with small typos.
1659+ fn detect_derive_attribute(
1660+ &self,
1661+ err: &mut Diag<'_>,
1662+ ident: Ident,
1663+ parent_scope: &ParentScope<'ra>,
1664+ sugg_span: Option<Span>,
1665+ ) {
1666+ // Find all of the `derive`s in scope and collect their corresponding declared
1667+ // attributes.
1668+ // FIXME: this only works if the crate that owns the macro that has the helper_attr
1669+ // has already been imported.
1670+ let mut derives = vec![];
1671+ let mut all_attrs: FxHashMap<Symbol, Vec<_>> = FxHashMap::default();
1672+ // We're collecting these in a hashmap, and handle ordering the output further down.
1673+ #[allow(rustc::potential_query_instability)]
1674+ for (def_id, data) in &self.macro_map {
1675+ for helper_attr in &data.ext.helper_attrs {
1676+ let item_name = self.tcx.item_name(*def_id);
1677+ all_attrs.entry(*helper_attr).or_default().push(item_name);
1678+ if helper_attr == &ident.name {
1679+ derives.push(item_name);
1680+ }
1681+ }
1682+ }
1683+ let kind = MacroKind::Derive.descr();
1684+ if !derives.is_empty() {
1685+ // We found an exact match for the missing attribute in a `derive` macro. Suggest it.
1686+ derives.sort();
1687+ derives.dedup();
1688+ let msg = match &derives[..] {
1689+ [derive] => format!(" `{derive}`"),
1690+ [start @ .., last] => format!(
1691+ "s {} and `{last}`",
1692+ start.iter().map(|d| format!("`{d}`")).collect::<Vec<_>>().join(", ")
1693+ ),
1694+ [] => unreachable!("we checked for this to be non-empty 10 lines above!?"),
1695+ };
1696+ let msg = format!(
1697+ "`{}` is an attribute that can be used by the {kind}{msg}, you might be \
1698+ missing a `derive` attribute",
1699+ ident.name,
1700+ );
1701+ let sugg_span = if let ModuleKind::Def(DefKind::Enum, id, _) = parent_scope.module.kind
1702+ {
1703+ let span = self.def_span(id);
1704+ if span.from_expansion() {
1705+ None
1706+ } else {
1707+ // For enum variants sugg_span is empty but we can get the enum's Span.
1708+ Some(span.shrink_to_lo())
1709+ }
1710+ } else {
1711+ // For items this `Span` will be populated, everything else it'll be None.
1712+ sugg_span
1713+ };
1714+ match sugg_span {
1715+ Some(span) => {
1716+ err.span_suggestion_verbose(
1717+ span,
1718+ msg,
1719+ format!(
1720+ "#[derive({})]\n",
1721+ derives
1722+ .iter()
1723+ .map(|d| d.to_string())
1724+ .collect::<Vec<String>>()
1725+ .join(", ")
1726+ ),
1727+ Applicability::MaybeIncorrect,
1728+ );
1729+ }
1730+ None => {
1731+ err.note(msg);
1732+ }
1733+ }
1734+ } else {
1735+ // We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
1736+ #[allow(rustc::potential_query_instability)] // We're immediately sorting these.
1737+ let mut all_attr_names: Vec<Symbol> = all_attrs.keys().cloned().collect();
1738+ all_attr_names.sort();
1739+ if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None)
1740+ && let Some(macros) = all_attrs.get(&best_match)
1741+ {
1742+ let msg = match ¯os[..] {
1743+ [] => return,
1744+ [name] => format!(" `{name}` accepts"),
1745+ [start @ .., end] => format!(
1746+ "s {} and `{end}` accept",
1747+ start.iter().map(|m| format!("`{m}`")).collect::<Vec<_>>().join(", "),
1748+ ),
1749+ };
1750+ let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute");
1751+ err.span_suggestion_verbose(
1752+ ident.span,
1753+ msg,
1754+ best_match,
1755+ Applicability::MaybeIncorrect,
1756+ );
1757+ }
1758+ }
1759+ }
1760+
16261761 pub(crate) fn add_typo_suggestion(
16271762 &self,
16281763 err: &mut Diag<'_>,
0 commit comments