Skip to content

Commit 2e51aa6

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; | ``` Look at proc-macro attributes when encountering unknown attribute ``` error: cannot find attribute `sede` in this scope --> src/main.rs:18:7 | 18 | #[sede(untagged)] | ^^^^ | help: the derive macros `Serialize` and `Deserialize` accept the similarly named `serde` attribute | 18 | #[serde(untagged)] | ~~~~~ error: cannot find attribute `serde` in this scope --> src/main.rs:12:7 | 12 | #[serde(untagged)] | ^^^^^ | = note: `serde` is in scope, but it is a crate, not an attribute help: `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`, you might be missing a `derive` attribute | 10 | #[derive(Serialize, Deserialize)] | ```
1 parent 44f415c commit 2e51aa6

16 files changed

+423
-7
lines changed

compiler/rustc_resolve/src/diagnostics.rs

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_ast::{
77
use rustc_ast_pretty::pprust;
88
use rustc_attr_data_structures::{self as attr, Stability};
99
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10-
use rustc_data_structures::unord::UnordSet;
10+
use rustc_data_structures::unord::{UnordMap, UnordSet};
1111
use rustc_errors::codes::*;
1212
use rustc_errors::{
1313
Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, MultiSpan, SuggestionStyle,
@@ -1054,6 +1054,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
10541054
false,
10551055
false,
10561056
None,
1057+
None,
10571058
) else {
10581059
continue;
10591060
};
@@ -1482,15 +1483,45 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14821483
parent_scope: &ParentScope<'ra>,
14831484
ident: Ident,
14841485
krate: &Crate,
1486+
sugg_span: Option<Span>,
14851487
) {
1488+
// Bring imported but unused `derive` macros into `macro_map` so we ensure they can be used
1489+
// for suggestions.
1490+
self.visit_scopes(
1491+
ScopeSet::Macro(MacroKind::Derive),
1492+
&parent_scope,
1493+
ident.span.ctxt(),
1494+
|this, scope, _use_prelude, _ctxt| {
1495+
let Scope::Module(m, _) = scope else {
1496+
return None;
1497+
};
1498+
for (_, resolution) in this.resolutions(m).borrow().iter() {
1499+
let Some(binding) = resolution.borrow().binding else {
1500+
continue;
1501+
};
1502+
let Res::Def(DefKind::Macro(MacroKind::Derive | MacroKind::Attr), def_id) =
1503+
binding.res()
1504+
else {
1505+
continue;
1506+
};
1507+
// By doing this all *imported* macros get added to the `macro_map` even if they
1508+
// are *unused*, which makes the later suggestions find them and work.
1509+
let _ = this.get_macro_by_def_id(def_id);
1510+
}
1511+
None::<()>
1512+
},
1513+
);
1514+
14861515
let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind);
14871516
let suggestion = self.early_lookup_typo_candidate(
14881517
ScopeSet::Macro(macro_kind),
14891518
parent_scope,
14901519
ident,
14911520
is_expected,
14921521
);
1493-
self.add_typo_suggestion(err, suggestion, ident.span);
1522+
if !self.add_typo_suggestion(err, suggestion, ident.span) {
1523+
self.detect_derive_attribute(err, ident, parent_scope, sugg_span);
1524+
}
14941525

14951526
let import_suggestions =
14961527
self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
@@ -1623,6 +1654,110 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
16231654
}
16241655
}
16251656

1657+
/// Given an attribute macro that failed to be resolved, look for `derive` macros that could
1658+
/// provide it, either as-is or with small typos.
1659+
fn detect_derive_attribute(
1660+
&self,
1661+
err: &mut Diag<'_>,
1662+
ident: Ident,
1663+
parent_scope: &ParentScope<'ra>,
1664+
sugg_span: Option<Span>,
1665+
) {
1666+
// Find all of the `derive`s in scope and collect their corresponding declared
1667+
// attributes.
1668+
// FIXME: this only works if the crate that owns the macro that has the helper_attr
1669+
// has already been imported.
1670+
let mut derives = vec![];
1671+
let mut all_attrs: UnordMap<Symbol, Vec<_>> = FxHashMap::default();
1672+
// We're collecting these in a hashmap, and handle ordering the output further down.
1673+
#[allow(rustc::potential_query_instability)]
1674+
for (def_id, data) in &self.macro_map {
1675+
for helper_attr in &data.ext.helper_attrs {
1676+
let item_name = self.tcx.item_name(*def_id);
1677+
all_attrs.entry(*helper_attr).or_default().push(item_name);
1678+
if helper_attr == &ident.name {
1679+
derives.push(item_name);
1680+
}
1681+
}
1682+
}
1683+
let kind = MacroKind::Derive.descr();
1684+
if !derives.is_empty() {
1685+
// We found an exact match for the missing attribute in a `derive` macro. Suggest it.
1686+
derives.sort();
1687+
derives.dedup();
1688+
let msg = match &derives[..] {
1689+
[derive] => format!(" `{derive}`"),
1690+
[start @ .., last] => format!(
1691+
"s {} and `{last}`",
1692+
start.iter().map(|d| format!("`{d}`")).collect::<Vec<_>>().join(", ")
1693+
),
1694+
[] => unreachable!("we checked for this to be non-empty 10 lines above!?"),
1695+
};
1696+
let msg = format!(
1697+
"`{}` is an attribute that can be used by the {kind}{msg}, you might be \
1698+
missing a `derive` attribute",
1699+
ident.name,
1700+
);
1701+
let sugg_span = if let ModuleKind::Def(DefKind::Enum, id, _) = parent_scope.module.kind
1702+
{
1703+
let span = self.def_span(id);
1704+
if span.from_expansion() {
1705+
None
1706+
} else {
1707+
// For enum variants sugg_span is empty but we can get the enum's Span.
1708+
Some(span.shrink_to_lo())
1709+
}
1710+
} else {
1711+
// For items this `Span` will be populated, everything else it'll be None.
1712+
sugg_span
1713+
};
1714+
match sugg_span {
1715+
Some(span) => {
1716+
err.span_suggestion_verbose(
1717+
span,
1718+
msg,
1719+
format!(
1720+
"#[derive({})]\n",
1721+
derives
1722+
.iter()
1723+
.map(|d| d.to_string())
1724+
.collect::<Vec<String>>()
1725+
.join(", ")
1726+
),
1727+
Applicability::MaybeIncorrect,
1728+
);
1729+
}
1730+
None => {
1731+
err.note(msg);
1732+
}
1733+
}
1734+
} else {
1735+
// We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
1736+
#[allow(rustc::potential_query_instability)] // We're immediately sorting these.
1737+
let mut all_attr_names: Vec<Symbol> = all_attrs.keys().cloned().collect();
1738+
all_attr_names.sort_by(|a, b| a.as_str().cmp(b.as_str()));
1739+
if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None)
1740+
&& let Some(macros) = all_attrs.get(&best_match)
1741+
{
1742+
let msg = match &macros[..] {
1743+
[] => return,
1744+
[name] => format!(" `{name}` accepts"),
1745+
[start @ .., end] => format!(
1746+
"s {} and `{end}` accept",
1747+
start.iter().map(|m| format!("`{m}`")).collect::<Vec<_>>().join(", "),
1748+
),
1749+
};
1750+
let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute");
1751+
err.span_suggestion_verbose(
1752+
ident.span,
1753+
msg,
1754+
best_match,
1755+
Applicability::MaybeIncorrect,
1756+
);
1757+
}
1758+
}
1759+
}
1760+
16261761
pub(crate) fn add_typo_suggestion(
16271762
&self,
16281763
err: &mut Diag<'_>,

compiler/rustc_resolve/src/ident.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
460460
true,
461461
force,
462462
ignore_import,
463+
None,
463464
) {
464465
Ok((Some(ext), _)) => {
465466
if ext.helper_attrs.contains(&ident.name) {

compiler/rustc_resolve/src/late.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4509,7 +4509,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
45094509
let path_seg = |seg: &Segment| PathSegment::from_ident(seg.ident);
45104510
let path = Path { segments: path.iter().map(path_seg).collect(), span, tokens: None };
45114511
if let Ok((_, res)) =
4512-
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None)
4512+
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None, None)
45134513
{
45144514
return Ok(Some(PartialRes::new(res)));
45154515
}

compiler/rustc_resolve/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ pub struct Resolver<'ra, 'tcx> {
11391139
proc_macro_stubs: FxHashSet<LocalDefId>,
11401140
/// Traces collected during macro resolution and validated when it's complete.
11411141
single_segment_macro_resolutions:
1142-
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>)>,
1142+
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>, Option<Span>)>,
11431143
multi_segment_macro_resolutions:
11441144
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'ra>, Option<Res>, Namespace)>,
11451145
builtin_attrs: Vec<(Ident, ParentScope<'ra>)>,

compiler/rustc_resolve/src/macros.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use rustc_attr_data_structures::StabilityLevel;
1212
use rustc_data_structures::intern::Interned;
1313
use rustc_errors::{Applicability, DiagCtxtHandle, 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::{
@@ -294,6 +295,14 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
294295
&& self.tcx.def_kind(mod_def_id) == DefKind::Mod
295296
})
296297
.map(|&InvocationParent { parent_def: mod_def_id, .. }| mod_def_id);
298+
let sugg_span = match &invoc.kind {
299+
InvocationKind::Attr { item: Annotatable::Item(item), .. }
300+
if !item.span.from_expansion() =>
301+
{
302+
Some(item.span.shrink_to_lo())
303+
}
304+
_ => None,
305+
};
297306
let (ext, res) = self.smart_resolve_macro_path(
298307
path,
299308
kind,
@@ -304,6 +313,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
304313
force,
305314
deleg_impl,
306315
looks_like_invoc_in_mod_inert_attr,
316+
sugg_span,
307317
)?;
308318

309319
let span = invoc.span();
@@ -386,6 +396,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
386396
true,
387397
force,
388398
None,
399+
None,
389400
) {
390401
Ok((Some(ext), _)) => {
391402
if !ext.helper_attrs.is_empty() {
@@ -528,6 +539,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
528539
force: bool,
529540
deleg_impl: Option<LocalDefId>,
530541
invoc_in_mod_inert_attr: Option<LocalDefId>,
542+
suggestion_span: Option<Span>,
531543
) -> Result<(Arc<SyntaxExtension>, Res), Indeterminate> {
532544
let (ext, res) = match self.resolve_macro_or_delegation_path(
533545
path,
@@ -538,6 +550,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
538550
deleg_impl,
539551
invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)),
540552
None,
553+
suggestion_span,
541554
) {
542555
Ok((Some(ext), res)) => (ext, res),
543556
Ok((None, res)) => (self.dummy_ext(kind), res),
@@ -681,6 +694,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
681694
trace: bool,
682695
force: bool,
683696
ignore_import: Option<Import<'ra>>,
697+
suggestion_span: Option<Span>,
684698
) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
685699
self.resolve_macro_or_delegation_path(
686700
path,
@@ -691,6 +705,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
691705
None,
692706
None,
693707
ignore_import,
708+
suggestion_span,
694709
)
695710
}
696711

@@ -704,6 +719,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
704719
deleg_impl: Option<LocalDefId>,
705720
invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>,
706721
ignore_import: Option<Import<'ra>>,
722+
suggestion_span: Option<Span>,
707723
) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
708724
let path_span = ast_path.span;
709725
let mut path = Segment::from_path(ast_path);
@@ -768,6 +784,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
768784
kind,
769785
*parent_scope,
770786
binding.ok(),
787+
suggestion_span,
771788
));
772789
}
773790

@@ -905,7 +922,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
905922
}
906923

907924
let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
908-
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
925+
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
909926
match self.early_resolve_ident_in_lexical_scope(
910927
ident,
911928
ScopeSet::Macro(kind),
@@ -946,7 +963,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
946963
expected,
947964
ident,
948965
});
949-
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate);
966+
self.unresolved_macro_suggestions(
967+
&mut err,
968+
kind,
969+
&parent_scope,
970+
ident,
971+
krate,
972+
sugg_span,
973+
);
950974
err.emit();
951975
}
952976
}

tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,18 +583,32 @@ error: cannot find attribute `multipart_suggestion` in this scope
583583
|
584584
LL | #[multipart_suggestion(no_crate_suggestion)]
585585
| ^^^^^^^^^^^^^^^^^^^^
586+
|
587+
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
588+
|
589+
LL + #[derive(Subdiagnostic)]
590+
LL | struct MultipartSuggestion {
591+
|
586592

587593
error: cannot find attribute `multipart_suggestion` in this scope
588594
--> $DIR/diagnostic-derive.rs:647:3
589595
|
590596
LL | #[multipart_suggestion()]
591597
| ^^^^^^^^^^^^^^^^^^^^
598+
|
599+
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
600+
|
601+
LL + #[derive(Subdiagnostic)]
602+
LL | struct MultipartSuggestion {
603+
|
592604

593605
error: cannot find attribute `multipart_suggestion` in this scope
594606
--> $DIR/diagnostic-derive.rs:651:7
595607
|
596608
LL | #[multipart_suggestion(no_crate_suggestion)]
597609
| ^^^^^^^^^^^^^^^^^^^^
610+
|
611+
= note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
598612

599613
error[E0425]: cannot find value `nonsense` in module `crate::fluent_generated`
600614
--> $DIR/diagnostic-derive.rs:75:8

tests/ui/macros/auxiliary/serde.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//@ force-host
2+
//@ no-prefer-dynamic
3+
4+
#![crate_type = "proc-macro"]
5+
#![feature(proc_macro_quote)]
6+
7+
extern crate proc_macro;
8+
9+
use proc_macro::*;
10+
11+
#[proc_macro_derive(Serialize, attributes(serde))]
12+
pub fn serialize(ts: TokenStream) -> TokenStream {
13+
quote!{}
14+
}
15+
16+
#[proc_macro_derive(Deserialize, attributes(serde))]
17+
pub fn deserialize(ts: TokenStream) -> TokenStream {
18+
quote!{}
19+
}

0 commit comments

Comments
 (0)