Skip to content

Commit

Permalink
Look at proc-macro attributes when encountering unknown attribute
Browse files Browse the repository at this point in the history
```
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 rust-lang#47608.
  • Loading branch information
estebank committed Mar 30, 2023
1 parent 2fb0e8d commit 43a6cd6
Show file tree
Hide file tree
Showing 17 changed files with 414 additions and 23 deletions.
126 changes: 122 additions & 4 deletions compiler/rustc_resolve/src/diagnostics.rs
Expand Up @@ -4,7 +4,7 @@ use rustc_ast::ptr::P;
use rustc_ast::visit::{self, Visitor};
use rustc_ast::{self as ast, Crate, ItemKind, ModKind, NodeId, Path, CRATE_NODE_ID};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{
pluralize, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
};
Expand Down Expand Up @@ -1016,6 +1016,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
parent_scope,
false,
false,
None,
) {
suggestions.extend(
ext.helper_attrs
Expand Down Expand Up @@ -1331,15 +1332,126 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
macro_kind: MacroKind,
parent_scope: &ParentScope<'a>,
ident: Ident,
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) {
// FIXME: this only works if 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 {
// FIXME: we should also do Levenshtein distance checks here.
derives.push(item_name);
}
}
}
let kind = MacroKind::Derive.descr();
if !derives.is_empty() {
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 {
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)
&& !macros.is_empty()
{
let msg = match &macros[..] {
[] => unreachable!("we checked above in the if-let"),
[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,
);
}
}
}

let import_suggestions =
self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
Expand All @@ -1364,14 +1476,20 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
err.help("have you added the `#[macro_use]` on the module/import?");
return;
}

if ident.name == kw::Default
&& let ModuleKind::Def(DefKind::Enum, def_id, _) = parent_scope.module.kind
{
let span = self.def_span(def_id);
let source_map = self.tcx.sess.source_map();
let head_span = source_map.guess_head_span(span);
if let Ok(head) = source_map.span_to_snippet(head_span) {
err.span_suggestion(head_span, "consider adding a derive", format!("#[derive(Default)]\n{head}"), Applicability::MaybeIncorrect);
if let Ok(_) = source_map.span_to_snippet(head_span) {
err.span_suggestion(
head_span.shrink_to_lo(),
"consider adding a derive",
format!("#[derive(Default)]\n"),
Applicability::MaybeIncorrect,
);
} else {
err.span_help(
head_span,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_resolve/src/ident.rs
Expand Up @@ -459,6 +459,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
parent_scope,
true,
force,
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
Expand Up @@ -3706,7 +3706,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, '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)
self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false, None)
{
return Ok(Some(PartialRes::new(res)));
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_resolve/src/lib.rs
Expand Up @@ -974,9 +974,9 @@ pub struct Resolver<'a, '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<'a>, Option<&'a NameBinding<'a>>)>,
Vec<(Ident, MacroKind, ParentScope<'a>, Option<&'a NameBinding<'a>>, Option<Span>)>,
multi_segment_macro_resolutions:
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>)>,
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>, Option<Span>)>,
builtin_attrs: Vec<(Ident, ParentScope<'a>)>,
/// `derive(Copy)` marks items they are applied to so they are treated specially later.
/// Derive macros cannot modify the item themselves and have to store the markers in the global
Expand Down
53 changes: 39 additions & 14 deletions compiler/rustc_resolve/src/macros.rs
Expand Up @@ -276,6 +276,14 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
let parent_scope = &ParentScope { derives, ..parent_scope };
let supports_macro_expansion = invoc.fragment_kind.supports_macro_expansion();
let node_id = invoc.expansion_data.lint_node_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 @@ -285,6 +293,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
node_id,
force,
soft_custom_inner_attributes_gate(path, invoc),
sugg_span,
)?;

let span = invoc.span();
Expand Down Expand Up @@ -369,6 +378,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
&parent_scope,
true,
force,
None,
) {
Ok((Some(ext), _)) => {
if !ext.helper_attrs.is_empty() {
Expand Down Expand Up @@ -485,14 +495,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
node_id: NodeId,
force: bool,
soft_custom_inner_attributes_gate: bool,
sugg_span: Option<Span>,
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
let (ext, res) = match self.resolve_macro_path(path, Some(kind), parent_scope, true, force)
{
Ok((Some(ext), res)) => (ext, res),
Ok((None, res)) => (self.dummy_ext(kind), res),
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
Err(Determinacy::Undetermined) => return Err(Indeterminate),
};
let (ext, res) =
match self.resolve_macro_path(path, Some(kind), parent_scope, true, force, sugg_span) {
Ok((Some(ext), res)) => (ext, res),
Ok((None, res)) => (self.dummy_ext(kind), res),
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
Err(Determinacy::Undetermined) => return Err(Indeterminate),
};

// Report errors for the resolved macro.
for segment in &path.segments {
Expand Down Expand Up @@ -585,6 +596,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
parent_scope: &ParentScope<'a>,
trace: bool,
force: bool,
sugg_span: Option<Span>,
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
let path_span = path.span;
let mut path = Segment::from_path(path);
Expand Down Expand Up @@ -616,6 +628,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
kind,
*parent_scope,
res.ok(),
sugg_span,
));
}

Expand All @@ -642,6 +655,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
kind,
*parent_scope,
binding.ok(),
sugg_span,
));
}

Expand Down Expand Up @@ -688,7 +702,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
};

let macro_resolutions = mem::take(&mut self.multi_segment_macro_resolutions);
for (mut path, path_span, kind, parent_scope, initial_res) in macro_resolutions {
for (mut path, path_span, kind, parent_scope, initial_res, _sugg_span) in macro_resolutions
{
// FIXME: Path resolution will ICE if segment IDs present.
for seg in &mut path {
seg.id = None;
Expand All @@ -713,9 +728,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
let exclamation_span = sm.next_point(span);
suggestion = Some((
vec![(exclamation_span, "".to_string())],
format!("{} is not a macro, but a {}, try to remove `!`", Segment::names_to_string(&path), partial_res.base_res().descr()),
Applicability::MaybeIncorrect
));
format!(
"{} is not a macro, but a {}, try to remove `!`",
Segment::names_to_string(&path),
partial_res.base_res().descr(),
),
Applicability::MaybeIncorrect,
));
}
(span, label)
} else {
Expand All @@ -738,7 +757,7 @@ impl<'a, 'tcx> Resolver<'a, '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 @@ -771,9 +790,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
}
Err(..) => {
let expected = kind.descr_expected();
let msg = format!("cannot find {} `{}` in this scope", expected, ident);
let msg = format!("cannot find {expected} `{ident}` in this scope");
let mut err = self.tcx.sess.struct_span_err(ident.span, &msg);
self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident);
self.unresolved_macro_suggestions(
&mut err,
kind,
&parent_scope,
ident,
sugg_span,
);
err.emit();
}
}
Expand Down
12 changes: 12 additions & 0 deletions tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
Expand Up @@ -646,18 +646,30 @@ 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)]
|

error: cannot find attribute `multipart_suggestion` in this scope
--> $DIR/diagnostic-derive.rs:645: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)]
|

error: cannot find attribute `multipart_suggestion` in this scope
--> $DIR/diagnostic-derive.rs:649: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:70:8
Expand Down
3 changes: 1 addition & 2 deletions tests/ui/enum/suggest-default-attribute.stderr
Expand Up @@ -6,8 +6,7 @@ LL | #[default]
|
help: consider adding a derive
|
LL + #[derive(Default)]
LL ~ pub enum Test {
LL | #[derive(Default)]
|

error: aborting due to previous error
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/macros/auxiliary/serde.rs
@@ -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!{}
}

0 comments on commit 43a6cd6

Please sign in to comment.