@@ -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 ,
@@ -1420,6 +1420,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14201420 parent_scope : & ParentScope < ' ra > ,
14211421 ident : Ident ,
14221422 krate : & Crate ,
1423+ sugg_span : Option < Span > ,
14231424 ) {
14241425 let is_expected = & |res : Res | res. macro_kind ( ) == Some ( macro_kind) ;
14251426 let suggestion = self . early_lookup_typo_candidate (
@@ -1428,7 +1429,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14281429 ident,
14291430 is_expected,
14301431 ) ;
1431- self . add_typo_suggestion ( err, suggestion, ident. span ) ;
1432+ if !self . add_typo_suggestion ( err, suggestion, ident. span ) {
1433+ self . detect_derive_attribute ( err, ident, parent_scope, sugg_span) ;
1434+ }
14321435
14331436 let import_suggestions =
14341437 self . lookup_import_candidates ( ident, Namespace :: MacroNS , parent_scope, is_expected) ;
@@ -1561,6 +1564,106 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
15611564 }
15621565 }
15631566
1567+ /// Given an attribute macro that failed to be resolved, look for `derive` macros that could
1568+ /// provide it, either as-is or with small typos.
1569+ fn detect_derive_attribute (
1570+ & self ,
1571+ err : & mut Diag < ' _ > ,
1572+ ident : Ident ,
1573+ parent_scope : & ParentScope < ' ra > ,
1574+ sugg_span : Option < Span > ,
1575+ ) {
1576+ // Find all of the `derive`s in scope and collect their corresponding declared
1577+ // attributes.
1578+ // FIXME: this only works if the crate that owns the macro that has the helper_attr
1579+ // has already been imported.
1580+ let mut derives = vec ! [ ] ;
1581+ let mut all_attrs: FxHashMap < Symbol , Vec < _ > > = FxHashMap :: default ( ) ;
1582+ for ( def_id, data) in & self . macro_map {
1583+ for helper_attr in & data. ext . helper_attrs {
1584+ let item_name = self . tcx . item_name ( * def_id) ;
1585+ all_attrs. entry ( * helper_attr) . or_default ( ) . push ( item_name) ;
1586+ if helper_attr == & ident. name {
1587+ derives. push ( item_name) ;
1588+ }
1589+ }
1590+ }
1591+ let kind = MacroKind :: Derive . descr ( ) ;
1592+ if !derives. is_empty ( ) {
1593+ // We found an exact match for the missing attribute in a `derive` macro. Suggest it.
1594+ derives. sort ( ) ;
1595+ derives. dedup ( ) ;
1596+ let msg = match & derives[ ..] {
1597+ [ derive] => format ! ( " `{derive}`" ) ,
1598+ [ start @ .., last] => format ! (
1599+ "s {} and `{last}`" ,
1600+ start. iter( ) . map( |d| format!( "`{d}`" ) ) . collect:: <Vec <_>>( ) . join( ", " )
1601+ ) ,
1602+ [ ] => unreachable ! ( "we checked for this to be non-empty 10 lines above!?" ) ,
1603+ } ;
1604+ let msg = format ! (
1605+ "`{}` is an attribute that can be used by the {kind}{msg}, you might be \
1606+ missing a `derive` attribute",
1607+ ident. name,
1608+ ) ;
1609+ let sugg_span = if let ModuleKind :: Def ( DefKind :: Enum , id, _) = parent_scope. module . kind
1610+ {
1611+ let span = self . def_span ( id) ;
1612+ if span. from_expansion ( ) {
1613+ None
1614+ } else {
1615+ // For enum variants sugg_span is empty but we can get the enum's Span.
1616+ Some ( span. shrink_to_lo ( ) )
1617+ }
1618+ } else {
1619+ // For items this `Span` will be populated, everything else it'll be None.
1620+ sugg_span
1621+ } ;
1622+ match sugg_span {
1623+ Some ( span) => {
1624+ err. span_suggestion_verbose (
1625+ span,
1626+ msg,
1627+ format ! (
1628+ "#[derive({})]\n " ,
1629+ derives
1630+ . iter( )
1631+ . map( |d| d. to_string( ) )
1632+ . collect:: <Vec <String >>( )
1633+ . join( ", " )
1634+ ) ,
1635+ Applicability :: MaybeIncorrect ,
1636+ ) ;
1637+ }
1638+ None => {
1639+ err. note ( msg) ;
1640+ }
1641+ }
1642+ } else {
1643+ // We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
1644+ let all_attr_names: Vec < Symbol > = all_attrs. keys ( ) . cloned ( ) . collect ( ) ;
1645+ if let Some ( best_match) = find_best_match_for_name ( & all_attr_names, ident. name , None )
1646+ && let Some ( macros) = all_attrs. get ( & best_match)
1647+ {
1648+ let msg = match & macros[ ..] {
1649+ [ ] => return ,
1650+ [ name] => format ! ( " `{name}` accepts" ) ,
1651+ [ start @ .., end] => format ! (
1652+ "s {} and `{end}` accept" ,
1653+ start. iter( ) . map( |m| format!( "`{m}`" ) ) . collect:: <Vec <_>>( ) . join( ", " ) ,
1654+ ) ,
1655+ } ;
1656+ let msg = format ! ( "the {kind}{msg} the similarly named `{best_match}` attribute" ) ;
1657+ err. span_suggestion_verbose (
1658+ ident. span ,
1659+ msg,
1660+ best_match,
1661+ Applicability :: MaybeIncorrect ,
1662+ ) ;
1663+ }
1664+ }
1665+ }
1666+
15641667 pub ( crate ) fn add_typo_suggestion (
15651668 & self ,
15661669 err : & mut Diag < ' _ > ,
0 commit comments