Skip to content

Commit 4600e59

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 8231e85 commit 4600e59

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,
@@ -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<'_>,

compiler/rustc_resolve/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ pub struct Resolver<'ra, 'tcx> {
11211121
proc_macro_stubs: FxHashSet<LocalDefId>,
11221122
/// Traces collected during macro resolution and validated when it's complete.
11231123
single_segment_macro_resolutions:
1124-
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>)>,
1124+
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>, Option<Span>)>,
11251125
multi_segment_macro_resolutions:
11261126
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'ra>, Option<Res>, Namespace)>,
11271127
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
@@ -288,6 +288,14 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
288288
&& self.tcx.def_kind(mod_def_id) == DefKind::Mod
289289
})
290290
.map(|&InvocationParent { parent_def: mod_def_id, .. }| mod_def_id);
291+
let sugg_span = match &invoc.kind {
292+
InvocationKind::Attr { item: Annotatable::Item(item), .. }
293+
if !item.span.from_expansion() =>
294+
{
295+
Some(item.span.shrink_to_lo())
296+
}
297+
_ => None,
298+
};
291299
let (ext, res) = self.smart_resolve_macro_path(
292300
path,
293301
kind,
@@ -298,6 +306,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
298306
force,
299307
deleg_impl,
300308
looks_like_invoc_in_mod_inert_attr,
309+
sugg_span,
301310
)?;
302311

303312
let span = invoc.span();
@@ -529,6 +538,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
529538
force: bool,
530539
deleg_impl: Option<LocalDefId>,
531540
invoc_in_mod_inert_attr: Option<LocalDefId>,
541+
suggestion_span: Option<Span>,
532542
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
533543
let (ext, res) = match self.resolve_macro_or_delegation_path(
534544
path,
@@ -539,6 +549,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
539549
deleg_impl,
540550
invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)),
541551
None,
552+
suggestion_span,
542553
) {
543554
Ok((Some(ext), res)) => (ext, res),
544555
Ok((None, res)) => (self.dummy_ext(kind), res),
@@ -692,6 +703,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
692703
None,
693704
None,
694705
ignore_import,
706+
None,
695707
)
696708
}
697709

@@ -705,6 +717,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
705717
deleg_impl: Option<LocalDefId>,
706718
invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>,
707719
ignore_import: Option<Import<'ra>>,
720+
suggestion_span: Option<Span>,
708721
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
709722
let path_span = ast_path.span;
710723
let mut path = Segment::from_path(ast_path);
@@ -769,6 +782,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
769782
kind,
770783
*parent_scope,
771784
binding.ok(),
785+
suggestion_span,
772786
));
773787
}
774788

@@ -900,7 +914,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
900914
}
901915

902916
let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
903-
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
917+
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
904918
match self.early_resolve_ident_in_lexical_scope(
905919
ident,
906920
ScopeSet::Macro(kind),
@@ -941,7 +955,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
941955
expected,
942956
ident,
943957
});
944-
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate);
958+
self.unresolved_macro_suggestions(
959+
&mut err,
960+
kind,
961+
&parent_scope,
962+
ident,
963+
krate,
964+
sugg_span,
965+
);
945966
err.emit();
946967
}
947968
}

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)