@@ -7,7 +7,7 @@ use rustc_ast::{
77use rustc_ast_pretty:: pprust;
88use rustc_attr_data_structures:: { self as attr, Stability } ;
99use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
10- use rustc_data_structures:: unord:: UnordSet ;
10+ use rustc_data_structures:: unord:: { UnordMap , UnordSet } ;
1111use rustc_errors:: codes:: * ;
1212use rustc_errors:: {
1313 Applicability , Diag , DiagCtxtHandle , ErrorGuaranteed , MultiSpan , SuggestionStyle ,
@@ -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,105 @@ 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: UnordMap < Symbol , Vec < _ > > = UnordMap :: 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+ let mut derives: Vec < String > = derives. into_iter ( ) . map ( |d| d. to_string ( ) ) . collect ( ) ;
1687+ derives. sort ( ) ;
1688+ derives. dedup ( ) ;
1689+ let msg = match & derives[ ..] {
1690+ [ derive] => format ! ( " `{derive}`" ) ,
1691+ [ start @ .., last] => format ! (
1692+ "s {} and `{last}`" ,
1693+ start. iter( ) . map( |d| format!( "`{d}`" ) ) . collect:: <Vec <_>>( ) . join( ", " )
1694+ ) ,
1695+ [ ] => unreachable ! ( "we checked for this to be non-empty 10 lines above!?" ) ,
1696+ } ;
1697+ let msg = format ! (
1698+ "`{}` is an attribute that can be used by the {kind}{msg}, you might be \
1699+ missing a `derive` attribute",
1700+ ident. name,
1701+ ) ;
1702+ let sugg_span = if let ModuleKind :: Def ( DefKind :: Enum , id, _) = parent_scope. module . kind
1703+ {
1704+ let span = self . def_span ( id) ;
1705+ if span. from_expansion ( ) {
1706+ None
1707+ } else {
1708+ // For enum variants sugg_span is empty but we can get the enum's Span.
1709+ Some ( span. shrink_to_lo ( ) )
1710+ }
1711+ } else {
1712+ // For items this `Span` will be populated, everything else it'll be None.
1713+ sugg_span
1714+ } ;
1715+ match sugg_span {
1716+ Some ( span) => {
1717+ err. span_suggestion_verbose (
1718+ span,
1719+ msg,
1720+ format ! ( "#[derive({})]\n " , derives. join( ", " ) ) ,
1721+ Applicability :: MaybeIncorrect ,
1722+ ) ;
1723+ }
1724+ None => {
1725+ err. note ( msg) ;
1726+ }
1727+ }
1728+ } else {
1729+ // We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
1730+ let all_attr_names = all_attrs. keys ( ) . map ( |s| * s) . into_sorted_stable_ord ( ) ;
1731+ if let Some ( best_match) = find_best_match_for_name ( & all_attr_names, ident. name , None )
1732+ && let Some ( macros) = all_attrs. get ( & best_match)
1733+ {
1734+ let mut macros: Vec < String > = macros. into_iter ( ) . map ( |d| d. to_string ( ) ) . collect ( ) ;
1735+ macros. sort ( ) ;
1736+ macros. dedup ( ) ;
1737+ let msg = match & macros[ ..] {
1738+ [ ] => return ,
1739+ [ name] => format ! ( " `{name}` accepts" ) ,
1740+ [ start @ .., end] => format ! (
1741+ "s {} and `{end}` accept" ,
1742+ start. iter( ) . map( |m| format!( "`{m}`" ) ) . collect:: <Vec <_>>( ) . join( ", " ) ,
1743+ ) ,
1744+ } ;
1745+ let msg = format ! ( "the {kind}{msg} the similarly named `{best_match}` attribute" ) ;
1746+ err. span_suggestion_verbose (
1747+ ident. span ,
1748+ msg,
1749+ best_match,
1750+ Applicability :: MaybeIncorrect ,
1751+ ) ;
1752+ }
1753+ }
1754+ }
1755+
16261756 pub ( crate ) fn add_typo_suggestion (
16271757 & self ,
16281758 err : & mut Diag < ' _ > ,
0 commit comments