Skip to content

Commit 43a6cd6

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)] | ``` Mitigate #47608.
1 parent 2fb0e8d commit 43a6cd6

17 files changed

+414
-23
lines changed

compiler/rustc_resolve/src/diagnostics.rs

+122-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,126 @@ 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+
// FIXME: this only works if the macro that has the helper_attr has already
1368+
// been imported.
1369+
let mut derives = vec![];
1370+
let mut all_attrs: FxHashMap<Symbol, Vec<_>> = FxHashMap::default();
1371+
for (def_id, data) in &self.macro_map {
1372+
for helper_attr in &data.ext.helper_attrs {
1373+
let item_name = self.tcx.item_name(*def_id);
1374+
all_attrs.entry(*helper_attr).or_default().push(item_name);
1375+
if helper_attr == &ident.name {
1376+
// FIXME: we should also do Levenshtein distance checks here.
1377+
derives.push(item_name);
1378+
}
1379+
}
1380+
}
1381+
let kind = MacroKind::Derive.descr();
1382+
if !derives.is_empty() {
1383+
derives.sort();
1384+
derives.dedup();
1385+
let msg = match &derives[..] {
1386+
[derive] => format!(" `{derive}`"),
1387+
[start @ .., last] => format!(
1388+
"s {} and `{last}`",
1389+
start.iter().map(|d| format!("`{d}`")).collect::<Vec<_>>().join(", ")
1390+
),
1391+
[] => unreachable!("we checked for this to be non-empty 10 lines above!?"),
1392+
};
1393+
let msg = format!(
1394+
"`{}` is an attribute that can be used by the {kind}{msg}, you might be missing a \
1395+
`derive` attribute",
1396+
ident.name,
1397+
);
1398+
let sugg_span =
1399+
if let ModuleKind::Def(DefKind::Enum, id, _) = parent_scope.module.kind {
1400+
let span = self.def_span(id);
1401+
if span.from_expansion() {
1402+
None
1403+
} else {
1404+
// For enum variants, `sugg_span` is empty, but we can get the `enum`'s `Span`.
1405+
Some(span.shrink_to_lo())
1406+
}
1407+
} else {
1408+
// For items, this `Span` will be populated, everything else it'll be `None`.
1409+
sugg_span
1410+
};
1411+
match sugg_span {
1412+
Some(span) => {
1413+
err.span_suggestion_verbose(
1414+
span,
1415+
&msg,
1416+
format!(
1417+
"#[derive({})]\n",
1418+
derives
1419+
.iter()
1420+
.map(|d| d.to_string())
1421+
.collect::<Vec<String>>()
1422+
.join(", ")
1423+
),
1424+
Applicability::MaybeIncorrect,
1425+
);
1426+
}
1427+
None => {
1428+
err.note(&msg);
1429+
}
1430+
}
1431+
} else {
1432+
let all_attr_names: Vec<Symbol> = all_attrs.keys().cloned().collect();
1433+
if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None)
1434+
&& let Some(macros) = all_attrs.get(&best_match)
1435+
&& !macros.is_empty()
1436+
{
1437+
let msg = match &macros[..] {
1438+
[] => unreachable!("we checked above in the if-let"),
1439+
[name] => format!(" `{name}` accepts"),
1440+
[start @ .., end] => format!(
1441+
"s {} and `{end}` accept",
1442+
start.iter().map(|m| format!("`{m}`")).collect::<Vec<_>>().join(", "),
1443+
),
1444+
};
1445+
let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute");
1446+
err.span_suggestion_verbose(
1447+
ident.span,
1448+
&msg,
1449+
best_match,
1450+
Applicability::MaybeIncorrect,
1451+
);
1452+
}
1453+
}
1454+
}
13431455

13441456
let import_suggestions =
13451457
self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
@@ -1364,14 +1476,20 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
13641476
err.help("have you added the `#[macro_use]` on the module/import?");
13651477
return;
13661478
}
1479+
13671480
if ident.name == kw::Default
13681481
&& let ModuleKind::Def(DefKind::Enum, def_id, _) = parent_scope.module.kind
13691482
{
13701483
let span = self.def_span(def_id);
13711484
let source_map = self.tcx.sess.source_map();
13721485
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);
1486+
if let Ok(_) = source_map.span_to_snippet(head_span) {
1487+
err.span_suggestion(
1488+
head_span.shrink_to_lo(),
1489+
"consider adding a derive",
1490+
format!("#[derive(Default)]\n"),
1491+
Applicability::MaybeIncorrect,
1492+
);
13751493
} else {
13761494
err.span_help(
13771495
head_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
@@ -3706,7 +3706,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
37063706
let path_seg = |seg: &Segment| PathSegment::from_ident(seg.ident);
37073707
let path = Path { segments: path.iter().map(path_seg).collect(), span, tokens: None };
37083708
if let Ok((_, res)) =
3709-
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false)
3709+
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None)
37103710
{
37113711
return Ok(Some(PartialRes::new(res)));
37123712
}

compiler/rustc_resolve/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -974,9 +974,9 @@ pub struct Resolver<'a, 'tcx> {
974974
proc_macro_stubs: FxHashSet<LocalDefId>,
975975
/// Traces collected during macro resolution and validated when it's complete.
976976
single_segment_macro_resolutions:
977-
Vec<(Ident, MacroKind, ParentScope<'a>, Option<&'a NameBinding<'a>>)>,
977+
Vec<(Ident, MacroKind, ParentScope<'a>, Option<&'a NameBinding<'a>>, Option<Span>)>,
978978
multi_segment_macro_resolutions:
979-
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>)>,
979+
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>, Option<Span>)>,
980980
builtin_attrs: Vec<(Ident, ParentScope<'a>)>,
981981
/// `derive(Copy)` marks items they are applied to so they are treated specially later.
982982
/// 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
@@ -276,6 +276,14 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
276276
let parent_scope = &ParentScope { derives, ..parent_scope };
277277
let supports_macro_expansion = invoc.fragment_kind.supports_macro_expansion();
278278
let node_id = invoc.expansion_data.lint_node_id;
279+
let sugg_span = match &invoc.kind {
280+
InvocationKind::Attr { item: Annotatable::Item(item), .. }
281+
if !item.span.from_expansion() =>
282+
{
283+
Some(item.span.shrink_to_lo())
284+
}
285+
_ => None,
286+
};
279287
let (ext, res) = self.smart_resolve_macro_path(
280288
path,
281289
kind,
@@ -285,6 +293,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
285293
node_id,
286294
force,
287295
soft_custom_inner_attributes_gate(path, invoc),
296+
sugg_span,
288297
)?;
289298

290299
let span = invoc.span();
@@ -369,6 +378,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
369378
&parent_scope,
370379
true,
371380
force,
381+
None,
372382
) {
373383
Ok((Some(ext), _)) => {
374384
if !ext.helper_attrs.is_empty() {
@@ -485,14 +495,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
485495
node_id: NodeId,
486496
force: bool,
487497
soft_custom_inner_attributes_gate: bool,
498+
sugg_span: Option<Span>,
488499
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
489-
let (ext, res) = match self.resolve_macro_path(path, Some(kind), parent_scope, true, force)
490-
{
491-
Ok((Some(ext), res)) => (ext, res),
492-
Ok((None, res)) => (self.dummy_ext(kind), res),
493-
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
494-
Err(Determinacy::Undetermined) => return Err(Indeterminate),
495-
};
500+
let (ext, res) =
501+
match self.resolve_macro_path(path, Some(kind), parent_scope, true, force, sugg_span) {
502+
Ok((Some(ext), res)) => (ext, res),
503+
Ok((None, res)) => (self.dummy_ext(kind), res),
504+
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
505+
Err(Determinacy::Undetermined) => return Err(Indeterminate),
506+
};
496507

497508
// Report errors for the resolved macro.
498509
for segment in &path.segments {
@@ -585,6 +596,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
585596
parent_scope: &ParentScope<'a>,
586597
trace: bool,
587598
force: bool,
599+
sugg_span: Option<Span>,
588600
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
589601
let path_span = path.span;
590602
let mut path = Segment::from_path(path);
@@ -616,6 +628,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
616628
kind,
617629
*parent_scope,
618630
res.ok(),
631+
sugg_span,
619632
));
620633
}
621634

@@ -642,6 +655,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
642655
kind,
643656
*parent_scope,
644657
binding.ok(),
658+
sugg_span,
645659
));
646660
}
647661

@@ -688,7 +702,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
688702
};
689703

690704
let macro_resolutions = mem::take(&mut self.multi_segment_macro_resolutions);
691-
for (mut path, path_span, kind, parent_scope, initial_res) in macro_resolutions {
705+
for (mut path, path_span, kind, parent_scope, initial_res, _sugg_span) in macro_resolutions
706+
{
692707
// FIXME: Path resolution will ICE if segment IDs present.
693708
for seg in &mut path {
694709
seg.id = None;
@@ -713,9 +728,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
713728
let exclamation_span = sm.next_point(span);
714729
suggestion = Some((
715730
vec![(exclamation_span, "".to_string())],
716-
format!("{} is not a macro, but a {}, try to remove `!`", Segment::names_to_string(&path), partial_res.base_res().descr()),
717-
Applicability::MaybeIncorrect
718-
));
731+
format!(
732+
"{} is not a macro, but a {}, try to remove `!`",
733+
Segment::names_to_string(&path),
734+
partial_res.base_res().descr(),
735+
),
736+
Applicability::MaybeIncorrect,
737+
));
719738
}
720739
(span, label)
721740
} else {
@@ -738,7 +757,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
738757
}
739758

740759
let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
741-
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
760+
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
742761
match self.early_resolve_ident_in_lexical_scope(
743762
ident,
744763
ScopeSet::Macro(kind),
@@ -771,9 +790,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
771790
}
772791
Err(..) => {
773792
let expected = kind.descr_expected();
774-
let msg = format!("cannot find {} `{}` in this scope", expected, ident);
793+
let msg = format!("cannot find {expected} `{ident}` in this scope");
775794
let mut err = self.tcx.sess.struct_span_err(ident.span, &msg);
776-
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident);
795+
self.unresolved_macro_suggestions(
796+
&mut err,
797+
kind,
798+
&parent_scope,
799+
ident,
800+
sugg_span,
801+
);
777802
err.emit();
778803
}
779804
}

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

+12
Original file line numberDiff line numberDiff line change
@@ -646,18 +646,30 @@ error: cannot find attribute `multipart_suggestion` in this scope
646646
|
647647
LL | #[multipart_suggestion(no_crate_suggestion)]
648648
| ^^^^^^^^^^^^^^^^^^^^
649+
|
650+
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
651+
|
652+
LL | #[derive(Subdiagnostic)]
653+
|
649654

650655
error: cannot find attribute `multipart_suggestion` in this scope
651656
--> $DIR/diagnostic-derive.rs:645:3
652657
|
653658
LL | #[multipart_suggestion()]
654659
| ^^^^^^^^^^^^^^^^^^^^
660+
|
661+
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
662+
|
663+
LL | #[derive(Subdiagnostic)]
664+
|
655665

656666
error: cannot find attribute `multipart_suggestion` in this scope
657667
--> $DIR/diagnostic-derive.rs:649:7
658668
|
659669
LL | #[multipart_suggestion(no_crate_suggestion)]
660670
| ^^^^^^^^^^^^^^^^^^^^
671+
|
672+
= note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
661673

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

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ LL | #[default]
66
|
77
help: consider adding a derive
88
|
9-
LL + #[derive(Default)]
10-
LL ~ pub enum Test {
9+
LL | #[derive(Default)]
1110
|
1211

1312
error: aborting due to previous error

tests/ui/macros/auxiliary/serde.rs

+19
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)