Skip to content

Commit 321b3ce

Browse files
committed
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)] 11 | struct Foo { | ``` Mitigate rust-lang#47608.
1 parent 276fa29 commit 321b3ce

17 files changed

+432
-22
lines changed

compiler/rustc_resolve/src/diagnostics.rs

+132-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_ast::ptr::P;
44
use rustc_ast::visit::{self, Visitor};
55
use rustc_ast::{self as ast, Crate, ItemKind, ModKind, NodeId, Path, CRATE_NODE_ID};
66
use rustc_ast_pretty::pprust;
7-
use rustc_data_structures::fx::FxHashSet;
7+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
88
use rustc_errors::{
99
pluralize, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
1010
};
@@ -1016,6 +1016,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
10161016
parent_scope,
10171017
false,
10181018
false,
1019+
None,
10191020
) {
10201021
suggestions.extend(
10211022
ext.helper_attrs
@@ -1331,15 +1332,40 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
13311332
macro_kind: MacroKind,
13321333
parent_scope: &ParentScope<'a>,
13331334
ident: Ident,
1335+
sugg_span: Option<Span>,
13341336
) {
1337+
// Bring imported but unused `derive` macros into `macro_map` so we ensure they can be used
1338+
// for suggestions.
1339+
self.visit_scopes(
1340+
ScopeSet::Macro(MacroKind::Derive),
1341+
&parent_scope,
1342+
ident.span.ctxt(),
1343+
|this, scope, _use_prelude, _ctxt| {
1344+
let Scope::Module(m, _) = scope else { return None; };
1345+
for (_, resolution) in this.resolutions(m).borrow().iter() {
1346+
let Some(binding) = resolution.borrow().binding else { continue; };
1347+
let Res::Def(
1348+
DefKind::Macro(MacroKind::Derive | MacroKind::Attr),
1349+
def_id,
1350+
) = binding.res() else { continue; };
1351+
// By doing this all *imported* macros get added to the `macro_map` even if they
1352+
// are *unused*, which makes the later suggestions find them and work.
1353+
let _ = this.get_macro_by_def_id(def_id);
1354+
}
1355+
None::<()>
1356+
},
1357+
);
1358+
13351359
let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind);
13361360
let suggestion = self.early_lookup_typo_candidate(
13371361
ScopeSet::Macro(macro_kind),
13381362
parent_scope,
13391363
ident,
13401364
is_expected,
13411365
);
1342-
self.add_typo_suggestion(err, suggestion, ident.span);
1366+
if !self.add_typo_suggestion(err, suggestion, ident.span) {
1367+
self.add_derive_for_attribute(err, sugg_span, ident, parent_scope);
1368+
}
13431369

13441370
let import_suggestions =
13451371
self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
@@ -1364,14 +1390,20 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
13641390
err.help("have you added the `#[macro_use]` on the module/import?");
13651391
return;
13661392
}
1393+
13671394
if ident.name == kw::Default
13681395
&& let ModuleKind::Def(DefKind::Enum, def_id, _) = parent_scope.module.kind
13691396
{
13701397
let span = self.def_span(def_id);
13711398
let source_map = self.tcx.sess.source_map();
13721399
let head_span = source_map.guess_head_span(span);
1373-
if let Ok(head) = source_map.span_to_snippet(head_span) {
1374-
err.span_suggestion(head_span, "consider adding a derive", format!("#[derive(Default)]\n{head}"), Applicability::MaybeIncorrect);
1400+
if let Ok(_) = source_map.span_to_snippet(head_span) {
1401+
err.span_suggestion(
1402+
head_span.shrink_to_lo(),
1403+
"consider adding a derive",
1404+
format!("#[derive(Default)]\n"),
1405+
Applicability::MaybeIncorrect,
1406+
);
13751407
} else {
13761408
err.span_help(
13771409
head_span,
@@ -1494,6 +1526,102 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
14941526
true
14951527
}
14961528

1529+
fn add_derive_for_attribute(
1530+
&self,
1531+
err: &mut Diagnostic,
1532+
sugg_span: Option<Span>,
1533+
ident: Ident,
1534+
parent_scope: &ParentScope<'_>,
1535+
) {
1536+
// FIXME: this only works if the macro that has the helper_attr has already
1537+
// been imported.
1538+
let mut derives = vec![];
1539+
let mut all_attrs: FxHashMap<Symbol, Vec<_>> = FxHashMap::default();
1540+
for (def_id, data) in &self.macro_map {
1541+
for helper_attr in &data.ext.helper_attrs {
1542+
let item_name = self.tcx.item_name(*def_id);
1543+
all_attrs.entry(*helper_attr).or_default().push(item_name);
1544+
if helper_attr == &ident.name {
1545+
// FIXME: we should also do Levenshtein distance checks here.
1546+
derives.push(item_name);
1547+
}
1548+
}
1549+
}
1550+
let kind = MacroKind::Derive.descr();
1551+
if !derives.is_empty() {
1552+
derives.sort();
1553+
derives.dedup();
1554+
let msg = match &derives[..] {
1555+
[derive] => format!(" `{derive}`"),
1556+
[start @ .., last] => format!(
1557+
"s {} and `{last}`",
1558+
start.iter().map(|d| format!("`{d}`")).collect::<Vec<_>>().join(", ")
1559+
),
1560+
[] => unreachable!("we checked for this to be non-empty 10 lines above!?"),
1561+
};
1562+
let msg = format!(
1563+
"`{}` is an attribute that can be used by the {kind}{msg}, you might be missing a \
1564+
`derive` attribute",
1565+
ident.name,
1566+
);
1567+
let sugg_span = if let ModuleKind::Def(DefKind::Enum, id, _) = parent_scope.module.kind
1568+
{
1569+
let span = self.def_span(id);
1570+
if span.from_expansion() {
1571+
None
1572+
} else {
1573+
// For enum variants, `sugg_span` is empty, but we can get the `enum`'s `Span`.
1574+
Some(span.shrink_to_lo())
1575+
}
1576+
} else {
1577+
// For items, this `Span` will be populated, everything else it'll be `None`.
1578+
sugg_span
1579+
};
1580+
match sugg_span {
1581+
Some(span) => {
1582+
err.span_suggestion_verbose(
1583+
span,
1584+
&msg,
1585+
format!(
1586+
"#[derive({})]\n",
1587+
derives
1588+
.iter()
1589+
.map(|d| d.to_string())
1590+
.collect::<Vec<String>>()
1591+
.join(", ")
1592+
),
1593+
Applicability::MaybeIncorrect,
1594+
);
1595+
}
1596+
None => {
1597+
err.note(&msg);
1598+
}
1599+
}
1600+
} else {
1601+
let all_attr_names: Vec<Symbol> = all_attrs.keys().cloned().collect();
1602+
if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None)
1603+
&& let Some(macros) = all_attrs.get(&best_match)
1604+
&& !macros.is_empty()
1605+
{
1606+
let msg = match &macros[..] {
1607+
[] => unreachable!("we checked above in the if-let"),
1608+
[name] => format!(" `{name}` accepts"),
1609+
[start @ .., end] => format!(
1610+
"s {} and `{end}` accept",
1611+
start.iter().map(|m| format!("`{m}`")).collect::<Vec<_>>().join(", "),
1612+
),
1613+
};
1614+
let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute");
1615+
err.span_suggestion_verbose(
1616+
ident.span,
1617+
&msg,
1618+
best_match,
1619+
Applicability::MaybeIncorrect,
1620+
);
1621+
}
1622+
}
1623+
}
1624+
14971625
fn binding_description(&self, b: &NameBinding<'_>, ident: Ident, from_prelude: bool) -> String {
14981626
let res = b.res();
14991627
if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) {

compiler/rustc_resolve/src/ident.rs

+1
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
459459
parent_scope,
460460
true,
461461
force,
462+
None,
462463
) {
463464
Ok((Some(ext), _)) => {
464465
if ext.helper_attrs.contains(&ident.name) {

compiler/rustc_resolve/src/late.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3715,7 +3715,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
37153715
let path_seg = |seg: &Segment| PathSegment::from_ident(seg.ident);
37163716
let path = Path { segments: path.iter().map(path_seg).collect(), span, tokens: None };
37173717
if let Ok((_, res)) =
3718-
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false)
3718+
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None)
37193719
{
37203720
return Ok(Some(PartialRes::new(res)));
37213721
}

compiler/rustc_resolve/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -975,9 +975,9 @@ pub struct Resolver<'a, 'tcx> {
975975
proc_macro_stubs: FxHashSet<LocalDefId>,
976976
/// Traces collected during macro resolution and validated when it's complete.
977977
single_segment_macro_resolutions:
978-
Vec<(Ident, MacroKind, ParentScope<'a>, Option<&'a NameBinding<'a>>)>,
978+
Vec<(Ident, MacroKind, ParentScope<'a>, Option<&'a NameBinding<'a>>, Option<Span>)>,
979979
multi_segment_macro_resolutions:
980-
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>)>,
980+
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>, Option<Span>)>,
981981
builtin_attrs: Vec<(Ident, ParentScope<'a>)>,
982982
/// `derive(Copy)` marks items they are applied to so they are treated specially later.
983983
/// Derive macros cannot modify the item themselves and have to store the markers in the global

compiler/rustc_resolve/src/macros.rs

+39-14
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,14 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
277277
let parent_scope = &ParentScope { derives, ..parent_scope };
278278
let supports_macro_expansion = invoc.fragment_kind.supports_macro_expansion();
279279
let node_id = invoc.expansion_data.lint_node_id;
280+
let sugg_span = match &invoc.kind {
281+
InvocationKind::Attr { item: Annotatable::Item(item), .. }
282+
if !item.span.from_expansion() =>
283+
{
284+
Some(item.span.shrink_to_lo())
285+
}
286+
_ => None,
287+
};
280288
let (ext, res) = self.smart_resolve_macro_path(
281289
path,
282290
kind,
@@ -286,6 +294,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
286294
node_id,
287295
force,
288296
soft_custom_inner_attributes_gate(path, invoc),
297+
sugg_span,
289298
)?;
290299

291300
let span = invoc.span();
@@ -370,6 +379,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
370379
&parent_scope,
371380
true,
372381
force,
382+
None,
373383
) {
374384
Ok((Some(ext), _)) => {
375385
if !ext.helper_attrs.is_empty() {
@@ -486,14 +496,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
486496
node_id: NodeId,
487497
force: bool,
488498
soft_custom_inner_attributes_gate: bool,
499+
sugg_span: Option<Span>,
489500
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
490-
let (ext, res) = match self.resolve_macro_path(path, Some(kind), parent_scope, true, force)
491-
{
492-
Ok((Some(ext), res)) => (ext, res),
493-
Ok((None, res)) => (self.dummy_ext(kind), res),
494-
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
495-
Err(Determinacy::Undetermined) => return Err(Indeterminate),
496-
};
501+
let (ext, res) =
502+
match self.resolve_macro_path(path, Some(kind), parent_scope, true, force, sugg_span) {
503+
Ok((Some(ext), res)) => (ext, res),
504+
Ok((None, res)) => (self.dummy_ext(kind), res),
505+
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
506+
Err(Determinacy::Undetermined) => return Err(Indeterminate),
507+
};
497508

498509
// Report errors for the resolved macro.
499510
for segment in &path.segments {
@@ -603,6 +614,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
603614
parent_scope: &ParentScope<'a>,
604615
trace: bool,
605616
force: bool,
617+
sugg_span: Option<Span>,
606618
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
607619
let path_span = path.span;
608620
let mut path = Segment::from_path(path);
@@ -634,6 +646,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
634646
kind,
635647
*parent_scope,
636648
res.ok(),
649+
sugg_span,
637650
));
638651
}
639652

@@ -660,6 +673,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
660673
kind,
661674
*parent_scope,
662675
binding.ok(),
676+
sugg_span,
663677
));
664678
}
665679

@@ -706,7 +720,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
706720
};
707721

708722
let macro_resolutions = mem::take(&mut self.multi_segment_macro_resolutions);
709-
for (mut path, path_span, kind, parent_scope, initial_res) in macro_resolutions {
723+
for (mut path, path_span, kind, parent_scope, initial_res, _sugg_span) in macro_resolutions
724+
{
710725
// FIXME: Path resolution will ICE if segment IDs present.
711726
for seg in &mut path {
712727
seg.id = None;
@@ -731,9 +746,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
731746
let exclamation_span = sm.next_point(span);
732747
suggestion = Some((
733748
vec![(exclamation_span, "".to_string())],
734-
format!("{} is not a macro, but a {}, try to remove `!`", Segment::names_to_string(&path), partial_res.base_res().descr()),
735-
Applicability::MaybeIncorrect
736-
));
749+
format!(
750+
"{} is not a macro, but a {}, try to remove `!`",
751+
Segment::names_to_string(&path),
752+
partial_res.base_res().descr(),
753+
),
754+
Applicability::MaybeIncorrect,
755+
));
737756
}
738757
(span, label)
739758
} else {
@@ -756,7 +775,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
756775
}
757776

758777
let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
759-
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
778+
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
760779
match self.early_resolve_ident_in_lexical_scope(
761780
ident,
762781
ScopeSet::Macro(kind),
@@ -789,9 +808,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
789808
}
790809
Err(..) => {
791810
let expected = kind.descr_expected();
792-
let msg = format!("cannot find {} `{}` in this scope", expected, ident);
811+
let msg = format!("cannot find {expected} `{ident}` in this scope");
793812
let mut err = self.tcx.sess.struct_span_err(ident.span, &msg);
794-
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident);
813+
self.unresolved_macro_suggestions(
814+
&mut err,
815+
kind,
816+
&parent_scope,
817+
ident,
818+
sugg_span,
819+
);
795820
err.emit();
796821
}
797822
}

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

+14
Original file line numberDiff line numberDiff line change
@@ -613,18 +613,32 @@ error: cannot find attribute `multipart_suggestion` in this scope
613613
|
614614
LL | #[multipart_suggestion(no_crate_suggestion)]
615615
| ^^^^^^^^^^^^^^^^^^^^
616+
|
617+
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
618+
|
619+
LL + #[derive(Subdiagnostic)]
620+
LL | struct MultipartSuggestion {
621+
|
616622

617623
error: cannot find attribute `multipart_suggestion` in this scope
618624
--> $DIR/diagnostic-derive.rs:642:3
619625
|
620626
LL | #[multipart_suggestion()]
621627
| ^^^^^^^^^^^^^^^^^^^^
628+
|
629+
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
630+
|
631+
LL + #[derive(Subdiagnostic)]
632+
LL | struct MultipartSuggestion {
633+
|
622634

623635
error: cannot find attribute `multipart_suggestion` in this scope
624636
--> $DIR/diagnostic-derive.rs:646:7
625637
|
626638
LL | #[multipart_suggestion(no_crate_suggestion)]
627639
| ^^^^^^^^^^^^^^^^^^^^
640+
|
641+
= note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
628642

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

tests/ui/enum/suggest-default-attribute.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ LL | #[default]
77
help: consider adding a derive
88
|
99
LL + #[derive(Default)]
10-
LL ~ pub enum Test {
10+
LL | pub enum Test {
1111
|
1212

1313
error: aborting due to previous error

0 commit comments

Comments
 (0)