@@ -5,7 +5,7 @@ use rustc_ast::{
5
5
self as ast, CRATE_NODE_ID , Crate , ItemKind , MetaItemInner , MetaItemKind , ModKind , NodeId , Path ,
6
6
} ;
7
7
use rustc_ast_pretty:: pprust;
8
- use rustc_data_structures:: fx:: FxHashSet ;
8
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
9
9
use rustc_errors:: codes:: * ;
10
10
use rustc_errors:: {
11
11
Applicability , Diag , DiagCtxtHandle , ErrorGuaranteed , MultiSpan , SuggestionStyle ,
@@ -1424,6 +1424,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
1424
1424
parent_scope : & ParentScope < ' ra > ,
1425
1425
ident : Ident ,
1426
1426
krate : & Crate ,
1427
+ sugg_span : Option < Span > ,
1427
1428
) {
1428
1429
let is_expected = & |res : Res | res. macro_kind ( ) == Some ( macro_kind) ;
1429
1430
let suggestion = self . early_lookup_typo_candidate (
@@ -1432,7 +1433,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
1432
1433
ident,
1433
1434
is_expected,
1434
1435
) ;
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
+ }
1436
1439
1437
1440
let import_suggestions =
1438
1441
self . lookup_import_candidates ( ident, Namespace :: MacroNS , parent_scope, is_expected) ;
@@ -1565,6 +1568,106 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
1565
1568
}
1566
1569
}
1567
1570
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
+
1568
1671
pub ( crate ) fn add_typo_suggestion (
1569
1672
& self ,
1570
1673
err : & mut Diag < ' _ > ,
0 commit comments