@@ -5,7 +5,7 @@ use rustc_ast::{
55 self as ast, CRATE_NODE_ID , Crate , ItemKind , MetaItemInner , MetaItemKind , ModKind , NodeId , Path ,
66} ;
77use rustc_ast_pretty:: pprust;
8- use rustc_data_structures:: fx:: FxHashSet ;
8+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
99use rustc_errors:: codes:: * ;
1010use rustc_errors:: {
1111 Applicability , Diag , DiagCtxtHandle , ErrorGuaranteed , MultiSpan , SuggestionStyle ,
@@ -1424,6 +1424,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14241424 parent_scope : & ParentScope < ' ra > ,
14251425 ident : Ident ,
14261426 krate : & Crate ,
1427+ sugg_span : Option < Span > ,
14271428 ) {
14281429 let is_expected = & |res : Res | res. macro_kind ( ) == Some ( macro_kind) ;
14291430 let suggestion = self . early_lookup_typo_candidate (
@@ -1432,7 +1433,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14321433 ident,
14331434 is_expected,
14341435 ) ;
1435- self . add_typo_suggestion ( err, suggestion, ident. span ) ;
1436+ if !self . add_typo_suggestion ( err, suggestion, ident. span ) {
1437+ self . detect_derive_attribute ( err, ident, parent_scope, sugg_span) ;
1438+ }
14361439
14371440 let import_suggestions =
14381441 self . lookup_import_candidates ( ident, Namespace :: MacroNS , parent_scope, is_expected) ;
@@ -1565,6 +1568,106 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
15651568 }
15661569 }
15671570
1571+ /// Given an attribute macro that failed to be resolved, look for `derive` macros that could
1572+ /// provide it, either as-is or with small typos.
1573+ fn detect_derive_attribute (
1574+ & self ,
1575+ err : & mut Diag < ' _ > ,
1576+ ident : Ident ,
1577+ parent_scope : & ParentScope < ' ra > ,
1578+ sugg_span : Option < Span > ,
1579+ ) {
1580+ // Find all of the `derive`s in scope and collect their corresponding declared
1581+ // attributes.
1582+ // FIXME: this only works if the crate that owns the macro that has the helper_attr
1583+ // has already been imported.
1584+ let mut derives = vec ! [ ] ;
1585+ let mut all_attrs: FxHashMap < Symbol , Vec < _ > > = FxHashMap :: default ( ) ;
1586+ for ( def_id, data) in & self . macro_map {
1587+ for helper_attr in & data. ext . helper_attrs {
1588+ let item_name = self . tcx . item_name ( * def_id) ;
1589+ all_attrs. entry ( * helper_attr) . or_default ( ) . push ( item_name) ;
1590+ if helper_attr == & ident. name {
1591+ derives. push ( item_name) ;
1592+ }
1593+ }
1594+ }
1595+ let kind = MacroKind :: Derive . descr ( ) ;
1596+ if !derives. is_empty ( ) {
1597+ // We found an exact match for the missing attribute in a `derive` macro. Suggest it.
1598+ derives. sort ( ) ;
1599+ derives. dedup ( ) ;
1600+ let msg = match & derives[ ..] {
1601+ [ derive] => format ! ( " `{derive}`" ) ,
1602+ [ start @ .., last] => format ! (
1603+ "s {} and `{last}`" ,
1604+ start. iter( ) . map( |d| format!( "`{d}`" ) ) . collect:: <Vec <_>>( ) . join( ", " )
1605+ ) ,
1606+ [ ] => unreachable ! ( "we checked for this to be non-empty 10 lines above!?" ) ,
1607+ } ;
1608+ let msg = format ! (
1609+ "`{}` is an attribute that can be used by the {kind}{msg}, you might be \
1610+ missing a `derive` attribute",
1611+ ident. name,
1612+ ) ;
1613+ let sugg_span = if let ModuleKind :: Def ( DefKind :: Enum , id, _) = parent_scope. module . kind
1614+ {
1615+ let span = self . def_span ( id) ;
1616+ if span. from_expansion ( ) {
1617+ None
1618+ } else {
1619+ // For enum variants sugg_span is empty but we can get the enum's Span.
1620+ Some ( span. shrink_to_lo ( ) )
1621+ }
1622+ } else {
1623+ // For items this `Span` will be populated, everything else it'll be None.
1624+ sugg_span
1625+ } ;
1626+ match sugg_span {
1627+ Some ( span) => {
1628+ err. span_suggestion_verbose (
1629+ span,
1630+ msg,
1631+ format ! (
1632+ "#[derive({})]\n " ,
1633+ derives
1634+ . iter( )
1635+ . map( |d| d. to_string( ) )
1636+ . collect:: <Vec <String >>( )
1637+ . join( ", " )
1638+ ) ,
1639+ Applicability :: MaybeIncorrect ,
1640+ ) ;
1641+ }
1642+ None => {
1643+ err. note ( msg) ;
1644+ }
1645+ }
1646+ } else {
1647+ // We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
1648+ let all_attr_names: Vec < Symbol > = all_attrs. keys ( ) . cloned ( ) . collect ( ) ;
1649+ if let Some ( best_match) = find_best_match_for_name ( & all_attr_names, ident. name , None )
1650+ && let Some ( macros) = all_attrs. get ( & best_match)
1651+ {
1652+ let msg = match & macros[ ..] {
1653+ [ ] => return ,
1654+ [ name] => format ! ( " `{name}` accepts" ) ,
1655+ [ start @ .., end] => format ! (
1656+ "s {} and `{end}` accept" ,
1657+ start. iter( ) . map( |m| format!( "`{m}`" ) ) . collect:: <Vec <_>>( ) . join( ", " ) ,
1658+ ) ,
1659+ } ;
1660+ let msg = format ! ( "the {kind}{msg} the similarly named `{best_match}` attribute" ) ;
1661+ err. span_suggestion_verbose (
1662+ ident. span ,
1663+ msg,
1664+ best_match,
1665+ Applicability :: MaybeIncorrect ,
1666+ ) ;
1667+ }
1668+ }
1669+ }
1670+
15681671 pub ( crate ) fn add_typo_suggestion (
15691672 & self ,
15701673 err : & mut Diag < ' _ > ,
0 commit comments