Skip to content

Commit a7d68b9

Browse files
committed
Detect when attribute is provided by missing derive macro
``` error: cannot find attribute `empty_helper` in this scope --> $DIR/derive-helper-legacy-limits.rs:17:3 | LL | #[empty_helper] | ^^^^^^^^^^^^ | help: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute | LL + #[derive(Empty)] LL | struct S2; | ```
1 parent dd84b7d commit a7d68b9

File tree

6 files changed

+143
-5
lines changed

6 files changed

+143
-5
lines changed

compiler/rustc_resolve/src/diagnostics.rs

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_ast::{
55
self as ast, CRATE_NODE_ID, Crate, ItemKind, MetaItemInner, MetaItemKind, ModKind, NodeId, Path,
66
};
77
use rustc_ast_pretty::pprust;
8-
use rustc_data_structures::fx::FxHashSet;
8+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
99
use rustc_errors::codes::*;
1010
use 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<'_>,

compiler/rustc_resolve/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ pub struct Resolver<'ra, 'tcx> {
11181118
proc_macro_stubs: FxHashSet<LocalDefId>,
11191119
/// Traces collected during macro resolution and validated when it's complete.
11201120
single_segment_macro_resolutions:
1121-
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>)>,
1121+
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>, Option<Span>)>,
11221122
multi_segment_macro_resolutions:
11231123
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'ra>, Option<Res>, Namespace)>,
11241124
builtin_attrs: Vec<(Ident, ParentScope<'ra>)>,

compiler/rustc_resolve/src/macros.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
309309
&& self.tcx.def_kind(mod_def_id) == DefKind::Mod
310310
})
311311
.map(|&InvocationParent { parent_def: mod_def_id, .. }| mod_def_id);
312+
let sugg_span = match &invoc.kind {
313+
InvocationKind::Attr { item: Annotatable::Item(item), .. }
314+
if !item.span.from_expansion() =>
315+
{
316+
Some(item.span.shrink_to_lo())
317+
}
318+
_ => None,
319+
};
312320
let (ext, res) = self.smart_resolve_macro_path(
313321
path,
314322
kind,
@@ -320,6 +328,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
320328
soft_custom_inner_attributes_gate(path, invoc),
321329
deleg_impl,
322330
looks_like_invoc_in_mod_inert_attr,
331+
sugg_span,
323332
)?;
324333

325334
let span = invoc.span();
@@ -552,6 +561,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
552561
soft_custom_inner_attributes_gate: bool,
553562
deleg_impl: Option<LocalDefId>,
554563
invoc_in_mod_inert_attr: Option<LocalDefId>,
564+
suggestion_span: Option<Span>,
555565
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
556566
let (ext, res) = match self.resolve_macro_or_delegation_path(
557567
path,
@@ -562,6 +572,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
562572
deleg_impl,
563573
invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)),
564574
None,
575+
suggestion_span,
565576
) {
566577
Ok((Some(ext), res)) => (ext, res),
567578
Ok((None, res)) => (self.dummy_ext(kind), res),
@@ -725,6 +736,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
725736
None,
726737
None,
727738
ignore_import,
739+
None,
728740
)
729741
}
730742

@@ -738,6 +750,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
738750
deleg_impl: Option<LocalDefId>,
739751
invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>,
740752
ignore_import: Option<Import<'ra>>,
753+
suggestion_span: Option<Span>,
741754
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
742755
let path_span = ast_path.span;
743756
let mut path = Segment::from_path(ast_path);
@@ -802,6 +815,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
802815
kind,
803816
*parent_scope,
804817
binding.ok(),
818+
suggestion_span,
805819
));
806820
}
807821

@@ -933,7 +947,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
933947
}
934948

935949
let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
936-
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
950+
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
937951
match self.early_resolve_ident_in_lexical_scope(
938952
ident,
939953
ScopeSet::Macro(kind),
@@ -974,7 +988,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
974988
expected,
975989
ident,
976990
});
977-
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate);
991+
self.unresolved_macro_suggestions(
992+
&mut err,
993+
kind,
994+
&parent_scope,
995+
ident,
996+
krate,
997+
sugg_span,
998+
);
978999
err.emit();
9791000
}
9801001
}

tests/ui/proc-macro/derive-helper-legacy-limits.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ error: cannot find attribute `empty_helper` in this scope
33
|
44
LL | #[empty_helper]
55
| ^^^^^^^^^^^^
6+
|
7+
help: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
8+
|
9+
LL + #[derive(Empty)]
10+
LL | struct S2;
11+
|
612

713
error: aborting due to 1 previous error
814

tests/ui/proc-macro/derive-helper-shadowing.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ error: cannot find attribute `empty_helper` in this scope
1616
LL | #[derive(GenHelperUse)]
1717
| ^^^^^^^^^^^^
1818
|
19+
= note: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
1920
= note: this error originates in the derive macro `GenHelperUse` (in Nightly builds, run with -Z macro-backtrace for more info)
2021
help: consider importing this attribute macro through its public re-export
2122
|
@@ -31,6 +32,7 @@ LL | #[empty_helper]
3132
LL | gen_helper_use!();
3233
| ----------------- in this macro invocation
3334
|
35+
= note: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
3436
= note: this error originates in the macro `gen_helper_use` (in Nightly builds, run with -Z macro-backtrace for more info)
3537
help: consider importing this attribute macro through its public re-export
3638
|

tests/ui/proc-macro/disappearing-resolution.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ error: cannot find attribute `empty_helper` in this scope
33
|
44
LL | #[empty_helper]
55
| ^^^^^^^^^^^^
6+
|
7+
help: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
8+
|
9+
LL + #[derive(Empty)]
10+
LL | struct S;
11+
|
612

713
error[E0603]: derive macro import `Empty` is private
814
--> $DIR/disappearing-resolution.rs:11:8

0 commit comments

Comments
 (0)