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 #110342

Closed
wants to merge 1 commit into from
Closed
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
136 changes: 132 additions & 4 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
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,40 @@ 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) {
self.add_derive_for_attribute(err, sugg_span, ident, parent_scope);
}

let import_suggestions =
self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected);
Expand All @@ -1364,14 +1390,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 Expand Up @@ -1494,6 +1526,102 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
true
}

fn add_derive_for_attribute(
&self,
err: &mut Diagnostic,
sugg_span: Option<Span>,
ident: Ident,
parent_scope: &ParentScope<'_>,
) {
// 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,
);
}
}
}

fn binding_description(&self, b: &NameBinding<'_>, ident: Ident, from_prelude: bool) -> String {
let res = b.res();
if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) {
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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -3715,7 +3715,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
Original file line number Diff line number Diff line change
Expand Up @@ -975,9 +975,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
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,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 @@ -286,6 +294,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 @@ -370,6 +379,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 @@ -486,14 +496,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 @@ -603,6 +614,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 @@ -634,6 +646,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
kind,
*parent_scope,
res.ok(),
sugg_span,
));
}

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

Expand Down Expand Up @@ -706,7 +720,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 @@ -731,9 +746,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 @@ -756,7 +775,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 @@ -789,9 +808,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
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 @@ -613,18 +613,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:642: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:646: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:69:8
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/enum/suggest-default-attribute.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ LL | #[default]
help: consider adding a derive
|
LL + #[derive(Default)]
LL ~ pub enum Test {
LL | pub enum Test {
|

error: aborting due to previous error
Expand Down
Loading