diff options
| author | Esteban Küber <esteban@kuber.com.ar> | 2024-12-28 02:19:35 +0000 |
|---|---|---|
| committer | Esteban Küber <esteban@kuber.com.ar> | 2025-06-12 21:28:49 +0000 |
| commit | 92a798dac0e2e13380d053b532ef36f1eddf9acf (patch) | |
| tree | 430fd3c78aed15d8d3ab6f8fc813f09af35fcedd /compiler/rustc_resolve | |
| parent | 6c8138de8f1c96b2f66adbbc0e37c73525444750 (diff) | |
| download | rust-92a798dac0e2e13380d053b532ef36f1eddf9acf.tar.gz rust-92a798dac0e2e13380d053b532ef36f1eddf9acf.zip | |
Detect when attribute is provided by missing `derive` macro
``` error: cannot find attribute `empty_helper` in this scope --> $DIR/derive-helper-legacy-limits.rs:17:3 | 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; | ``` 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)] | ```
Diffstat (limited to 'compiler/rustc_resolve')
| -rw-r--r-- | compiler/rustc_resolve/src/diagnostics.rs | 134 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/ident.rs | 1 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/late.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/lib.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/macros.rs | 30 |
5 files changed, 162 insertions, 7 deletions
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 201b1c0a493..9149974a617 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -7,7 +7,7 @@ use rustc_ast::{ use rustc_ast_pretty::pprust; use rustc_attr_data_structures::{self as attr, Stability}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::unord::UnordSet; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, MultiSpan, SuggestionStyle, @@ -1054,6 +1054,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { false, false, None, + None, ) else { continue; }; @@ -1482,7 +1483,35 @@ 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), @@ -1490,7 +1519,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { 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); @@ -1623,6 +1654,105 @@ 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: UnordMap<Symbol, Vec<_>> = UnordMap::default(); + // We're collecting these in a hashmap, and handle ordering the output further down. + #[allow(rustc::potential_query_instability)] + 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. + let mut derives: Vec<String> = derives.into_iter().map(|d| d.to_string()).collect(); + 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.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 = all_attrs.keys().map(|s| *s).into_sorted_stable_ord(); + 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 mut macros: Vec<String> = macros.into_iter().map(|d| d.to_string()).collect(); + macros.sort(); + macros.dedup(); + let msg = match ¯os[..] { + [] => 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<'_>, diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 180d6af219d..68fbe48ebcb 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -460,6 +460,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { true, force, ignore_import, + None, ) { Ok((Some(ext), _)) => { if ext.helper_attrs.contains(&ident.name) { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 3dc285fdab6..fa4b024c422 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -4509,7 +4509,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))); } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 9ba70abd4d9..2ff48c503c5 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1139,7 +1139,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>)>, diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index ee905065b96..e80d4422e37 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -12,7 +12,8 @@ use rustc_attr_data_structures::StabilityLevel; use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, DiagCtxtHandle, 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::{ @@ -294,6 +295,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, @@ -304,6 +313,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(); } } |
