Skip to content
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

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Expand Up @@ -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,
Expand Down Expand Up @@ -1054,6 +1054,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
false,
false,
None,
None,
) {
suggestions.extend(
ext.helper_attrs
Expand Down Expand Up @@ -1424,15 +1425,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);
Expand Down Expand Up @@ -1565,6 +1596,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<'_>,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
true,
force,
ignore_import,
None,
) {
Ok((Some(ext), _)) => {
if ext.helper_attrs.contains(&ident.name) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4367,7 +4367,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)));
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,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>)>,
Expand Down
27 changes: 25 additions & 2 deletions compiler/rustc_resolve/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,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,
Expand All @@ -320,6 +328,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
soft_custom_inner_attributes_gate(path, invoc),
deleg_impl,
looks_like_invoc_in_mod_inert_attr,
sugg_span,
)?;

let span = invoc.span();
Expand Down Expand Up @@ -409,6 +418,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
true,
force,
None,
None,
) {
Ok((Some(ext), _)) => {
if !ext.helper_attrs.is_empty() {
Expand Down Expand Up @@ -552,6 +562,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
soft_custom_inner_attributes_gate: bool,
deleg_impl: Option<LocalDefId>,
invoc_in_mod_inert_attr: Option<LocalDefId>,
suggestion_span: Option<Span>,
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
let (ext, res) = match self.resolve_macro_or_delegation_path(
path,
Expand All @@ -562,6 +573,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),
Expand Down Expand Up @@ -715,6 +727,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
trace: bool,
force: bool,
ignore_import: Option<Import<'ra>>,
suggestion_span: Option<Span>,
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
self.resolve_macro_or_delegation_path(
path,
Expand All @@ -725,6 +738,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
None,
None,
ignore_import,
suggestion_span,
)
}

Expand All @@ -738,6 +752,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<Lrc<SyntaxExtension>>, Res), Determinacy> {
let path_span = ast_path.span;
let mut path = Segment::from_path(ast_path);
Expand Down Expand Up @@ -802,6 +817,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
kind,
*parent_scope,
binding.ok(),
suggestion_span,
));
}

Expand Down Expand Up @@ -933,7 +949,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),
Expand Down Expand Up @@ -974,7 +990,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();
}
}
Expand Down
14 changes: 14 additions & 0 deletions tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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 `Serialize` and `Deserialize` accept the similarly named `serde` attribute
}

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

fn main() {}
Loading
Loading