Skip to content

Look at proc-macro attributes when encountering unknown attribute #134841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 133 additions & 2 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ use rustc_ast::{
self as ast, CRATE_NODE_ID, Crate, ItemKind, MetaItemInner, MetaItemKind, ModKind, NodeId, Path,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, MultiSpan, SuggestionStyle,
@@ -1041,6 +1041,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
false,
false,
None,
None,
) else {
continue;
};
@@ -1416,15 +1417,45 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
parent_scope: &ParentScope<'ra>,
ident: Ident,
krate: &Crate,
sugg_span: Option<Span>,
) {
// Bring imported but unused `derive` macros into `macro_map` so we ensure they can be used
// for suggestions.
self.visit_scopes(
ScopeSet::Macro(MacroKind::Derive),
&parent_scope,
ident.span.ctxt(),
|this, scope, _use_prelude, _ctxt| {
let Scope::Module(m, _) = scope else {
return None;
};
for (_, resolution) in this.resolutions(m).borrow().iter() {
let Some(binding) = resolution.borrow().binding else {
continue;
};
let Res::Def(DefKind::Macro(MacroKind::Derive | MacroKind::Attr), def_id) =
binding.res()
else {
continue;
};
// By doing this all *imported* macros get added to the `macro_map` even if they
// are *unused*, which makes the later suggestions find them and work.
let _ = this.get_macro_by_def_id(def_id);
}
None::<()>
},
);

let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind);
let suggestion = self.early_lookup_typo_candidate(
ScopeSet::Macro(macro_kind),
parent_scope,
ident,
is_expected,
);
self.add_typo_suggestion(err, suggestion, ident.span);
if !self.add_typo_suggestion(err, suggestion, ident.span) {
self.detect_derive_attribute(err, ident, parent_scope, sugg_span);
}

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

/// Given an attribute macro that failed to be resolved, look for `derive` macros that could
/// provide it, either as-is or with small typos.
fn detect_derive_attribute(
&self,
err: &mut Diag<'_>,
ident: Ident,
parent_scope: &ParentScope<'ra>,
sugg_span: Option<Span>,
) {
// Find all of the `derive`s in scope and collect their corresponding declared
// attributes.
// FIXME: this only works if the crate that owns the macro that has the helper_attr
// has already been imported.
let mut derives = vec![];
let mut all_attrs: FxHashMap<Symbol, Vec<_>> = FxHashMap::default();
for (def_id, data) in &self.macro_map {
for helper_attr in &data.ext.helper_attrs {
let item_name = self.tcx.item_name(*def_id);
all_attrs.entry(*helper_attr).or_default().push(item_name);
if helper_attr == &ident.name {
derives.push(item_name);
}
}
}
let kind = MacroKind::Derive.descr();
if !derives.is_empty() {
// We found an exact match for the missing attribute in a `derive` macro. Suggest it.
derives.sort();
derives.dedup();
let msg = match &derives[..] {
[derive] => format!(" `{derive}`"),
[start @ .., last] => format!(
"s {} and `{last}`",
start.iter().map(|d| format!("`{d}`")).collect::<Vec<_>>().join(", ")
),
[] => unreachable!("we checked for this to be non-empty 10 lines above!?"),
};
let msg = format!(
"`{}` is an attribute that can be used by the {kind}{msg}, you might be \
missing a `derive` attribute",
ident.name,
);
let sugg_span = if let ModuleKind::Def(DefKind::Enum, id, _) = parent_scope.module.kind
{
let span = self.def_span(id);
if span.from_expansion() {
None
} else {
// For enum variants sugg_span is empty but we can get the enum's Span.
Some(span.shrink_to_lo())
}
} else {
// For items this `Span` will be populated, everything else it'll be None.
sugg_span
};
match sugg_span {
Some(span) => {
err.span_suggestion_verbose(
span,
msg,
format!(
"#[derive({})]\n",
derives
.iter()
.map(|d| d.to_string())
.collect::<Vec<String>>()
.join(", ")
),
Applicability::MaybeIncorrect,
);
}
None => {
err.note(msg);
}
}
} else {
// We didn't find an exact match. Look for close matches. If any, suggest fixing typo.
let all_attr_names: Vec<Symbol> = all_attrs.keys().cloned().collect();
if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None)
&& let Some(macros) = all_attrs.get(&best_match)
{
let msg = match &macros[..] {
[] => return,
[name] => format!(" `{name}` accepts"),
[start @ .., end] => format!(
"s {} and `{end}` accept",
start.iter().map(|m| format!("`{m}`")).collect::<Vec<_>>().join(", "),
),
};
let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute");
err.span_suggestion_verbose(
ident.span,
msg,
best_match,
Applicability::MaybeIncorrect,
);
}
}
}

pub(crate) fn add_typo_suggestion(
&self,
err: &mut Diag<'_>,
1 change: 1 addition & 0 deletions compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
@@ -463,6 +463,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
true,
force,
ignore_import,
None,
) {
Ok((Some(ext), _)) => {
if ext.helper_attrs.contains(&ident.name) {
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
@@ -4406,7 +4406,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
let path_seg = |seg: &Segment| PathSegment::from_ident(seg.ident);
let path = Path { segments: path.iter().map(path_seg).collect(), span, tokens: None };
if let Ok((_, res)) =
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None)
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None, None)
{
return Ok(Some(PartialRes::new(res)));
}
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1146,7 +1146,7 @@ pub struct Resolver<'ra, 'tcx> {
proc_macro_stubs: FxHashSet<LocalDefId>,
/// Traces collected during macro resolution and validated when it's complete.
single_segment_macro_resolutions:
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>)>,
Vec<(Ident, MacroKind, ParentScope<'ra>, Option<NameBinding<'ra>>, Option<Span>)>,
multi_segment_macro_resolutions:
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'ra>, Option<Res>, Namespace)>,
builtin_attrs: Vec<(Ident, ParentScope<'ra>)>,
30 changes: 27 additions & 3 deletions compiler/rustc_resolve/src/macros.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,8 @@ use rustc_attr_parsing::StabilityLevel;
use rustc_data_structures::intern::Interned;
use rustc_errors::{Applicability, StashKey};
use rustc_expand::base::{
DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind,
Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension,
SyntaxExtensionKind,
};
use rustc_expand::compile_declarative_macro;
use rustc_expand::expand::{
@@ -287,6 +288,14 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
&& self.tcx.def_kind(mod_def_id) == DefKind::Mod
})
.map(|&InvocationParent { parent_def: mod_def_id, .. }| mod_def_id);
let sugg_span = match &invoc.kind {
InvocationKind::Attr { item: Annotatable::Item(item), .. }
if !item.span.from_expansion() =>
{
Some(item.span.shrink_to_lo())
}
_ => None,
};
let (ext, res) = self.smart_resolve_macro_path(
path,
kind,
@@ -297,6 +306,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
force,
deleg_impl,
looks_like_invoc_in_mod_inert_attr,
sugg_span,
)?;

let span = invoc.span();
@@ -386,6 +396,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
true,
force,
None,
None,
) {
Ok((Some(ext), _)) => {
if !ext.helper_attrs.is_empty() {
@@ -528,6 +539,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
force: bool,
deleg_impl: Option<LocalDefId>,
invoc_in_mod_inert_attr: Option<LocalDefId>,
suggestion_span: Option<Span>,
) -> Result<(Arc<SyntaxExtension>, Res), Indeterminate> {
let (ext, res) = match self.resolve_macro_or_delegation_path(
path,
@@ -538,6 +550,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
deleg_impl,
invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)),
None,
suggestion_span,
) {
Ok((Some(ext), res)) => (ext, res),
Ok((None, res)) => (self.dummy_ext(kind), res),
@@ -681,6 +694,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
trace: bool,
force: bool,
ignore_import: Option<Import<'ra>>,
suggestion_span: Option<Span>,
) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
self.resolve_macro_or_delegation_path(
path,
@@ -691,6 +705,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
None,
None,
ignore_import,
suggestion_span,
)
}

@@ -704,6 +719,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
deleg_impl: Option<LocalDefId>,
invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>,
ignore_import: Option<Import<'ra>>,
suggestion_span: Option<Span>,
) -> Result<(Option<Arc<SyntaxExtension>>, Res), Determinacy> {
let path_span = ast_path.span;
let mut path = Segment::from_path(ast_path);
@@ -768,6 +784,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
kind,
*parent_scope,
binding.ok(),
suggestion_span,
));
}

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

let macro_resolutions = mem::take(&mut self.single_segment_macro_resolutions);
for (ident, kind, parent_scope, initial_binding) in macro_resolutions {
for (ident, kind, parent_scope, initial_binding, sugg_span) in macro_resolutions {
match self.early_resolve_ident_in_lexical_scope(
ident,
ScopeSet::Macro(kind),
@@ -946,7 +963,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
expected,
ident,
});
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate);
self.unresolved_macro_suggestions(
&mut err,
kind,
&parent_scope,
ident,
krate,
sugg_span,
);
err.emit();
}
}
14 changes: 14 additions & 0 deletions tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
Original file line number Diff line number Diff line change
@@ -583,18 +583,32 @@ error: cannot find attribute `multipart_suggestion` in this scope
|
LL | #[multipart_suggestion(no_crate_suggestion)]
| ^^^^^^^^^^^^^^^^^^^^
|
help: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute
|
LL + #[derive(Subdiagnostic)]
LL | struct MultipartSuggestion {
|

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

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

error[E0425]: cannot find value `nonsense` in module `crate::fluent_generated`
--> $DIR/diagnostic-derive.rs:75:8
19 changes: 19 additions & 0 deletions tests/ui/macros/auxiliary/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@ force-host
//@ no-prefer-dynamic

#![crate_type = "proc-macro"]
#![feature(proc_macro_quote)]

extern crate proc_macro;

use proc_macro::*;

#[proc_macro_derive(Serialize, attributes(serde))]
pub fn serialize(ts: TokenStream) -> TokenStream {
quote!{}
}

#[proc_macro_derive(Deserialize, attributes(serde))]
pub fn deserialize(ts: TokenStream) -> TokenStream {
quote!{}
}
33 changes: 33 additions & 0 deletions tests/ui/macros/missing-derive-1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@aux-build:serde.rs

// derive macros imported and used

extern crate serde;
use serde::{Serialize, Deserialize};

#[serde(untagged)] //~ ERROR cannot find attribute `serde`
enum A { //~ HELP `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`
A,
B,
}

enum B { //~ HELP `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`
A,
#[serde(untagged)] //~ ERROR cannot find attribute `serde`
B,
}

enum C {
A,
#[sede(untagged)] //~ ERROR cannot find attribute `sede`
B, //~^ HELP the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum D {
A,
B,
}

fn main() {}
47 changes: 47 additions & 0 deletions tests/ui/macros/missing-derive-1.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
error: cannot find attribute `serde` in this scope
--> $DIR/missing-derive-1.rs:8:3
|
LL | #[serde(untagged)]
| ^^^^^
|
note: `serde` is imported here, but it is a crate, not an attribute
--> $DIR/missing-derive-1.rs:5:1
|
LL | extern crate serde;
| ^^^^^^^^^^^^^^^^^^^
help: `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`, you might be missing a `derive` attribute
|
LL + #[derive(Serialize, Deserialize)]
LL | enum A {
|

error: cannot find attribute `serde` in this scope
--> $DIR/missing-derive-1.rs:16:7
|
LL | #[serde(untagged)]
| ^^^^^
|
note: `serde` is imported here, but it is a crate, not an attribute
--> $DIR/missing-derive-1.rs:5:1
|
LL | extern crate serde;
| ^^^^^^^^^^^^^^^^^^^
help: `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`, you might be missing a `derive` attribute
|
LL + #[derive(Serialize, Deserialize)]
LL | enum B {
|

error: cannot find attribute `sede` in this scope
--> $DIR/missing-derive-1.rs:22:7
|
LL | #[sede(untagged)]
| ^^^^
|
help: the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
|
LL | #[serde(untagged)]
| +

error: aborting due to 3 previous errors

26 changes: 26 additions & 0 deletions tests/ui/macros/missing-derive-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//@aux-build:serde.rs

// derive macros imported but unused

extern crate serde;
use serde::{Serialize, Deserialize};

#[serde(untagged)] //~ ERROR cannot find attribute `serde`
enum A { //~ HELP `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`
A,
B,
}

enum B { //~ HELP `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`
A,
#[serde(untagged)] //~ ERROR cannot find attribute `serde`
B,
}

enum C {
A,
#[sede(untagged)] //~ ERROR cannot find attribute `sede`
B, //~^ HELP the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
}

fn main() {}
47 changes: 47 additions & 0 deletions tests/ui/macros/missing-derive-2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
error: cannot find attribute `sede` in this scope
--> $DIR/missing-derive-2.rs:22:7
|
LL | #[sede(untagged)]
| ^^^^
|
help: the derive macros `Deserialize` and `Serialize` accept the similarly named `serde` attribute
|
LL | #[serde(untagged)]
| +

error: cannot find attribute `serde` in this scope
--> $DIR/missing-derive-2.rs:16:7
|
LL | #[serde(untagged)]
| ^^^^^
|
note: `serde` is imported here, but it is a crate, not an attribute
--> $DIR/missing-derive-2.rs:5:1
|
LL | extern crate serde;
| ^^^^^^^^^^^^^^^^^^^
help: `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`, you might be missing a `derive` attribute
|
LL + #[derive(Serialize, Deserialize)]
LL | enum B {
|

error: cannot find attribute `serde` in this scope
--> $DIR/missing-derive-2.rs:8:3
|
LL | #[serde(untagged)]
| ^^^^^
|
note: `serde` is imported here, but it is a crate, not an attribute
--> $DIR/missing-derive-2.rs:5:1
|
LL | extern crate serde;
| ^^^^^^^^^^^^^^^^^^^
help: `serde` is an attribute that can be used by the derive macros `Serialize` and `Deserialize`, you might be missing a `derive` attribute
|
LL + #[derive(Serialize, Deserialize)]
LL | enum A {
|

error: aborting due to 3 previous errors

24 changes: 24 additions & 0 deletions tests/ui/macros/missing-derive-3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@aux-build:serde.rs

// derive macros not imported, but namespace imported. Not yet handled.
extern crate serde;

#[serde(untagged)] //~ ERROR cannot find attribute `serde`
enum A {
A,
B,
}

enum B {
A,
#[serde(untagged)] //~ ERROR cannot find attribute `serde`
B,
}

enum C {
A,
#[sede(untagged)] //~ ERROR cannot find attribute `sede`
B,
}

fn main() {}
32 changes: 32 additions & 0 deletions tests/ui/macros/missing-derive-3.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: cannot find attribute `sede` in this scope
--> $DIR/missing-derive-3.rs:20:7
|
LL | #[sede(untagged)]
| ^^^^

error: cannot find attribute `serde` in this scope
--> $DIR/missing-derive-3.rs:14:7
|
LL | #[serde(untagged)]
| ^^^^^
|
note: `serde` is imported here, but it is a crate, not an attribute
--> $DIR/missing-derive-3.rs:4:1
|
LL | extern crate serde;
| ^^^^^^^^^^^^^^^^^^^

error: cannot find attribute `serde` in this scope
--> $DIR/missing-derive-3.rs:6:3
|
LL | #[serde(untagged)]
| ^^^^^
|
note: `serde` is imported here, but it is a crate, not an attribute
--> $DIR/missing-derive-3.rs:4:1
|
LL | extern crate serde;
| ^^^^^^^^^^^^^^^^^^^

error: aborting due to 3 previous errors

6 changes: 6 additions & 0 deletions tests/ui/proc-macro/derive-helper-legacy-limits.stderr
Original file line number Diff line number Diff line change
@@ -3,6 +3,12 @@ error: cannot find attribute `empty_helper` in this scope
|
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;
|

error: aborting due to 1 previous error

2 changes: 2 additions & 0 deletions tests/ui/proc-macro/derive-helper-shadowing.stderr
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ error: cannot find attribute `empty_helper` in this scope
LL | #[derive(GenHelperUse)]
| ^^^^^^^^^^^^
|
= note: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
= note: this error originates in the derive macro `GenHelperUse` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider importing this attribute macro through its public re-export
|
@@ -31,6 +32,7 @@ LL | #[empty_helper]
LL | gen_helper_use!();
| ----------------- in this macro invocation
|
= note: `empty_helper` is an attribute that can be used by the derive macro `Empty`, you might be missing a `derive` attribute
= note: this error originates in the macro `gen_helper_use` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider importing this attribute macro through its public re-export
|
6 changes: 6 additions & 0 deletions tests/ui/proc-macro/disappearing-resolution.stderr
Original file line number Diff line number Diff line change
@@ -3,6 +3,12 @@ error: cannot find attribute `empty_helper` in this scope
|
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 S;
|

error[E0603]: derive macro import `Empty` is private
--> $DIR/disappearing-resolution.rs:11:8