Skip to content

Commit 7f1d531

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 385970f commit 7f1d531

File tree

6 files changed

+145
-6
lines changed

6 files changed

+145
-6
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,
@@ -1416,6 +1416,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14161416
parent_scope: &ParentScope<'ra>,
14171417
ident: Ident,
14181418
krate: &Crate,
1419+
sugg_span: Option<Span>,
14191420
) {
14201421
let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind);
14211422
let suggestion = self.early_lookup_typo_candidate(
@@ -1424,7 +1425,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14241425
ident,
14251426
is_expected,
14261427
);
1427-
self.add_typo_suggestion(err, suggestion, ident.span);
1428+
if !self.add_typo_suggestion(err, suggestion, ident.span) {
1429+
self.detect_derive_attribute(err, ident, parent_scope, sugg_span);
1430+
}
14281431

14291432
let import_suggestions =
14301433
self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
@@ -1557,6 +1560,106 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
15571560
}
15581561
}
15591562

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

compiler/rustc_resolve/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1146,7 +1146,7 @@ pub struct Resolver<'ra, 'tcx> {
11461146
proc_macro_stubs: FxHashSet<LocalDefId>,
11471147
/// Traces collected during macro resolution and validated when it's complete.
11481148
single_segment_macro_resolutions:
1149-
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>)>,
1149+
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>, Option<Span>)>,
11501150
multi_segment_macro_resolutions:
11511151
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'ra>, Option<Res>, Namespace)>,
11521152
builtin_attrs: Vec<(Ident, ParentScope<'ra>)>,

compiler/rustc_resolve/src/macros.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use rustc_attr_parsing::StabilityLevel;
1212
use rustc_data_structures::intern::Interned;
1313
use rustc_errors::{Applicability, StashKey};
1414
use rustc_expand::base::{
15-
DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind,
15+
Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension,
16+
SyntaxExtensionKind,
1617
};
1718
use rustc_expand::compile_declarative_macro;
1819
use rustc_expand::expand::{
@@ -287,6 +288,14 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
287288
&& self.tcx.def_kind(mod_def_id) == DefKind::Mod
288289
})
289290
.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+
};
290299
let (ext, res) = self.smart_resolve_macro_path(
291300
path,
292301
kind,
@@ -297,6 +306,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
297306
force,
298307
deleg_impl,
299308
looks_like_invoc_in_mod_inert_attr,
309+
sugg_span,
300310
)?;
301311

302312
let span = invoc.span();
@@ -528,6 +538,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
528538
force: bool,
529539
deleg_impl: Option<LocalDefId>,
530540
invoc_in_mod_inert_attr: Option<LocalDefId>,
541+
suggestion_span: Option<Span>,
531542
) -> Result<(Arc<SyntaxExtension>, Res), Indeterminate> {
532543
let (ext, res) = match self.resolve_macro_or_delegation_path(
533544
path,
@@ -538,6 +549,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
538549
deleg_impl,
539550
invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)),
540551
None,
552+
suggestion_span,
541553
) {
542554
Ok((Some(ext), res)) => (ext, res),
543555
Ok((None, res)) => (self.dummy_ext(kind), res),
@@ -691,6 +703,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
691703
None,
692704
None,
693705
ignore_import,
706+
None,
694707
)
695708
}
696709

@@ -704,6 +717,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
704717
deleg_impl: Option<LocalDefId>,
705718
invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>,
706719
ignore_import: Option<Import<'ra>>,
720+
suggestion_span: Option<Span>,
707721
) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
708722
let path_span = ast_path.span;
709723
let mut path = Segment::from_path(ast_path);
@@ -768,6 +782,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
768782
kind,
769783
*parent_scope,
770784
binding.ok(),
785+
suggestion_span,
771786
));
772787
}
773788

@@ -905,7 +920,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
905920
}
906921

907922
let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
908-
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
923+
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
909924
match self.early_resolve_ident_in_lexical_scope(
910925
ident,
911926
ScopeSet::Macro(kind),
@@ -946,7 +961,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
946961
expected,
947962
ident,
948963
});
949-
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate);
964+
self.unresolved_macro_suggestions(
965+
&mut err,
966+
kind,
967+
&parent_scope,
968+
ident,
969+
krate,
970+
sugg_span,
971+
);
950972
err.emit();
951973
}
952974
}

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)