From 2de1207818723b801380a808e92d4c844855d9bb Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 10 Jul 2026 12:18:51 +0200 Subject: [PATCH 1/2] Apply MCP 1003 and move `diagnostics.rs` into its own module and move `error_helper.rs` into the `diagnostics` folder in `rustc_resolve` --- compiler/rustc_resolve/src/build_reduced_graph.rs | 2 +- .../src/{error_helper.rs => diagnostics/impls.rs} | 0 .../src/{diagnostics.rs => diagnostics/mod.rs} | 2 ++ compiler/rustc_resolve/src/imports.rs | 2 +- compiler/rustc_resolve/src/late/diagnostics.rs | 2 +- compiler/rustc_resolve/src/lib.rs | 6 +++--- 6 files changed, 8 insertions(+), 6 deletions(-) rename compiler/rustc_resolve/src/{error_helper.rs => diagnostics/impls.rs} (100%) rename compiler/rustc_resolve/src/{diagnostics.rs => diagnostics/mod.rs} (99%) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index c35dc02fb1de1..974577fd89a54 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -31,7 +31,7 @@ use tracing::debug; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::DefCollector; -use crate::error_helper::{OnUnknownData, StructCtor}; +use crate::diagnostics::impls::{OnUnknownData, StructCtor}; use crate::imports::{ImportData, ImportKind}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; diff --git a/compiler/rustc_resolve/src/error_helper.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs similarity index 100% rename from compiler/rustc_resolve/src/error_helper.rs rename to compiler/rustc_resolve/src/diagnostics/impls.rs diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs similarity index 99% rename from compiler/rustc_resolve/src/diagnostics.rs rename to compiler/rustc_resolve/src/diagnostics/mod.rs index 22cc6a581f19a..94616f1c679ea 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -10,6 +10,8 @@ use rustc_span::{Ident, Span, Spanned, Symbol}; use crate::Res; use crate::late::PatternSource; +pub(crate) mod impls; + #[derive(Diagnostic)] #[diag("can't use {$is_self -> [true] `Self` diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 379ae992c9a6e..db7c95fbd511e 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -25,13 +25,13 @@ use rustc_span::{Ident, Span, Symbol, kw, sym}; use tracing::debug; use crate::Namespace::{self, *}; +use crate::diagnostics::impls::{OnUnknownData, Suggestion}; use crate::diagnostics::{ self, CannotBeReexportedCratePublic, CannotBeReexportedCratePublicNS, CannotBeReexportedPrivate, CannotBeReexportedPrivateNS, CannotDetermineImportResolution, CannotGlobImportAllCrates, ConsiderAddingMacroExport, ConsiderMarkingAsPub, ConsiderMarkingAsPubCrate, }; -use crate::error_helper::{OnUnknownData, Suggestion}; use crate::ref_mut::{CmCell, CmRefCell}; use crate::{ AmbiguityError, BindingKey, CmResolver, Decl, DeclData, DeclKind, Determinacy, Finalize, diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 668278f13d9cf..4e901cd76b92e 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -32,7 +32,7 @@ use thin_vec::{ThinVec, thin_vec}; use tracing::debug; use super::NoConstantGenericsReason; -use crate::error_helper::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; +use crate::diagnostics::impls::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; use crate::late::{ AliasPossibility, LateResolutionVisitor, LifetimeBinderKind, LifetimeRes, LifetimeRibKind, LifetimeUseSet, QSelf, RibKind, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 911350665ed32..56e107c0c3369 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -29,7 +29,6 @@ use std::{fmt, mem}; use diagnostics::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; use effective_visibilities::EffectiveVisibilitiesVisitor; -use error_helper::{ImportSuggestion, LabelSuggestion, StructCtor, Suggestion}; use hygiene::Macros20NormalizedSyntaxContext; use imports::{Import, ImportData, ImportKind, NameResolution, PendingDecl}; use late::{ @@ -77,7 +76,9 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use smallvec::{SmallVec, smallvec}; use tracing::{debug, instrument}; -use crate::error_helper::OnUnknownData; +use crate::diagnostics::impls::{ + ImportSuggestion, LabelSuggestion, OnUnknownData, StructCtor, Suggestion, +}; use crate::imports::NameResolutionRef; use crate::ref_mut::{CmCell, CmRefCell}; @@ -86,7 +87,6 @@ mod check_unused; mod def_collector; mod diagnostics; mod effective_visibilities; -mod error_helper; mod ident; mod imports; mod late; From 9e2a32ebc4b745dcec84b9c5ee040c9e1dd700d6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 10 Jul 2026 12:45:06 +0200 Subject: [PATCH 2/2] Merge `rustc_resolve/late/diagnostics.rs` into `rustc_resolve/diagnostics/impls.rs` --- .../rustc_resolve/src/diagnostics/impls.rs | 4665 ++++++++++++++++- compiler/rustc_resolve/src/late.rs | 113 +- .../rustc_resolve/src/late/diagnostics.rs | 4650 ---------------- 3 files changed, 4697 insertions(+), 4731 deletions(-) delete mode 100644 compiler/rustc_resolve/src/late/diagnostics.rs diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 8ab193afe0eee..b04065932fe48 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1,43 +1,48 @@ // ignore-tidy-filelength -use std::mem; -use std::ops::ControlFlow; + +use std::borrow::Cow; +use std::ops::{ControlFlow, Deref}; +use std::{iter, mem}; use itertools::Itertools as _; -use rustc_ast::visit::{self, Visitor}; +use rustc_ast::visit::{self, FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty}; use rustc_ast::{ - self as ast, CRATE_NODE_ID, Crate, DUMMY_NODE_ID, ItemKind, ModKind, NodeId, Path, - join_path_idents, + self as ast, AngleBracketedArg, AssocItemKind, CRATE_NODE_ID, Crate, DUMMY_NODE_ID, Expr, + ExprKind, GenericArg, GenericArgs, GenericParam, GenericParamKind, Item, ItemKind, MethodCall, + ModKind, NodeId, Path, PathSegment, QSelf, Ty, TyKind, join_path_idents, }; -use rustc_ast_pretty::pprust; +use rustc_ast_pretty::pprust::{path_to_string, where_bound_predicate_to_string}; use rustc_attr_parsing::AttributeParser; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::unord::{UnordMap, UnordSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; +use rustc_data_structures::unord::{UnordItems, UnordMap, UnordSet}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, struct_span_code_err, }; use rustc_feature::BUILTIN_ATTRIBUTES; +use rustc_hir as hir; use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_hir::attrs::{AttributeKind, CfgEntry, StrippedCfgItem}; use rustc_hir::def::Namespace::{self, *}; -use rustc_hir::def::{CtorKind, CtorOf, DefKind, MacroKinds, NonMacroAttrKind, PerNS}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, LifetimeRes, MacroKinds, NonMacroAttrKind, PerNS}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; -use rustc_hir::{Attribute, PrimTy, Stability, StabilityLevel, find_attr}; +use rustc_hir::{Attribute, MissingLifetimeKind, PrimTy, Stability, StabilityLevel, find_attr}; use rustc_middle::bug; -use rustc_middle::ty::{TyCtxt, Visibility}; -use rustc_session::Session; +use rustc_middle::ty::{self, TyCtxt, Visibility}; use rustc_session::lint::builtin::{ ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_IMPORT_VISIBILITIES, AMBIGUOUS_PANIC_IMPORTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, }; use rustc_session::utils::was_invoked_from_cargo; -use rustc_span::edit_distance::find_best_match_for_name; +use rustc_session::{Session, lint}; +use rustc_span::edit_distance::{edit_distance, find_best_match_for_name}; use rustc_span::edition::Edition; use rustc_span::hygiene::MacroKind; use rustc_span::source_map::SourceMap; use rustc_span::{ - BytePos, Ident, RemapPathScopeComponents, Span, Spanned, Symbol, SyntaxContext, kw, sym, + BytePos, DUMMY_SP, DesugaringKind, Ident, RemapPathScopeComponents, Span, Spanned, Symbol, + SyntaxContext, kw, sym, }; use thin_vec::{ThinVec, thin_vec}; use tracing::{debug, instrument}; @@ -49,13 +54,17 @@ use crate::diagnostics::{ }; use crate::hygiene::Macros20NormalizedSyntaxContext; use crate::imports::{Import, ImportKind, UnresolvedImportError, import_path_to_string}; -use crate::late::{DiagMetadata, PatternSource, Rib}; +use crate::late::{ + AliasPossibility, DiagMetadata, LateResolutionVisitor, LifetimeBinderKind, LifetimeRibKind, + LifetimeUseSet, NoConstantGenericsReason, PatternSource, Rib, RibKind, +}; +use crate::ty::fast_reject::SimplifiedType; use crate::{ AmbiguityError, AmbiguityKind, AmbiguityWarning, BindingError, BindingKey, Decl, DeclKind, DelayedVisResolutionError, Finalize, ForwardGenericParamBanReason, HasGenericParams, IdentKey, LateDecl, MacroRulesScope, Module, ModuleKind, ModuleOrUniformRoot, ParentScope, PathResult, - PrivacyError, Res, ResolutionError, Resolver, Scope, ScopeSet, Segment, UseError, Used, - VisResolutionError, path_names_to_string, + PathSource, PrivacyError, Res, ResolutionError, Resolver, Scope, ScopeSet, Segment, UseError, + Used, VisResolutionError, path_names_to_string, }; /// A vector of spans and replacements, a message and applicability. @@ -1392,7 +1401,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path_span: path.span, // intentionally converting to String, as the text would also be used as // in suggestion context - path_str: pprust::path_to_string(&path), + path_str: path_to_string(&path), }) } VisResolutionError::AncestorOnly(span) => { @@ -2891,8 +2900,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if module_def_id == Some(CRATE_DEF_ID.to_def_id()) { let is_mod = |res| matches!(res, Res::Def(DefKind::Mod, _)); let mut candidates = self.lookup_import_candidates(ident, TypeNS, parent_scope, is_mod); - candidates - .sort_by_cached_key(|c| (c.path.segments.len(), pprust::path_to_string(&c.path))); + candidates.sort_by_cached_key(|c| (c.path.segments.len(), path_to_string(&c.path))); if let Some(candidate) = candidates.get(0) { let path = { // remove the possible common prefix of the path @@ -2908,7 +2916,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { message, String::from("unresolved import"), Some(( - vec![(ident.span, pprust::path_to_string(&path))], + vec![(ident.span, path_to_string(&path))], String::from("a similar path exists"), Applicability::MaybeIncorrect, )), @@ -3832,7 +3840,7 @@ fn show_candidates( // Don't suggest `#[doc(hidden)]` items from other crates if c.doc_visible { accessible_path_strings.push(( - pprust::path_to_string(&c.path), + path_to_string(&c.path), c.descr, c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), &c.note, @@ -3841,7 +3849,7 @@ fn show_candidates( } } else { inaccessible_path_strings.push(( - pprust::path_to_string(&c.path), + path_to_string(&c.path), c.descr, c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), &c.note, @@ -4160,3 +4168,4614 @@ impl OnUnknownData { } } } + +/// A field or associated item from self type suggested in case of resolution failure. +enum AssocSuggestion { + Field(Span), + MethodWithSelf { called: bool }, + AssocFn { called: bool }, + AssocType, + AssocConst, +} + +impl AssocSuggestion { + fn action(&self) -> &'static str { + match self { + AssocSuggestion::Field(_) => "use the available field", + AssocSuggestion::MethodWithSelf { called: true } => { + "call the method with the fully-qualified path" + } + AssocSuggestion::MethodWithSelf { called: false } => { + "refer to the method with the fully-qualified path" + } + AssocSuggestion::AssocFn { called: true } => "call the associated function", + AssocSuggestion::AssocFn { called: false } => "refer to the associated function", + AssocSuggestion::AssocConst => "use the associated `const`", + AssocSuggestion::AssocType => "use the associated type", + } + } +} + +fn is_self_type(path: &[Segment], namespace: Namespace) -> bool { + namespace == TypeNS && path.len() == 1 && path[0].ident.name == kw::SelfUpper +} + +fn is_self_value(path: &[Segment], namespace: Namespace) -> bool { + namespace == ValueNS && path.len() == 1 && path[0].ident.name == kw::SelfLower +} + +fn path_to_string_without_assoc_item_bindings(path: &Path) -> String { + let mut path = path.clone(); + for segment in &mut path.segments { + let mut remove_args = false; + if let Some(args) = segment.args.as_deref_mut() + && let ast::GenericArgs::AngleBracketed(angle_bracketed) = args + { + angle_bracketed.args.retain(|arg| matches!(arg, ast::AngleBracketedArg::Arg(_))); + remove_args = angle_bracketed.args.is_empty(); + } + if remove_args { + segment.args = None; + } + } + path_to_string(&path) +} + +/// Gets the stringified path for an enum from an `ImportSuggestion` for an enum variant. +fn import_candidate_to_enum_paths(suggestion: &ImportSuggestion) -> (String, String) { + let variant_path = &suggestion.path; + let variant_path_string = path_names_to_string(variant_path); + + let path_len = suggestion.path.segments.len(); + let enum_path = ast::Path { + span: suggestion.path.span, + segments: suggestion.path.segments[0..path_len - 1].iter().cloned().collect(), + tokens: None, + }; + let enum_path_string = path_names_to_string(&enum_path); + + (variant_path_string, enum_path_string) +} + +/// Description of an elided lifetime. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub(crate) struct MissingLifetime { + /// Used to overwrite the resolution with the suggestion, to avoid cascading errors. + pub id: NodeId, + /// As we cannot yet emit lints in this crate and have to buffer them instead, + /// we need to associate each lint with some `NodeId`, + /// however for some `MissingLifetime`s their `NodeId`s are "fake", + /// in a sense that they are temporary and not get preserved down the line, + /// which means that the lints for those nodes will not get emitted. + /// To combat this, we can try to use some other `NodeId`s as a fallback option. + pub id_for_lint: NodeId, + /// Where to suggest adding the lifetime. + pub span: Span, + /// How the lifetime was introduced, to have the correct space and comma. + pub kind: MissingLifetimeKind, + /// Number of elided lifetimes, used for elision in path. + pub count: usize, +} + +/// Description of the lifetimes appearing in a function parameter. +/// This is used to provide a literal explanation to the elision failure. +#[derive(Debug)] +pub(crate) struct ElisionFnParameter { + /// The index of the argument in the original definition. + pub index: usize, + /// The name of the argument if it's a simple ident. + pub ident: Option, + /// The number of lifetimes in the parameter. + pub lifetime_count: usize, + /// The span of the parameter. + pub span: Span, +} + +/// Description of lifetimes that appear as candidates for elision. +/// This is used to suggest introducing an explicit lifetime. +#[derive(Clone, Copy, Debug)] +pub(crate) enum LifetimeElisionCandidate { + /// This is not a real lifetime, or it is a named lifetime, in which case we won't suggest anything. + Ignore, + Missing(MissingLifetime), +} + +/// Only used for diagnostics. +#[derive(Debug)] +struct BaseError { + msg: String, + fallback_label: String, + span: Span, + span_label: Option<(Span, &'static str)>, + could_be_expr: bool, + suggestion: Option<(Span, &'static str, String)>, + module: Option, +} + +#[derive(Debug)] +enum TypoCandidate { + Typo(TypoSuggestion), + Shadowed(Res, Option), + None, +} + +impl TypoCandidate { + fn to_opt_suggestion(self) -> Option { + match self { + TypoCandidate::Typo(sugg) => Some(sugg), + TypoCandidate::Shadowed(_, _) | TypoCandidate::None => None, + } + } +} + +impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { + fn trait_assoc_type_def_id_by_name( + &mut self, + trait_def_id: DefId, + assoc_name: Symbol, + ) -> Option { + let module = self.r.get_module(trait_def_id)?; + self.r.resolutions(module).borrow().iter().find_map(|(key, resolution)| { + if key.ident.name != assoc_name { + return None; + } + let resolution = resolution.borrow(); + let binding = resolution.best_decl()?; + match binding.res() { + Res::Def(DefKind::AssocTy, def_id) => Some(def_id), + _ => None, + } + }) + } + + /// This does best-effort work to generate suggestions for associated types. + fn suggest_assoc_type_from_bounds( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, 'ast, 'ra>, + path: &[Segment], + ident_span: Span, + ) -> bool { + // Filter out cases where we cannot emit meaningful suggestions. + if source.namespace() != TypeNS { + return false; + } + let [segment] = path else { return false }; + if segment.has_generic_args { + return false; + } + if !ident_span.can_be_used_for_suggestions() { + return false; + } + let assoc_name = segment.ident.name; + if assoc_name == kw::Underscore { + return false; + } + + // Map: type parameter name -> (trait def id -> (assoc type def id, trait paths as written)). + // We keep a set of paths per trait so we can detect cases like + // `T: Trait + Trait` where suggesting `T::Assoc` would be ambiguous. + let mut matching_bounds: FxIndexMap< + Symbol, + FxIndexMap)>, + > = FxIndexMap::default(); + + let mut record_bound = |this: &mut Self, + ty_param: Symbol, + poly_trait_ref: &ast::PolyTraitRef| { + // Avoid generating suggestions we can't print in a well-formed way. + if !poly_trait_ref.bound_generic_params.is_empty() { + return; + } + if poly_trait_ref.modifiers != ast::TraitBoundModifiers::NONE { + return; + } + let Some(trait_seg) = poly_trait_ref.trait_ref.path.segments.last() else { + return; + }; + let Some(partial_res) = this.r.partial_res_map.get(&trait_seg.id) else { + return; + }; + let Some(trait_def_id) = partial_res.full_res().and_then(|res| res.opt_def_id()) else { + return; + }; + let Some(assoc_type_def_id) = + this.trait_assoc_type_def_id_by_name(trait_def_id, assoc_name) + else { + return; + }; + + // Preserve `::` and generic args so we don't generate broken suggestions like + // `::Assoc` for bounds written as `T: ::Foo<'a>`, while stripping + // associated-item bindings that are rejected in qualified paths. + let trait_path = + path_to_string_without_assoc_item_bindings(&poly_trait_ref.trait_ref.path); + let trait_bounds = matching_bounds.entry(ty_param).or_default(); + let trait_bounds = trait_bounds + .entry(trait_def_id) + .or_insert_with(|| (assoc_type_def_id, FxIndexSet::default())); + debug_assert_eq!(trait_bounds.0, assoc_type_def_id); + trait_bounds.1.insert(trait_path); + }; + + let mut record_from_generics = |this: &mut Self, generics: &ast::Generics| { + for param in &generics.params { + let ast::GenericParamKind::Type { .. } = param.kind else { continue }; + for bound in ¶m.bounds { + let ast::GenericBound::Trait(poly_trait_ref) = bound else { continue }; + record_bound(this, param.ident.name, poly_trait_ref); + } + } + + for predicate in &generics.where_clause.predicates { + let ast::WherePredicateKind::BoundPredicate(where_bound) = &predicate.kind else { + continue; + }; + + let ast::TyKind::Path(None, bounded_path) = &where_bound.bounded_ty.kind else { + continue; + }; + let [ast::PathSegment { ident, args: None, .. }] = &bounded_path.segments[..] + else { + continue; + }; + + // Only suggest for bounds that are explicitly on an in-scope type parameter. + let Some(partial_res) = this.r.partial_res_map.get(&where_bound.bounded_ty.id) + else { + continue; + }; + if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::TyParam, _))) { + continue; + } + + for bound in &where_bound.bounds { + let ast::GenericBound::Trait(poly_trait_ref) = bound else { continue }; + record_bound(this, ident.name, poly_trait_ref); + } + } + }; + + if let Some(item) = self.diag_metadata.current_item + && let Some(generics) = item.kind.generics() + { + record_from_generics(self, generics); + } + + if let Some(item) = self.diag_metadata.current_item + && matches!(item.kind, ItemKind::Impl(..)) + && let Some(assoc) = self.diag_metadata.current_impl_item + { + let generics = match &assoc.kind { + AssocItemKind::Const(ast::ConstItem { generics, .. }) + | AssocItemKind::Fn(ast::Fn { generics, .. }) + | AssocItemKind::Type(ast::TyAlias { generics, .. }) => Some(generics), + AssocItemKind::Delegation(..) + | AssocItemKind::MacCall(..) + | AssocItemKind::DelegationMac(..) => None, + }; + if let Some(generics) = generics { + record_from_generics(self, generics); + } + } + + let mut suggestions: FxIndexSet = FxIndexSet::default(); + for (ty_param, traits) in matching_bounds { + let ty_param = ty_param.to_ident_string(); + let trait_paths_len: usize = traits.values().map(|(_, paths)| paths.len()).sum(); + if traits.len() == 1 && trait_paths_len == 1 { + let assoc_type_def_id = traits.values().next().unwrap().0; + let assoc_segment = format!( + "{}{}", + assoc_name, + self.r.item_required_generic_args_suggestion(assoc_type_def_id) + ); + suggestions.insert(format!("{ty_param}::{assoc_segment}")); + } else { + for (assoc_type_def_id, trait_paths) in traits.into_values() { + let assoc_segment = format!( + "{}{}", + assoc_name, + self.r.item_required_generic_args_suggestion(assoc_type_def_id) + ); + for trait_path in trait_paths { + suggestions + .insert(format!("<{ty_param} as {trait_path}>::{assoc_segment}")); + } + } + } + } + + if suggestions.is_empty() { + return false; + } + + let mut suggestions: Vec = suggestions.into_iter().collect(); + suggestions.sort(); + + err.span_suggestions_with_style( + ident_span, + "you might have meant to use an associated type of the same name", + suggestions, + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + + true + } + + fn make_base_error( + &mut self, + path: &[Segment], + span: Span, + source: PathSource<'_, 'ast, 'ra>, + res: Option, + ) -> BaseError { + // Make the base error. + let mut expected = source.descr_expected(); + let path_str = Segment::names_to_string(path); + let item_str = path.last().unwrap().ident; + + if let Some(res) = res { + BaseError { + msg: format!("expected {}, found {} `{}`", expected, res.descr(), path_str), + fallback_label: format!("not a {expected}"), + span, + span_label: match res { + Res::Def(DefKind::TyParam, def_id) => { + Some((self.r.def_span(def_id), "found this type parameter")) + } + _ => None, + }, + could_be_expr: match res { + Res::Def(DefKind::Fn, _) => { + // Verify whether this is a fn call or an Fn used as a type. + self.r + .tcx + .sess + .source_map() + .span_to_snippet(span) + .is_ok_and(|snippet| snippet.ends_with(')')) + } + Res::Def( + DefKind::Ctor(..) + | DefKind::AssocFn + | DefKind::Const { .. } + | DefKind::AssocConst { .. }, + _, + ) + | Res::SelfCtor(_) + | Res::PrimTy(_) + | Res::Local(_) => true, + _ => false, + }, + suggestion: None, + module: None, + } + } else { + let mut span_label = None; + let item_ident = path.last().unwrap().ident; + let item_span = item_ident.span; + let (mod_prefix, mod_str, module, suggestion) = if path.len() == 1 { + debug!(?self.diag_metadata.current_impl_items); + debug!(?self.diag_metadata.current_function); + let suggestion = if self.current_trait_ref.is_none() + && let Some((fn_kind, _)) = self.diag_metadata.current_function + && let Some(FnCtxt::Assoc(_)) = fn_kind.ctxt() + && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = fn_kind + && let Some(items) = self.diag_metadata.current_impl_items + && let Some(item) = items.iter().find(|i| { + i.kind.ident().is_some_and(|ident| { + // Don't suggest if the item is in Fn signature arguments (#112590). + ident.name == item_str.name && !sig.span.contains(item_span) + }) + }) { + let sp = item_span.shrink_to_lo(); + + // Account for `Foo { field }` when suggesting `self.field` so we result on + // `Foo { field: self.field }`. + let field = match source { + PathSource::Expr(Some(Expr { kind: ExprKind::Struct(expr), .. })) => { + expr.fields.iter().find(|f| f.ident == item_ident) + } + _ => None, + }; + let pre = if let Some(field) = field + && field.is_shorthand + { + format!("{item_ident}: ") + } else { + String::new() + }; + // Ensure we provide a structured suggestion for an assoc fn only for + // expressions that are actually a fn call. + let is_call = match field { + Some(ast::ExprField { expr, .. }) => { + matches!(expr.kind, ExprKind::Call(..)) + } + _ => matches!( + source, + PathSource::Expr(Some(Expr { kind: ExprKind::Call(..), .. })), + ), + }; + + match &item.kind { + AssocItemKind::Fn(fn_) + if (!sig.decl.has_self() || !is_call) && fn_.sig.decl.has_self() => + { + // Ensure that we only suggest `self.` if `self` is available, + // you can't call `fn foo(&self)` from `fn bar()` (#115992). + // We also want to mention that the method exists. + span_label = Some(( + fn_.ident.span, + "a method by that name is available on `Self` here", + )); + None + } + AssocItemKind::Fn(fn_) if !fn_.sig.decl.has_self() && !is_call => { + span_label = Some(( + fn_.ident.span, + "an associated function by that name is available on `Self` here", + )); + None + } + AssocItemKind::Fn(fn_) if fn_.sig.decl.has_self() => { + Some((sp, "consider using the method on `Self`", format!("{pre}self."))) + } + AssocItemKind::Fn(_) => Some(( + sp, + "consider using the associated function on `Self`", + format!("{pre}Self::"), + )), + AssocItemKind::Const(..) => Some(( + sp, + "consider using the associated constant on `Self`", + format!("{pre}Self::"), + )), + _ => None, + } + } else { + None + }; + (String::new(), "this scope".to_string(), None, suggestion) + } else if path.len() == 2 && path[0].ident.name == kw::PathRoot { + if self.r.tcx.sess.edition() > Edition::Edition2015 { + // In edition 2018 onwards, the `::foo` syntax may only pull from the extern prelude + // which overrides all other expectations of item type + expected = "crate"; + (String::new(), "the list of imported crates".to_string(), None, None) + } else { + ( + String::new(), + "the crate root".to_string(), + Some(CRATE_DEF_ID.to_def_id()), + None, + ) + } + } else if path.len() == 2 && path[0].ident.name == kw::Crate { + (String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), None) + } else { + let mod_path = &path[..path.len() - 1]; + let mod_res = self.resolve_path(mod_path, Some(TypeNS), None, source); + let mod_prefix = match mod_res { + PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(), + _ => None, + }; + + let module_did = mod_prefix.as_ref().and_then(Res::mod_def_id); + + let mod_prefix = + mod_prefix.map_or_else(String::new, |res| format!("{} ", res.descr())); + (mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), module_did, None) + }; + + let (fallback_label, suggestion) = if path_str == "async" + && expected.starts_with("struct") + { + ("`async` blocks are only allowed in Rust 2018 or later".to_string(), suggestion) + } else { + // check if we are in situation of typo like `True` instead of `true`. + let override_suggestion = + if ["true", "false"].contains(&item_str.to_string().to_lowercase().as_str()) { + let item_typo = item_str.to_string().to_lowercase(); + Some((item_span, "you may want to use a bool value instead", item_typo)) + // FIXME(vincenzopalazzo): make the check smarter, + // and maybe expand with levenshtein distance checks + } else if item_str.as_str() == "printf" { + Some(( + item_span, + "you may have meant to use the `print` macro", + "print!".to_owned(), + )) + } else { + suggestion + }; + (format!("not found in {mod_str}"), override_suggestion) + }; + + BaseError { + msg: format!("cannot find {expected} `{item_str}` in {mod_prefix}{mod_str}"), + fallback_label, + span: item_span, + span_label, + could_be_expr: false, + suggestion, + module, + } + } + } + + /// Try to suggest for a module path that cannot be resolved. + /// Such as `fmt::Debug` where `fmt` is not resolved without importing, + /// here we search with `lookup_import_candidates` for a module named `fmt` + /// with `TypeNS` as namespace. + /// + /// We need a separate function here because we won't suggest for a path with single segment + /// and we won't change `SourcePath` api `is_expected` to match `Type` with `DefKind::Mod` + pub(crate) fn smart_resolve_partial_mod_path_errors( + &mut self, + prefix_path: &[Segment], + following_seg: Option<&Segment>, + ) -> Vec { + if let Some(segment) = prefix_path.last() + && let Some(following_seg) = following_seg + { + let candidates = self.r.lookup_import_candidates( + segment.ident, + Namespace::TypeNS, + &self.parent_scope, + &|res: Res| matches!(res, Res::Def(DefKind::Mod, _)), + ); + // double check next seg is valid + candidates + .into_iter() + .filter(|candidate| { + if let Some(def_id) = candidate.did + && let Some(module) = self.r.get_module(def_id) + { + Some(def_id) != self.parent_scope.module.opt_def_id() + && self + .r + .resolutions(module) + .borrow() + .iter() + .any(|(key, _r)| key.ident.name == following_seg.ident.name) + } else { + false + } + }) + .collect::>() + } else { + Vec::new() + } + } + + /// Handles error reporting for `smart_resolve_path_fragment` function. + /// Creates base error and amends it with one short label and possibly some longer helps/notes. + #[tracing::instrument(skip(self), level = "debug")] + pub(crate) fn smart_resolve_report_errors( + &mut self, + path: &[Segment], + following_seg: Option<&Segment>, + span: Span, + source: PathSource<'_, 'ast, 'ra>, + res: Option, + qself: Option<&QSelf>, + ) -> (Diag<'tcx>, Vec) { + debug!(?res, ?source); + let base_error = self.make_base_error(path, span, source, res); + + let code = source.error_code(res.is_some()); + let mut err = self.r.dcx().struct_span_err(base_error.span, base_error.msg.clone()); + err.code(code); + + // Try to get the span of the identifier within the path's syntax context + // (if that's different). + if let Some(within_macro_span) = + base_error.span.within_macro(span, self.r.tcx.sess.source_map()) + { + err.span_label(within_macro_span, "due to this macro variable"); + } + + self.detect_missing_binding_available_from_pattern(&mut err, path, following_seg); + self.suggest_at_operator_in_slice_pat_with_range(&mut err, path); + self.suggest_range_struct_destructuring(&mut err, path, source); + self.suggest_swapping_misplaced_self_ty_and_trait(&mut err, source, res, base_error.span); + + if let Some((span, label)) = base_error.span_label { + err.span_label(span, label); + } + + if let Some(ref sugg) = base_error.suggestion { + err.span_suggestion_verbose(sugg.0, sugg.1, &sugg.2, Applicability::MaybeIncorrect); + } + + self.suggest_changing_type_to_const_param(&mut err, res, source, path, following_seg, span); + self.explain_functions_in_pattern(&mut err, res, source); + + if self.suggest_pattern_match_with_let(&mut err, source, span) { + // Fallback label. + err.span_label(base_error.span, base_error.fallback_label); + return (err, Vec::new()); + } + + self.suggest_self_or_self_ref(&mut err, path, span); + self.detect_assoc_type_constraint_meant_as_path(&mut err, &base_error); + self.detect_rtn_with_fully_qualified_path( + &mut err, + path, + following_seg, + span, + source, + res, + qself, + ); + if self.suggest_self_ty(&mut err, source, path, span) + || self.suggest_self_value(&mut err, source, path, span) + { + return (err, Vec::new()); + } + + if let Some((did, item)) = self.lookup_doc_alias_name(path, source.namespace()) { + let item_name = item.name; + let suggestion_name = self.r.tcx.item_name(did); + err.span_suggestion( + item.span, + format!("`{suggestion_name}` has a name defined in the doc alias attribute as `{item_name}`"), + suggestion_name, + Applicability::MaybeIncorrect + ); + + return (err, Vec::new()); + }; + + let (found, suggested_candidates, mut candidates) = self.try_lookup_name_relaxed( + &mut err, + source, + path, + following_seg, + span, + res, + &base_error, + ); + if found { + return (err, candidates); + } + + if self.suggest_shadowed(&mut err, source, path, following_seg, span) { + // if there is already a shadowed name, don'suggest candidates for importing + candidates.clear(); + } + + let mut fallback = self.suggest_trait_and_bounds(&mut err, source, res, span, &base_error); + fallback |= self.suggest_typo( + &mut err, + source, + path, + following_seg, + span, + &base_error, + suggested_candidates, + ); + + if fallback { + // Fallback label. + err.span_label(base_error.span, base_error.fallback_label); + } + self.err_code_special_cases(&mut err, source, path, span); + + let module = base_error.module.unwrap_or_else(|| CRATE_DEF_ID.to_def_id()); + self.r.find_cfg_stripped(&mut err, &path.last().unwrap().ident.name, module); + + (err, candidates) + } + + fn detect_rtn_with_fully_qualified_path( + &self, + err: &mut Diag<'_>, + path: &[Segment], + following_seg: Option<&Segment>, + span: Span, + source: PathSource<'_, '_, '_>, + res: Option, + qself: Option<&QSelf>, + ) { + if let Some(Res::Def(DefKind::AssocFn, _)) = res + && let PathSource::TraitItem(TypeNS, _) = source + && let None = following_seg + && let Some(qself) = qself + && let TyKind::Path(None, ty_path) = &qself.ty.kind + && ty_path.segments.len() == 1 + && self.diag_metadata.current_where_predicate.is_some() + { + err.span_suggestion_verbose( + span, + "you might have meant to use the return type notation syntax", + format!("{}::{}(..)", ty_path.segments[0].ident, path[path.len() - 1].ident), + Applicability::MaybeIncorrect, + ); + } + } + + fn detect_assoc_type_constraint_meant_as_path( + &self, + err: &mut Diag<'_>, + base_error: &BaseError, + ) { + let Some(ty) = self.diag_metadata.current_type_path else { + return; + }; + let TyKind::Path(_, path) = &ty.kind else { + return; + }; + for segment in &path.segments { + let Some(params) = &segment.args else { + continue; + }; + let ast::GenericArgs::AngleBracketed(params) = params.deref() else { + continue; + }; + for param in ¶ms.args { + let ast::AngleBracketedArg::Constraint(constraint) = param else { + continue; + }; + let ast::AssocItemConstraintKind::Bound { bounds } = &constraint.kind else { + continue; + }; + for bound in bounds { + let ast::GenericBound::Trait(trait_ref) = bound else { + continue; + }; + if trait_ref.modifiers == ast::TraitBoundModifiers::NONE + && base_error.span == trait_ref.span + { + err.span_suggestion_verbose( + constraint.ident.span.between(trait_ref.span), + "you might have meant to write a path instead of an associated type bound", + "::", + Applicability::MachineApplicable, + ); + } + } + } + } + } + + fn suggest_self_or_self_ref(&mut self, err: &mut Diag<'_>, path: &[Segment], span: Span) { + if !self.self_type_is_available() { + return; + } + let Some(path_last_segment) = path.last() else { return }; + let item_str = path_last_segment.ident; + // Emit help message for fake-self from other languages (e.g., `this` in JavaScript). + if ["this", "my"].contains(&item_str.as_str()) { + err.span_suggestion_short( + span, + "you might have meant to use `self` here instead", + "self", + Applicability::MaybeIncorrect, + ); + if !self.self_value_is_available(path[0].ident.span) { + if let Some((FnKind::Fn(_, _, ast::Fn { sig, .. }), fn_span)) = + &self.diag_metadata.current_function + { + let (span, sugg) = if let Some(param) = sig.decl.inputs.get(0) { + (param.span.shrink_to_lo(), "&self, ") + } else { + ( + self.r + .tcx + .sess + .source_map() + .span_through_char(*fn_span, '(') + .shrink_to_hi(), + "&self", + ) + }; + err.span_suggestion_verbose( + span, + "if you meant to use `self`, you are also missing a `self` receiver \ + argument", + sugg, + Applicability::MaybeIncorrect, + ); + } + } + } + } + + fn try_lookup_name_relaxed( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + path: &[Segment], + following_seg: Option<&Segment>, + span: Span, + res: Option, + base_error: &BaseError, + ) -> (bool, FxHashSet, Vec) { + let span = match following_seg { + Some(_) if path[0].ident.span.eq_ctxt(path[path.len() - 1].ident.span) => { + // The path `span` that comes in includes any following segments, which we don't + // want to replace in the suggestions. + path[0].ident.span.to(path[path.len() - 1].ident.span) + } + _ => span, + }; + let mut suggested_candidates = FxHashSet::default(); + // Try to lookup name in more relaxed fashion for better error reporting. + let ident = path.last().unwrap().ident; + let is_expected = &|res| source.is_expected(res); + let ns = source.namespace(); + let is_enum_variant = &|res| matches!(res, Res::Def(DefKind::Variant, _)); + let path_str = Segment::names_to_string(path); + let ident_span = path.last().map_or(span, |ident| ident.ident.span); + let mut candidates = self + .r + .lookup_import_candidates(ident, ns, &self.parent_scope, is_expected) + .into_iter() + .filter(|ImportSuggestion { did, .. }| { + match (did, res.and_then(|res| res.opt_def_id())) { + (Some(suggestion_did), Some(actual_did)) => *suggestion_did != actual_did, + _ => true, + } + }) + .collect::>(); + // Try to filter out intrinsics candidates, as long as we have + // some other candidates to suggest. + let intrinsic_candidates: Vec<_> = candidates + .extract_if(.., |sugg| { + let path = path_names_to_string(&sugg.path); + path.starts_with("core::intrinsics::") || path.starts_with("std::intrinsics::") + }) + .collect(); + if candidates.is_empty() { + // Put them back if we have no more candidates to suggest... + candidates = intrinsic_candidates; + } + let crate_def_id = CRATE_DEF_ID.to_def_id(); + if candidates.is_empty() && is_expected(Res::Def(DefKind::Enum, crate_def_id)) { + let mut enum_candidates: Vec<_> = self + .r + .lookup_import_candidates(ident, ns, &self.parent_scope, is_enum_variant) + .into_iter() + .map(|suggestion| import_candidate_to_enum_paths(&suggestion)) + .filter(|(_, enum_ty_path)| !enum_ty_path.starts_with("std::prelude::")) + .collect(); + if !enum_candidates.is_empty() { + enum_candidates.sort(); + + // Contextualize for E0425 "cannot find type", but don't belabor the point + // (that it's a variant) for E0573 "expected type, found variant". + let preamble = if res.is_none() { + let others = match enum_candidates.len() { + 1 => String::new(), + 2 => " and 1 other".to_owned(), + n => format!(" and {n} others"), + }; + format!("there is an enum variant `{}`{}; ", enum_candidates[0].0, others) + } else { + String::new() + }; + let msg = format!("{preamble}try using the variant's enum"); + + suggested_candidates.extend( + enum_candidates + .iter() + .map(|(_variant_path, enum_ty_path)| enum_ty_path.clone()), + ); + err.span_suggestions( + span, + msg, + enum_candidates.into_iter().map(|(_variant_path, enum_ty_path)| enum_ty_path), + Applicability::MachineApplicable, + ); + } + } + + // Try finding a suitable replacement. + let typo_sugg = self + .lookup_typo_candidate(path, following_seg, source.namespace(), is_expected) + .to_opt_suggestion() + .filter(|sugg| !suggested_candidates.contains(sugg.candidate.as_str())); + if let [segment] = path + && !matches!(source, PathSource::Delegation) + && self.self_type_is_available() + { + if let Some(candidate) = + self.lookup_assoc_candidate(ident, ns, is_expected, source.is_call()) + { + let self_is_available = self.self_value_is_available(segment.ident.span); + // Account for `Foo { field }` when suggesting `self.field` so we result on + // `Foo { field: self.field }`. + let pre = match source { + PathSource::Expr(Some(Expr { kind: ExprKind::Struct(expr), .. })) + if expr + .fields + .iter() + .any(|f| f.ident == segment.ident && f.is_shorthand) => + { + format!("{path_str}: ") + } + _ => String::new(), + }; + match candidate { + AssocSuggestion::Field(field_span) => { + if self_is_available { + let source_map = self.r.tcx.sess.source_map(); + let field_is_format_named_arg = matches!( + span.desugaring_kind(), + Some(DesugaringKind::FormatLiteral { .. }) + ) && source_map + .span_to_source(span, |s, start, _| { + Ok(s.get(start.saturating_sub(1)..start) == Some("{")) + }) + .unwrap_or(false); + if field_is_format_named_arg { + err.help( + format!("you might have meant to use the available field in a format string: `\"{{}}\", self.{}`", segment.ident.name), + ); + } else { + err.span_suggestion_verbose( + span.shrink_to_lo(), + "you might have meant to use the available field", + format!("{pre}self."), + Applicability::MaybeIncorrect, + ); + } + } else { + err.span_label(field_span, "a field by that name exists in `Self`"); + } + } + AssocSuggestion::MethodWithSelf { called } if self_is_available => { + let msg = if called { + "you might have meant to call the method" + } else { + "you might have meant to refer to the method" + }; + err.span_suggestion_verbose( + span.shrink_to_lo(), + msg, + "self.", + Applicability::MachineApplicable, + ); + } + AssocSuggestion::MethodWithSelf { .. } + | AssocSuggestion::AssocFn { .. } + | AssocSuggestion::AssocConst + | AssocSuggestion::AssocType => { + err.span_suggestion_verbose( + span.shrink_to_lo(), + format!("you might have meant to {}", candidate.action()), + "Self::", + Applicability::MachineApplicable, + ); + } + } + self.r.add_typo_suggestion(err, typo_sugg, ident_span); + return (true, suggested_candidates, candidates); + } + + // If the first argument in call is `self` suggest calling a method. + if let Some((call_span, args_span)) = self.call_has_self_arg(source) { + let mut args_snippet = String::new(); + if let Some(args_span) = args_span + && let Ok(snippet) = self.r.tcx.sess.source_map().span_to_snippet(args_span) + { + args_snippet = snippet; + } + + if let Some(Res::Def(DefKind::Struct, def_id)) = res { + if let Some(ctor) = self.r.struct_ctor(def_id) + && ctor.has_private_fields(self.parent_scope.module, self.r) + { + if matches!( + ctor.res, + Res::Def(DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), _) + ) { + self.update_err_for_private_tuple_struct_fields(err, &source, def_id); + } + err.note("constructor is not visible here due to private fields"); + } + } else { + err.span_suggestion( + call_span, + format!("try calling `{ident}` as a method"), + format!("self.{path_str}({args_snippet})"), + Applicability::MachineApplicable, + ); + } + + return (true, suggested_candidates, candidates); + } + } + + // Try context-dependent help if relaxed lookup didn't work. + if let Some(res) = res { + if self.smart_resolve_context_dependent_help( + err, + span, + source, + path, + res, + &path_str, + &base_error.fallback_label, + ) { + // We do this to avoid losing a secondary span when we override the main error span. + self.r.add_typo_suggestion(err, typo_sugg, ident_span); + return (true, suggested_candidates, candidates); + } + } + + // Try to find in last block rib + if let Some(rib) = &self.last_block_rib { + for (ident, &res) in &rib.bindings { + if let Res::Local(_) = res + && path.len() == 1 + && ident.span.eq_ctxt(path[0].ident.span) + && ident.name == path[0].ident.name + { + err.span_help( + ident.span, + format!("the binding `{path_str}` is available in a different scope in the same function"), + ); + return (true, suggested_candidates, candidates); + } + } + } + + if candidates.is_empty() { + candidates = self.smart_resolve_partial_mod_path_errors(path, following_seg); + } + + (false, suggested_candidates, candidates) + } + + fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> { + let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { + for resolution in r.resolutions(m).borrow().values() { + let Some(did) = + resolution.borrow().best_decl().and_then(|binding| binding.res().opt_def_id()) + else { + continue; + }; + if did.is_local() { + // We don't record the doc alias name in the local crate + // because the people who write doc alias are usually not + // confused by them. + continue; + } + if let Some(d) = hir::find_attr!(r.tcx, did, Doc(d) => d) + && d.aliases.contains_key(&item_name) + { + return Some(did); + } + } + None + }; + + if path.len() == 1 { + for rib in self.ribs[ns].iter().rev() { + let item = path[0].ident; + if let RibKind::Module(module) | RibKind::Block(Some(module)) = rib.kind + && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item.name) + { + return Some((did, item)); + } + } + } else { + // Finds to the last resolved module item in the path + // and searches doc aliases within that module. + // + // Example: For the path `a::b::last_resolved::not_exist::c::d`, + // we will try to find any item has doc aliases named `not_exist` + // in `last_resolved` module. + // + // - Use `skip(1)` because the final segment must remain unresolved. + for (idx, seg) in path.iter().enumerate().rev().skip(1) { + let Some(id) = seg.id else { + continue; + }; + let Some(res) = self.r.partial_res_map.get(&id) else { + continue; + }; + if let Res::Def(DefKind::Mod, module) = res.expect_full_res() + && let module = self.r.expect_module(module) + && let item = path[idx + 1].ident + && let Some(did) = find_doc_alias_name(self.r, module, item.name) + { + return Some((did, item)); + } + break; + } + } + None + } + + fn suggest_trait_and_bounds( + &self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + res: Option, + span: Span, + base_error: &BaseError, + ) -> bool { + let is_macro = + base_error.span.from_expansion() && base_error.span.desugaring_kind().is_none(); + let mut fallback = false; + + if let ( + PathSource::Trait(AliasPossibility::Maybe), + Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)), + false, + ) = (source, res, is_macro) + && let Some(bounds @ [first_bound, .., last_bound]) = + self.diag_metadata.current_trait_object + { + fallback = true; + let spans: Vec = bounds + .iter() + .map(|bound| bound.span()) + .filter(|&sp| sp != base_error.span) + .collect(); + + let start_span = first_bound.span(); + // `end_span` is the end of the poly trait ref (Foo + 'baz + Bar><) + let end_span = last_bound.span(); + // `last_bound_span` is the last bound of the poly trait ref (Foo + >'baz< + Bar) + let last_bound_span = spans.last().cloned().unwrap(); + let mut multi_span: MultiSpan = spans.clone().into(); + for sp in spans { + let msg = if sp == last_bound_span { + format!( + "...because of {these} bound{s}", + these = pluralize!("this", bounds.len() - 1), + s = pluralize!(bounds.len() - 1), + ) + } else { + String::new() + }; + multi_span.push_span_label(sp, msg); + } + multi_span.push_span_label(base_error.span, "expected this type to be a trait..."); + err.span_help( + multi_span, + "`+` is used to constrain a \"trait object\" type with lifetimes or \ + auto-traits; structs and enums can't be bound in that way", + ); + if bounds.iter().all(|bound| match bound { + ast::GenericBound::Outlives(_) | ast::GenericBound::Use(..) => true, + ast::GenericBound::Trait(tr) => tr.span == base_error.span, + }) { + let mut sugg = vec![]; + if base_error.span != start_span { + sugg.push((start_span.until(base_error.span), String::new())); + } + if base_error.span != end_span { + sugg.push((base_error.span.shrink_to_hi().to(end_span), String::new())); + } + + err.multipart_suggestion( + "if you meant to use a type and not a trait here, remove the bounds", + sugg, + Applicability::MaybeIncorrect, + ); + } + } + + fallback |= self.restrict_assoc_type_in_where_clause(span, err); + fallback + } + + fn suggest_typo( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, 'ast, 'ra>, + path: &[Segment], + following_seg: Option<&Segment>, + span: Span, + base_error: &BaseError, + suggested_candidates: FxHashSet, + ) -> bool { + let is_expected = &|res| source.is_expected(res); + let ident_span = path.last().map_or(span, |ident| ident.ident.span); + + // Prefer suggestions based on associated types from in-scope bounds (e.g. `T::Item`) + // over purely edit-distance-based identifier suggestions. + // Otherwise suggestions could be verbose. + if self.suggest_assoc_type_from_bounds(err, source, path, ident_span) { + return false; + } + + let typo_sugg = + self.lookup_typo_candidate(path, following_seg, source.namespace(), is_expected); + let mut fallback = false; + let typo_sugg = typo_sugg + .to_opt_suggestion() + .filter(|sugg| !suggested_candidates.contains(sugg.candidate.as_str())); + if !self.r.add_typo_suggestion(err, typo_sugg, ident_span) { + fallback = true; + match self.diag_metadata.current_let_binding { + Some((pat_sp, Some(ty_sp), None)) + if ty_sp.contains(base_error.span) && base_error.could_be_expr => + { + err.span_suggestion_verbose( + pat_sp.between(ty_sp), + "use `=` if you meant to assign", + " = ", + Applicability::MaybeIncorrect, + ); + } + _ => {} + } + + // If the trait has a single item (which wasn't matched by the algorithm), suggest it + let suggestion = self.get_single_associated_item(path, &source, is_expected); + self.r.add_typo_suggestion(err, suggestion, ident_span); + } + + if self.let_binding_suggestion(err, ident_span) { + fallback = false; + } + + fallback + } + + fn suggest_shadowed( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + path: &[Segment], + following_seg: Option<&Segment>, + span: Span, + ) -> bool { + let is_expected = &|res| source.is_expected(res); + let typo_sugg = + self.lookup_typo_candidate(path, following_seg, source.namespace(), is_expected); + let is_in_same_file = &|sp1, sp2| { + let source_map = self.r.tcx.sess.source_map(); + let file1 = source_map.span_to_filename(sp1); + let file2 = source_map.span_to_filename(sp2); + file1 == file2 + }; + // print 'you might have meant' if the candidate is (1) is a shadowed name with + // accessible definition and (2) either defined in the same crate as the typo + // (could be in a different file) or introduced in the same file as the typo + // (could belong to a different crate) + if let TypoCandidate::Shadowed(res, Some(sugg_span)) = typo_sugg + && res.opt_def_id().is_some_and(|id| id.is_local() || is_in_same_file(span, sugg_span)) + { + err.span_label( + sugg_span, + format!("you might have meant to refer to this {}", res.descr()), + ); + return true; + } + false + } + + fn err_code_special_cases( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + path: &[Segment], + span: Span, + ) { + if let Some(err_code) = err.code { + if err_code == E0425 { + for label_rib in &self.label_ribs { + for (label_ident, node_id) in &label_rib.bindings { + let ident = path.last().unwrap().ident; + if format!("'{ident}") == label_ident.to_string() { + err.span_label(label_ident.span, "a label with a similar name exists"); + if let PathSource::Expr(Some(Expr { + kind: ExprKind::Break(None, Some(_)), + .. + })) = source + { + err.span_suggestion( + span, + "use the similarly named label", + label_ident.name, + Applicability::MaybeIncorrect, + ); + // Do not lint against unused label when we suggest them. + self.diag_metadata.unused_labels.swap_remove(node_id); + } + } + } + } + + self.suggest_ident_hidden_by_hygiene(err, path, span); + // cannot find type in this scope + if let Some(correct) = Self::likely_rust_type(path) { + err.span_suggestion( + span, + "perhaps you intended to use this type", + correct, + Applicability::MaybeIncorrect, + ); + } + } + } + } + + fn suggest_ident_hidden_by_hygiene(&self, err: &mut Diag<'_>, path: &[Segment], span: Span) { + let [segment] = path else { return }; + + let ident = segment.ident; + let callsite_span = span.source_callsite(); + for rib in self.ribs[ValueNS].iter().rev() { + for (binding_ident, _) in &rib.bindings { + // Case 1: the identifier is defined in the same scope as the macro is called + if binding_ident.name == ident.name + && !binding_ident.span.eq_ctxt(span) + && !binding_ident.span.from_expansion() + && binding_ident.span.lo() < callsite_span.lo() + { + err.span_help( + binding_ident.span, + "an identifier with the same name exists, but is not accessible due to macro hygiene", + ); + return; + } + + // Case 2: the identifier is defined in a macro call in the same scope + if binding_ident.name == ident.name + && binding_ident.span.from_expansion() + && binding_ident.span.source_callsite().eq_ctxt(callsite_span) + && binding_ident.span.source_callsite().lo() < callsite_span.lo() + { + err.span_help( + binding_ident.span, + "an identifier with the same name is defined here, but is not accessible due to macro hygiene", + ); + return; + } + } + } + } + + /// Emit special messages for unresolved `Self` and `self`. + fn suggest_self_ty( + &self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + path: &[Segment], + span: Span, + ) -> bool { + if !is_self_type(path, source.namespace()) { + return false; + } + err.code(E0411); + err.span_label(span, "`Self` is only available in impls, traits, and type definitions"); + if let Some(item) = self.diag_metadata.current_item + && let Some(ident) = item.kind.ident() + { + err.span_label( + ident.span, + format!("`Self` not allowed in {} {}", item.kind.article(), item.kind.descr()), + ); + } + true + } + + fn suggest_self_value( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + path: &[Segment], + span: Span, + ) -> bool { + if !is_self_value(path, source.namespace()) { + return false; + } + + debug!("smart_resolve_path_fragment: E0424, source={:?}", source); + err.code(E0424); + err.span_label( + span, + match source { + PathSource::Pat => { + "`self` value is a keyword and may not be bound to variables or shadowed" + } + _ => "`self` value is a keyword only available in methods with a `self` parameter", + }, + ); + + // using `let self` is wrong even if we're not in an associated method or if we're in a macro expansion. + // So, we should return early if we're in a pattern, see issue #143134. + if matches!(source, PathSource::Pat) { + return true; + } + + let is_assoc_fn = self.self_type_is_available(); + let self_from_macro = "a `self` parameter, but a macro invocation can only \ + access identifiers it receives from parameters"; + if let Some((fn_kind, fn_span)) = &self.diag_metadata.current_function { + // The current function has a `self` parameter, but we were unable to resolve + // a reference to `self`. This can only happen if the `self` identifier we + // are resolving came from a different hygiene context or a variable binding. + // But variable binding error is returned early above. + if fn_kind.decl().inputs.get(0).is_some_and(|p| p.is_self()) { + err.span_label(*fn_span, format!("this function has {self_from_macro}")); + } else { + let doesnt = if is_assoc_fn { + let (span, sugg) = fn_kind + .decl() + .inputs + .get(0) + .map(|p| (p.span.shrink_to_lo(), "&self, ")) + .unwrap_or_else(|| { + // Try to look for the "(" after the function name, if possible. + // This avoids placing the suggestion into the visibility specifier. + let span = fn_kind + .ident() + .map_or(*fn_span, |ident| fn_span.with_lo(ident.span.hi())); + ( + self.r + .tcx + .sess + .source_map() + .span_through_char(span, '(') + .shrink_to_hi(), + "&self", + ) + }); + err.span_suggestion_verbose( + span, + "add a `self` receiver parameter to make the associated `fn` a method", + sugg, + Applicability::MaybeIncorrect, + ); + "doesn't" + } else { + "can't" + }; + if let Some(ident) = fn_kind.ident() { + err.span_label( + ident.span, + format!("this function {doesnt} have a `self` parameter"), + ); + } + } + } else if let Some(item) = self.diag_metadata.current_item { + if matches!(item.kind, ItemKind::Delegation(..)) { + err.span_label(item.span, format!("delegation supports {self_from_macro}")); + } else { + let span = if let Some(ident) = item.kind.ident() { ident.span } else { item.span }; + err.span_label( + span, + format!("`self` not allowed in {} {}", item.kind.article(), item.kind.descr()), + ); + } + } + true + } + + fn detect_missing_binding_available_from_pattern( + &self, + err: &mut Diag<'_>, + path: &[Segment], + following_seg: Option<&Segment>, + ) { + let [segment] = path else { return }; + let None = following_seg else { return }; + for rib in self.ribs[ValueNS].iter().rev() { + let patterns_with_skipped_bindings = + self.r.tcx.with_stable_hashing_context(|mut hcx| { + rib.patterns_with_skipped_bindings.to_sorted(&mut hcx, true) + }); + for (def_id, spans) in patterns_with_skipped_bindings { + if let DefKind::Struct | DefKind::Variant = self.r.tcx.def_kind(*def_id) + && let Some(fields) = self.r.field_idents(*def_id) + { + for field in fields { + if field.name == segment.ident.name { + if spans.iter().all(|(_, had_error)| had_error.is_err()) { + // This resolution error will likely be fixed by fixing a + // syntax error in a pattern, so it is irrelevant to the user. + let multispan: MultiSpan = + spans.iter().map(|(s, _)| *s).collect::>().into(); + err.span_note( + multispan, + "this pattern had a recovered parse error which likely lost \ + the expected fields", + ); + err.downgrade_to_delayed_bug(); + } + let ty = self.r.tcx.item_name(*def_id); + for (span, _) in spans { + err.span_label( + *span, + format!( + "this pattern doesn't include `{field}`, which is \ + available in `{ty}`", + ), + ); + } + } + } + } + } + } + } + + fn suggest_at_operator_in_slice_pat_with_range(&self, err: &mut Diag<'_>, path: &[Segment]) { + let Some(pat) = self.diag_metadata.current_pat else { return }; + let (bound, side, range) = match &pat.kind { + ast::PatKind::Range(Some(bound), None, range) => (bound, Side::Start, range), + ast::PatKind::Range(None, Some(bound), range) => (bound, Side::End, range), + _ => return, + }; + if let ExprKind::Path(None, range_path) = &bound.kind + && let [segment] = &range_path.segments[..] + && let [s] = path + && segment.ident == s.ident + && segment.ident.span.eq_ctxt(range.span) + { + // We've encountered `[first, rest..]` (#88404) or `[first, ..rest]` (#120591) + // where the user might have meant `[first, rest @ ..]`. + let (span, snippet) = match side { + Side::Start => (segment.ident.span.between(range.span), " @ ".into()), + Side::End => (range.span.to(segment.ident.span), format!("{} @ ..", segment.ident)), + }; + err.subdiagnostic(diagnostics::UnexpectedResUseAtOpInSlicePatWithRangeSugg { + span, + ident: segment.ident, + snippet, + }); + } + + enum Side { + Start, + End, + } + } + + fn suggest_range_struct_destructuring( + &mut self, + err: &mut Diag<'_>, + path: &[Segment], + source: PathSource<'_, '_, '_>, + ) { + if !matches!(source, PathSource::Pat | PathSource::TupleStruct(..) | PathSource::Expr(..)) { + return; + } + + let Some(pat) = self.diag_metadata.current_pat else { return }; + let ast::PatKind::Range(start, end, end_kind) = &pat.kind else { return }; + + let [segment] = path else { return }; + let failing_span = segment.ident.span; + + let in_start = start.as_ref().is_some_and(|e| e.span.contains(failing_span)); + let in_end = end.as_ref().is_some_and(|e| e.span.contains(failing_span)); + + if !in_start && !in_end { + return; + } + + let start_snippet = + start.as_ref().and_then(|e| self.r.tcx.sess.source_map().span_to_snippet(e.span).ok()); + let end_snippet = + end.as_ref().and_then(|e| self.r.tcx.sess.source_map().span_to_snippet(e.span).ok()); + + let field = |name: &str, val: String| { + if val == name { val } else { format!("{name}: {val}") } + }; + + let mut resolve_short_name = |short: Symbol, full: &str| -> String { + let ident = Ident::with_dummy_span(short); + let path = Segment::from_path(&Path::from_ident(ident)); + + match self.resolve_path(&path, Some(TypeNS), None, PathSource::Type) { + PathResult::NonModule(..) => short.to_string(), + _ => full.to_string(), + } + }; + // FIXME(new_range): Also account for new range types + let (struct_path, fields) = match (start_snippet, end_snippet, &end_kind.node) { + (Some(start), Some(end), ast::RangeEnd::Excluded) => ( + resolve_short_name(sym::Range, "std::ops::Range"), + vec![field("start", start), field("end", end)], + ), + (Some(start), Some(end), ast::RangeEnd::Included(_)) => ( + resolve_short_name(sym::RangeInclusive, "std::ops::RangeInclusive"), + vec![field("start", start), field("end", end)], + ), + (Some(start), None, _) => ( + resolve_short_name(sym::RangeFrom, "std::ops::RangeFrom"), + vec![field("start", start)], + ), + (None, Some(end), ast::RangeEnd::Excluded) => { + (resolve_short_name(sym::RangeTo, "std::ops::RangeTo"), vec![field("end", end)]) + } + (None, Some(end), ast::RangeEnd::Included(_)) => ( + resolve_short_name(sym::RangeToInclusive, "std::ops::RangeToInclusive"), + vec![field("end", end)], + ), + _ => return, + }; + + err.span_suggestion_verbose( + pat.span, + format!("if you meant to destructure a range use a struct pattern"), + format!("{} {{ {} }}", struct_path, fields.join(", ")), + Applicability::MaybeIncorrect, + ); + + err.note( + "range patterns match against the start and end of a range; \ + to bind the components, use a struct pattern", + ); + } + + fn suggest_swapping_misplaced_self_ty_and_trait( + &mut self, + err: &mut Diag<'_>, + source: PathSource<'_, 'ast, 'ra>, + res: Option, + span: Span, + ) { + if let Some((trait_ref, self_ty)) = + self.diag_metadata.currently_processing_impl_trait.clone() + && let TyKind::Path(_, self_ty_path) = &self_ty.kind + && let PathResult::Module(ModuleOrUniformRoot::Module(module)) = + self.resolve_path(&Segment::from_path(self_ty_path), Some(TypeNS), None, source) + && module.def_kind() == Some(DefKind::Trait) + && trait_ref.path.span == span + && let PathSource::Trait(_) = source + && let Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)) = res + && let Ok(self_ty_str) = self.r.tcx.sess.source_map().span_to_snippet(self_ty.span) + && let Ok(trait_ref_str) = + self.r.tcx.sess.source_map().span_to_snippet(trait_ref.path.span) + { + err.multipart_suggestion( + "`impl` items mention the trait being implemented first and the type it is being implemented for second", + vec![(trait_ref.path.span, self_ty_str), (self_ty.span, trait_ref_str)], + Applicability::MaybeIncorrect, + ); + } + } + + fn explain_functions_in_pattern( + &self, + err: &mut Diag<'_>, + res: Option, + source: PathSource<'_, '_, '_>, + ) { + let PathSource::TupleStruct(_, _) = source else { return }; + let Some(Res::Def(DefKind::Fn, _)) = res else { return }; + err.primary_message("expected a pattern, found a function call"); + err.note("function calls are not allowed in patterns: "); + } + + fn suggest_changing_type_to_const_param( + &self, + err: &mut Diag<'_>, + res: Option, + source: PathSource<'_, '_, '_>, + path: &[Segment], + following_seg: Option<&Segment>, + span: Span, + ) { + if let PathSource::Expr(None) = source + && let Some(Res::Def(DefKind::TyParam, _)) = res + && following_seg.is_none() + && let [segment] = path + { + // We have something like + // impl From<[T; N]> for VecWrapper { + // fn from(slice: [T; N]) -> Self { + // VecWrapper(slice.to_vec()) + // } + // } + // where `N` is a type param but should likely have been a const param. + let Some(item) = self.diag_metadata.current_item else { return }; + let Some(generics) = item.kind.generics() else { return }; + let Some(span) = generics.params.iter().find_map(|param| { + // Only consider type params with no bounds. + if param.bounds.is_empty() && param.ident.name == segment.ident.name { + Some(param.ident.span) + } else { + None + } + }) else { + return; + }; + err.subdiagnostic(diagnostics::UnexpectedResChangeTyParamToConstParamSugg { + before: span.shrink_to_lo(), + after: span.shrink_to_hi(), + }); + return; + } + let PathSource::Trait(_) = source else { return }; + + // We don't include `DefKind::Str` and `DefKind::AssocTy` as they can't be reached here anyway. + let applicability = match res { + Some(Res::PrimTy(PrimTy::Int(_) | PrimTy::Uint(_) | PrimTy::Bool | PrimTy::Char)) => { + Applicability::MachineApplicable + } + // FIXME(const_generics): Add `DefKind::TyParam` and `SelfTyParam` once we support generic + // const generics. Of course, `Struct` and `Enum` may contain ty params, too, but the + // benefits of including them here outweighs the small number of false positives. + Some(Res::Def(DefKind::Struct | DefKind::Enum, _)) + if self.r.features.adt_const_params() || self.r.features.min_adt_const_params() => + { + Applicability::MaybeIncorrect + } + _ => return, + }; + + let Some(item) = self.diag_metadata.current_item else { return }; + let Some(generics) = item.kind.generics() else { return }; + + let param = generics.params.iter().find_map(|param| { + // Only consider type params with exactly one trait bound. + if let [bound] = &*param.bounds + && let ast::GenericBound::Trait(tref) = bound + && tref.modifiers == ast::TraitBoundModifiers::NONE + && tref.span == span + && param.ident.span.eq_ctxt(span) + { + Some(param.ident.span) + } else { + None + } + }); + + if let Some(param) = param { + err.subdiagnostic(diagnostics::UnexpectedResChangeTyToConstParamSugg { + span: param.shrink_to_lo(), + applicability, + }); + } + } + + fn suggest_pattern_match_with_let( + &self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + span: Span, + ) -> bool { + if let PathSource::Expr(_) = source + && let Some(Expr { span: expr_span, kind: ExprKind::Assign(lhs, _, _), .. }) = + self.diag_metadata.in_if_condition + { + // Icky heuristic so we don't suggest: + // `if (i + 2) = 2` => `if let (i + 2) = 2` (approximately pattern) + // `if 2 = i` => `if let 2 = i` (lhs needs to contain error span) + if lhs.is_approximately_pattern() && lhs.span.contains(span) { + err.span_suggestion_verbose( + expr_span.shrink_to_lo(), + "you might have meant to use pattern matching", + "let ", + Applicability::MaybeIncorrect, + ); + return true; + } + } + false + } + + fn get_single_associated_item( + &mut self, + path: &[Segment], + source: &PathSource<'_, 'ast, 'ra>, + filter_fn: &impl Fn(Res) -> bool, + ) -> Option { + if let crate::PathSource::TraitItem(_, _) = source { + let mod_path = &path[..path.len() - 1]; + if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = + self.resolve_path(mod_path, None, None, *source) + { + let targets: Vec<_> = self + .r + .resolutions(module) + .borrow() + .iter() + .filter_map(|(key, resolution)| { + let resolution = resolution.borrow(); + resolution.best_decl().map(|binding| binding.res()).and_then(|res| { + if filter_fn(res) { + Some((key.ident.name, resolution.orig_ident_span, res)) + } else { + None + } + }) + }) + .collect(); + if let &[(name, orig_ident_span, res)] = targets.as_slice() { + return Some(TypoSuggestion::single_item(name, orig_ident_span, res)); + } + } + } + None + } + + /// Given `where ::Baz: String`, suggest `where T: Bar`. + fn restrict_assoc_type_in_where_clause(&self, span: Span, err: &mut Diag<'_>) -> bool { + // Detect that we are actually in a `where` predicate. + let Some(ast::WherePredicate { + kind: + ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate { + bounded_ty, + bound_generic_params, + bounds, + }), + span: where_span, + .. + }) = self.diag_metadata.current_where_predicate + else { + return false; + }; + if !bound_generic_params.is_empty() { + return false; + } + + // Confirm that the target is an associated type. + let ast::TyKind::Path(Some(qself), path) = &bounded_ty.kind else { return false }; + // use this to verify that ident is a type param. + let Some(partial_res) = self.r.partial_res_map.get(&bounded_ty.id) else { return false }; + if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::AssocTy, _))) { + return false; + } + + let peeled_ty = qself.ty.peel_refs(); + let ast::TyKind::Path(None, type_param_path) = &peeled_ty.kind else { return false }; + // Confirm that the `SelfTy` is a type parameter. + let Some(partial_res) = self.r.partial_res_map.get(&peeled_ty.id) else { + return false; + }; + if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::TyParam, _))) { + return false; + } + let ([ast::PathSegment { args: None, .. }], [ast::GenericBound::Trait(poly_trait_ref)]) = + (&type_param_path.segments[..], &bounds[..]) + else { + return false; + }; + let [ast::PathSegment { ident, args: None, id }] = + &poly_trait_ref.trait_ref.path.segments[..] + else { + return false; + }; + if poly_trait_ref.modifiers != ast::TraitBoundModifiers::NONE { + return false; + } + if ident.span == span { + let Some(partial_res) = self.r.partial_res_map.get(&id) else { + return false; + }; + if !matches!(partial_res.full_res(), Some(Res::Def(..))) { + return false; + } + + let Some(new_where_bound_predicate) = + mk_where_bound_predicate(path, poly_trait_ref, &qself.ty) + else { + return false; + }; + err.span_suggestion_verbose( + *where_span, + format!("constrain the associated type to `{ident}`"), + where_bound_predicate_to_string(&new_where_bound_predicate), + Applicability::MaybeIncorrect, + ); + } + true + } + + /// Check if the source is call expression and the first argument is `self`. If true, + /// return the span of whole call and the span for all arguments expect the first one (`self`). + fn call_has_self_arg(&self, source: PathSource<'_, '_, '_>) -> Option<(Span, Option)> { + let mut has_self_arg = None; + if let PathSource::Expr(Some(parent)) = source + && let ExprKind::Call(_, args) = &parent.kind + && !args.is_empty() + { + let mut expr_kind = &args[0].kind; + loop { + match expr_kind { + ExprKind::Path(_, arg_name) if arg_name.segments.len() == 1 => { + if arg_name.segments[0].ident.name == kw::SelfLower { + let call_span = parent.span; + let tail_args_span = if args.len() > 1 { + Some(Span::new( + args[1].span.lo(), + args.last().unwrap().span.hi(), + call_span.ctxt(), + None, + )) + } else { + None + }; + has_self_arg = Some((call_span, tail_args_span)); + } + break; + } + ExprKind::AddrOf(_, _, expr) => expr_kind = &expr.kind, + _ => break, + } + } + } + has_self_arg + } + + fn followed_by_brace(&self, span: Span) -> (bool, Option) { + // HACK(estebank): find a better way to figure out that this was a + // parser issue where a struct literal is being used on an expression + // where a brace being opened means a block is being started. Look + // ahead for the next text to see if `span` is followed by a `{`. + let sm = self.r.tcx.sess.source_map(); + if let Some(open_brace_span) = sm.span_followed_by(span, "{") { + // In case this could be a struct literal that needs to be surrounded + // by parentheses, find the appropriate span. + let close_brace_span = + sm.span_to_next_source(open_brace_span).ok().and_then(|next_source| { + // Find the matching `}` accounting for nested braces. + let mut depth: u32 = 1; + let offset = next_source.char_indices().find_map(|(i, c)| { + match c { + '{' => depth += 1, + '}' if depth == 1 => return Some(i), + '}' => depth -= 1, + _ => {} + } + None + })?; + let start = open_brace_span.hi() + rustc_span::BytePos(offset as u32); + Some(open_brace_span.with_lo(start).with_hi(start + rustc_span::BytePos(1))) + }); + let closing_brace = close_brace_span.map(|sp| span.to(sp)); + (true, closing_brace) + } else { + (false, None) + } + } + + fn update_err_for_private_tuple_struct_fields( + &mut self, + err: &mut Diag<'_>, + source: &PathSource<'_, '_, '_>, + def_id: DefId, + ) -> Option> { + match source { + // e.g. `if let Enum::TupleVariant(field1, field2) = _` + PathSource::TupleStruct(_, pattern_spans) => { + err.primary_message( + "cannot match against a tuple struct which contains private fields", + ); + + // Use spans of the tuple struct pattern. + Some(Vec::from(*pattern_spans)) + } + // e.g. `let _ = Enum::TupleVariant(field1, field2);` + PathSource::Expr(Some(Expr { + kind: ExprKind::Call(path, args), + span: call_span, + .. + })) => { + err.primary_message( + "cannot initialize a tuple struct which contains private fields", + ); + self.suggest_alternative_construction_methods( + def_id, + err, + path.span, + *call_span, + &args[..], + ); + + self.r + .field_idents(def_id) + .map(|fields| fields.iter().map(|f| f.span).collect::>()) + } + _ => None, + } + } + + /// Provides context-dependent help for errors reported by the `smart_resolve_path_fragment` + /// function. + /// Returns `true` if able to provide context-dependent help. + fn smart_resolve_context_dependent_help( + &mut self, + err: &mut Diag<'_>, + span: Span, + source: PathSource<'_, '_, '_>, + path: &[Segment], + res: Res, + path_str: &str, + fallback_label: &str, + ) -> bool { + let ns = source.namespace(); + let is_expected = &|res| source.is_expected(res); + + let path_sep = |this: &Self, err: &mut Diag<'_>, expr: &Expr, kind: DefKind| { + const MESSAGE: &str = "use the path separator to refer to an item"; + + let (lhs_span, rhs_span) = match &expr.kind { + ExprKind::Field(base, ident) => (base.span, ident.span), + ExprKind::MethodCall(MethodCall { receiver, span, .. }) => (receiver.span, *span), + _ => return false, + }; + + if lhs_span.eq_ctxt(rhs_span) { + err.span_suggestion_verbose( + lhs_span.between(rhs_span), + MESSAGE, + "::", + Applicability::MaybeIncorrect, + ); + true + } else if matches!(kind, DefKind::Struct | DefKind::TyAlias) + && let Some(lhs_source_span) = lhs_span.find_ancestor_inside(expr.span) + && let Ok(snippet) = this.r.tcx.sess.source_map().span_to_snippet(lhs_source_span) + { + // The LHS is a type that originates from a macro call. + // We have to add angle brackets around it. + + err.span_suggestion_verbose( + lhs_source_span.until(rhs_span), + MESSAGE, + format!("<{snippet}>::"), + Applicability::MaybeIncorrect, + ); + true + } else { + // Either we were unable to obtain the source span / the snippet or + // the LHS originates from a macro call and it is not a type and thus + // there is no way to replace `.` with `::` and still somehow suggest + // valid Rust code. + + false + } + }; + + let find_span = |source: &PathSource<'_, '_, '_>, err: &mut Diag<'_>| { + match source { + PathSource::Expr(Some(Expr { span, kind: ExprKind::Call(_, _), .. })) + | PathSource::TupleStruct(span, _) => { + // We want the main underline to cover the suggested code as well for + // cleaner output. + err.span(*span); + *span + } + _ => span, + } + }; + + let bad_struct_syntax_suggestion = |this: &mut Self, err: &mut Diag<'_>, def_id: DefId| { + let (followed_by_brace, closing_brace) = this.followed_by_brace(span); + + match source { + PathSource::Expr(Some( + parent @ Expr { kind: ExprKind::Field(..) | ExprKind::MethodCall(..), .. }, + )) if path_sep(this, err, parent, DefKind::Struct) => {} + PathSource::Expr( + None + | Some(Expr { + kind: + ExprKind::Path(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::If(..) + | ExprKind::While(..) + | ExprKind::ForLoop { .. } + | ExprKind::Match(..), + .. + }), + ) if followed_by_brace => { + if let Some(sp) = closing_brace { + err.span_label(span, fallback_label.to_string()); + err.multipart_suggestion( + "surround the struct literal with parentheses", + vec![ + (sp.shrink_to_lo(), "(".to_string()), + (sp.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + err.span_label( + span, // Note the parentheses surrounding the suggestion below + format!( + "you might want to surround a struct literal with parentheses: \ + `({path_str} {{ /* fields */ }})`?" + ), + ); + } + } + PathSource::Expr(_) | PathSource::TupleStruct(..) | PathSource::Pat => { + let span = find_span(&source, err); + err.span_label(this.r.def_span(def_id), format!("`{path_str}` defined here")); + + let (tail, descr, applicability, old_fields) = match source { + PathSource::Pat => ("", "pattern", Applicability::MachineApplicable, None), + PathSource::TupleStruct(_, args) => ( + "", + "pattern", + Applicability::MachineApplicable, + Some( + args.iter() + .map(|a| this.r.tcx.sess.source_map().span_to_snippet(*a).ok()) + .collect::>>(), + ), + ), + _ => (": val", "literal", Applicability::HasPlaceholders, None), + }; + + // Imprecise for local structs without ctors, we don't keep fields for them. + let has_private_fields = match def_id.as_local() { + Some(def_id) => this.r.struct_ctors.get(&def_id).is_some_and(|ctor| { + ctor.has_private_fields(this.parent_scope.module, this.r) + }), + None => this.r.tcx.associated_item_def_ids(def_id).iter().any(|field_id| { + let vis = this.r.tcx.visibility(*field_id); + !this.r.is_accessible_from(vis, this.parent_scope.module) + }), + }; + if !has_private_fields { + // If the fields of the type are private, we shouldn't be suggesting using + // the struct literal syntax at all, as that will cause a subsequent error. + let fields = this.r.field_idents(def_id); + let has_fields = fields.as_ref().is_some_and(|f| !f.is_empty()); + + if let PathSource::Expr(Some(Expr { + kind: ExprKind::Call(path, args), + span, + .. + })) = source + && !args.is_empty() + && let Some(fields) = &fields + && args.len() == fields.len() + // Make sure we have same number of args as fields + { + let path_span = path.span; + let mut parts = Vec::new(); + + // Start with the opening brace + parts.push(( + path_span.shrink_to_hi().until(args[0].span), + "{".to_owned(), + )); + + for (field, arg) in fields.iter().zip(args.iter()) { + // Add the field name before the argument + parts.push((arg.span.shrink_to_lo(), format!("{}: ", field))); + } + + // Add the closing brace + parts.push(( + args.last().unwrap().span.shrink_to_hi().until(span.shrink_to_hi()), + "}".to_owned(), + )); + + err.multipart_suggestion( + format!("use struct {descr} syntax instead of calling"), + parts, + applicability, + ); + } else { + let (fields, applicability) = match fields { + Some(fields) => { + let fields = if let Some(old_fields) = old_fields { + fields + .iter() + .enumerate() + .map(|(idx, new)| (new, old_fields.get(idx))) + .map(|(new, old)| { + if let Some(Some(old)) = old + && new.as_str() != old + { + format!("{new}: {old}") + } else { + new.to_string() + } + }) + .collect::>() + } else { + fields + .iter() + .map(|f| format!("{f}{tail}")) + .collect::>() + }; + + (fields.join(", "), applicability) + } + None => { + ("/* fields */".to_string(), Applicability::HasPlaceholders) + } + }; + let pad = if has_fields { " " } else { "" }; + err.span_suggestion( + span, + format!("use struct {descr} syntax instead"), + format!("{path_str} {{{pad}{fields}{pad}}}"), + applicability, + ); + } + } + if let PathSource::Expr(Some(Expr { + kind: ExprKind::Call(path, args), + span: call_span, + .. + })) = source + { + this.suggest_alternative_construction_methods( + def_id, + err, + path.span, + *call_span, + &args[..], + ); + } + } + _ => { + err.span_label(span, fallback_label.to_string()); + } + } + }; + + match (res, source) { + ( + Res::Def(DefKind::Macro(kinds), def_id), + PathSource::Expr(Some(Expr { + kind: ExprKind::Index(..) | ExprKind::Call(..), .. + })) + | PathSource::Struct(_), + ) if kinds.contains(MacroKinds::BANG) => { + // Don't suggest macro if it's unstable. + let suggestable = def_id.is_local() + || self.r.tcx.lookup_stability(def_id).is_none_or(|s| s.is_stable()); + + err.span_label(span, fallback_label.to_string()); + + // Don't suggest `!` for a macro invocation if there are generic args + if path + .last() + .is_some_and(|segment| !segment.has_generic_args && !segment.has_lifetime_args) + && suggestable + { + err.span_suggestion_verbose( + span.shrink_to_hi(), + "use `!` to invoke the macro", + "!", + Applicability::MaybeIncorrect, + ); + } + + if path_str == "try" && span.is_rust_2015() { + err.note("if you want the `try` keyword, you need Rust 2018 or later"); + } + } + (Res::Def(DefKind::Macro(kinds), _), _) if kinds.contains(MacroKinds::BANG) => { + err.span_label(span, fallback_label.to_string()); + } + (Res::Def(DefKind::TyAlias, def_id), PathSource::Trait(_)) => { + err.span_label(span, "type aliases cannot be used as traits"); + if self.r.tcx.sess.is_nightly_build() { + let msg = "you might have meant to use `#![feature(trait_alias)]` instead of a \ + `type` alias"; + let span = self.r.def_span(def_id); + if let Ok(snip) = self.r.tcx.sess.source_map().span_to_snippet(span) { + // The span contains a type alias so we should be able to + // replace `type` with `trait`. + let snip = snip.replacen("type", "trait", 1); + err.span_suggestion(span, msg, snip, Applicability::MaybeIncorrect); + } else { + err.span_help(span, msg); + } + } + } + ( + Res::Def(kind @ (DefKind::Mod | DefKind::Trait | DefKind::TyAlias), _), + PathSource::Expr(Some(parent)), + ) if path_sep(self, err, parent, kind) => { + return true; + } + ( + Res::Def(DefKind::Enum, def_id), + PathSource::TupleStruct(..) | PathSource::Expr(..), + ) => { + self.suggest_using_enum_variant(err, source, def_id, span); + } + (Res::Def(DefKind::Struct, def_id), source) if ns == ValueNS => { + if let PathSource::Expr(Some(parent)) = source + && let ExprKind::Field(..) | ExprKind::MethodCall(..) = parent.kind + { + bad_struct_syntax_suggestion(self, err, def_id); + return true; + } + let Some(ctor) = self.r.struct_ctor(def_id) else { + bad_struct_syntax_suggestion(self, err, def_id); + return true; + }; + + // A type is re-exported and has an inaccessible constructor because it has fields + // that are inaccessible from the reexport's scope, extend the diagnostic. + let is_accessible = self.r.is_accessible_from(ctor.vis, self.parent_scope.module); + if is_accessible + && let mod_path = &path[..path.len() - 1] + && let PathResult::Module(ModuleOrUniformRoot::Module(import_mod)) = + self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Module) + && ctor.has_private_fields(import_mod, self.r) + && let Ok(import_decl) = self.r.cm().maybe_resolve_ident_in_module( + ModuleOrUniformRoot::Module(import_mod), + path.last().unwrap().ident, + TypeNS, + &self.parent_scope, + None, + ) + { + err.span_note( + import_decl.span, + "the type is accessed through this re-export, but the type's constructor \ + is not visible in this import's scope due to private fields", + ); + if !ctor.has_private_fields(self.parent_scope.module, self.r) { + err.span_suggestion_verbose( + span, + "the type can be constructed directly, because its fields are \ + available from the current scope", + // Using `tcx.def_path_str` causes the compiler to hang. + // We don't need to handle foreign crate types because in that case you + // can't access the ctor either way. + format!( + "crate{}", // The method already has leading `::`. + self.r.tcx.def_path(def_id).to_string_no_crate_verbose(), + ), + Applicability::MachineApplicable, + ); + } + self.update_err_for_private_tuple_struct_fields(err, &source, def_id); + } + if !is_expected(ctor.res) || is_accessible { + return true; + } + + let field_spans = + self.update_err_for_private_tuple_struct_fields(err, &source, def_id); + + if let Some(spans) = field_spans + .filter(|spans| spans.len() > 0 && ctor.field_visibilities.len() == spans.len()) + { + let non_visible_spans: Vec = iter::zip(&ctor.field_visibilities, &spans) + .filter(|(vis, _)| { + !self.r.is_accessible_from(**vis, self.parent_scope.module) + }) + .map(|(_, span)| *span) + .collect(); + + if non_visible_spans.len() > 0 { + if let Some(fields) = self.r.field_visibility_spans.get(&def_id) { + err.multipart_suggestion( + format!( + "consider making the field{} publicly accessible", + pluralize!(fields.len()) + ), + fields.iter().map(|span| (*span, "pub ".to_string())).collect(), + Applicability::MaybeIncorrect, + ); + } + + let mut m: MultiSpan = non_visible_spans.clone().into(); + non_visible_spans + .into_iter() + .for_each(|s| m.push_span_label(s, "private field")); + err.span_note(m, "constructor is not visible here due to private fields"); + } + + return true; + } + + err.span_label(span, "constructor is not visible here due to private fields"); + } + (Res::Def(DefKind::Union | DefKind::Variant, def_id), _) if ns == ValueNS => { + bad_struct_syntax_suggestion(self, err, def_id); + } + (Res::Def(DefKind::Ctor(_, CtorKind::Const), def_id), _) if ns == ValueNS => { + match source { + PathSource::Expr(_) | PathSource::TupleStruct(..) | PathSource::Pat => { + let span = find_span(&source, err); + err.span_label( + self.r.def_span(def_id), + format!("`{path_str}` defined here"), + ); + err.span_suggestion( + span, + "use this syntax instead", + path_str, + Applicability::MaybeIncorrect, + ); + } + _ => return false, + } + } + (Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_def_id), _) if ns == ValueNS => { + let def_id = self.r.tcx.parent(ctor_def_id); + err.span_label(self.r.def_span(def_id), format!("`{path_str}` defined here")); + let fields = self.r.field_idents(def_id).map_or_else( + || "/* fields */".to_string(), + |field_ids| vec!["_"; field_ids.len()].join(", "), + ); + err.span_suggestion( + span, + "use the tuple variant pattern syntax instead", + format!("{path_str}({fields})"), + Applicability::HasPlaceholders, + ); + } + (Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }, _) if ns == ValueNS => { + err.span_label(span, fallback_label.to_string()); + err.note("can't use `Self` as a constructor, you must use the implemented struct"); + } + ( + Res::Def(DefKind::TyAlias | DefKind::AssocTy, _), + PathSource::TraitItem(ValueNS, PathSource::TupleStruct(whole, args)), + ) => { + err.note("can't use a type alias as tuple pattern"); + + let mut suggestion = Vec::new(); + + if let &&[first, ..] = args + && let &&[.., last] = args + { + suggestion.extend([ + // "0: " has to be included here so that the fix is machine applicable. + // + // If this would only add " { " and then the code below add "0: ", + // rustfix would crash, because end of this suggestion is the same as start + // of the suggestion below. Thus, we have to merge these... + (span.between(first), " { 0: ".to_owned()), + (last.between(whole.shrink_to_hi()), " }".to_owned()), + ]); + + suggestion.extend( + args.iter() + .enumerate() + .skip(1) // See above + .map(|(index, &arg)| (arg.shrink_to_lo(), format!("{index}: "))), + ) + } else { + suggestion.push((span.between(whole.shrink_to_hi()), " {}".to_owned())); + } + + err.multipart_suggestion( + "use struct pattern instead", + suggestion, + Applicability::MachineApplicable, + ); + } + ( + Res::Def(DefKind::TyAlias | DefKind::AssocTy, _), + PathSource::TraitItem( + ValueNS, + PathSource::Expr(Some(ast::Expr { + span: whole, + kind: ast::ExprKind::Call(_, args), + .. + })), + ), + ) => { + err.note("can't use a type alias as a constructor"); + + let mut suggestion = Vec::new(); + + if let [first, ..] = &**args + && let [.., last] = &**args + { + suggestion.extend([ + // "0: " has to be included here so that the fix is machine applicable. + // + // If this would only add " { " and then the code below add "0: ", + // rustfix would crash, because end of this suggestion is the same as start + // of the suggestion below. Thus, we have to merge these... + (span.between(first.span), " { 0: ".to_owned()), + (last.span.between(whole.shrink_to_hi()), " }".to_owned()), + ]); + + suggestion.extend( + args.iter() + .enumerate() + .skip(1) // See above + .map(|(index, arg)| (arg.span.shrink_to_lo(), format!("{index}: "))), + ) + } else { + suggestion.push((span.between(whole.shrink_to_hi()), " {}".to_owned())); + } + + err.multipart_suggestion( + "use struct expression instead", + suggestion, + Applicability::MachineApplicable, + ); + } + _ => return false, + } + true + } + + fn suggest_alternative_construction_methods( + &mut self, + def_id: DefId, + err: &mut Diag<'_>, + path_span: Span, + call_span: Span, + args: &[Box], + ) { + if def_id.is_local() { + // Doing analysis on local `DefId`s would cause infinite recursion. + return; + } + // Look at all the associated functions without receivers in the type's + // inherent impls to look for builders that return `Self` + let mut items = self + .r + .tcx + .inherent_impls(def_id) + .iter() + .flat_map(|&i| self.r.tcx.associated_items(i).in_definition_order()) + // Only assoc fn with no receivers. + .filter(|item| item.is_fn() && !item.is_method()) + .filter_map(|item| { + // Only assoc fns that return `Self` + let fn_sig = self.r.tcx.fn_sig(item.def_id).skip_binder(); + // Don't normalize the return type, because that can cause cycle errors. + let ret_ty = fn_sig.output().skip_binder(); + let ty::Adt(def, _args) = ret_ty.kind() else { + return None; + }; + let input_len = fn_sig.inputs().skip_binder().len(); + if def.did() != def_id { + return None; + } + let name = item.name(); + let order = !name.as_str().starts_with("new"); + Some((order, name, input_len)) + }) + .collect::>(); + items.sort_by_key(|(order, _, _)| *order); + let suggestion = |name, args| { + format!("::{name}({})", std::iter::repeat_n("_", args).collect::>().join(", ")) + }; + match &items[..] { + [] => {} + [(_, name, len)] if *len == args.len() => { + err.span_suggestion_verbose( + path_span.shrink_to_hi(), + format!("you might have meant to use the `{name}` associated function",), + format!("::{name}"), + Applicability::MaybeIncorrect, + ); + } + [(_, name, len)] => { + err.span_suggestion_verbose( + path_span.shrink_to_hi().with_hi(call_span.hi()), + format!("you might have meant to use the `{name}` associated function",), + suggestion(name, *len), + Applicability::MaybeIncorrect, + ); + } + _ => { + err.span_suggestions_with_style( + path_span.shrink_to_hi().with_hi(call_span.hi()), + "you might have meant to use an associated function to build this type", + items.iter().map(|(_, name, len)| suggestion(name, *len)), + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + } + } + // We'd ideally use `type_implements_trait` but don't have access to + // the trait solver here. We can't use `get_diagnostic_item` or + // `all_traits` in resolve either. So instead we abuse the import + // suggestion machinery to get `std::default::Default` and perform some + // checks to confirm that we got *only* that trait. We then see if the + // Adt we have has a direct implementation of `Default`. If so, we + // provide a structured suggestion. + let default_trait = self + .r + .lookup_import_candidates( + Ident::with_dummy_span(sym::Default), + Namespace::TypeNS, + &self.parent_scope, + &|res: Res| matches!(res, Res::Def(DefKind::Trait, _)), + ) + .iter() + .filter_map(|candidate| candidate.did) + .find(|did| find_attr!(self.r.tcx, *did, RustcDiagnosticItem(sym::Default))); + let Some(default_trait) = default_trait else { + return; + }; + if self + .r + .extern_crate_map + .items() + // FIXME: This doesn't include impls like `impl Default for String`. + .flat_map(|(_, crate_)| { + UnordItems::new( + self.r.tcx.implementations_of_trait((*crate_, default_trait)).into_iter(), + ) + }) + .filter_map(|(_, simplified_self_ty)| *simplified_self_ty) + .filter_map(|simplified_self_ty| match simplified_self_ty { + SimplifiedType::Adt(did) => Some(did), + _ => None, + }) + .any(|did| did == def_id) + { + err.multipart_suggestion( + "consider using the `Default` trait", + vec![ + (path_span.shrink_to_lo(), "<".to_string()), + ( + path_span.shrink_to_hi().with_hi(call_span.hi()), + " as std::default::Default>::default()".to_string(), + ), + ], + Applicability::MaybeIncorrect, + ); + } + } + + /// Given the target `ident` and `kind`, search for the similarly named associated item + /// in `self.current_trait_ref`. + pub(crate) fn find_similarly_named_assoc_item( + &mut self, + ident: Symbol, + kind: &AssocItemKind, + ) -> Option { + let (module, _) = self.current_trait_ref.as_ref()?; + if ident == kw::Underscore { + // We do nothing for `_`. + return None; + } + + let targets = self + .r + .resolutions(*module) + .borrow() + .iter() + .filter_map(|(key, res)| res.borrow().best_decl().map(|binding| (key, binding.res()))) + .filter(|(_, res)| match (kind, res) { + (AssocItemKind::Const(..), Res::Def(DefKind::AssocConst { .. }, _)) => true, + (AssocItemKind::Fn(_), Res::Def(DefKind::AssocFn, _)) => true, + (AssocItemKind::Type(..), Res::Def(DefKind::AssocTy, _)) => true, + (AssocItemKind::Delegation(_), Res::Def(DefKind::AssocFn, _)) => true, + _ => false, + }) + .map(|(key, _)| key.ident.name) + .collect::>(); + + find_best_match_for_name(&targets, ident, None) + } + + fn lookup_assoc_candidate( + &mut self, + ident: Ident, + ns: Namespace, + filter_fn: FilterFn, + called: bool, + ) -> Option + where + FilterFn: Fn(Res) -> bool, + { + fn extract_node_id(t: &Ty) -> Option { + match t.kind { + TyKind::Path(None, _) => Some(t.id), + TyKind::Ref(_, ref mut_ty) => extract_node_id(&mut_ty.ty), + // This doesn't handle the remaining `Ty` variants as they are not + // that commonly the self_type, it might be interesting to provide + // support for those in future. + _ => None, + } + } + // Fields are generally expected in the same contexts as locals. + if filter_fn(Res::Local(ast::DUMMY_NODE_ID)) { + if let Some(node_id) = self.diag_metadata.current_self_type.and_then(extract_node_id) + && let Some(resolution) = self.r.partial_res_map.get(&node_id) + && let Some(Res::Def(DefKind::Struct | DefKind::Union, did)) = resolution.full_res() + && let Some(fields) = self.r.field_idents(did) + && let Some(field) = fields.iter().find(|id| ident.name == id.name) + { + // Look for a field with the same name in the current self_type. + return Some(AssocSuggestion::Field(field.span)); + } + } + + if let Some(items) = self.diag_metadata.current_trait_assoc_items { + for assoc_item in items { + if let Some(assoc_ident) = assoc_item.kind.ident() + && assoc_ident == ident + { + return Some(match &assoc_item.kind { + ast::AssocItemKind::Const(..) => AssocSuggestion::AssocConst, + ast::AssocItemKind::Fn(ast::Fn { sig, .. }) if sig.decl.has_self() => { + AssocSuggestion::MethodWithSelf { called } + } + ast::AssocItemKind::Fn(..) => AssocSuggestion::AssocFn { called }, + ast::AssocItemKind::Type(..) => AssocSuggestion::AssocType, + ast::AssocItemKind::Delegation(..) + if self + .r + .owners + .get(&assoc_item.id) + .and_then(|o| self.r.delegation_fn_sigs.get(&o.def_id)) + .is_some_and(|sig| sig.has_self) => + { + AssocSuggestion::MethodWithSelf { called } + } + ast::AssocItemKind::Delegation(..) => AssocSuggestion::AssocFn { called }, + ast::AssocItemKind::MacCall(_) | ast::AssocItemKind::DelegationMac(..) => { + continue; + } + }); + } + } + } + + // Look for associated items in the current trait. + if let Some((module, _)) = self.current_trait_ref + && let Ok(binding) = self.r.cm().maybe_resolve_ident_in_module( + ModuleOrUniformRoot::Module(module), + ident, + ns, + &self.parent_scope, + None, + ) + { + let res = binding.res(); + if filter_fn(res) { + match res { + Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => { + let has_self = match def_id.as_local() { + Some(def_id) => self + .r + .delegation_fn_sigs + .get(&def_id) + .is_some_and(|sig| sig.has_self), + None => { + self.r.tcx.fn_arg_idents(def_id).first().is_some_and(|&ident| { + matches!(ident, Some(Ident { name: kw::SelfLower, .. })) + }) + } + }; + if has_self { + return Some(AssocSuggestion::MethodWithSelf { called }); + } else { + return Some(AssocSuggestion::AssocFn { called }); + } + } + Res::Def(DefKind::AssocConst { .. }, _) => { + return Some(AssocSuggestion::AssocConst); + } + Res::Def(DefKind::AssocTy, _) => { + return Some(AssocSuggestion::AssocType); + } + _ => {} + } + } + } + + None + } + + fn lookup_typo_candidate( + &mut self, + path: &[Segment], + following_seg: Option<&Segment>, + ns: Namespace, + filter_fn: &impl Fn(Res) -> bool, + ) -> TypoCandidate { + let mut names = Vec::new(); + if let [segment] = path { + let mut ctxt = segment.ident.span.ctxt(); + + // Search in lexical scope. + // Walk backwards up the ribs in scope and collect candidates. + for rib in self.ribs[ns].iter().rev() { + let rib_ctxt = if rib.kind.contains_params() { + ctxt.normalize_to_macros_2_0() + } else { + ctxt.normalize_to_macro_rules() + }; + + // Locals and type parameters + for (ident, &res) in &rib.bindings { + if filter_fn(res) && ident.span.ctxt() == rib_ctxt { + names.push(TypoSuggestion::new(ident.name, ident.span, res)); + } + } + + if let RibKind::Block(Some(module)) = rib.kind { + self.r.add_module_candidates( + module.to_module(), + &mut names, + &filter_fn, + Some(ctxt), + ); + } else if let RibKind::Module(module) = rib.kind { + // Encountered a module item, abandon ribs and look into that module and preludes. + let parent_scope = + &ParentScope { module: module.to_module(), ..self.parent_scope }; + self.r.add_scope_set_candidates( + &mut names, + ScopeSet::All(ns), + parent_scope, + segment.ident.span.with_ctxt(ctxt), + filter_fn, + ); + break; + } + + if let RibKind::MacroDefinition(def) = rib.kind + && def == self.r.macro_def(ctxt) + { + // If an invocation of this macro created `ident`, give up on `ident` + // and switch to `ident`'s source from the macro definition. + ctxt.remove_mark(); + } + } + } else { + // Search in module. + let mod_path = &path[..path.len() - 1]; + if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = + self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Type) + { + self.r.add_module_candidates(module, &mut names, &filter_fn, None); + } + } + + // if next_seg is present, let's filter everything that does not continue the path + if let Some(following_seg) = following_seg { + names.retain(|suggestion| match suggestion.res { + Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _) => { + // FIXME: this is not totally accurate, but mostly works + suggestion.candidate != following_seg.ident.name + } + Res::Def(DefKind::Mod, def_id) => { + let module = self.r.expect_module(def_id); + self.r + .resolutions(module) + .borrow() + .iter() + .any(|(key, _)| key.ident.name == following_seg.ident.name) + } + _ => true, + }); + } + let name = path[path.len() - 1].ident.name; + // Make sure error reporting is deterministic. + names.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); + + match find_best_match_for_name( + &names.iter().map(|suggestion| suggestion.candidate).collect::>(), + name, + None, + ) { + Some(found) => { + let Some(sugg) = names.into_iter().find(|suggestion| suggestion.candidate == found) + else { + return TypoCandidate::None; + }; + if found == name { + TypoCandidate::Shadowed(sugg.res, sugg.span) + } else { + TypoCandidate::Typo(sugg) + } + } + _ => TypoCandidate::None, + } + } + + // Returns the name of the Rust type approximately corresponding to + // a type name in another programming language. + fn likely_rust_type(path: &[Segment]) -> Option { + let name = path[path.len() - 1].ident.as_str(); + // Common Java types + Some(match name { + "byte" => sym::u8, // In Java, bytes are signed, but in practice one almost always wants unsigned bytes. + "short" => sym::i16, + "Bool" => sym::bool, + "Boolean" => sym::bool, + "boolean" => sym::bool, + "int" => sym::i32, + "long" => sym::i64, + "float" => sym::f32, + "double" => sym::f64, + _ => return None, + }) + } + + // try to give a suggestion for this pattern: `name = blah`, which is common in other languages + // suggest `let name = blah` to introduce a new binding + fn let_binding_suggestion(&self, err: &mut Diag<'_>, ident_span: Span) -> bool { + if ident_span.from_expansion() { + return false; + } + + // only suggest when the code is a assignment without prefix code + if let Some(Expr { kind: ExprKind::Assign(lhs, ..), .. }) = self.diag_metadata.in_assignment + && let ast::ExprKind::Path(None, ref path) = lhs.kind + && self.r.tcx.sess.source_map().is_line_before_span_empty(ident_span) + { + let (span, text) = match path.segments.first() { + Some(seg) if let Some(name) = seg.ident.as_str().strip_prefix("let") => { + // a special case for #117894 + let name = name.trim_prefix('_'); + (ident_span, format!("let {name}")) + } + _ => (ident_span.shrink_to_lo(), "let ".to_string()), + }; + + err.span_suggestion_verbose( + span, + "you might have meant to introduce a new binding", + text, + Applicability::MaybeIncorrect, + ); + return true; + } + + // a special case for #133713 + // '=' maybe a typo of `:`, which is a type annotation instead of assignment + if err.code == Some(E0423) + && let Some((let_span, None, Some(val_span))) = self.diag_metadata.current_let_binding + && val_span.contains(ident_span) + && val_span.lo() == ident_span.lo() + { + err.span_suggestion_verbose( + let_span.shrink_to_hi().to(val_span.shrink_to_lo()), + "you might have meant to use `:` for type annotation", + ": ", + Applicability::MaybeIncorrect, + ); + return true; + } + false + } + + fn find_module(&self, def_id: DefId) -> Option<(Module<'ra>, ImportSuggestion)> { + let mut result = None; + let mut seen_modules = FxHashSet::default(); + let mut worklist = vec![(self.r.graph_root.to_module(), ThinVec::new(), true)]; + + while let Some((in_module, path_segments, doc_visible)) = worklist.pop() { + // abort if the module is already found + if result.is_some() { + break; + } + + in_module.for_each_child(self.r, |r, ident, orig_ident_span, _, name_binding| { + // abort if the module is already found or if name_binding is private external + if result.is_some() || !name_binding.vis().is_visible_locally() { + return; + } + if let Some(module_def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let doc_visible = doc_visible + && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); + if module_def_id == def_id { + let path = + Path { span: name_binding.span, segments: path_segments, tokens: None }; + result = Some(( + r.expect_module(module_def_id), + ImportSuggestion { + did: Some(def_id), + descr: "module", + path, + accessible: true, + doc_visible, + note: None, + via_import: false, + is_stable: true, + }, + )); + } else { + // add the module to the lookup + if seen_modules.insert(module_def_id) { + let module = r.expect_module(module_def_id); + worklist.push((module, path_segments, doc_visible)); + } + } + } + }); + } + + result + } + + fn collect_enum_ctors(&self, def_id: DefId) -> Option> { + self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| { + let mut variants = Vec::new(); + enum_module.for_each_child(self.r, |_, ident, orig_ident_span, _, name_binding| { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() { + let mut segms = enum_import_suggestion.path.segments.clone(); + segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let path = Path { span: name_binding.span, segments: segms, tokens: None }; + variants.push((path, def_id, kind)); + } + }); + variants + }) + } + + /// Adds a suggestion for using an enum's variant when an enum is used instead. + fn suggest_using_enum_variant( + &self, + err: &mut Diag<'_>, + source: PathSource<'_, '_, '_>, + def_id: DefId, + span: Span, + ) { + let Some(variant_ctors) = self.collect_enum_ctors(def_id) else { + err.note("you might have meant to use one of the enum's variants"); + return; + }; + + // If the expression is a field-access or method-call, try to find a variant with the field/method name + // that could have been intended, and suggest replacing the `.` with `::`. + // Otherwise, suggest adding `::VariantName` after the enum; + // and if the expression is call-like, only suggest tuple variants. + let (suggest_path_sep_dot_span, suggest_only_tuple_variants) = match source { + // `Type(a, b)` in a pattern, only suggest adding a tuple variant after `Type`. + PathSource::TupleStruct(..) => (None, true), + PathSource::Expr(Some(expr)) => match &expr.kind { + // `Type(a, b)`, only suggest adding a tuple variant after `Type`. + ExprKind::Call(..) => (None, true), + // `Type.Foo(a, b)`, suggest replacing `.` -> `::` if variant `Foo` exists and is a tuple variant, + // otherwise suggest adding a variant after `Type`. + ExprKind::MethodCall(MethodCall { + receiver, + span, + seg: PathSegment { ident, .. }, + .. + }) => { + let dot_span = receiver.span.between(*span); + let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| { + *ctor_kind == CtorKind::Fn + && path.segments.last().is_some_and(|seg| seg.ident == *ident) + }); + (found_tuple_variant.then_some(dot_span), false) + } + // `Type.Foo`, suggest replacing `.` -> `::` if variant `Foo` exists and is a unit or tuple variant, + // otherwise suggest adding a variant after `Type`. + ExprKind::Field(base, ident) => { + let dot_span = base.span.between(ident.span); + let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| { + path.segments.last().is_some_and(|seg| seg.ident == *ident) + }); + (found_tuple_or_unit_variant.then_some(dot_span), false) + } + _ => (None, false), + }, + _ => (None, false), + }; + + if let Some(dot_span) = suggest_path_sep_dot_span { + err.span_suggestion_verbose( + dot_span, + "use the path separator to refer to a variant", + "::", + Applicability::MaybeIncorrect, + ); + } else if suggest_only_tuple_variants { + // Suggest only tuple variants regardless of whether they have fields and do not + // suggest path with added parentheses. + let mut suggestable_variants = variant_ctors + .iter() + .filter(|(.., kind)| *kind == CtorKind::Fn) + .map(|(variant, ..)| path_names_to_string(variant)) + .collect::>(); + suggestable_variants.sort(); + + let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len(); + + let source_msg = if matches!(source, PathSource::TupleStruct(..)) { + "to match against" + } else { + "to construct" + }; + + if !suggestable_variants.is_empty() { + let msg = if non_suggestable_variant_count == 0 && suggestable_variants.len() == 1 { + format!("try {source_msg} the enum's variant") + } else { + format!("try {source_msg} one of the enum's variants") + }; + + err.span_suggestions( + span, + msg, + suggestable_variants, + Applicability::MaybeIncorrect, + ); + } + + // If the enum has no tuple variants.. + if non_suggestable_variant_count == variant_ctors.len() { + err.help(format!("the enum has no tuple variants {source_msg}")); + } + + // If there are also non-tuple variants.. + if non_suggestable_variant_count == 1 { + err.help(format!("you might have meant {source_msg} the enum's non-tuple variant")); + } else if non_suggestable_variant_count >= 1 { + err.help(format!( + "you might have meant {source_msg} one of the enum's non-tuple variants" + )); + } + } else { + let needs_placeholder = |ctor_def_id: DefId, kind: CtorKind| { + let def_id = self.r.tcx.parent(ctor_def_id); + match kind { + CtorKind::Const => false, + CtorKind::Fn => { + !self.r.field_idents(def_id).is_some_and(|field_ids| field_ids.is_empty()) + } + } + }; + + let mut suggestable_variants = variant_ctors + .iter() + .filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind)) + .map(|(variant, _, kind)| (path_names_to_string(variant), kind)) + .map(|(variant, kind)| match kind { + CtorKind::Const => variant, + CtorKind::Fn => format!("({variant}())"), + }) + .collect::>(); + suggestable_variants.sort(); + let no_suggestable_variant = suggestable_variants.is_empty(); + + if !no_suggestable_variant { + let msg = if suggestable_variants.len() == 1 { + "you might have meant to use the following enum variant" + } else { + "you might have meant to use one of the following enum variants" + }; + + err.span_suggestions( + span, + msg, + suggestable_variants, + Applicability::MaybeIncorrect, + ); + } + + let mut suggestable_variants_with_placeholders = variant_ctors + .iter() + .filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind)) + .map(|(variant, _, kind)| (path_names_to_string(variant), kind)) + .filter_map(|(variant, kind)| match kind { + CtorKind::Fn => Some(format!("({variant}(/* fields */))")), + _ => None, + }) + .collect::>(); + suggestable_variants_with_placeholders.sort(); + + if !suggestable_variants_with_placeholders.is_empty() { + let msg = + match (no_suggestable_variant, suggestable_variants_with_placeholders.len()) { + (true, 1) => "the following enum variant is available", + (true, _) => "the following enum variants are available", + (false, 1) => "alternatively, the following enum variant is available", + (false, _) => { + "alternatively, the following enum variants are also available" + } + }; + + err.span_suggestions( + span, + msg, + suggestable_variants_with_placeholders, + Applicability::HasPlaceholders, + ); + } + }; + + if def_id.is_local() { + err.span_note(self.r.def_span(def_id), "the enum is defined here"); + } + } + + /// Detects missing const parameters in `impl` blocks and suggests adding them. + /// + /// When a const parameter is used in the self type of an `impl` but not declared + /// in the `impl`'s own generic parameter list, this function emits a targeted + /// diagnostic with a suggestion to add it at the correct position. + /// + /// Example: + /// + /// ```rust,ignore (suggested field is not completely correct, it should be a single suggestion) + /// struct C; + /// + /// impl Foo for C {} + /// // ^ the struct `C` in `C` is used as the self type + /// // ^ ^ ^ but A, X and P are not declared on the impl + /// + /// Suggested fix: + /// + /// impl Foo for C {} + /// + /// Current behavior (suggestions are emitted one-by-one): + /// + /// impl Foo for C {} + /// impl Foo for C {} + /// impl Foo for C {} + /// + /// Ideally the suggestion should aggregate them into a single line: + /// + /// impl Foo for C {} + /// ``` + /// + pub(crate) fn detect_and_suggest_const_parameter_error( + &mut self, + path: &[Segment], + source: PathSource<'_, 'ast, 'ra>, + ) -> Option> { + let Some(item) = self.diag_metadata.current_item else { return None }; + let ItemKind::Impl(impl_) = &item.kind else { return None }; + let self_ty = &impl_.self_ty; + + // Represents parameter to the struct whether `A`, `X` or `P` + let [current_parameter] = path else { + return None; + }; + + let target_ident = current_parameter.ident; + + // Find the parent segment i.e `C` in `C` + let visitor = ParentPathVisitor::new(self_ty, target_ident); + + let Some(parent_segment) = visitor.parent else { + return None; + }; + + let Some(args) = parent_segment.args.as_ref() else { + return None; + }; + + let GenericArgs::AngleBracketed(angle) = args.as_ref() else { + return None; + }; + + // Build map: NodeId of each usage in C -> its position + // e.g NodeId(A) -> 0, NodeId(X) -> 1, NodeId(C) -> 2 + let usage_to_pos: FxHashMap = angle + .args + .iter() + .enumerate() + .filter_map(|(pos, arg)| { + if let AngleBracketedArg::Arg(GenericArg::Type(ty)) = arg + && let TyKind::Path(_, path) = &ty.kind + && let [segment] = path.segments.as_slice() + { + Some((segment.id, pos)) + } else { + None + } + }) + .collect(); + + // Get the position of the missing param in C + // e.g for missing `B` in `C` this gives idx=1 + let Some(idx) = current_parameter.id.and_then(|id| usage_to_pos.get(&id).copied()) else { + return None; + }; + + // Now resolve the parent struct `C` to get its definition + let ns = source.namespace(); + let segment = Segment::from(parent_segment); + let segments = [segment]; + let finalize = Finalize::new(parent_segment.id, parent_segment.ident.span); + + if let Ok(Some(resolve)) = self.resolve_qpath_anywhere( + &None, + &segments, + ns, + source.defer_to_typeck(), + finalize, + source, + ) && let Some(resolve) = resolve.full_res() + && let Res::Def(_, def_id) = resolve + && def_id.is_local() + && let Some(local_def_id) = def_id.as_local() + && let Some(struct_generics) = self.r.struct_generics.get(&local_def_id) + && let Some(target_param) = &struct_generics.params.get(idx) + && let GenericParamKind::Const { ty, .. } = &target_param.kind + && let TyKind::Path(_, path) = &ty.kind + { + let full_type = path + .segments + .iter() + .map(|seg| seg.ident.to_string()) + .collect::>() + .join("::"); + + // Find the first impl param whose position in C + // is strictly greater than our missing param's index + // e.g missing B(idx=1), impl has A(pos=0) and C(pos=2) + // C has pos=2 > 1 so insert before C + let next_impl_param = impl_.generics.params.iter().find(|impl_param| { + angle + .args + .iter() + .find_map(|arg| { + if let AngleBracketedArg::Arg(GenericArg::Type(ty)) = arg + && let TyKind::Path(_, path) = &ty.kind + && let [segment] = path.segments.as_slice() + && segment.ident == impl_param.ident + { + usage_to_pos.get(&segment.id).copied() + } else { + None + } + }) + .map_or(false, |pos| pos > idx) + }); + + let (insert_span, snippet) = match next_impl_param { + Some(next_param) => { + // Insert in the middle before next_param + // e.g impl -> impl + ( + next_param.span().shrink_to_lo(), + format!("const {}: {}, ", target_ident, full_type), + ) + } + None => match impl_.generics.params.last() { + Some(last) => { + // Append after last existing param + // e.g impl -> impl + ( + last.span().shrink_to_hi(), + format!(", const {}: {}", target_ident, full_type), + ) + } + None => { + // No generics at all on impl + // e.g impl Foo for C -> impl Foo for C + ( + impl_.generics.span.shrink_to_hi(), + format!("", target_ident, full_type), + ) + } + }, + }; + + let mut err = self.r.dcx().struct_span_err( + target_ident.span, + format!("cannot find const `{}` in this scope", target_ident), + ); + + err.code(E0425); + + err.span_label(target_ident.span, "not found in this scope"); + + err.span_label( + target_param.span(), + format!("corresponding const parameter on the type defined here",), + ); + + err.subdiagnostic(diagnostics::UnexpectedMissingConstParameter { + span: insert_span, + snippet, + item_name: format!("{}", target_ident), + item_location: String::from("impl"), + }); + + return Some(err); + } + + None + } + + pub(crate) fn suggest_adding_generic_parameter( + &mut self, + path: &[Segment], + source: PathSource<'_, 'ast, 'ra>, + ) -> (Option<(Span, &'static str, String, Applicability)>, Option>) { + let (ident, span) = match path { + [segment] + if !segment.has_generic_args + && segment.ident.name != kw::SelfUpper + && segment.ident.name != kw::Dyn => + { + (segment.ident.to_string(), segment.ident.span) + } + _ => return (None, None), + }; + let mut iter = ident.chars().map(|c| c.is_uppercase()); + let single_uppercase_char = + matches!(iter.next(), Some(true)) && matches!(iter.next(), None); + if !self.diag_metadata.currently_processing_generic_args && !single_uppercase_char { + return (None, None); + } + match ( + self.diag_metadata.current_item, + single_uppercase_char, + self.diag_metadata.currently_processing_generic_args, + ) { + (Some(Item { kind: ItemKind::Fn(fn_), .. }), _, _) if fn_.ident.name == sym::main => { + // Ignore `fn main()` as we don't want to suggest `fn main()` + } + ( + Some(Item { + kind: + kind @ ItemKind::Fn(..) + | kind @ ItemKind::Enum(..) + | kind @ ItemKind::Struct(..) + | kind @ ItemKind::Union(..), + .. + }), + true, + _, + ) + // Without the 2nd `true`, we'd suggest `impl ` for `impl T` when a type `T` isn't found + | (Some(Item { kind: kind @ ItemKind::Impl(..), .. }), true, true) + | (Some(Item { kind, .. }), false, _) => { + if let Some(generics) = kind.generics() { + if span.overlaps(generics.span) { + // Avoid the following: + // error[E0405]: cannot find trait `A` in this scope + // --> $DIR/typo-suggestion-named-underscore.rs:CC:LL + // | + // L | fn foo(x: T) {} // Shouldn't suggest underscore + // | ^- help: you might be missing a type parameter: `, A` + // | | + // | not found in this scope + return (None, None); + } + + let (msg, sugg) = match source { + PathSource::Type | PathSource::PreciseCapturingArg(TypeNS) => { + if let Some(err) = + self.detect_and_suggest_const_parameter_error(path, source) + { + return (None, Some(err)); + } + ("you might be missing a type parameter", ident) + } + PathSource::Expr(_) | PathSource::PreciseCapturingArg(ValueNS) => ( + "you might be missing a const parameter", + format!("const {ident}: /* Type */"), + ), + _ => return (None, None), + }; + let (span, sugg) = if let [.., param] = &generics.params[..] { + let span = if let [.., bound] = ¶m.bounds[..] { + bound.span() + } else if let GenericParam { + kind: GenericParamKind::Const { ty, span: _, default }, + .. + } = param + { + default.as_ref().map(|def| def.value.span).unwrap_or(ty.span) + } else { + param.ident.span + }; + (span, format!(", {sugg}")) + } else { + (generics.span, format!("<{sugg}>")) + }; + // Do not suggest if this is coming from macro expansion. + if span.can_be_used_for_suggestions() { + return ( + Some((span.shrink_to_hi(), msg, sugg, Applicability::MaybeIncorrect)), + None, + ); + } + } + } + _ => {} + } + (None, None) + } + + /// Given the target `label`, search the `rib_index`th label rib for similarly named labels, + /// optionally returning the closest match and whether it is reachable. + pub(crate) fn suggestion_for_label_in_rib( + &self, + rib_index: usize, + label: Ident, + ) -> Option { + // Are ribs from this `rib_index` within scope? + let within_scope = self.is_label_valid_from_rib(rib_index); + + let rib = &self.label_ribs[rib_index]; + let names = rib + .bindings + .iter() + .filter(|(id, _)| id.span.eq_ctxt(label.span)) + .map(|(id, _)| id.name) + .collect::>(); + + find_best_match_for_name(&names, label.name, None).map(|symbol| { + // Upon finding a similar name, get the ident that it was from - the span + // contained within helps make a useful diagnostic. In addition, determine + // whether this candidate is within scope. + let (ident, _) = rib.bindings.iter().find(|(ident, _)| ident.name == symbol).unwrap(); + (*ident, within_scope) + }) + } + + pub(crate) fn maybe_report_lifetime_uses( + &mut self, + generics_span: Span, + params: &[ast::GenericParam], + ) { + for (param_index, param) in params.iter().enumerate() { + let GenericParamKind::Lifetime = param.kind else { continue }; + + let def_id = self.r.local_def_id(param.id); + + let use_set = self.lifetime_uses.remove(&def_id); + debug!( + "Use set for {:?}({:?} at {:?}) is {:?}", + def_id, param.ident, param.ident.span, use_set + ); + + let deletion_span = || { + if params.len() == 1 { + // if sole lifetime, remove the entire `<>` brackets + Some(generics_span) + } else if param_index == 0 { + // if removing within `<>` brackets, we also want to + // delete a leading or trailing comma as appropriate + match ( + param.span().find_ancestor_inside(generics_span), + params[param_index + 1].span().find_ancestor_inside(generics_span), + ) { + (Some(param_span), Some(next_param_span)) => { + Some(param_span.to(next_param_span.shrink_to_lo())) + } + _ => None, + } + } else { + // if removing within `<>` brackets, we also want to + // delete a leading or trailing comma as appropriate + match ( + param.span().find_ancestor_inside(generics_span), + params[param_index - 1].span().find_ancestor_inside(generics_span), + ) { + (Some(param_span), Some(prev_param_span)) => { + Some(prev_param_span.shrink_to_hi().to(param_span)) + } + _ => None, + } + } + }; + match use_set { + Some(LifetimeUseSet::Many) => {} + // A lifetime bound is a real use of that lifetime parameter, even + // though visiting a bound like `'b: 'a` only records a use of `'a`. + Some(LifetimeUseSet::One { .. }) if !param.bounds.is_empty() => {} + Some(LifetimeUseSet::One { use_span, use_ctxt }) => { + let param_ident = param.ident; + let deletion_span = + if param.bounds.is_empty() { deletion_span() } else { None }; + self.r.lint_buffer.dyn_buffer_lint_any( + lint::builtin::SINGLE_USE_LIFETIMES, + param.id, + param_ident.span, + move |dcx, level, sess| { + debug!(?param_ident, ?param_ident.span, ?use_span); + + let elidable = matches!(use_ctxt, LifetimeCtxt::Ref); + let suggestion = if let Some(deletion_span) = deletion_span { + let (use_span, replace_lt) = if elidable { + let use_span = sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_extend_while_whitespace(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + debug!(?deletion_span, ?use_span); + + // issue 107998 for the case such as a wrong function pointer type + // `deletion_span` is empty and there is no need to report lifetime uses here + let deletion_span = if deletion_span.is_empty() { + None + } else { + Some(deletion_span) + }; + Some(diagnostics::SingleUseLifetimeSugg { + deletion_span, + use_span, + replace_lt, + }) + } else { + None + }; + diagnostics::SingleUseLifetime { + suggestion, + param_span: param_ident.span, + use_span, + ident: param_ident, + } + .into_diag(dcx, level) + }, + ); + } + None => { + debug!(?param.ident, ?param.ident.span); + let deletion_span = deletion_span(); + + // if the lifetime originates from expanded code, we won't be able to remove it #104432 + if deletion_span.is_some_and(|sp| !sp.in_derive_expansion()) { + self.r.lint_buffer.buffer_lint( + lint::builtin::UNUSED_LIFETIMES, + param.id, + param.ident.span, + diagnostics::UnusedLifetime { deletion_span, ident: param.ident }, + ); + } + } + } + } + } + + pub(crate) fn emit_undeclared_lifetime_error( + &self, + lifetime_ref: &ast::Lifetime, + outer_lifetime_ref: Option, + ) -> ErrorGuaranteed { + debug_assert_ne!(lifetime_ref.ident.name, kw::UnderscoreLifetime); + let mut err = if let Some(outer) = outer_lifetime_ref { + struct_span_code_err!( + self.r.dcx(), + lifetime_ref.ident.span, + E0401, + "can't use generic parameters from outer item", + ) + .with_span_label(lifetime_ref.ident.span, "use of generic parameter from outer item") + .with_span_label(outer.span, "lifetime parameter from outer item") + } else { + struct_span_code_err!( + self.r.dcx(), + lifetime_ref.ident.span, + E0261, + "use of undeclared lifetime name `{}`", + lifetime_ref.ident + ) + .with_span_label(lifetime_ref.ident.span, "undeclared lifetime") + }; + + // Check if this is a typo of `'static`. + if edit_distance(lifetime_ref.ident.name.as_str(), "'static", 2).is_some() { + err.span_suggestion_verbose( + lifetime_ref.ident.span, + "you may have misspelled the `'static` lifetime", + "'static", + Applicability::MachineApplicable, + ); + } else { + self.suggest_introducing_lifetime( + &mut err, + Some(lifetime_ref.ident), + |err, _, span, message, suggestion, span_suggs| { + err.multipart_suggestion( + message, + std::iter::once((span, suggestion)).chain(span_suggs).collect(), + Applicability::MaybeIncorrect, + ); + true + }, + ); + } + + err.emit() + } + + fn suggest_introducing_lifetime( + &self, + err: &mut Diag<'_>, + name: Option, + suggest: impl Fn( + &mut Diag<'_>, + bool, + Span, + Cow<'static, str>, + String, + Vec<(Span, String)>, + ) -> bool, + ) { + self.suggest_introducing_lifetime_filtered(err, name, |_| true, suggest); + } + + pub(crate) fn suggest_introducing_lifetime_for_assoc_ty_binding( + &self, + err: &mut Diag<'_>, + lifetime: Span, + ) { + self.suggest_introducing_lifetime_filtered( + err, + None, + |kind| { + !matches!( + kind, + LifetimeBinderKind::FnPtrType + | LifetimeBinderKind::PolyTrait + | LifetimeBinderKind::WhereBound + ) + }, + |err, _higher_ranked, span, message, intro_sugg, _| { + err.multipart_suggestion( + message, + vec![(span, intro_sugg), (lifetime.shrink_to_hi(), "'a ".to_string())], + Applicability::MaybeIncorrect, + ); + false + }, + ); + } + + fn suggest_introducing_lifetime_filtered( + &self, + err: &mut Diag<'_>, + name: Option, + mut consider: impl FnMut(LifetimeBinderKind) -> bool, + suggest: impl Fn( + &mut Diag<'_>, + bool, + Span, + Cow<'static, str>, + String, + Vec<(Span, String)>, + ) -> bool, + ) { + let mut suggest_note = true; + for rib in self.lifetime_ribs.iter().rev() { + let mut should_continue = true; + match rib.kind { + LifetimeRibKind::Generics { binder, span, kind } => { + // Avoid suggesting placing lifetime parameters on constant items unless the relevant + // feature is enabled. Suggest the parent item as a possible location if applicable. + if let LifetimeBinderKind::ConstItem = kind + && !self.r.tcx().features().generic_const_items() + { + continue; + } + if matches!(kind, LifetimeBinderKind::ImplAssocType) || !consider(kind) { + continue; + } + + if !span.can_be_used_for_suggestions() + && suggest_note + && let Some(name) = name + { + suggest_note = false; // Avoid displaying the same help multiple times. + err.span_label( + span, + format!( + "lifetime `{name}` is missing in item created through this procedural macro", + ), + ); + continue; + } + + let higher_ranked = matches!( + kind, + LifetimeBinderKind::FnPtrType + | LifetimeBinderKind::PolyTrait + | LifetimeBinderKind::WhereBound + ); + + let mut rm_inner_binders: FxIndexSet = Default::default(); + let (span, sugg) = if span.is_empty() { + let mut binder_idents: FxIndexSet = Default::default(); + binder_idents.insert(name.unwrap_or(Ident::from_str("'a"))); + + // We need to special case binders in the following situation: + // Change `T: for<'a> Trait + 'b` to `for<'a, 'b> T: Trait + 'b` + // T: for<'a> Trait + 'b + // ^^^^^^^ remove existing inner binder `for<'a>` + // for<'a, 'b> T: Trait + 'b + // ^^^^^^^^^^^ suggest outer binder `for<'a, 'b>` + if let LifetimeBinderKind::WhereBound = kind + && let Some(predicate) = self.diag_metadata.current_where_predicate + && let ast::WherePredicateKind::BoundPredicate( + ast::WhereBoundPredicate { bounded_ty, bounds, .. }, + ) = &predicate.kind + && bounded_ty.id == binder + { + for bound in bounds { + if let ast::GenericBound::Trait(poly_trait_ref) = bound + && let span = poly_trait_ref + .span + .with_hi(poly_trait_ref.trait_ref.path.span.lo()) + && !span.is_empty() + { + rm_inner_binders.insert(span); + poly_trait_ref.bound_generic_params.iter().for_each(|v| { + binder_idents.insert(v.ident); + }); + } + } + } + + let binders_sugg: String = std::iter::Iterator::intersperse( + binder_idents.into_iter().map(|ident| ident.to_string()), + ", ".to_owned(), + ) + .collect(); + let sugg = format!( + "{}<{}>{}", + if higher_ranked { "for" } else { "" }, + binders_sugg, + if higher_ranked { " " } else { "" }, + ); + (span, sugg) + } else { + let span = self + .r + .tcx + .sess + .source_map() + .span_through_char(span, '<') + .shrink_to_hi(); + let sugg = + format!("{}, ", name.map(|i| i.to_string()).as_deref().unwrap_or("'a")); + (span, sugg) + }; + + if higher_ranked { + let message = Cow::from(format!( + "consider making the {} lifetime-generic with a new `{}` lifetime", + kind.descr(), + name.map(|i| i.to_string()).as_deref().unwrap_or("'a"), + )); + should_continue = suggest( + err, + true, + span, + message, + sugg, + if !rm_inner_binders.is_empty() { + rm_inner_binders + .into_iter() + .map(|v| (v, "".to_string())) + .collect::>() + } else { + vec![] + }, + ); + err.note_once( + "for more information on higher-ranked polymorphism, visit \ + https://doc.rust-lang.org/nomicon/hrtb.html", + ); + } else if let Some(name) = name { + let message = + Cow::from(format!("consider introducing lifetime `{name}` here")); + should_continue = suggest(err, false, span, message, sugg, vec![]); + } else { + let message = Cow::from("consider introducing a named lifetime parameter"); + should_continue = suggest(err, false, span, message, sugg, vec![]); + } + } + LifetimeRibKind::Item | LifetimeRibKind::ConstParamTy => break, + _ => {} + } + if !should_continue { + break; + } + } + } + + pub(crate) fn emit_non_static_lt_in_const_param_ty_error( + &self, + lifetime_ref: &ast::Lifetime, + ) -> ErrorGuaranteed { + self.r + .dcx() + .create_err(diagnostics::ParamInTyOfConstParam { + span: lifetime_ref.ident.span, + name: lifetime_ref.ident.name, + }) + .emit() + } + + /// Non-static lifetimes are prohibited in anonymous constants under `min_const_generics`. + /// This function will emit an error if `generic_const_exprs` is not enabled, the body identified by + /// `body_id` is an anonymous constant and `lifetime_ref` is non-static. + pub(crate) fn emit_forbidden_non_static_lifetime_error( + &self, + cause: NoConstantGenericsReason, + lifetime_ref: &ast::Lifetime, + ) -> ErrorGuaranteed { + match cause { + NoConstantGenericsReason::IsEnumDiscriminant => self + .r + .dcx() + .create_err(diagnostics::ParamInEnumDiscriminant { + span: lifetime_ref.ident.span, + name: lifetime_ref.ident.name, + param_kind: diagnostics::ParamKindInEnumDiscriminant::Lifetime, + }) + .emit(), + NoConstantGenericsReason::NonTrivialConstArg => { + assert!(!self.r.features.generic_const_exprs()); + self.r + .dcx() + .create_err(diagnostics::ParamInNonTrivialAnonConst { + span: lifetime_ref.ident.span, + name: lifetime_ref.ident.name, + param_kind: diagnostics::ParamKindInNonTrivialAnonConst::Lifetime, + help: self.r.tcx.sess.is_nightly_build(), + is_gca: self.r.features.generic_const_args(), + help_gca: self.r.features.generic_const_args(), + }) + .emit() + } + } + } + + pub(crate) fn report_missing_lifetime_specifiers<'a>( + &mut self, + lifetime_refs: impl Clone + IntoIterator, + function_param_lifetimes: Option<(Vec, Vec)>, + ) -> ErrorGuaranteed { + let num_lifetimes: usize = lifetime_refs.clone().into_iter().map(|lt| lt.count).sum(); + let spans: Vec<_> = lifetime_refs.clone().into_iter().map(|lt| lt.span).collect(); + + let mut err = struct_span_code_err!( + self.r.dcx(), + spans, + E0106, + "missing lifetime specifier{}", + pluralize!(num_lifetimes) + ); + self.add_missing_lifetime_specifiers_label( + &mut err, + lifetime_refs, + function_param_lifetimes, + ); + err.emit() + } + + fn add_missing_lifetime_specifiers_label<'a>( + &mut self, + err: &mut Diag<'_>, + lifetime_refs: impl Clone + IntoIterator, + function_param_lifetimes: Option<(Vec, Vec)>, + ) { + for < in lifetime_refs.clone() { + err.span_label( + lt.span, + format!( + "expected {} lifetime parameter{}", + if lt.count == 1 { "named".to_string() } else { lt.count.to_string() }, + pluralize!(lt.count), + ), + ); + } + + let mut in_scope_lifetimes: Vec<_> = self + .lifetime_ribs + .iter() + .rev() + .take_while(|rib| { + !matches!(rib.kind, LifetimeRibKind::Item | LifetimeRibKind::ConstParamTy) + }) + .flat_map(|rib| rib.bindings.iter()) + .map(|(&ident, &res)| (ident, res)) + .filter(|(ident, _)| ident.name != kw::UnderscoreLifetime) + .collect(); + debug!(?in_scope_lifetimes); + + let mut maybe_static = false; + debug!(?function_param_lifetimes); + if let Some((param_lifetimes, params)) = &function_param_lifetimes { + let elided_len = param_lifetimes.len(); + let num_params = params.len(); + + let mut m = String::new(); + + for (i, info) in params.iter().enumerate() { + let ElisionFnParameter { ident, index, lifetime_count, span } = *info; + debug_assert_ne!(lifetime_count, 0); + + err.span_label(span, ""); + + if i != 0 { + if i + 1 < num_params { + m.push_str(", "); + } else if num_params == 2 { + m.push_str(" or "); + } else { + m.push_str(", or "); + } + } + + let help_name = if let Some(ident) = ident { + format!("`{ident}`") + } else { + format!("argument {}", index + 1) + }; + + if lifetime_count == 1 { + m.push_str(&help_name[..]) + } else { + m.push_str(&format!("one of {help_name}'s {lifetime_count} lifetimes")[..]) + } + } + + if num_params == 0 { + err.help( + "this function's return type contains a borrowed value, but there is no value \ + for it to be borrowed from", + ); + if in_scope_lifetimes.is_empty() { + maybe_static = true; + in_scope_lifetimes = vec![( + Ident::with_dummy_span(kw::StaticLifetime), + (DUMMY_NODE_ID, LifetimeRes::Static), + )]; + } + } else if elided_len == 0 { + err.help( + "this function's return type contains a borrowed value with an elided \ + lifetime, but the lifetime cannot be derived from the arguments", + ); + if in_scope_lifetimes.is_empty() { + maybe_static = true; + in_scope_lifetimes = vec![( + Ident::with_dummy_span(kw::StaticLifetime), + (DUMMY_NODE_ID, LifetimeRes::Static), + )]; + } + } else if num_params == 1 { + err.help(format!( + "this function's return type contains a borrowed value, but the signature does \ + not say which {m} it is borrowed from", + )); + } else { + err.help(format!( + "this function's return type contains a borrowed value, but the signature does \ + not say whether it is borrowed from {m}", + )); + } + } + + #[allow(rustc::symbol_intern_string_literal)] + let existing_name = match &in_scope_lifetimes[..] { + [] => Symbol::intern("'a"), + [(existing, _)] => existing.name, + _ => Symbol::intern("'lifetime"), + }; + + let mut spans_suggs: Vec<_> = Vec::new(); + let source_map = self.r.tcx.sess.source_map(); + let build_sugg = |lt: MissingLifetime| match lt.kind { + MissingLifetimeKind::Underscore => { + debug_assert_eq!(lt.count, 1); + (lt.span, existing_name.to_string()) + } + MissingLifetimeKind::Ampersand => { + debug_assert_eq!(lt.count, 1); + (lt.span.shrink_to_hi(), format!("{existing_name} ")) + } + MissingLifetimeKind::Comma => { + let sugg: String = std::iter::Iterator::intersperse( + std::iter::repeat_n(existing_name.as_str(), lt.count), + ", ", + ) + .collect(); + let is_empty_brackets = source_map.span_followed_by(lt.span, ">").is_some(); + let sugg = if is_empty_brackets { sugg } else { format!("{sugg}, ") }; + (lt.span.shrink_to_hi(), sugg) + } + MissingLifetimeKind::Brackets => { + let sugg: String = std::iter::once("<") + .chain(std::iter::Iterator::intersperse( + std::iter::repeat_n(existing_name.as_str(), lt.count), + ", ", + )) + .chain([">"]) + .collect(); + (lt.span.shrink_to_hi(), sugg) + } + }; + for < in lifetime_refs.clone() { + spans_suggs.push(build_sugg(lt)); + } + debug!(?spans_suggs); + match in_scope_lifetimes.len() { + 0 => { + if let Some((param_lifetimes, _)) = function_param_lifetimes { + for lt in param_lifetimes { + spans_suggs.push(build_sugg(lt)) + } + } + self.suggest_introducing_lifetime( + err, + None, + |err, higher_ranked, span, message, intro_sugg, _| { + err.multipart_suggestion( + message, + std::iter::once((span, intro_sugg)) + .chain(spans_suggs.clone()) + .collect(), + Applicability::MaybeIncorrect, + ); + higher_ranked + }, + ); + } + 1 => { + let post = if maybe_static { + let mut lifetime_refs = lifetime_refs.clone().into_iter(); + let owned = if let Some(lt) = lifetime_refs.next() + && lifetime_refs.next().is_none() + && lt.kind != MissingLifetimeKind::Ampersand + { + ", or if you will only have owned values" + } else { + "" + }; + format!( + ", but this is uncommon unless you're returning a borrowed value from a \ + `const` or a `static`{owned}", + ) + } else { + String::new() + }; + err.multipart_suggestion( + format!("consider using the `{existing_name}` lifetime{post}"), + spans_suggs, + Applicability::MaybeIncorrect, + ); + if maybe_static { + // FIXME: what follows are general suggestions, but we'd want to perform some + // minimal flow analysis to provide more accurate suggestions. For example, if + // we identified that the return expression references only one argument, we + // would suggest borrowing only that argument, and we'd skip the prior + // "use `'static`" suggestion entirely. + let mut lifetime_refs = lifetime_refs.clone().into_iter(); + if let Some(lt) = lifetime_refs.next() + && lifetime_refs.next().is_none() + && (lt.kind == MissingLifetimeKind::Ampersand + || lt.kind == MissingLifetimeKind::Underscore) + { + let pre = if let Some((kind, _span)) = self.diag_metadata.current_function + && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind + && !sig.decl.inputs.is_empty() + && let sugg = sig + .decl + .inputs + .iter() + .filter_map(|param| { + if param.ty.span.contains(lt.span) { + // We don't want to suggest `fn elision(_: &fn() -> &i32)` + // when we have `fn elision(_: fn() -> &i32)` + None + } else if let TyKind::CVarArgs = param.ty.kind { + // Don't suggest `&...` for ffi fn with varargs + None + } else if let TyKind::ImplTrait(..) = ¶m.ty.kind { + // We handle these in the next `else if` branch. + None + } else { + Some((param.ty.span.shrink_to_lo(), "&".to_string())) + } + }) + .collect::>() + && !sugg.is_empty() + { + let (the, s) = if sig.decl.inputs.len() == 1 { + ("the", "") + } else { + ("one of the", "s") + }; + let dotdotdot = + if lt.kind == MissingLifetimeKind::Ampersand { "..." } else { "" }; + err.multipart_suggestion( + format!( + "instead, you are more likely to want to change {the} \ + argument{s} to be borrowed{dotdotdot}", + ), + sugg, + Applicability::MaybeIncorrect, + ); + "...or alternatively, you might want" + } else if (lt.kind == MissingLifetimeKind::Ampersand + || lt.kind == MissingLifetimeKind::Underscore) + && let Some((kind, _span)) = self.diag_metadata.current_function + && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind + && let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output + && !sig.decl.inputs.is_empty() + && let arg_refs = sig + .decl + .inputs + .iter() + .filter_map(|param| match ¶m.ty.kind { + TyKind::ImplTrait(_, bounds) => Some(bounds), + _ => None, + }) + .flat_map(|bounds| bounds.into_iter()) + .collect::>() + && !arg_refs.is_empty() + { + // We have a situation like + // fn g(mut x: impl Iterator) -> Option<&()> + // So we look at every ref in the trait bound. If there's any, we + // suggest + // fn g<'a>(mut x: impl Iterator) -> Option<&'a ()> + let mut lt_finder = + LifetimeFinder { lifetime: lt.span, found: None, seen: vec![] }; + for bound in arg_refs { + if let ast::GenericBound::Trait(trait_ref) = bound { + lt_finder.visit_trait_ref(&trait_ref.trait_ref); + } + } + lt_finder.visit_ty(ret_ty); + let spans_suggs: Vec<_> = lt_finder + .seen + .iter() + .filter_map(|ty| match &ty.kind { + TyKind::Ref(_, mut_ty) => { + let span = ty.span.with_hi(mut_ty.ty.span.lo()); + Some((span, "&'a ".to_string())) + } + _ => None, + }) + .collect(); + self.suggest_introducing_lifetime( + err, + None, + |err, higher_ranked, span, message, intro_sugg, _| { + err.multipart_suggestion( + message, + std::iter::once((span, intro_sugg)) + .chain(spans_suggs.clone()) + .collect(), + Applicability::MaybeIncorrect, + ); + higher_ranked + }, + ); + "alternatively, you might want" + } else { + "instead, you are more likely to want" + }; + let mut owned_sugg = lt.kind == MissingLifetimeKind::Ampersand; + let mut sugg_is_str_to_string = false; + let mut sugg = vec![(lt.span, String::new())]; + if let Some((kind, _span)) = self.diag_metadata.current_function + && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind + { + let mut lt_finder = + LifetimeFinder { lifetime: lt.span, found: None, seen: vec![] }; + for param in &sig.decl.inputs { + lt_finder.visit_ty(¶m.ty); + } + if let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output { + lt_finder.visit_ty(ret_ty); + let mut ret_lt_finder = + LifetimeFinder { lifetime: lt.span, found: None, seen: vec![] }; + ret_lt_finder.visit_ty(ret_ty); + if let [Ty { span, kind: TyKind::Ref(_, mut_ty), .. }] = + &ret_lt_finder.seen[..] + { + // We might have a situation like + // fn g(mut x: impl Iterator) -> Option<&'_ ()> + // but `lt.span` only points at `'_`, so to suggest `-> Option<()>` + // we need to find a more accurate span to end up with + // fn g<'a>(mut x: impl Iterator) -> Option<()> + sugg = vec![(span.with_hi(mut_ty.ty.span.lo()), String::new())]; + owned_sugg = true; + } + } + if let Some(ty) = lt_finder.found { + if let TyKind::Path(None, path) = &ty.kind { + // Check if the path being borrowed is likely to be owned. + let path: Vec<_> = Segment::from_path(path); + match self.resolve_path( + &path, + Some(TypeNS), + None, + PathSource::Type, + ) { + PathResult::Module(ModuleOrUniformRoot::Module(module)) => { + match module.res() { + Some(Res::PrimTy(PrimTy::Str)) => { + // Don't suggest `-> str`, suggest `-> String`. + sugg = vec![( + lt.span.with_hi(ty.span.hi()), + "String".to_string(), + )]; + sugg_is_str_to_string = true; + } + Some(Res::PrimTy(..)) => {} + Some(Res::Def( + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::ForeignTy + | DefKind::AssocTy + | DefKind::OpaqueTy + | DefKind::TyParam, + _, + )) => {} + _ => { + // Do not suggest in all other cases. + owned_sugg = false; + } + } + } + PathResult::NonModule(res) => { + match res.base_res() { + Res::PrimTy(PrimTy::Str) => { + // Don't suggest `-> str`, suggest `-> String`. + sugg = vec![( + lt.span.with_hi(ty.span.hi()), + "String".to_string(), + )]; + sugg_is_str_to_string = true; + } + Res::PrimTy(..) => {} + Res::Def( + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::ForeignTy + | DefKind::AssocTy + | DefKind::OpaqueTy + | DefKind::TyParam, + _, + ) => {} + _ => { + // Do not suggest in all other cases. + owned_sugg = false; + } + } + } + _ => { + // Do not suggest in all other cases. + owned_sugg = false; + } + } + } + if let TyKind::Slice(inner_ty) = &ty.kind { + // Don't suggest `-> [T]`, suggest `-> Vec`. + sugg = vec![ + (lt.span.with_hi(inner_ty.span.lo()), "Vec<".to_string()), + (ty.span.with_lo(inner_ty.span.hi()), ">".to_string()), + ]; + } + } + } + if owned_sugg { + if let Some(span) = + self.find_ref_prefix_span_for_owned_suggestion(lt.span) + && !sugg_is_str_to_string + { + sugg = vec![(span, String::new())]; + } + err.multipart_suggestion( + format!("{pre} to return an owned value"), + sugg, + Applicability::MaybeIncorrect, + ); + } + } + } + } + _ => { + let lifetime_spans: Vec<_> = + in_scope_lifetimes.iter().map(|(ident, _)| ident.span).collect(); + err.span_note(lifetime_spans, "these named lifetimes are available to use"); + + if spans_suggs.len() > 0 { + // This happens when we have `Foo` where we point at the space before `T`, + // but this can be confusing so we give a suggestion with placeholders. + err.multipart_suggestion( + "consider using one of the available lifetimes here", + spans_suggs, + Applicability::HasPlaceholders, + ); + } + } + } + } + + fn find_ref_prefix_span_for_owned_suggestion(&self, lifetime: Span) -> Option { + let mut finder = RefPrefixSpanFinder { lifetime, span: None }; + if let Some(item) = self.diag_metadata.current_item { + finder.visit_item(item); + } else if let Some((kind, _span)) = self.diag_metadata.current_function + && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind + { + for param in &sig.decl.inputs { + finder.visit_ty(¶m.ty); + } + if let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output { + finder.visit_ty(ret_ty); + } + } + finder.span + } +} + +fn mk_where_bound_predicate( + path: &Path, + poly_trait_ref: &ast::PolyTraitRef, + ty: &Ty, +) -> Option { + let modified_segments = { + let mut segments = path.segments.clone(); + let [preceding @ .., second_last, last] = segments.as_mut_slice() else { + return None; + }; + let mut segments = ThinVec::from(preceding); + + let added_constraint = ast::AngleBracketedArg::Constraint(ast::AssocItemConstraint { + id: DUMMY_NODE_ID, + ident: last.ident, + gen_args: None, + kind: ast::AssocItemConstraintKind::Equality { + term: ast::Term::Ty(Box::new(ast::Ty { + kind: ast::TyKind::Path(None, poly_trait_ref.trait_ref.path.clone()), + id: DUMMY_NODE_ID, + span: DUMMY_SP, + tokens: None, + })), + }, + span: DUMMY_SP, + }); + + match second_last.args.as_deref_mut() { + Some(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { args, .. })) => { + args.push(added_constraint); + } + Some(_) => return None, + None => { + second_last.args = + Some(Box::new(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { + args: ThinVec::from([added_constraint]), + span: DUMMY_SP, + }))); + } + } + + segments.push(second_last.clone()); + segments + }; + + let new_where_bound_predicate = ast::WhereBoundPredicate { + bound_generic_params: ThinVec::new(), + bounded_ty: Box::new(ty.clone()), + bounds: thin_vec![ast::GenericBound::Trait(ast::PolyTraitRef { + bound_generic_params: ThinVec::new(), + modifiers: ast::TraitBoundModifiers::NONE, + trait_ref: ast::TraitRef { + path: ast::Path { segments: modified_segments, span: DUMMY_SP, tokens: None }, + ref_id: DUMMY_NODE_ID, + }, + span: DUMMY_SP, + parens: ast::Parens::No, + })], + }; + + Some(new_where_bound_predicate) +} + +/// Report lifetime/lifetime shadowing as an error. +pub(crate) fn signal_lifetime_shadowing( + sess: &Session, + orig: Ident, + shadower: Ident, +) -> ErrorGuaranteed { + struct_span_code_err!( + sess.dcx(), + shadower.span, + E0496, + "lifetime name `{}` shadows a lifetime name that is already in scope", + orig.name, + ) + .with_span_label(orig.span, "first declared here") + .with_span_label(shadower.span, format!("lifetime `{}` already in scope", orig.name)) + .emit() +} + +struct LifetimeFinder<'ast> { + lifetime: Span, + found: Option<&'ast Ty>, + seen: Vec<&'ast Ty>, +} + +impl<'ast> Visitor<'ast> for LifetimeFinder<'ast> { + fn visit_ty(&mut self, t: &'ast Ty) { + if let TyKind::Ref(_, mut_ty) | TyKind::PinnedRef(_, mut_ty) = &t.kind { + self.seen.push(t); + if t.span.lo() == self.lifetime.lo() { + self.found = Some(&mut_ty.ty); + } + } + walk_ty(self, t) + } +} + +struct RefPrefixSpanFinder { + lifetime: Span, + span: Option, +} + +impl<'ast> Visitor<'ast> for RefPrefixSpanFinder { + fn visit_ty(&mut self, t: &'ast Ty) { + if self.span.is_some() { + return; + } + if let TyKind::Ref(_, mut_ty) | TyKind::PinnedRef(_, mut_ty) = &t.kind + && t.span.lo() == self.lifetime.lo() + { + self.span = Some(t.span.with_hi(mut_ty.ty.span.lo())); + return; + } + walk_ty(self, t); + } +} + +/// Shadowing involving a label is only a warning for historical reasons. +//FIXME: make this a proper lint. +pub(crate) fn signal_label_shadowing(sess: &Session, orig: Span, shadower: Ident) { + let name = shadower.name; + let shadower = shadower.span; + sess.dcx() + .struct_span_warn( + shadower, + format!("label name `{name}` shadows a label name that is already in scope"), + ) + .with_span_label(orig, "first declared here") + .with_span_label(shadower, format!("label `{name}` already in scope")) + .emit(); +} + +struct ParentPathVisitor<'a> { + target: Ident, + parent: Option<&'a PathSegment>, + stack: Vec<&'a Ty>, +} + +impl<'a> ParentPathVisitor<'a> { + fn new(self_ty: &'a Ty, target: Ident) -> Self { + let mut v = ParentPathVisitor { target, parent: None, stack: Vec::new() }; + + v.visit_ty(self_ty); + v + } +} + +impl<'a> Visitor<'a> for ParentPathVisitor<'a> { + fn visit_ty(&mut self, ty: &'a Ty) { + if self.parent.is_some() { + return; + } + + // push current type + self.stack.push(ty); + + if let TyKind::Path(_, path) = &ty.kind + // is this just `N`? + && let [segment] = path.segments.as_slice() + && segment.ident == self.target + // parent is previous element in stack + && let [.., parent_ty, _ty] = self.stack.as_slice() + && let TyKind::Path(_, parent_path) = &parent_ty.kind + { + self.parent = parent_path.segments.first(); + } + + walk_ty(self, ty); + + self.stack.pop(); + } +} diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 4ae56ac7c6f3d..11a9b4362d04c 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -39,16 +39,13 @@ use smallvec::{SmallVec, smallvec}; use thin_vec::ThinVec; use tracing::{debug, instrument, trace}; +use crate::diagnostics::impls::{ElisionFnParameter, LifetimeElisionCandidate, MissingLifetime}; use crate::{ BindingError, BindingKey, Decl, DelegationFnSig, Finalize, IdentKey, LateDecl, LocalModule, Module, ModuleOrUniformRoot, ParentScope, PathResult, Res, ResolutionError, Resolver, Segment, - Stage, TyCtxt, UseError, Used, path_names_to_string, rustdoc, with_owner, + Stage, TyCtxt, UseError, Used, diagnostics, path_names_to_string, rustdoc, with_owner, }; -mod diagnostics; - -use diagnostics::{ElisionFnParameter, LifetimeElisionCandidate, MissingLifetime}; - #[derive(Copy, Clone, Debug)] struct BindingInfo { span: Span, @@ -82,7 +79,7 @@ enum AnonConstKind { } impl PatternSource { - fn descr(self) -> &'static str { + pub(crate) fn descr(self) -> &'static str { match self { PatternSource::Match => "match binding", PatternSource::Let => "let binding", @@ -301,13 +298,13 @@ impl<'ra, R> Rib<'ra, R> { } #[derive(Clone, Copy, Debug)] -enum LifetimeUseSet { +pub(crate) enum LifetimeUseSet { One { use_span: Span, use_ctxt: visit::LifetimeCtxt }, Many, } #[derive(Copy, Clone, Debug)] -enum LifetimeRibKind { +pub(crate) enum LifetimeRibKind { // -- Ribs introducing named lifetimes // /// This rib declares generic parameters. @@ -376,7 +373,7 @@ impl LifetimeRibKind { } #[derive(Copy, Clone, Debug)] -enum LifetimeBinderKind { +pub(crate) enum LifetimeBinderKind { FnPtrType, PolyTrait, WhereBound, @@ -392,7 +389,7 @@ enum LifetimeBinderKind { } impl LifetimeBinderKind { - fn descr(self) -> &'static str { + pub(crate) fn descr(self) -> &'static str { use LifetimeBinderKind::*; match self { FnPtrType => "type", @@ -408,14 +405,14 @@ impl LifetimeBinderKind { } #[derive(Debug)] -struct LifetimeRib { - kind: LifetimeRibKind, +pub(crate) struct LifetimeRib { + pub(crate) kind: LifetimeRibKind, // We need to preserve insertion order for async fns. - bindings: FxIndexMap, + pub(crate) bindings: FxIndexMap, } impl LifetimeRib { - fn new(kind: LifetimeRibKind) -> LifetimeRib { + pub(crate) fn new(kind: LifetimeRibKind) -> LifetimeRib { LifetimeRib { bindings: Default::default(), kind } } } @@ -462,7 +459,7 @@ pub(crate) enum PathSource<'a, 'ast, 'ra> { } impl PathSource<'_, '_, '_> { - fn namespace(self) -> Namespace { + pub(crate) fn namespace(self) -> Namespace { match self { PathSource::Type | PathSource::Trait(_) @@ -481,7 +478,7 @@ impl PathSource<'_, '_, '_> { } } - fn defer_to_typeck(self) -> bool { + pub(crate) fn defer_to_typeck(self) -> bool { match self { PathSource::Type | PathSource::Expr(..) @@ -500,7 +497,7 @@ impl PathSource<'_, '_, '_> { } } - fn descr_expected(self) -> &'static str { + pub(crate) fn descr_expected(self) -> &'static str { match &self { PathSource::DefineOpaques => "type alias or associated type with opaqaue types", PathSource::Type => "type", @@ -544,7 +541,7 @@ impl PathSource<'_, '_, '_> { } } - fn is_call(self) -> bool { + pub(crate) fn is_call(self) -> bool { matches!(self, PathSource::Expr(Some(&Expr { kind: ExprKind::Call(..), .. }))) } @@ -652,7 +649,7 @@ impl PathSource<'_, '_, '_> { } } - fn error_code(self, has_unexpected_resolution: bool) -> ErrCode { + pub(crate) fn error_code(self, has_unexpected_resolution: bool) -> ErrCode { match (self, has_unexpected_resolution) { (PathSource::Trait(_), true) => E0404, (PathSource::Trait(_), false) => E0405, @@ -726,112 +723,112 @@ pub(crate) struct UnnecessaryQualification<'ra> { #[derive(Default, Debug)] pub(crate) struct DiagMetadata<'ast> { /// The current trait's associated items' ident, used for diagnostic suggestions. - current_trait_assoc_items: Option<&'ast [Box]>, + pub(crate) current_trait_assoc_items: Option<&'ast [Box]>, /// The current self type if inside an impl (used for better errors). pub(crate) current_self_type: Option<&'ast Ty>, /// The current self item if inside an ADT (used for better errors). - current_self_item: Option, + pub(crate) current_self_item: Option, /// The current item being evaluated (used for suggestions and more detail in errors). pub(crate) current_item: Option<&'ast Item>, /// When processing generic arguments and encountering an unresolved ident not found, /// suggest introducing a type or const param depending on the context. - currently_processing_generic_args: bool, + pub(crate) currently_processing_generic_args: bool, /// The current enclosing (non-closure) function (used for better errors). - current_function: Option<(FnKind<'ast>, Span)>, + pub(crate) current_function: Option<(FnKind<'ast>, Span)>, /// A list of labels as of yet unused. Labels will be removed from this map when /// they are used (in a `break` or `continue` statement) - unused_labels: FxIndexMap, + pub(crate) unused_labels: FxIndexMap, /// Only used for better errors on `let : ;`. - current_let_binding: Option<(Span, Option, Option)>, + pub(crate) current_let_binding: Option<(Span, Option, Option)>, - current_pat: Option<&'ast Pat>, + pub(crate) current_pat: Option<&'ast Pat>, /// Used to detect possible `if let` written without `let` and to provide structured suggestion. - in_if_condition: Option<&'ast Expr>, + pub(crate) in_if_condition: Option<&'ast Expr>, /// Used to detect possible new binding written without `let` and to provide structured suggestion. - in_assignment: Option<&'ast Expr>, - is_assign_rhs: bool, + pub(crate) in_assignment: Option<&'ast Expr>, + pub(crate) is_assign_rhs: bool, /// If we are setting an associated type in trait impl, is it a non-GAT type? - in_non_gat_assoc_type: Option, + pub(crate) in_non_gat_assoc_type: Option, /// Used to detect possible `.` -> `..` typo when calling methods. - in_range: Option<(&'ast Expr, &'ast Expr)>, + pub(crate) in_range: Option<(&'ast Expr, &'ast Expr)>, /// If we are currently in a trait object definition. Used to point at the bounds when /// encountering a struct or enum. - current_trait_object: Option<&'ast [ast::GenericBound]>, + pub(crate) current_trait_object: Option<&'ast [ast::GenericBound]>, /// Given `where ::Baz: String`, suggest `where T: Bar`. - current_where_predicate: Option<&'ast WherePredicate>, + pub(crate) current_where_predicate: Option<&'ast WherePredicate>, /// Whether we are visiting an associated type equality binding like `Trait`. - in_assoc_ty_binding: bool, + pub(crate) in_assoc_ty_binding: bool, - current_type_path: Option<&'ast Ty>, + pub(crate) current_type_path: Option<&'ast Ty>, /// The current impl items (used to suggest). - current_impl_items: Option<&'ast [Box]>, + pub(crate) current_impl_items: Option<&'ast [Box]>, /// The current impl items (used to suggest). - current_impl_item: Option<&'ast AssocItem>, + pub(crate) current_impl_item: Option<&'ast AssocItem>, /// When processing impl trait - currently_processing_impl_trait: Option<(TraitRef, Ty)>, + pub(crate) currently_processing_impl_trait: Option<(TraitRef, Ty)>, /// Accumulate the errors due to missed lifetime elision, /// and report them all at once for each function. - current_elision_failures: Vec<(MissingLifetime, Either>)>, + pub(crate) current_elision_failures: Vec<(MissingLifetime, Either>)>, } -struct LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { - r: &'a mut Resolver<'ra, 'tcx>, +pub(crate) struct LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { + pub(crate) r: &'a mut Resolver<'ra, 'tcx>, /// The module that represents the current item scope. - parent_scope: ParentScope<'ra>, + pub(crate) parent_scope: ParentScope<'ra>, /// The current set of local scopes for types and values. - ribs: PerNS>>, + pub(crate) ribs: PerNS>>, /// Previous popped `rib`, only used for diagnostic. - last_block_rib: Option>, + pub(crate) last_block_rib: Option>, /// The current set of local scopes, for labels. - label_ribs: Vec>, + pub(crate) label_ribs: Vec>, /// The current set of local scopes for lifetimes. - lifetime_ribs: Vec, + pub(crate) lifetime_ribs: Vec, /// We are looking for lifetimes in an elision context. /// The set contains all the resolutions that we encountered so far. /// They will be used to determine the correct lifetime for the fn return type. /// The `LifetimeElisionCandidate` is used for diagnostics, to suggest introducing named /// lifetimes. - lifetime_elision_candidates: Option>, + pub(crate) lifetime_elision_candidates: Option>, /// The trait that the current context can refer to. - current_trait_ref: Option<(Module<'ra>, TraitRef)>, + pub(crate) current_trait_ref: Option<(Module<'ra>, TraitRef)>, /// Fields used to add information to diagnostic errors. - diag_metadata: Box>, + pub(crate) diag_metadata: Box>, /// State used to know whether to ignore resolution errors for function bodies. /// /// In particular, rustdoc uses this to avoid giving errors for `cfg()` items. /// In most cases this will be `None`, in which case errors will always be reported. /// If it is `true`, then it will be updated when entering a nested function or trait body. - in_func_body: bool, + pub(crate) in_func_body: bool, /// Count the number of places a lifetime is used. - lifetime_uses: FxHashMap, + pub(crate) lifetime_uses: FxHashMap, } impl<'ra, 'tcx> AsRef> for LateResolutionVisitor<'_, '_, 'ra, 'tcx> { @@ -1563,7 +1560,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ) } - fn resolve_path( + pub(crate) fn resolve_path( &mut self, path: &[Segment], opt_ns: Option, // `None` indicates a module path in import @@ -2738,7 +2735,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } /// Determine whether or not a label from the `rib_index`th label rib is reachable. - fn is_label_valid_from_rib(&self, rib_index: usize) -> bool { + pub(crate) fn is_label_valid_from_rib(&self, rib_index: usize) -> bool { let ribs = &self.label_ribs[rib_index + 1..]; ribs.iter().all(|rib| !rib.kind.is_label_barrier()) } @@ -3123,7 +3120,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { if let GenericParamKind::Lifetime = param.kind && let Some(&original) = seen_lifetimes.get(&ident) { - let guar = diagnostics::signal_lifetime_shadowing( + let guar = diagnostics::impls::signal_lifetime_shadowing( self.r.tcx.sess, original, param.ident, @@ -4808,13 +4805,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { partial_res } - fn self_type_is_available(&mut self) -> bool { + pub(crate) fn self_type_is_available(&mut self) -> bool { let binding = self .maybe_resolve_ident_in_lexical_scope(Ident::with_dummy_span(kw::SelfUpper), TypeNS); if let Some(LateDecl::RibDef(res)) = binding { res != Res::Err } else { false } } - fn self_value_is_available(&mut self, self_span: Span) -> bool { + pub(crate) fn self_value_is_available(&mut self, self_span: Span) -> bool { let ident = Ident::new(kw::SelfLower, self_span); let binding = self.maybe_resolve_ident_in_lexical_scope(ident, ValueNS); if let Some(LateDecl::RibDef(res)) = binding { res != Res::Err } else { false } @@ -4839,7 +4836,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } // Resolve in alternative namespaces if resolution in the primary namespace fails. - fn resolve_qpath_anywhere( + pub(crate) fn resolve_qpath_anywhere( &mut self, qself: &Option>, path: &[Segment], @@ -5041,7 +5038,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } if let Ok((_, orig_span)) = self.resolve_label(label.ident) { - diagnostics::signal_label_shadowing(self.r.tcx.sess, orig_span, label.ident) + diagnostics::impls::signal_label_shadowing(self.r.tcx.sess, orig_span, label.ident) } self.with_label_rib(RibKind::Normal, |this| { diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs deleted file mode 100644 index 4e901cd76b92e..0000000000000 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ /dev/null @@ -1,4650 +0,0 @@ -// ignore-tidy-filelength - -use std::borrow::Cow; -use std::iter; -use std::ops::Deref; - -use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty}; -use rustc_ast::{ - self as ast, AngleBracketedArg, AssocItemKind, DUMMY_NODE_ID, Expr, ExprKind, GenericArg, - GenericArgs, GenericParam, GenericParamKind, Item, ItemKind, MethodCall, NodeId, Path, - PathSegment, Ty, TyKind, -}; -use rustc_ast_pretty::pprust::{path_to_string, where_bound_predicate_to_string}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; -use rustc_data_structures::unord::UnordItems; -use rustc_errors::codes::*; -use rustc_errors::{ - Applicability, Diag, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, - struct_span_code_err, -}; -use rustc_hir as hir; -use rustc_hir::def::Namespace::{self, *}; -use rustc_hir::def::{CtorKind, CtorOf, DefKind, MacroKinds}; -use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; -use rustc_hir::{MissingLifetimeKind, PrimTy, find_attr}; -use rustc_middle::ty; -use rustc_session::{Session, lint}; -use rustc_span::edit_distance::{edit_distance, find_best_match_for_name}; -use rustc_span::edition::Edition; -use rustc_span::{DUMMY_SP, DesugaringKind, Ident, Span, Symbol, kw, sym}; -use thin_vec::{ThinVec, thin_vec}; -use tracing::debug; - -use super::NoConstantGenericsReason; -use crate::diagnostics::impls::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; -use crate::late::{ - AliasPossibility, LateResolutionVisitor, LifetimeBinderKind, LifetimeRes, LifetimeRibKind, - LifetimeUseSet, QSelf, RibKind, -}; -use crate::ty::fast_reject::SimplifiedType; -use crate::{ - Finalize, Module, ModuleOrUniformRoot, ParentScope, PathResult, PathSource, Res, Resolver, - ScopeSet, Segment, diagnostics, path_names_to_string, -}; - -/// A field or associated item from self type suggested in case of resolution failure. -enum AssocSuggestion { - Field(Span), - MethodWithSelf { called: bool }, - AssocFn { called: bool }, - AssocType, - AssocConst, -} - -impl AssocSuggestion { - fn action(&self) -> &'static str { - match self { - AssocSuggestion::Field(_) => "use the available field", - AssocSuggestion::MethodWithSelf { called: true } => { - "call the method with the fully-qualified path" - } - AssocSuggestion::MethodWithSelf { called: false } => { - "refer to the method with the fully-qualified path" - } - AssocSuggestion::AssocFn { called: true } => "call the associated function", - AssocSuggestion::AssocFn { called: false } => "refer to the associated function", - AssocSuggestion::AssocConst => "use the associated `const`", - AssocSuggestion::AssocType => "use the associated type", - } - } -} - -fn is_self_type(path: &[Segment], namespace: Namespace) -> bool { - namespace == TypeNS && path.len() == 1 && path[0].ident.name == kw::SelfUpper -} - -fn is_self_value(path: &[Segment], namespace: Namespace) -> bool { - namespace == ValueNS && path.len() == 1 && path[0].ident.name == kw::SelfLower -} - -fn path_to_string_without_assoc_item_bindings(path: &Path) -> String { - let mut path = path.clone(); - for segment in &mut path.segments { - let mut remove_args = false; - if let Some(args) = segment.args.as_deref_mut() - && let ast::GenericArgs::AngleBracketed(angle_bracketed) = args - { - angle_bracketed.args.retain(|arg| matches!(arg, ast::AngleBracketedArg::Arg(_))); - remove_args = angle_bracketed.args.is_empty(); - } - if remove_args { - segment.args = None; - } - } - path_to_string(&path) -} - -/// Gets the stringified path for an enum from an `ImportSuggestion` for an enum variant. -fn import_candidate_to_enum_paths(suggestion: &ImportSuggestion) -> (String, String) { - let variant_path = &suggestion.path; - let variant_path_string = path_names_to_string(variant_path); - - let path_len = suggestion.path.segments.len(); - let enum_path = ast::Path { - span: suggestion.path.span, - segments: suggestion.path.segments[0..path_len - 1].iter().cloned().collect(), - tokens: None, - }; - let enum_path_string = path_names_to_string(&enum_path); - - (variant_path_string, enum_path_string) -} - -/// Description of an elided lifetime. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(super) struct MissingLifetime { - /// Used to overwrite the resolution with the suggestion, to avoid cascading errors. - pub id: NodeId, - /// As we cannot yet emit lints in this crate and have to buffer them instead, - /// we need to associate each lint with some `NodeId`, - /// however for some `MissingLifetime`s their `NodeId`s are "fake", - /// in a sense that they are temporary and not get preserved down the line, - /// which means that the lints for those nodes will not get emitted. - /// To combat this, we can try to use some other `NodeId`s as a fallback option. - pub id_for_lint: NodeId, - /// Where to suggest adding the lifetime. - pub span: Span, - /// How the lifetime was introduced, to have the correct space and comma. - pub kind: MissingLifetimeKind, - /// Number of elided lifetimes, used for elision in path. - pub count: usize, -} - -/// Description of the lifetimes appearing in a function parameter. -/// This is used to provide a literal explanation to the elision failure. -#[derive(Debug)] -pub(super) struct ElisionFnParameter { - /// The index of the argument in the original definition. - pub index: usize, - /// The name of the argument if it's a simple ident. - pub ident: Option, - /// The number of lifetimes in the parameter. - pub lifetime_count: usize, - /// The span of the parameter. - pub span: Span, -} - -/// Description of lifetimes that appear as candidates for elision. -/// This is used to suggest introducing an explicit lifetime. -#[derive(Clone, Copy, Debug)] -pub(super) enum LifetimeElisionCandidate { - /// This is not a real lifetime, or it is a named lifetime, in which case we won't suggest anything. - Ignore, - Missing(MissingLifetime), -} - -/// Only used for diagnostics. -#[derive(Debug)] -struct BaseError { - msg: String, - fallback_label: String, - span: Span, - span_label: Option<(Span, &'static str)>, - could_be_expr: bool, - suggestion: Option<(Span, &'static str, String)>, - module: Option, -} - -#[derive(Debug)] -enum TypoCandidate { - Typo(TypoSuggestion), - Shadowed(Res, Option), - None, -} - -impl TypoCandidate { - fn to_opt_suggestion(self) -> Option { - match self { - TypoCandidate::Typo(sugg) => Some(sugg), - TypoCandidate::Shadowed(_, _) | TypoCandidate::None => None, - } - } -} - -impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { - fn trait_assoc_type_def_id_by_name( - &mut self, - trait_def_id: DefId, - assoc_name: Symbol, - ) -> Option { - let module = self.r.get_module(trait_def_id)?; - self.r.resolutions(module).borrow().iter().find_map(|(key, resolution)| { - if key.ident.name != assoc_name { - return None; - } - let resolution = resolution.borrow(); - let binding = resolution.best_decl()?; - match binding.res() { - Res::Def(DefKind::AssocTy, def_id) => Some(def_id), - _ => None, - } - }) - } - - /// This does best-effort work to generate suggestions for associated types. - fn suggest_assoc_type_from_bounds( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, 'ast, 'ra>, - path: &[Segment], - ident_span: Span, - ) -> bool { - // Filter out cases where we cannot emit meaningful suggestions. - if source.namespace() != TypeNS { - return false; - } - let [segment] = path else { return false }; - if segment.has_generic_args { - return false; - } - if !ident_span.can_be_used_for_suggestions() { - return false; - } - let assoc_name = segment.ident.name; - if assoc_name == kw::Underscore { - return false; - } - - // Map: type parameter name -> (trait def id -> (assoc type def id, trait paths as written)). - // We keep a set of paths per trait so we can detect cases like - // `T: Trait + Trait` where suggesting `T::Assoc` would be ambiguous. - let mut matching_bounds: FxIndexMap< - Symbol, - FxIndexMap)>, - > = FxIndexMap::default(); - - let mut record_bound = |this: &mut Self, - ty_param: Symbol, - poly_trait_ref: &ast::PolyTraitRef| { - // Avoid generating suggestions we can't print in a well-formed way. - if !poly_trait_ref.bound_generic_params.is_empty() { - return; - } - if poly_trait_ref.modifiers != ast::TraitBoundModifiers::NONE { - return; - } - let Some(trait_seg) = poly_trait_ref.trait_ref.path.segments.last() else { - return; - }; - let Some(partial_res) = this.r.partial_res_map.get(&trait_seg.id) else { - return; - }; - let Some(trait_def_id) = partial_res.full_res().and_then(|res| res.opt_def_id()) else { - return; - }; - let Some(assoc_type_def_id) = - this.trait_assoc_type_def_id_by_name(trait_def_id, assoc_name) - else { - return; - }; - - // Preserve `::` and generic args so we don't generate broken suggestions like - // `::Assoc` for bounds written as `T: ::Foo<'a>`, while stripping - // associated-item bindings that are rejected in qualified paths. - let trait_path = - path_to_string_without_assoc_item_bindings(&poly_trait_ref.trait_ref.path); - let trait_bounds = matching_bounds.entry(ty_param).or_default(); - let trait_bounds = trait_bounds - .entry(trait_def_id) - .or_insert_with(|| (assoc_type_def_id, FxIndexSet::default())); - debug_assert_eq!(trait_bounds.0, assoc_type_def_id); - trait_bounds.1.insert(trait_path); - }; - - let mut record_from_generics = |this: &mut Self, generics: &ast::Generics| { - for param in &generics.params { - let ast::GenericParamKind::Type { .. } = param.kind else { continue }; - for bound in ¶m.bounds { - let ast::GenericBound::Trait(poly_trait_ref) = bound else { continue }; - record_bound(this, param.ident.name, poly_trait_ref); - } - } - - for predicate in &generics.where_clause.predicates { - let ast::WherePredicateKind::BoundPredicate(where_bound) = &predicate.kind else { - continue; - }; - - let ast::TyKind::Path(None, bounded_path) = &where_bound.bounded_ty.kind else { - continue; - }; - let [ast::PathSegment { ident, args: None, .. }] = &bounded_path.segments[..] - else { - continue; - }; - - // Only suggest for bounds that are explicitly on an in-scope type parameter. - let Some(partial_res) = this.r.partial_res_map.get(&where_bound.bounded_ty.id) - else { - continue; - }; - if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::TyParam, _))) { - continue; - } - - for bound in &where_bound.bounds { - let ast::GenericBound::Trait(poly_trait_ref) = bound else { continue }; - record_bound(this, ident.name, poly_trait_ref); - } - } - }; - - if let Some(item) = self.diag_metadata.current_item - && let Some(generics) = item.kind.generics() - { - record_from_generics(self, generics); - } - - if let Some(item) = self.diag_metadata.current_item - && matches!(item.kind, ItemKind::Impl(..)) - && let Some(assoc) = self.diag_metadata.current_impl_item - { - let generics = match &assoc.kind { - AssocItemKind::Const(ast::ConstItem { generics, .. }) - | AssocItemKind::Fn(ast::Fn { generics, .. }) - | AssocItemKind::Type(ast::TyAlias { generics, .. }) => Some(generics), - AssocItemKind::Delegation(..) - | AssocItemKind::MacCall(..) - | AssocItemKind::DelegationMac(..) => None, - }; - if let Some(generics) = generics { - record_from_generics(self, generics); - } - } - - let mut suggestions: FxIndexSet = FxIndexSet::default(); - for (ty_param, traits) in matching_bounds { - let ty_param = ty_param.to_ident_string(); - let trait_paths_len: usize = traits.values().map(|(_, paths)| paths.len()).sum(); - if traits.len() == 1 && trait_paths_len == 1 { - let assoc_type_def_id = traits.values().next().unwrap().0; - let assoc_segment = format!( - "{}{}", - assoc_name, - self.r.item_required_generic_args_suggestion(assoc_type_def_id) - ); - suggestions.insert(format!("{ty_param}::{assoc_segment}")); - } else { - for (assoc_type_def_id, trait_paths) in traits.into_values() { - let assoc_segment = format!( - "{}{}", - assoc_name, - self.r.item_required_generic_args_suggestion(assoc_type_def_id) - ); - for trait_path in trait_paths { - suggestions - .insert(format!("<{ty_param} as {trait_path}>::{assoc_segment}")); - } - } - } - } - - if suggestions.is_empty() { - return false; - } - - let mut suggestions: Vec = suggestions.into_iter().collect(); - suggestions.sort(); - - err.span_suggestions_with_style( - ident_span, - "you might have meant to use an associated type of the same name", - suggestions, - Applicability::MaybeIncorrect, - SuggestionStyle::ShowAlways, - ); - - true - } - - fn make_base_error( - &mut self, - path: &[Segment], - span: Span, - source: PathSource<'_, 'ast, 'ra>, - res: Option, - ) -> BaseError { - // Make the base error. - let mut expected = source.descr_expected(); - let path_str = Segment::names_to_string(path); - let item_str = path.last().unwrap().ident; - - if let Some(res) = res { - BaseError { - msg: format!("expected {}, found {} `{}`", expected, res.descr(), path_str), - fallback_label: format!("not a {expected}"), - span, - span_label: match res { - Res::Def(DefKind::TyParam, def_id) => { - Some((self.r.def_span(def_id), "found this type parameter")) - } - _ => None, - }, - could_be_expr: match res { - Res::Def(DefKind::Fn, _) => { - // Verify whether this is a fn call or an Fn used as a type. - self.r - .tcx - .sess - .source_map() - .span_to_snippet(span) - .is_ok_and(|snippet| snippet.ends_with(')')) - } - Res::Def( - DefKind::Ctor(..) - | DefKind::AssocFn - | DefKind::Const { .. } - | DefKind::AssocConst { .. }, - _, - ) - | Res::SelfCtor(_) - | Res::PrimTy(_) - | Res::Local(_) => true, - _ => false, - }, - suggestion: None, - module: None, - } - } else { - let mut span_label = None; - let item_ident = path.last().unwrap().ident; - let item_span = item_ident.span; - let (mod_prefix, mod_str, module, suggestion) = if path.len() == 1 { - debug!(?self.diag_metadata.current_impl_items); - debug!(?self.diag_metadata.current_function); - let suggestion = if self.current_trait_ref.is_none() - && let Some((fn_kind, _)) = self.diag_metadata.current_function - && let Some(FnCtxt::Assoc(_)) = fn_kind.ctxt() - && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = fn_kind - && let Some(items) = self.diag_metadata.current_impl_items - && let Some(item) = items.iter().find(|i| { - i.kind.ident().is_some_and(|ident| { - // Don't suggest if the item is in Fn signature arguments (#112590). - ident.name == item_str.name && !sig.span.contains(item_span) - }) - }) { - let sp = item_span.shrink_to_lo(); - - // Account for `Foo { field }` when suggesting `self.field` so we result on - // `Foo { field: self.field }`. - let field = match source { - PathSource::Expr(Some(Expr { kind: ExprKind::Struct(expr), .. })) => { - expr.fields.iter().find(|f| f.ident == item_ident) - } - _ => None, - }; - let pre = if let Some(field) = field - && field.is_shorthand - { - format!("{item_ident}: ") - } else { - String::new() - }; - // Ensure we provide a structured suggestion for an assoc fn only for - // expressions that are actually a fn call. - let is_call = match field { - Some(ast::ExprField { expr, .. }) => { - matches!(expr.kind, ExprKind::Call(..)) - } - _ => matches!( - source, - PathSource::Expr(Some(Expr { kind: ExprKind::Call(..), .. })), - ), - }; - - match &item.kind { - AssocItemKind::Fn(fn_) - if (!sig.decl.has_self() || !is_call) && fn_.sig.decl.has_self() => - { - // Ensure that we only suggest `self.` if `self` is available, - // you can't call `fn foo(&self)` from `fn bar()` (#115992). - // We also want to mention that the method exists. - span_label = Some(( - fn_.ident.span, - "a method by that name is available on `Self` here", - )); - None - } - AssocItemKind::Fn(fn_) if !fn_.sig.decl.has_self() && !is_call => { - span_label = Some(( - fn_.ident.span, - "an associated function by that name is available on `Self` here", - )); - None - } - AssocItemKind::Fn(fn_) if fn_.sig.decl.has_self() => { - Some((sp, "consider using the method on `Self`", format!("{pre}self."))) - } - AssocItemKind::Fn(_) => Some(( - sp, - "consider using the associated function on `Self`", - format!("{pre}Self::"), - )), - AssocItemKind::Const(..) => Some(( - sp, - "consider using the associated constant on `Self`", - format!("{pre}Self::"), - )), - _ => None, - } - } else { - None - }; - (String::new(), "this scope".to_string(), None, suggestion) - } else if path.len() == 2 && path[0].ident.name == kw::PathRoot { - if self.r.tcx.sess.edition() > Edition::Edition2015 { - // In edition 2018 onwards, the `::foo` syntax may only pull from the extern prelude - // which overrides all other expectations of item type - expected = "crate"; - (String::new(), "the list of imported crates".to_string(), None, None) - } else { - ( - String::new(), - "the crate root".to_string(), - Some(CRATE_DEF_ID.to_def_id()), - None, - ) - } - } else if path.len() == 2 && path[0].ident.name == kw::Crate { - (String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), None) - } else { - let mod_path = &path[..path.len() - 1]; - let mod_res = self.resolve_path(mod_path, Some(TypeNS), None, source); - let mod_prefix = match mod_res { - PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(), - _ => None, - }; - - let module_did = mod_prefix.as_ref().and_then(Res::mod_def_id); - - let mod_prefix = - mod_prefix.map_or_else(String::new, |res| format!("{} ", res.descr())); - (mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), module_did, None) - }; - - let (fallback_label, suggestion) = if path_str == "async" - && expected.starts_with("struct") - { - ("`async` blocks are only allowed in Rust 2018 or later".to_string(), suggestion) - } else { - // check if we are in situation of typo like `True` instead of `true`. - let override_suggestion = - if ["true", "false"].contains(&item_str.to_string().to_lowercase().as_str()) { - let item_typo = item_str.to_string().to_lowercase(); - Some((item_span, "you may want to use a bool value instead", item_typo)) - // FIXME(vincenzopalazzo): make the check smarter, - // and maybe expand with levenshtein distance checks - } else if item_str.as_str() == "printf" { - Some(( - item_span, - "you may have meant to use the `print` macro", - "print!".to_owned(), - )) - } else { - suggestion - }; - (format!("not found in {mod_str}"), override_suggestion) - }; - - BaseError { - msg: format!("cannot find {expected} `{item_str}` in {mod_prefix}{mod_str}"), - fallback_label, - span: item_span, - span_label, - could_be_expr: false, - suggestion, - module, - } - } - } - - /// Try to suggest for a module path that cannot be resolved. - /// Such as `fmt::Debug` where `fmt` is not resolved without importing, - /// here we search with `lookup_import_candidates` for a module named `fmt` - /// with `TypeNS` as namespace. - /// - /// We need a separate function here because we won't suggest for a path with single segment - /// and we won't change `SourcePath` api `is_expected` to match `Type` with `DefKind::Mod` - pub(crate) fn smart_resolve_partial_mod_path_errors( - &mut self, - prefix_path: &[Segment], - following_seg: Option<&Segment>, - ) -> Vec { - if let Some(segment) = prefix_path.last() - && let Some(following_seg) = following_seg - { - let candidates = self.r.lookup_import_candidates( - segment.ident, - Namespace::TypeNS, - &self.parent_scope, - &|res: Res| matches!(res, Res::Def(DefKind::Mod, _)), - ); - // double check next seg is valid - candidates - .into_iter() - .filter(|candidate| { - if let Some(def_id) = candidate.did - && let Some(module) = self.r.get_module(def_id) - { - Some(def_id) != self.parent_scope.module.opt_def_id() - && self - .r - .resolutions(module) - .borrow() - .iter() - .any(|(key, _r)| key.ident.name == following_seg.ident.name) - } else { - false - } - }) - .collect::>() - } else { - Vec::new() - } - } - - /// Handles error reporting for `smart_resolve_path_fragment` function. - /// Creates base error and amends it with one short label and possibly some longer helps/notes. - #[tracing::instrument(skip(self), level = "debug")] - pub(crate) fn smart_resolve_report_errors( - &mut self, - path: &[Segment], - following_seg: Option<&Segment>, - span: Span, - source: PathSource<'_, 'ast, 'ra>, - res: Option, - qself: Option<&QSelf>, - ) -> (Diag<'tcx>, Vec) { - debug!(?res, ?source); - let base_error = self.make_base_error(path, span, source, res); - - let code = source.error_code(res.is_some()); - let mut err = self.r.dcx().struct_span_err(base_error.span, base_error.msg.clone()); - err.code(code); - - // Try to get the span of the identifier within the path's syntax context - // (if that's different). - if let Some(within_macro_span) = - base_error.span.within_macro(span, self.r.tcx.sess.source_map()) - { - err.span_label(within_macro_span, "due to this macro variable"); - } - - self.detect_missing_binding_available_from_pattern(&mut err, path, following_seg); - self.suggest_at_operator_in_slice_pat_with_range(&mut err, path); - self.suggest_range_struct_destructuring(&mut err, path, source); - self.suggest_swapping_misplaced_self_ty_and_trait(&mut err, source, res, base_error.span); - - if let Some((span, label)) = base_error.span_label { - err.span_label(span, label); - } - - if let Some(ref sugg) = base_error.suggestion { - err.span_suggestion_verbose(sugg.0, sugg.1, &sugg.2, Applicability::MaybeIncorrect); - } - - self.suggest_changing_type_to_const_param(&mut err, res, source, path, following_seg, span); - self.explain_functions_in_pattern(&mut err, res, source); - - if self.suggest_pattern_match_with_let(&mut err, source, span) { - // Fallback label. - err.span_label(base_error.span, base_error.fallback_label); - return (err, Vec::new()); - } - - self.suggest_self_or_self_ref(&mut err, path, span); - self.detect_assoc_type_constraint_meant_as_path(&mut err, &base_error); - self.detect_rtn_with_fully_qualified_path( - &mut err, - path, - following_seg, - span, - source, - res, - qself, - ); - if self.suggest_self_ty(&mut err, source, path, span) - || self.suggest_self_value(&mut err, source, path, span) - { - return (err, Vec::new()); - } - - if let Some((did, item)) = self.lookup_doc_alias_name(path, source.namespace()) { - let item_name = item.name; - let suggestion_name = self.r.tcx.item_name(did); - err.span_suggestion( - item.span, - format!("`{suggestion_name}` has a name defined in the doc alias attribute as `{item_name}`"), - suggestion_name, - Applicability::MaybeIncorrect - ); - - return (err, Vec::new()); - }; - - let (found, suggested_candidates, mut candidates) = self.try_lookup_name_relaxed( - &mut err, - source, - path, - following_seg, - span, - res, - &base_error, - ); - if found { - return (err, candidates); - } - - if self.suggest_shadowed(&mut err, source, path, following_seg, span) { - // if there is already a shadowed name, don'suggest candidates for importing - candidates.clear(); - } - - let mut fallback = self.suggest_trait_and_bounds(&mut err, source, res, span, &base_error); - fallback |= self.suggest_typo( - &mut err, - source, - path, - following_seg, - span, - &base_error, - suggested_candidates, - ); - - if fallback { - // Fallback label. - err.span_label(base_error.span, base_error.fallback_label); - } - self.err_code_special_cases(&mut err, source, path, span); - - let module = base_error.module.unwrap_or_else(|| CRATE_DEF_ID.to_def_id()); - self.r.find_cfg_stripped(&mut err, &path.last().unwrap().ident.name, module); - - (err, candidates) - } - - fn detect_rtn_with_fully_qualified_path( - &self, - err: &mut Diag<'_>, - path: &[Segment], - following_seg: Option<&Segment>, - span: Span, - source: PathSource<'_, '_, '_>, - res: Option, - qself: Option<&QSelf>, - ) { - if let Some(Res::Def(DefKind::AssocFn, _)) = res - && let PathSource::TraitItem(TypeNS, _) = source - && let None = following_seg - && let Some(qself) = qself - && let TyKind::Path(None, ty_path) = &qself.ty.kind - && ty_path.segments.len() == 1 - && self.diag_metadata.current_where_predicate.is_some() - { - err.span_suggestion_verbose( - span, - "you might have meant to use the return type notation syntax", - format!("{}::{}(..)", ty_path.segments[0].ident, path[path.len() - 1].ident), - Applicability::MaybeIncorrect, - ); - } - } - - fn detect_assoc_type_constraint_meant_as_path( - &self, - err: &mut Diag<'_>, - base_error: &BaseError, - ) { - let Some(ty) = self.diag_metadata.current_type_path else { - return; - }; - let TyKind::Path(_, path) = &ty.kind else { - return; - }; - for segment in &path.segments { - let Some(params) = &segment.args else { - continue; - }; - let ast::GenericArgs::AngleBracketed(params) = params.deref() else { - continue; - }; - for param in ¶ms.args { - let ast::AngleBracketedArg::Constraint(constraint) = param else { - continue; - }; - let ast::AssocItemConstraintKind::Bound { bounds } = &constraint.kind else { - continue; - }; - for bound in bounds { - let ast::GenericBound::Trait(trait_ref) = bound else { - continue; - }; - if trait_ref.modifiers == ast::TraitBoundModifiers::NONE - && base_error.span == trait_ref.span - { - err.span_suggestion_verbose( - constraint.ident.span.between(trait_ref.span), - "you might have meant to write a path instead of an associated type bound", - "::", - Applicability::MachineApplicable, - ); - } - } - } - } - } - - fn suggest_self_or_self_ref(&mut self, err: &mut Diag<'_>, path: &[Segment], span: Span) { - if !self.self_type_is_available() { - return; - } - let Some(path_last_segment) = path.last() else { return }; - let item_str = path_last_segment.ident; - // Emit help message for fake-self from other languages (e.g., `this` in JavaScript). - if ["this", "my"].contains(&item_str.as_str()) { - err.span_suggestion_short( - span, - "you might have meant to use `self` here instead", - "self", - Applicability::MaybeIncorrect, - ); - if !self.self_value_is_available(path[0].ident.span) { - if let Some((FnKind::Fn(_, _, ast::Fn { sig, .. }), fn_span)) = - &self.diag_metadata.current_function - { - let (span, sugg) = if let Some(param) = sig.decl.inputs.get(0) { - (param.span.shrink_to_lo(), "&self, ") - } else { - ( - self.r - .tcx - .sess - .source_map() - .span_through_char(*fn_span, '(') - .shrink_to_hi(), - "&self", - ) - }; - err.span_suggestion_verbose( - span, - "if you meant to use `self`, you are also missing a `self` receiver \ - argument", - sugg, - Applicability::MaybeIncorrect, - ); - } - } - } - } - - fn try_lookup_name_relaxed( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - path: &[Segment], - following_seg: Option<&Segment>, - span: Span, - res: Option, - base_error: &BaseError, - ) -> (bool, FxHashSet, Vec) { - let span = match following_seg { - Some(_) if path[0].ident.span.eq_ctxt(path[path.len() - 1].ident.span) => { - // The path `span` that comes in includes any following segments, which we don't - // want to replace in the suggestions. - path[0].ident.span.to(path[path.len() - 1].ident.span) - } - _ => span, - }; - let mut suggested_candidates = FxHashSet::default(); - // Try to lookup name in more relaxed fashion for better error reporting. - let ident = path.last().unwrap().ident; - let is_expected = &|res| source.is_expected(res); - let ns = source.namespace(); - let is_enum_variant = &|res| matches!(res, Res::Def(DefKind::Variant, _)); - let path_str = Segment::names_to_string(path); - let ident_span = path.last().map_or(span, |ident| ident.ident.span); - let mut candidates = self - .r - .lookup_import_candidates(ident, ns, &self.parent_scope, is_expected) - .into_iter() - .filter(|ImportSuggestion { did, .. }| { - match (did, res.and_then(|res| res.opt_def_id())) { - (Some(suggestion_did), Some(actual_did)) => *suggestion_did != actual_did, - _ => true, - } - }) - .collect::>(); - // Try to filter out intrinsics candidates, as long as we have - // some other candidates to suggest. - let intrinsic_candidates: Vec<_> = candidates - .extract_if(.., |sugg| { - let path = path_names_to_string(&sugg.path); - path.starts_with("core::intrinsics::") || path.starts_with("std::intrinsics::") - }) - .collect(); - if candidates.is_empty() { - // Put them back if we have no more candidates to suggest... - candidates = intrinsic_candidates; - } - let crate_def_id = CRATE_DEF_ID.to_def_id(); - if candidates.is_empty() && is_expected(Res::Def(DefKind::Enum, crate_def_id)) { - let mut enum_candidates: Vec<_> = self - .r - .lookup_import_candidates(ident, ns, &self.parent_scope, is_enum_variant) - .into_iter() - .map(|suggestion| import_candidate_to_enum_paths(&suggestion)) - .filter(|(_, enum_ty_path)| !enum_ty_path.starts_with("std::prelude::")) - .collect(); - if !enum_candidates.is_empty() { - enum_candidates.sort(); - - // Contextualize for E0425 "cannot find type", but don't belabor the point - // (that it's a variant) for E0573 "expected type, found variant". - let preamble = if res.is_none() { - let others = match enum_candidates.len() { - 1 => String::new(), - 2 => " and 1 other".to_owned(), - n => format!(" and {n} others"), - }; - format!("there is an enum variant `{}`{}; ", enum_candidates[0].0, others) - } else { - String::new() - }; - let msg = format!("{preamble}try using the variant's enum"); - - suggested_candidates.extend( - enum_candidates - .iter() - .map(|(_variant_path, enum_ty_path)| enum_ty_path.clone()), - ); - err.span_suggestions( - span, - msg, - enum_candidates.into_iter().map(|(_variant_path, enum_ty_path)| enum_ty_path), - Applicability::MachineApplicable, - ); - } - } - - // Try finding a suitable replacement. - let typo_sugg = self - .lookup_typo_candidate(path, following_seg, source.namespace(), is_expected) - .to_opt_suggestion() - .filter(|sugg| !suggested_candidates.contains(sugg.candidate.as_str())); - if let [segment] = path - && !matches!(source, PathSource::Delegation) - && self.self_type_is_available() - { - if let Some(candidate) = - self.lookup_assoc_candidate(ident, ns, is_expected, source.is_call()) - { - let self_is_available = self.self_value_is_available(segment.ident.span); - // Account for `Foo { field }` when suggesting `self.field` so we result on - // `Foo { field: self.field }`. - let pre = match source { - PathSource::Expr(Some(Expr { kind: ExprKind::Struct(expr), .. })) - if expr - .fields - .iter() - .any(|f| f.ident == segment.ident && f.is_shorthand) => - { - format!("{path_str}: ") - } - _ => String::new(), - }; - match candidate { - AssocSuggestion::Field(field_span) => { - if self_is_available { - let source_map = self.r.tcx.sess.source_map(); - let field_is_format_named_arg = matches!( - span.desugaring_kind(), - Some(DesugaringKind::FormatLiteral { .. }) - ) && source_map - .span_to_source(span, |s, start, _| { - Ok(s.get(start.saturating_sub(1)..start) == Some("{")) - }) - .unwrap_or(false); - if field_is_format_named_arg { - err.help( - format!("you might have meant to use the available field in a format string: `\"{{}}\", self.{}`", segment.ident.name), - ); - } else { - err.span_suggestion_verbose( - span.shrink_to_lo(), - "you might have meant to use the available field", - format!("{pre}self."), - Applicability::MaybeIncorrect, - ); - } - } else { - err.span_label(field_span, "a field by that name exists in `Self`"); - } - } - AssocSuggestion::MethodWithSelf { called } if self_is_available => { - let msg = if called { - "you might have meant to call the method" - } else { - "you might have meant to refer to the method" - }; - err.span_suggestion_verbose( - span.shrink_to_lo(), - msg, - "self.", - Applicability::MachineApplicable, - ); - } - AssocSuggestion::MethodWithSelf { .. } - | AssocSuggestion::AssocFn { .. } - | AssocSuggestion::AssocConst - | AssocSuggestion::AssocType => { - err.span_suggestion_verbose( - span.shrink_to_lo(), - format!("you might have meant to {}", candidate.action()), - "Self::", - Applicability::MachineApplicable, - ); - } - } - self.r.add_typo_suggestion(err, typo_sugg, ident_span); - return (true, suggested_candidates, candidates); - } - - // If the first argument in call is `self` suggest calling a method. - if let Some((call_span, args_span)) = self.call_has_self_arg(source) { - let mut args_snippet = String::new(); - if let Some(args_span) = args_span - && let Ok(snippet) = self.r.tcx.sess.source_map().span_to_snippet(args_span) - { - args_snippet = snippet; - } - - if let Some(Res::Def(DefKind::Struct, def_id)) = res { - if let Some(ctor) = self.r.struct_ctor(def_id) - && ctor.has_private_fields(self.parent_scope.module, self.r) - { - if matches!( - ctor.res, - Res::Def(DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), _) - ) { - self.update_err_for_private_tuple_struct_fields(err, &source, def_id); - } - err.note("constructor is not visible here due to private fields"); - } - } else { - err.span_suggestion( - call_span, - format!("try calling `{ident}` as a method"), - format!("self.{path_str}({args_snippet})"), - Applicability::MachineApplicable, - ); - } - - return (true, suggested_candidates, candidates); - } - } - - // Try context-dependent help if relaxed lookup didn't work. - if let Some(res) = res { - if self.smart_resolve_context_dependent_help( - err, - span, - source, - path, - res, - &path_str, - &base_error.fallback_label, - ) { - // We do this to avoid losing a secondary span when we override the main error span. - self.r.add_typo_suggestion(err, typo_sugg, ident_span); - return (true, suggested_candidates, candidates); - } - } - - // Try to find in last block rib - if let Some(rib) = &self.last_block_rib { - for (ident, &res) in &rib.bindings { - if let Res::Local(_) = res - && path.len() == 1 - && ident.span.eq_ctxt(path[0].ident.span) - && ident.name == path[0].ident.name - { - err.span_help( - ident.span, - format!("the binding `{path_str}` is available in a different scope in the same function"), - ); - return (true, suggested_candidates, candidates); - } - } - } - - if candidates.is_empty() { - candidates = self.smart_resolve_partial_mod_path_errors(path, following_seg); - } - - (false, suggested_candidates, candidates) - } - - fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> { - let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { - for resolution in r.resolutions(m).borrow().values() { - let Some(did) = - resolution.borrow().best_decl().and_then(|binding| binding.res().opt_def_id()) - else { - continue; - }; - if did.is_local() { - // We don't record the doc alias name in the local crate - // because the people who write doc alias are usually not - // confused by them. - continue; - } - if let Some(d) = hir::find_attr!(r.tcx, did, Doc(d) => d) - && d.aliases.contains_key(&item_name) - { - return Some(did); - } - } - None - }; - - if path.len() == 1 { - for rib in self.ribs[ns].iter().rev() { - let item = path[0].ident; - if let RibKind::Module(module) | RibKind::Block(Some(module)) = rib.kind - && let Some(did) = find_doc_alias_name(self.r, module.to_module(), item.name) - { - return Some((did, item)); - } - } - } else { - // Finds to the last resolved module item in the path - // and searches doc aliases within that module. - // - // Example: For the path `a::b::last_resolved::not_exist::c::d`, - // we will try to find any item has doc aliases named `not_exist` - // in `last_resolved` module. - // - // - Use `skip(1)` because the final segment must remain unresolved. - for (idx, seg) in path.iter().enumerate().rev().skip(1) { - let Some(id) = seg.id else { - continue; - }; - let Some(res) = self.r.partial_res_map.get(&id) else { - continue; - }; - if let Res::Def(DefKind::Mod, module) = res.expect_full_res() - && let module = self.r.expect_module(module) - && let item = path[idx + 1].ident - && let Some(did) = find_doc_alias_name(self.r, module, item.name) - { - return Some((did, item)); - } - break; - } - } - None - } - - fn suggest_trait_and_bounds( - &self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - res: Option, - span: Span, - base_error: &BaseError, - ) -> bool { - let is_macro = - base_error.span.from_expansion() && base_error.span.desugaring_kind().is_none(); - let mut fallback = false; - - if let ( - PathSource::Trait(AliasPossibility::Maybe), - Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)), - false, - ) = (source, res, is_macro) - && let Some(bounds @ [first_bound, .., last_bound]) = - self.diag_metadata.current_trait_object - { - fallback = true; - let spans: Vec = bounds - .iter() - .map(|bound| bound.span()) - .filter(|&sp| sp != base_error.span) - .collect(); - - let start_span = first_bound.span(); - // `end_span` is the end of the poly trait ref (Foo + 'baz + Bar><) - let end_span = last_bound.span(); - // `last_bound_span` is the last bound of the poly trait ref (Foo + >'baz< + Bar) - let last_bound_span = spans.last().cloned().unwrap(); - let mut multi_span: MultiSpan = spans.clone().into(); - for sp in spans { - let msg = if sp == last_bound_span { - format!( - "...because of {these} bound{s}", - these = pluralize!("this", bounds.len() - 1), - s = pluralize!(bounds.len() - 1), - ) - } else { - String::new() - }; - multi_span.push_span_label(sp, msg); - } - multi_span.push_span_label(base_error.span, "expected this type to be a trait..."); - err.span_help( - multi_span, - "`+` is used to constrain a \"trait object\" type with lifetimes or \ - auto-traits; structs and enums can't be bound in that way", - ); - if bounds.iter().all(|bound| match bound { - ast::GenericBound::Outlives(_) | ast::GenericBound::Use(..) => true, - ast::GenericBound::Trait(tr) => tr.span == base_error.span, - }) { - let mut sugg = vec![]; - if base_error.span != start_span { - sugg.push((start_span.until(base_error.span), String::new())); - } - if base_error.span != end_span { - sugg.push((base_error.span.shrink_to_hi().to(end_span), String::new())); - } - - err.multipart_suggestion( - "if you meant to use a type and not a trait here, remove the bounds", - sugg, - Applicability::MaybeIncorrect, - ); - } - } - - fallback |= self.restrict_assoc_type_in_where_clause(span, err); - fallback - } - - fn suggest_typo( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, 'ast, 'ra>, - path: &[Segment], - following_seg: Option<&Segment>, - span: Span, - base_error: &BaseError, - suggested_candidates: FxHashSet, - ) -> bool { - let is_expected = &|res| source.is_expected(res); - let ident_span = path.last().map_or(span, |ident| ident.ident.span); - - // Prefer suggestions based on associated types from in-scope bounds (e.g. `T::Item`) - // over purely edit-distance-based identifier suggestions. - // Otherwise suggestions could be verbose. - if self.suggest_assoc_type_from_bounds(err, source, path, ident_span) { - return false; - } - - let typo_sugg = - self.lookup_typo_candidate(path, following_seg, source.namespace(), is_expected); - let mut fallback = false; - let typo_sugg = typo_sugg - .to_opt_suggestion() - .filter(|sugg| !suggested_candidates.contains(sugg.candidate.as_str())); - if !self.r.add_typo_suggestion(err, typo_sugg, ident_span) { - fallback = true; - match self.diag_metadata.current_let_binding { - Some((pat_sp, Some(ty_sp), None)) - if ty_sp.contains(base_error.span) && base_error.could_be_expr => - { - err.span_suggestion_verbose( - pat_sp.between(ty_sp), - "use `=` if you meant to assign", - " = ", - Applicability::MaybeIncorrect, - ); - } - _ => {} - } - - // If the trait has a single item (which wasn't matched by the algorithm), suggest it - let suggestion = self.get_single_associated_item(path, &source, is_expected); - self.r.add_typo_suggestion(err, suggestion, ident_span); - } - - if self.let_binding_suggestion(err, ident_span) { - fallback = false; - } - - fallback - } - - fn suggest_shadowed( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - path: &[Segment], - following_seg: Option<&Segment>, - span: Span, - ) -> bool { - let is_expected = &|res| source.is_expected(res); - let typo_sugg = - self.lookup_typo_candidate(path, following_seg, source.namespace(), is_expected); - let is_in_same_file = &|sp1, sp2| { - let source_map = self.r.tcx.sess.source_map(); - let file1 = source_map.span_to_filename(sp1); - let file2 = source_map.span_to_filename(sp2); - file1 == file2 - }; - // print 'you might have meant' if the candidate is (1) is a shadowed name with - // accessible definition and (2) either defined in the same crate as the typo - // (could be in a different file) or introduced in the same file as the typo - // (could belong to a different crate) - if let TypoCandidate::Shadowed(res, Some(sugg_span)) = typo_sugg - && res.opt_def_id().is_some_and(|id| id.is_local() || is_in_same_file(span, sugg_span)) - { - err.span_label( - sugg_span, - format!("you might have meant to refer to this {}", res.descr()), - ); - return true; - } - false - } - - fn err_code_special_cases( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - path: &[Segment], - span: Span, - ) { - if let Some(err_code) = err.code { - if err_code == E0425 { - for label_rib in &self.label_ribs { - for (label_ident, node_id) in &label_rib.bindings { - let ident = path.last().unwrap().ident; - if format!("'{ident}") == label_ident.to_string() { - err.span_label(label_ident.span, "a label with a similar name exists"); - if let PathSource::Expr(Some(Expr { - kind: ExprKind::Break(None, Some(_)), - .. - })) = source - { - err.span_suggestion( - span, - "use the similarly named label", - label_ident.name, - Applicability::MaybeIncorrect, - ); - // Do not lint against unused label when we suggest them. - self.diag_metadata.unused_labels.swap_remove(node_id); - } - } - } - } - - self.suggest_ident_hidden_by_hygiene(err, path, span); - // cannot find type in this scope - if let Some(correct) = Self::likely_rust_type(path) { - err.span_suggestion( - span, - "perhaps you intended to use this type", - correct, - Applicability::MaybeIncorrect, - ); - } - } - } - } - - fn suggest_ident_hidden_by_hygiene(&self, err: &mut Diag<'_>, path: &[Segment], span: Span) { - let [segment] = path else { return }; - - let ident = segment.ident; - let callsite_span = span.source_callsite(); - for rib in self.ribs[ValueNS].iter().rev() { - for (binding_ident, _) in &rib.bindings { - // Case 1: the identifier is defined in the same scope as the macro is called - if binding_ident.name == ident.name - && !binding_ident.span.eq_ctxt(span) - && !binding_ident.span.from_expansion() - && binding_ident.span.lo() < callsite_span.lo() - { - err.span_help( - binding_ident.span, - "an identifier with the same name exists, but is not accessible due to macro hygiene", - ); - return; - } - - // Case 2: the identifier is defined in a macro call in the same scope - if binding_ident.name == ident.name - && binding_ident.span.from_expansion() - && binding_ident.span.source_callsite().eq_ctxt(callsite_span) - && binding_ident.span.source_callsite().lo() < callsite_span.lo() - { - err.span_help( - binding_ident.span, - "an identifier with the same name is defined here, but is not accessible due to macro hygiene", - ); - return; - } - } - } - } - - /// Emit special messages for unresolved `Self` and `self`. - fn suggest_self_ty( - &self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - path: &[Segment], - span: Span, - ) -> bool { - if !is_self_type(path, source.namespace()) { - return false; - } - err.code(E0411); - err.span_label(span, "`Self` is only available in impls, traits, and type definitions"); - if let Some(item) = self.diag_metadata.current_item - && let Some(ident) = item.kind.ident() - { - err.span_label( - ident.span, - format!("`Self` not allowed in {} {}", item.kind.article(), item.kind.descr()), - ); - } - true - } - - fn suggest_self_value( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - path: &[Segment], - span: Span, - ) -> bool { - if !is_self_value(path, source.namespace()) { - return false; - } - - debug!("smart_resolve_path_fragment: E0424, source={:?}", source); - err.code(E0424); - err.span_label( - span, - match source { - PathSource::Pat => { - "`self` value is a keyword and may not be bound to variables or shadowed" - } - _ => "`self` value is a keyword only available in methods with a `self` parameter", - }, - ); - - // using `let self` is wrong even if we're not in an associated method or if we're in a macro expansion. - // So, we should return early if we're in a pattern, see issue #143134. - if matches!(source, PathSource::Pat) { - return true; - } - - let is_assoc_fn = self.self_type_is_available(); - let self_from_macro = "a `self` parameter, but a macro invocation can only \ - access identifiers it receives from parameters"; - if let Some((fn_kind, fn_span)) = &self.diag_metadata.current_function { - // The current function has a `self` parameter, but we were unable to resolve - // a reference to `self`. This can only happen if the `self` identifier we - // are resolving came from a different hygiene context or a variable binding. - // But variable binding error is returned early above. - if fn_kind.decl().inputs.get(0).is_some_and(|p| p.is_self()) { - err.span_label(*fn_span, format!("this function has {self_from_macro}")); - } else { - let doesnt = if is_assoc_fn { - let (span, sugg) = fn_kind - .decl() - .inputs - .get(0) - .map(|p| (p.span.shrink_to_lo(), "&self, ")) - .unwrap_or_else(|| { - // Try to look for the "(" after the function name, if possible. - // This avoids placing the suggestion into the visibility specifier. - let span = fn_kind - .ident() - .map_or(*fn_span, |ident| fn_span.with_lo(ident.span.hi())); - ( - self.r - .tcx - .sess - .source_map() - .span_through_char(span, '(') - .shrink_to_hi(), - "&self", - ) - }); - err.span_suggestion_verbose( - span, - "add a `self` receiver parameter to make the associated `fn` a method", - sugg, - Applicability::MaybeIncorrect, - ); - "doesn't" - } else { - "can't" - }; - if let Some(ident) = fn_kind.ident() { - err.span_label( - ident.span, - format!("this function {doesnt} have a `self` parameter"), - ); - } - } - } else if let Some(item) = self.diag_metadata.current_item { - if matches!(item.kind, ItemKind::Delegation(..)) { - err.span_label(item.span, format!("delegation supports {self_from_macro}")); - } else { - let span = if let Some(ident) = item.kind.ident() { ident.span } else { item.span }; - err.span_label( - span, - format!("`self` not allowed in {} {}", item.kind.article(), item.kind.descr()), - ); - } - } - true - } - - fn detect_missing_binding_available_from_pattern( - &self, - err: &mut Diag<'_>, - path: &[Segment], - following_seg: Option<&Segment>, - ) { - let [segment] = path else { return }; - let None = following_seg else { return }; - for rib in self.ribs[ValueNS].iter().rev() { - let patterns_with_skipped_bindings = - self.r.tcx.with_stable_hashing_context(|mut hcx| { - rib.patterns_with_skipped_bindings.to_sorted(&mut hcx, true) - }); - for (def_id, spans) in patterns_with_skipped_bindings { - if let DefKind::Struct | DefKind::Variant = self.r.tcx.def_kind(*def_id) - && let Some(fields) = self.r.field_idents(*def_id) - { - for field in fields { - if field.name == segment.ident.name { - if spans.iter().all(|(_, had_error)| had_error.is_err()) { - // This resolution error will likely be fixed by fixing a - // syntax error in a pattern, so it is irrelevant to the user. - let multispan: MultiSpan = - spans.iter().map(|(s, _)| *s).collect::>().into(); - err.span_note( - multispan, - "this pattern had a recovered parse error which likely lost \ - the expected fields", - ); - err.downgrade_to_delayed_bug(); - } - let ty = self.r.tcx.item_name(*def_id); - for (span, _) in spans { - err.span_label( - *span, - format!( - "this pattern doesn't include `{field}`, which is \ - available in `{ty}`", - ), - ); - } - } - } - } - } - } - } - - fn suggest_at_operator_in_slice_pat_with_range(&self, err: &mut Diag<'_>, path: &[Segment]) { - let Some(pat) = self.diag_metadata.current_pat else { return }; - let (bound, side, range) = match &pat.kind { - ast::PatKind::Range(Some(bound), None, range) => (bound, Side::Start, range), - ast::PatKind::Range(None, Some(bound), range) => (bound, Side::End, range), - _ => return, - }; - if let ExprKind::Path(None, range_path) = &bound.kind - && let [segment] = &range_path.segments[..] - && let [s] = path - && segment.ident == s.ident - && segment.ident.span.eq_ctxt(range.span) - { - // We've encountered `[first, rest..]` (#88404) or `[first, ..rest]` (#120591) - // where the user might have meant `[first, rest @ ..]`. - let (span, snippet) = match side { - Side::Start => (segment.ident.span.between(range.span), " @ ".into()), - Side::End => (range.span.to(segment.ident.span), format!("{} @ ..", segment.ident)), - }; - err.subdiagnostic(diagnostics::UnexpectedResUseAtOpInSlicePatWithRangeSugg { - span, - ident: segment.ident, - snippet, - }); - } - - enum Side { - Start, - End, - } - } - - fn suggest_range_struct_destructuring( - &mut self, - err: &mut Diag<'_>, - path: &[Segment], - source: PathSource<'_, '_, '_>, - ) { - if !matches!(source, PathSource::Pat | PathSource::TupleStruct(..) | PathSource::Expr(..)) { - return; - } - - let Some(pat) = self.diag_metadata.current_pat else { return }; - let ast::PatKind::Range(start, end, end_kind) = &pat.kind else { return }; - - let [segment] = path else { return }; - let failing_span = segment.ident.span; - - let in_start = start.as_ref().is_some_and(|e| e.span.contains(failing_span)); - let in_end = end.as_ref().is_some_and(|e| e.span.contains(failing_span)); - - if !in_start && !in_end { - return; - } - - let start_snippet = - start.as_ref().and_then(|e| self.r.tcx.sess.source_map().span_to_snippet(e.span).ok()); - let end_snippet = - end.as_ref().and_then(|e| self.r.tcx.sess.source_map().span_to_snippet(e.span).ok()); - - let field = |name: &str, val: String| { - if val == name { val } else { format!("{name}: {val}") } - }; - - let mut resolve_short_name = |short: Symbol, full: &str| -> String { - let ident = Ident::with_dummy_span(short); - let path = Segment::from_path(&Path::from_ident(ident)); - - match self.resolve_path(&path, Some(TypeNS), None, PathSource::Type) { - PathResult::NonModule(..) => short.to_string(), - _ => full.to_string(), - } - }; - // FIXME(new_range): Also account for new range types - let (struct_path, fields) = match (start_snippet, end_snippet, &end_kind.node) { - (Some(start), Some(end), ast::RangeEnd::Excluded) => ( - resolve_short_name(sym::Range, "std::ops::Range"), - vec![field("start", start), field("end", end)], - ), - (Some(start), Some(end), ast::RangeEnd::Included(_)) => ( - resolve_short_name(sym::RangeInclusive, "std::ops::RangeInclusive"), - vec![field("start", start), field("end", end)], - ), - (Some(start), None, _) => ( - resolve_short_name(sym::RangeFrom, "std::ops::RangeFrom"), - vec![field("start", start)], - ), - (None, Some(end), ast::RangeEnd::Excluded) => { - (resolve_short_name(sym::RangeTo, "std::ops::RangeTo"), vec![field("end", end)]) - } - (None, Some(end), ast::RangeEnd::Included(_)) => ( - resolve_short_name(sym::RangeToInclusive, "std::ops::RangeToInclusive"), - vec![field("end", end)], - ), - _ => return, - }; - - err.span_suggestion_verbose( - pat.span, - format!("if you meant to destructure a range use a struct pattern"), - format!("{} {{ {} }}", struct_path, fields.join(", ")), - Applicability::MaybeIncorrect, - ); - - err.note( - "range patterns match against the start and end of a range; \ - to bind the components, use a struct pattern", - ); - } - - fn suggest_swapping_misplaced_self_ty_and_trait( - &mut self, - err: &mut Diag<'_>, - source: PathSource<'_, 'ast, 'ra>, - res: Option, - span: Span, - ) { - if let Some((trait_ref, self_ty)) = - self.diag_metadata.currently_processing_impl_trait.clone() - && let TyKind::Path(_, self_ty_path) = &self_ty.kind - && let PathResult::Module(ModuleOrUniformRoot::Module(module)) = - self.resolve_path(&Segment::from_path(self_ty_path), Some(TypeNS), None, source) - && module.def_kind() == Some(DefKind::Trait) - && trait_ref.path.span == span - && let PathSource::Trait(_) = source - && let Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)) = res - && let Ok(self_ty_str) = self.r.tcx.sess.source_map().span_to_snippet(self_ty.span) - && let Ok(trait_ref_str) = - self.r.tcx.sess.source_map().span_to_snippet(trait_ref.path.span) - { - err.multipart_suggestion( - "`impl` items mention the trait being implemented first and the type it is being implemented for second", - vec![(trait_ref.path.span, self_ty_str), (self_ty.span, trait_ref_str)], - Applicability::MaybeIncorrect, - ); - } - } - - fn explain_functions_in_pattern( - &self, - err: &mut Diag<'_>, - res: Option, - source: PathSource<'_, '_, '_>, - ) { - let PathSource::TupleStruct(_, _) = source else { return }; - let Some(Res::Def(DefKind::Fn, _)) = res else { return }; - err.primary_message("expected a pattern, found a function call"); - err.note("function calls are not allowed in patterns: "); - } - - fn suggest_changing_type_to_const_param( - &self, - err: &mut Diag<'_>, - res: Option, - source: PathSource<'_, '_, '_>, - path: &[Segment], - following_seg: Option<&Segment>, - span: Span, - ) { - if let PathSource::Expr(None) = source - && let Some(Res::Def(DefKind::TyParam, _)) = res - && following_seg.is_none() - && let [segment] = path - { - // We have something like - // impl From<[T; N]> for VecWrapper { - // fn from(slice: [T; N]) -> Self { - // VecWrapper(slice.to_vec()) - // } - // } - // where `N` is a type param but should likely have been a const param. - let Some(item) = self.diag_metadata.current_item else { return }; - let Some(generics) = item.kind.generics() else { return }; - let Some(span) = generics.params.iter().find_map(|param| { - // Only consider type params with no bounds. - if param.bounds.is_empty() && param.ident.name == segment.ident.name { - Some(param.ident.span) - } else { - None - } - }) else { - return; - }; - err.subdiagnostic(diagnostics::UnexpectedResChangeTyParamToConstParamSugg { - before: span.shrink_to_lo(), - after: span.shrink_to_hi(), - }); - return; - } - let PathSource::Trait(_) = source else { return }; - - // We don't include `DefKind::Str` and `DefKind::AssocTy` as they can't be reached here anyway. - let applicability = match res { - Some(Res::PrimTy(PrimTy::Int(_) | PrimTy::Uint(_) | PrimTy::Bool | PrimTy::Char)) => { - Applicability::MachineApplicable - } - // FIXME(const_generics): Add `DefKind::TyParam` and `SelfTyParam` once we support generic - // const generics. Of course, `Struct` and `Enum` may contain ty params, too, but the - // benefits of including them here outweighs the small number of false positives. - Some(Res::Def(DefKind::Struct | DefKind::Enum, _)) - if self.r.features.adt_const_params() || self.r.features.min_adt_const_params() => - { - Applicability::MaybeIncorrect - } - _ => return, - }; - - let Some(item) = self.diag_metadata.current_item else { return }; - let Some(generics) = item.kind.generics() else { return }; - - let param = generics.params.iter().find_map(|param| { - // Only consider type params with exactly one trait bound. - if let [bound] = &*param.bounds - && let ast::GenericBound::Trait(tref) = bound - && tref.modifiers == ast::TraitBoundModifiers::NONE - && tref.span == span - && param.ident.span.eq_ctxt(span) - { - Some(param.ident.span) - } else { - None - } - }); - - if let Some(param) = param { - err.subdiagnostic(diagnostics::UnexpectedResChangeTyToConstParamSugg { - span: param.shrink_to_lo(), - applicability, - }); - } - } - - fn suggest_pattern_match_with_let( - &self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - span: Span, - ) -> bool { - if let PathSource::Expr(_) = source - && let Some(Expr { span: expr_span, kind: ExprKind::Assign(lhs, _, _), .. }) = - self.diag_metadata.in_if_condition - { - // Icky heuristic so we don't suggest: - // `if (i + 2) = 2` => `if let (i + 2) = 2` (approximately pattern) - // `if 2 = i` => `if let 2 = i` (lhs needs to contain error span) - if lhs.is_approximately_pattern() && lhs.span.contains(span) { - err.span_suggestion_verbose( - expr_span.shrink_to_lo(), - "you might have meant to use pattern matching", - "let ", - Applicability::MaybeIncorrect, - ); - return true; - } - } - false - } - - fn get_single_associated_item( - &mut self, - path: &[Segment], - source: &PathSource<'_, 'ast, 'ra>, - filter_fn: &impl Fn(Res) -> bool, - ) -> Option { - if let crate::PathSource::TraitItem(_, _) = source { - let mod_path = &path[..path.len() - 1]; - if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = - self.resolve_path(mod_path, None, None, *source) - { - let targets: Vec<_> = self - .r - .resolutions(module) - .borrow() - .iter() - .filter_map(|(key, resolution)| { - let resolution = resolution.borrow(); - resolution.best_decl().map(|binding| binding.res()).and_then(|res| { - if filter_fn(res) { - Some((key.ident.name, resolution.orig_ident_span, res)) - } else { - None - } - }) - }) - .collect(); - if let &[(name, orig_ident_span, res)] = targets.as_slice() { - return Some(TypoSuggestion::single_item(name, orig_ident_span, res)); - } - } - } - None - } - - /// Given `where ::Baz: String`, suggest `where T: Bar`. - fn restrict_assoc_type_in_where_clause(&self, span: Span, err: &mut Diag<'_>) -> bool { - // Detect that we are actually in a `where` predicate. - let Some(ast::WherePredicate { - kind: - ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate { - bounded_ty, - bound_generic_params, - bounds, - }), - span: where_span, - .. - }) = self.diag_metadata.current_where_predicate - else { - return false; - }; - if !bound_generic_params.is_empty() { - return false; - } - - // Confirm that the target is an associated type. - let ast::TyKind::Path(Some(qself), path) = &bounded_ty.kind else { return false }; - // use this to verify that ident is a type param. - let Some(partial_res) = self.r.partial_res_map.get(&bounded_ty.id) else { return false }; - if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::AssocTy, _))) { - return false; - } - - let peeled_ty = qself.ty.peel_refs(); - let ast::TyKind::Path(None, type_param_path) = &peeled_ty.kind else { return false }; - // Confirm that the `SelfTy` is a type parameter. - let Some(partial_res) = self.r.partial_res_map.get(&peeled_ty.id) else { - return false; - }; - if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::TyParam, _))) { - return false; - } - let ([ast::PathSegment { args: None, .. }], [ast::GenericBound::Trait(poly_trait_ref)]) = - (&type_param_path.segments[..], &bounds[..]) - else { - return false; - }; - let [ast::PathSegment { ident, args: None, id }] = - &poly_trait_ref.trait_ref.path.segments[..] - else { - return false; - }; - if poly_trait_ref.modifiers != ast::TraitBoundModifiers::NONE { - return false; - } - if ident.span == span { - let Some(partial_res) = self.r.partial_res_map.get(&id) else { - return false; - }; - if !matches!(partial_res.full_res(), Some(Res::Def(..))) { - return false; - } - - let Some(new_where_bound_predicate) = - mk_where_bound_predicate(path, poly_trait_ref, &qself.ty) - else { - return false; - }; - err.span_suggestion_verbose( - *where_span, - format!("constrain the associated type to `{ident}`"), - where_bound_predicate_to_string(&new_where_bound_predicate), - Applicability::MaybeIncorrect, - ); - } - true - } - - /// Check if the source is call expression and the first argument is `self`. If true, - /// return the span of whole call and the span for all arguments expect the first one (`self`). - fn call_has_self_arg(&self, source: PathSource<'_, '_, '_>) -> Option<(Span, Option)> { - let mut has_self_arg = None; - if let PathSource::Expr(Some(parent)) = source - && let ExprKind::Call(_, args) = &parent.kind - && !args.is_empty() - { - let mut expr_kind = &args[0].kind; - loop { - match expr_kind { - ExprKind::Path(_, arg_name) if arg_name.segments.len() == 1 => { - if arg_name.segments[0].ident.name == kw::SelfLower { - let call_span = parent.span; - let tail_args_span = if args.len() > 1 { - Some(Span::new( - args[1].span.lo(), - args.last().unwrap().span.hi(), - call_span.ctxt(), - None, - )) - } else { - None - }; - has_self_arg = Some((call_span, tail_args_span)); - } - break; - } - ExprKind::AddrOf(_, _, expr) => expr_kind = &expr.kind, - _ => break, - } - } - } - has_self_arg - } - - fn followed_by_brace(&self, span: Span) -> (bool, Option) { - // HACK(estebank): find a better way to figure out that this was a - // parser issue where a struct literal is being used on an expression - // where a brace being opened means a block is being started. Look - // ahead for the next text to see if `span` is followed by a `{`. - let sm = self.r.tcx.sess.source_map(); - if let Some(open_brace_span) = sm.span_followed_by(span, "{") { - // In case this could be a struct literal that needs to be surrounded - // by parentheses, find the appropriate span. - let close_brace_span = - sm.span_to_next_source(open_brace_span).ok().and_then(|next_source| { - // Find the matching `}` accounting for nested braces. - let mut depth: u32 = 1; - let offset = next_source.char_indices().find_map(|(i, c)| { - match c { - '{' => depth += 1, - '}' if depth == 1 => return Some(i), - '}' => depth -= 1, - _ => {} - } - None - })?; - let start = open_brace_span.hi() + rustc_span::BytePos(offset as u32); - Some(open_brace_span.with_lo(start).with_hi(start + rustc_span::BytePos(1))) - }); - let closing_brace = close_brace_span.map(|sp| span.to(sp)); - (true, closing_brace) - } else { - (false, None) - } - } - - fn update_err_for_private_tuple_struct_fields( - &mut self, - err: &mut Diag<'_>, - source: &PathSource<'_, '_, '_>, - def_id: DefId, - ) -> Option> { - match source { - // e.g. `if let Enum::TupleVariant(field1, field2) = _` - PathSource::TupleStruct(_, pattern_spans) => { - err.primary_message( - "cannot match against a tuple struct which contains private fields", - ); - - // Use spans of the tuple struct pattern. - Some(Vec::from(*pattern_spans)) - } - // e.g. `let _ = Enum::TupleVariant(field1, field2);` - PathSource::Expr(Some(Expr { - kind: ExprKind::Call(path, args), - span: call_span, - .. - })) => { - err.primary_message( - "cannot initialize a tuple struct which contains private fields", - ); - self.suggest_alternative_construction_methods( - def_id, - err, - path.span, - *call_span, - &args[..], - ); - - self.r - .field_idents(def_id) - .map(|fields| fields.iter().map(|f| f.span).collect::>()) - } - _ => None, - } - } - - /// Provides context-dependent help for errors reported by the `smart_resolve_path_fragment` - /// function. - /// Returns `true` if able to provide context-dependent help. - fn smart_resolve_context_dependent_help( - &mut self, - err: &mut Diag<'_>, - span: Span, - source: PathSource<'_, '_, '_>, - path: &[Segment], - res: Res, - path_str: &str, - fallback_label: &str, - ) -> bool { - let ns = source.namespace(); - let is_expected = &|res| source.is_expected(res); - - let path_sep = |this: &Self, err: &mut Diag<'_>, expr: &Expr, kind: DefKind| { - const MESSAGE: &str = "use the path separator to refer to an item"; - - let (lhs_span, rhs_span) = match &expr.kind { - ExprKind::Field(base, ident) => (base.span, ident.span), - ExprKind::MethodCall(MethodCall { receiver, span, .. }) => (receiver.span, *span), - _ => return false, - }; - - if lhs_span.eq_ctxt(rhs_span) { - err.span_suggestion_verbose( - lhs_span.between(rhs_span), - MESSAGE, - "::", - Applicability::MaybeIncorrect, - ); - true - } else if matches!(kind, DefKind::Struct | DefKind::TyAlias) - && let Some(lhs_source_span) = lhs_span.find_ancestor_inside(expr.span) - && let Ok(snippet) = this.r.tcx.sess.source_map().span_to_snippet(lhs_source_span) - { - // The LHS is a type that originates from a macro call. - // We have to add angle brackets around it. - - err.span_suggestion_verbose( - lhs_source_span.until(rhs_span), - MESSAGE, - format!("<{snippet}>::"), - Applicability::MaybeIncorrect, - ); - true - } else { - // Either we were unable to obtain the source span / the snippet or - // the LHS originates from a macro call and it is not a type and thus - // there is no way to replace `.` with `::` and still somehow suggest - // valid Rust code. - - false - } - }; - - let find_span = |source: &PathSource<'_, '_, '_>, err: &mut Diag<'_>| { - match source { - PathSource::Expr(Some(Expr { span, kind: ExprKind::Call(_, _), .. })) - | PathSource::TupleStruct(span, _) => { - // We want the main underline to cover the suggested code as well for - // cleaner output. - err.span(*span); - *span - } - _ => span, - } - }; - - let bad_struct_syntax_suggestion = |this: &mut Self, err: &mut Diag<'_>, def_id: DefId| { - let (followed_by_brace, closing_brace) = this.followed_by_brace(span); - - match source { - PathSource::Expr(Some( - parent @ Expr { kind: ExprKind::Field(..) | ExprKind::MethodCall(..), .. }, - )) if path_sep(this, err, parent, DefKind::Struct) => {} - PathSource::Expr( - None - | Some(Expr { - kind: - ExprKind::Path(..) - | ExprKind::Binary(..) - | ExprKind::Unary(..) - | ExprKind::If(..) - | ExprKind::While(..) - | ExprKind::ForLoop { .. } - | ExprKind::Match(..), - .. - }), - ) if followed_by_brace => { - if let Some(sp) = closing_brace { - err.span_label(span, fallback_label.to_string()); - err.multipart_suggestion( - "surround the struct literal with parentheses", - vec![ - (sp.shrink_to_lo(), "(".to_string()), - (sp.shrink_to_hi(), ")".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } else { - err.span_label( - span, // Note the parentheses surrounding the suggestion below - format!( - "you might want to surround a struct literal with parentheses: \ - `({path_str} {{ /* fields */ }})`?" - ), - ); - } - } - PathSource::Expr(_) | PathSource::TupleStruct(..) | PathSource::Pat => { - let span = find_span(&source, err); - err.span_label(this.r.def_span(def_id), format!("`{path_str}` defined here")); - - let (tail, descr, applicability, old_fields) = match source { - PathSource::Pat => ("", "pattern", Applicability::MachineApplicable, None), - PathSource::TupleStruct(_, args) => ( - "", - "pattern", - Applicability::MachineApplicable, - Some( - args.iter() - .map(|a| this.r.tcx.sess.source_map().span_to_snippet(*a).ok()) - .collect::>>(), - ), - ), - _ => (": val", "literal", Applicability::HasPlaceholders, None), - }; - - // Imprecise for local structs without ctors, we don't keep fields for them. - let has_private_fields = match def_id.as_local() { - Some(def_id) => this.r.struct_ctors.get(&def_id).is_some_and(|ctor| { - ctor.has_private_fields(this.parent_scope.module, this.r) - }), - None => this.r.tcx.associated_item_def_ids(def_id).iter().any(|field_id| { - let vis = this.r.tcx.visibility(*field_id); - !this.r.is_accessible_from(vis, this.parent_scope.module) - }), - }; - if !has_private_fields { - // If the fields of the type are private, we shouldn't be suggesting using - // the struct literal syntax at all, as that will cause a subsequent error. - let fields = this.r.field_idents(def_id); - let has_fields = fields.as_ref().is_some_and(|f| !f.is_empty()); - - if let PathSource::Expr(Some(Expr { - kind: ExprKind::Call(path, args), - span, - .. - })) = source - && !args.is_empty() - && let Some(fields) = &fields - && args.len() == fields.len() - // Make sure we have same number of args as fields - { - let path_span = path.span; - let mut parts = Vec::new(); - - // Start with the opening brace - parts.push(( - path_span.shrink_to_hi().until(args[0].span), - "{".to_owned(), - )); - - for (field, arg) in fields.iter().zip(args.iter()) { - // Add the field name before the argument - parts.push((arg.span.shrink_to_lo(), format!("{}: ", field))); - } - - // Add the closing brace - parts.push(( - args.last().unwrap().span.shrink_to_hi().until(span.shrink_to_hi()), - "}".to_owned(), - )); - - err.multipart_suggestion( - format!("use struct {descr} syntax instead of calling"), - parts, - applicability, - ); - } else { - let (fields, applicability) = match fields { - Some(fields) => { - let fields = if let Some(old_fields) = old_fields { - fields - .iter() - .enumerate() - .map(|(idx, new)| (new, old_fields.get(idx))) - .map(|(new, old)| { - if let Some(Some(old)) = old - && new.as_str() != old - { - format!("{new}: {old}") - } else { - new.to_string() - } - }) - .collect::>() - } else { - fields - .iter() - .map(|f| format!("{f}{tail}")) - .collect::>() - }; - - (fields.join(", "), applicability) - } - None => { - ("/* fields */".to_string(), Applicability::HasPlaceholders) - } - }; - let pad = if has_fields { " " } else { "" }; - err.span_suggestion( - span, - format!("use struct {descr} syntax instead"), - format!("{path_str} {{{pad}{fields}{pad}}}"), - applicability, - ); - } - } - if let PathSource::Expr(Some(Expr { - kind: ExprKind::Call(path, args), - span: call_span, - .. - })) = source - { - this.suggest_alternative_construction_methods( - def_id, - err, - path.span, - *call_span, - &args[..], - ); - } - } - _ => { - err.span_label(span, fallback_label.to_string()); - } - } - }; - - match (res, source) { - ( - Res::Def(DefKind::Macro(kinds), def_id), - PathSource::Expr(Some(Expr { - kind: ExprKind::Index(..) | ExprKind::Call(..), .. - })) - | PathSource::Struct(_), - ) if kinds.contains(MacroKinds::BANG) => { - // Don't suggest macro if it's unstable. - let suggestable = def_id.is_local() - || self.r.tcx.lookup_stability(def_id).is_none_or(|s| s.is_stable()); - - err.span_label(span, fallback_label.to_string()); - - // Don't suggest `!` for a macro invocation if there are generic args - if path - .last() - .is_some_and(|segment| !segment.has_generic_args && !segment.has_lifetime_args) - && suggestable - { - err.span_suggestion_verbose( - span.shrink_to_hi(), - "use `!` to invoke the macro", - "!", - Applicability::MaybeIncorrect, - ); - } - - if path_str == "try" && span.is_rust_2015() { - err.note("if you want the `try` keyword, you need Rust 2018 or later"); - } - } - (Res::Def(DefKind::Macro(kinds), _), _) if kinds.contains(MacroKinds::BANG) => { - err.span_label(span, fallback_label.to_string()); - } - (Res::Def(DefKind::TyAlias, def_id), PathSource::Trait(_)) => { - err.span_label(span, "type aliases cannot be used as traits"); - if self.r.tcx.sess.is_nightly_build() { - let msg = "you might have meant to use `#![feature(trait_alias)]` instead of a \ - `type` alias"; - let span = self.r.def_span(def_id); - if let Ok(snip) = self.r.tcx.sess.source_map().span_to_snippet(span) { - // The span contains a type alias so we should be able to - // replace `type` with `trait`. - let snip = snip.replacen("type", "trait", 1); - err.span_suggestion(span, msg, snip, Applicability::MaybeIncorrect); - } else { - err.span_help(span, msg); - } - } - } - ( - Res::Def(kind @ (DefKind::Mod | DefKind::Trait | DefKind::TyAlias), _), - PathSource::Expr(Some(parent)), - ) if path_sep(self, err, parent, kind) => { - return true; - } - ( - Res::Def(DefKind::Enum, def_id), - PathSource::TupleStruct(..) | PathSource::Expr(..), - ) => { - self.suggest_using_enum_variant(err, source, def_id, span); - } - (Res::Def(DefKind::Struct, def_id), source) if ns == ValueNS => { - if let PathSource::Expr(Some(parent)) = source - && let ExprKind::Field(..) | ExprKind::MethodCall(..) = parent.kind - { - bad_struct_syntax_suggestion(self, err, def_id); - return true; - } - let Some(ctor) = self.r.struct_ctor(def_id) else { - bad_struct_syntax_suggestion(self, err, def_id); - return true; - }; - - // A type is re-exported and has an inaccessible constructor because it has fields - // that are inaccessible from the reexport's scope, extend the diagnostic. - let is_accessible = self.r.is_accessible_from(ctor.vis, self.parent_scope.module); - if is_accessible - && let mod_path = &path[..path.len() - 1] - && let PathResult::Module(ModuleOrUniformRoot::Module(import_mod)) = - self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Module) - && ctor.has_private_fields(import_mod, self.r) - && let Ok(import_decl) = self.r.cm().maybe_resolve_ident_in_module( - ModuleOrUniformRoot::Module(import_mod), - path.last().unwrap().ident, - TypeNS, - &self.parent_scope, - None, - ) - { - err.span_note( - import_decl.span, - "the type is accessed through this re-export, but the type's constructor \ - is not visible in this import's scope due to private fields", - ); - if !ctor.has_private_fields(self.parent_scope.module, self.r) { - err.span_suggestion_verbose( - span, - "the type can be constructed directly, because its fields are \ - available from the current scope", - // Using `tcx.def_path_str` causes the compiler to hang. - // We don't need to handle foreign crate types because in that case you - // can't access the ctor either way. - format!( - "crate{}", // The method already has leading `::`. - self.r.tcx.def_path(def_id).to_string_no_crate_verbose(), - ), - Applicability::MachineApplicable, - ); - } - self.update_err_for_private_tuple_struct_fields(err, &source, def_id); - } - if !is_expected(ctor.res) || is_accessible { - return true; - } - - let field_spans = - self.update_err_for_private_tuple_struct_fields(err, &source, def_id); - - if let Some(spans) = field_spans - .filter(|spans| spans.len() > 0 && ctor.field_visibilities.len() == spans.len()) - { - let non_visible_spans: Vec = iter::zip(&ctor.field_visibilities, &spans) - .filter(|(vis, _)| { - !self.r.is_accessible_from(**vis, self.parent_scope.module) - }) - .map(|(_, span)| *span) - .collect(); - - if non_visible_spans.len() > 0 { - if let Some(fields) = self.r.field_visibility_spans.get(&def_id) { - err.multipart_suggestion( - format!( - "consider making the field{} publicly accessible", - pluralize!(fields.len()) - ), - fields.iter().map(|span| (*span, "pub ".to_string())).collect(), - Applicability::MaybeIncorrect, - ); - } - - let mut m: MultiSpan = non_visible_spans.clone().into(); - non_visible_spans - .into_iter() - .for_each(|s| m.push_span_label(s, "private field")); - err.span_note(m, "constructor is not visible here due to private fields"); - } - - return true; - } - - err.span_label(span, "constructor is not visible here due to private fields"); - } - (Res::Def(DefKind::Union | DefKind::Variant, def_id), _) if ns == ValueNS => { - bad_struct_syntax_suggestion(self, err, def_id); - } - (Res::Def(DefKind::Ctor(_, CtorKind::Const), def_id), _) if ns == ValueNS => { - match source { - PathSource::Expr(_) | PathSource::TupleStruct(..) | PathSource::Pat => { - let span = find_span(&source, err); - err.span_label( - self.r.def_span(def_id), - format!("`{path_str}` defined here"), - ); - err.span_suggestion( - span, - "use this syntax instead", - path_str, - Applicability::MaybeIncorrect, - ); - } - _ => return false, - } - } - (Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_def_id), _) if ns == ValueNS => { - let def_id = self.r.tcx.parent(ctor_def_id); - err.span_label(self.r.def_span(def_id), format!("`{path_str}` defined here")); - let fields = self.r.field_idents(def_id).map_or_else( - || "/* fields */".to_string(), - |field_ids| vec!["_"; field_ids.len()].join(", "), - ); - err.span_suggestion( - span, - "use the tuple variant pattern syntax instead", - format!("{path_str}({fields})"), - Applicability::HasPlaceholders, - ); - } - (Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }, _) if ns == ValueNS => { - err.span_label(span, fallback_label.to_string()); - err.note("can't use `Self` as a constructor, you must use the implemented struct"); - } - ( - Res::Def(DefKind::TyAlias | DefKind::AssocTy, _), - PathSource::TraitItem(ValueNS, PathSource::TupleStruct(whole, args)), - ) => { - err.note("can't use a type alias as tuple pattern"); - - let mut suggestion = Vec::new(); - - if let &&[first, ..] = args - && let &&[.., last] = args - { - suggestion.extend([ - // "0: " has to be included here so that the fix is machine applicable. - // - // If this would only add " { " and then the code below add "0: ", - // rustfix would crash, because end of this suggestion is the same as start - // of the suggestion below. Thus, we have to merge these... - (span.between(first), " { 0: ".to_owned()), - (last.between(whole.shrink_to_hi()), " }".to_owned()), - ]); - - suggestion.extend( - args.iter() - .enumerate() - .skip(1) // See above - .map(|(index, &arg)| (arg.shrink_to_lo(), format!("{index}: "))), - ) - } else { - suggestion.push((span.between(whole.shrink_to_hi()), " {}".to_owned())); - } - - err.multipart_suggestion( - "use struct pattern instead", - suggestion, - Applicability::MachineApplicable, - ); - } - ( - Res::Def(DefKind::TyAlias | DefKind::AssocTy, _), - PathSource::TraitItem( - ValueNS, - PathSource::Expr(Some(ast::Expr { - span: whole, - kind: ast::ExprKind::Call(_, args), - .. - })), - ), - ) => { - err.note("can't use a type alias as a constructor"); - - let mut suggestion = Vec::new(); - - if let [first, ..] = &**args - && let [.., last] = &**args - { - suggestion.extend([ - // "0: " has to be included here so that the fix is machine applicable. - // - // If this would only add " { " and then the code below add "0: ", - // rustfix would crash, because end of this suggestion is the same as start - // of the suggestion below. Thus, we have to merge these... - (span.between(first.span), " { 0: ".to_owned()), - (last.span.between(whole.shrink_to_hi()), " }".to_owned()), - ]); - - suggestion.extend( - args.iter() - .enumerate() - .skip(1) // See above - .map(|(index, arg)| (arg.span.shrink_to_lo(), format!("{index}: "))), - ) - } else { - suggestion.push((span.between(whole.shrink_to_hi()), " {}".to_owned())); - } - - err.multipart_suggestion( - "use struct expression instead", - suggestion, - Applicability::MachineApplicable, - ); - } - _ => return false, - } - true - } - - fn suggest_alternative_construction_methods( - &mut self, - def_id: DefId, - err: &mut Diag<'_>, - path_span: Span, - call_span: Span, - args: &[Box], - ) { - if def_id.is_local() { - // Doing analysis on local `DefId`s would cause infinite recursion. - return; - } - // Look at all the associated functions without receivers in the type's - // inherent impls to look for builders that return `Self` - let mut items = self - .r - .tcx - .inherent_impls(def_id) - .iter() - .flat_map(|&i| self.r.tcx.associated_items(i).in_definition_order()) - // Only assoc fn with no receivers. - .filter(|item| item.is_fn() && !item.is_method()) - .filter_map(|item| { - // Only assoc fns that return `Self` - let fn_sig = self.r.tcx.fn_sig(item.def_id).skip_binder(); - // Don't normalize the return type, because that can cause cycle errors. - let ret_ty = fn_sig.output().skip_binder(); - let ty::Adt(def, _args) = ret_ty.kind() else { - return None; - }; - let input_len = fn_sig.inputs().skip_binder().len(); - if def.did() != def_id { - return None; - } - let name = item.name(); - let order = !name.as_str().starts_with("new"); - Some((order, name, input_len)) - }) - .collect::>(); - items.sort_by_key(|(order, _, _)| *order); - let suggestion = |name, args| { - format!("::{name}({})", std::iter::repeat_n("_", args).collect::>().join(", ")) - }; - match &items[..] { - [] => {} - [(_, name, len)] if *len == args.len() => { - err.span_suggestion_verbose( - path_span.shrink_to_hi(), - format!("you might have meant to use the `{name}` associated function",), - format!("::{name}"), - Applicability::MaybeIncorrect, - ); - } - [(_, name, len)] => { - err.span_suggestion_verbose( - path_span.shrink_to_hi().with_hi(call_span.hi()), - format!("you might have meant to use the `{name}` associated function",), - suggestion(name, *len), - Applicability::MaybeIncorrect, - ); - } - _ => { - err.span_suggestions_with_style( - path_span.shrink_to_hi().with_hi(call_span.hi()), - "you might have meant to use an associated function to build this type", - items.iter().map(|(_, name, len)| suggestion(name, *len)), - Applicability::MaybeIncorrect, - SuggestionStyle::ShowAlways, - ); - } - } - // We'd ideally use `type_implements_trait` but don't have access to - // the trait solver here. We can't use `get_diagnostic_item` or - // `all_traits` in resolve either. So instead we abuse the import - // suggestion machinery to get `std::default::Default` and perform some - // checks to confirm that we got *only* that trait. We then see if the - // Adt we have has a direct implementation of `Default`. If so, we - // provide a structured suggestion. - let default_trait = self - .r - .lookup_import_candidates( - Ident::with_dummy_span(sym::Default), - Namespace::TypeNS, - &self.parent_scope, - &|res: Res| matches!(res, Res::Def(DefKind::Trait, _)), - ) - .iter() - .filter_map(|candidate| candidate.did) - .find(|did| find_attr!(self.r.tcx, *did, RustcDiagnosticItem(sym::Default))); - let Some(default_trait) = default_trait else { - return; - }; - if self - .r - .extern_crate_map - .items() - // FIXME: This doesn't include impls like `impl Default for String`. - .flat_map(|(_, crate_)| { - UnordItems::new( - self.r.tcx.implementations_of_trait((*crate_, default_trait)).into_iter(), - ) - }) - .filter_map(|(_, simplified_self_ty)| *simplified_self_ty) - .filter_map(|simplified_self_ty| match simplified_self_ty { - SimplifiedType::Adt(did) => Some(did), - _ => None, - }) - .any(|did| did == def_id) - { - err.multipart_suggestion( - "consider using the `Default` trait", - vec![ - (path_span.shrink_to_lo(), "<".to_string()), - ( - path_span.shrink_to_hi().with_hi(call_span.hi()), - " as std::default::Default>::default()".to_string(), - ), - ], - Applicability::MaybeIncorrect, - ); - } - } - - /// Given the target `ident` and `kind`, search for the similarly named associated item - /// in `self.current_trait_ref`. - pub(crate) fn find_similarly_named_assoc_item( - &mut self, - ident: Symbol, - kind: &AssocItemKind, - ) -> Option { - let (module, _) = self.current_trait_ref.as_ref()?; - if ident == kw::Underscore { - // We do nothing for `_`. - return None; - } - - let targets = self - .r - .resolutions(*module) - .borrow() - .iter() - .filter_map(|(key, res)| res.borrow().best_decl().map(|binding| (key, binding.res()))) - .filter(|(_, res)| match (kind, res) { - (AssocItemKind::Const(..), Res::Def(DefKind::AssocConst { .. }, _)) => true, - (AssocItemKind::Fn(_), Res::Def(DefKind::AssocFn, _)) => true, - (AssocItemKind::Type(..), Res::Def(DefKind::AssocTy, _)) => true, - (AssocItemKind::Delegation(_), Res::Def(DefKind::AssocFn, _)) => true, - _ => false, - }) - .map(|(key, _)| key.ident.name) - .collect::>(); - - find_best_match_for_name(&targets, ident, None) - } - - fn lookup_assoc_candidate( - &mut self, - ident: Ident, - ns: Namespace, - filter_fn: FilterFn, - called: bool, - ) -> Option - where - FilterFn: Fn(Res) -> bool, - { - fn extract_node_id(t: &Ty) -> Option { - match t.kind { - TyKind::Path(None, _) => Some(t.id), - TyKind::Ref(_, ref mut_ty) => extract_node_id(&mut_ty.ty), - // This doesn't handle the remaining `Ty` variants as they are not - // that commonly the self_type, it might be interesting to provide - // support for those in future. - _ => None, - } - } - // Fields are generally expected in the same contexts as locals. - if filter_fn(Res::Local(ast::DUMMY_NODE_ID)) { - if let Some(node_id) = self.diag_metadata.current_self_type.and_then(extract_node_id) - && let Some(resolution) = self.r.partial_res_map.get(&node_id) - && let Some(Res::Def(DefKind::Struct | DefKind::Union, did)) = resolution.full_res() - && let Some(fields) = self.r.field_idents(did) - && let Some(field) = fields.iter().find(|id| ident.name == id.name) - { - // Look for a field with the same name in the current self_type. - return Some(AssocSuggestion::Field(field.span)); - } - } - - if let Some(items) = self.diag_metadata.current_trait_assoc_items { - for assoc_item in items { - if let Some(assoc_ident) = assoc_item.kind.ident() - && assoc_ident == ident - { - return Some(match &assoc_item.kind { - ast::AssocItemKind::Const(..) => AssocSuggestion::AssocConst, - ast::AssocItemKind::Fn(ast::Fn { sig, .. }) if sig.decl.has_self() => { - AssocSuggestion::MethodWithSelf { called } - } - ast::AssocItemKind::Fn(..) => AssocSuggestion::AssocFn { called }, - ast::AssocItemKind::Type(..) => AssocSuggestion::AssocType, - ast::AssocItemKind::Delegation(..) - if self - .r - .owners - .get(&assoc_item.id) - .and_then(|o| self.r.delegation_fn_sigs.get(&o.def_id)) - .is_some_and(|sig| sig.has_self) => - { - AssocSuggestion::MethodWithSelf { called } - } - ast::AssocItemKind::Delegation(..) => AssocSuggestion::AssocFn { called }, - ast::AssocItemKind::MacCall(_) | ast::AssocItemKind::DelegationMac(..) => { - continue; - } - }); - } - } - } - - // Look for associated items in the current trait. - if let Some((module, _)) = self.current_trait_ref - && let Ok(binding) = self.r.cm().maybe_resolve_ident_in_module( - ModuleOrUniformRoot::Module(module), - ident, - ns, - &self.parent_scope, - None, - ) - { - let res = binding.res(); - if filter_fn(res) { - match res { - Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => { - let has_self = match def_id.as_local() { - Some(def_id) => self - .r - .delegation_fn_sigs - .get(&def_id) - .is_some_and(|sig| sig.has_self), - None => { - self.r.tcx.fn_arg_idents(def_id).first().is_some_and(|&ident| { - matches!(ident, Some(Ident { name: kw::SelfLower, .. })) - }) - } - }; - if has_self { - return Some(AssocSuggestion::MethodWithSelf { called }); - } else { - return Some(AssocSuggestion::AssocFn { called }); - } - } - Res::Def(DefKind::AssocConst { .. }, _) => { - return Some(AssocSuggestion::AssocConst); - } - Res::Def(DefKind::AssocTy, _) => { - return Some(AssocSuggestion::AssocType); - } - _ => {} - } - } - } - - None - } - - fn lookup_typo_candidate( - &mut self, - path: &[Segment], - following_seg: Option<&Segment>, - ns: Namespace, - filter_fn: &impl Fn(Res) -> bool, - ) -> TypoCandidate { - let mut names = Vec::new(); - if let [segment] = path { - let mut ctxt = segment.ident.span.ctxt(); - - // Search in lexical scope. - // Walk backwards up the ribs in scope and collect candidates. - for rib in self.ribs[ns].iter().rev() { - let rib_ctxt = if rib.kind.contains_params() { - ctxt.normalize_to_macros_2_0() - } else { - ctxt.normalize_to_macro_rules() - }; - - // Locals and type parameters - for (ident, &res) in &rib.bindings { - if filter_fn(res) && ident.span.ctxt() == rib_ctxt { - names.push(TypoSuggestion::new(ident.name, ident.span, res)); - } - } - - if let RibKind::Block(Some(module)) = rib.kind { - self.r.add_module_candidates( - module.to_module(), - &mut names, - &filter_fn, - Some(ctxt), - ); - } else if let RibKind::Module(module) = rib.kind { - // Encountered a module item, abandon ribs and look into that module and preludes. - let parent_scope = - &ParentScope { module: module.to_module(), ..self.parent_scope }; - self.r.add_scope_set_candidates( - &mut names, - ScopeSet::All(ns), - parent_scope, - segment.ident.span.with_ctxt(ctxt), - filter_fn, - ); - break; - } - - if let RibKind::MacroDefinition(def) = rib.kind - && def == self.r.macro_def(ctxt) - { - // If an invocation of this macro created `ident`, give up on `ident` - // and switch to `ident`'s source from the macro definition. - ctxt.remove_mark(); - } - } - } else { - // Search in module. - let mod_path = &path[..path.len() - 1]; - if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = - self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Type) - { - self.r.add_module_candidates(module, &mut names, &filter_fn, None); - } - } - - // if next_seg is present, let's filter everything that does not continue the path - if let Some(following_seg) = following_seg { - names.retain(|suggestion| match suggestion.res { - Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _) => { - // FIXME: this is not totally accurate, but mostly works - suggestion.candidate != following_seg.ident.name - } - Res::Def(DefKind::Mod, def_id) => { - let module = self.r.expect_module(def_id); - self.r - .resolutions(module) - .borrow() - .iter() - .any(|(key, _)| key.ident.name == following_seg.ident.name) - } - _ => true, - }); - } - let name = path[path.len() - 1].ident.name; - // Make sure error reporting is deterministic. - names.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); - - match find_best_match_for_name( - &names.iter().map(|suggestion| suggestion.candidate).collect::>(), - name, - None, - ) { - Some(found) => { - let Some(sugg) = names.into_iter().find(|suggestion| suggestion.candidate == found) - else { - return TypoCandidate::None; - }; - if found == name { - TypoCandidate::Shadowed(sugg.res, sugg.span) - } else { - TypoCandidate::Typo(sugg) - } - } - _ => TypoCandidate::None, - } - } - - // Returns the name of the Rust type approximately corresponding to - // a type name in another programming language. - fn likely_rust_type(path: &[Segment]) -> Option { - let name = path[path.len() - 1].ident.as_str(); - // Common Java types - Some(match name { - "byte" => sym::u8, // In Java, bytes are signed, but in practice one almost always wants unsigned bytes. - "short" => sym::i16, - "Bool" => sym::bool, - "Boolean" => sym::bool, - "boolean" => sym::bool, - "int" => sym::i32, - "long" => sym::i64, - "float" => sym::f32, - "double" => sym::f64, - _ => return None, - }) - } - - // try to give a suggestion for this pattern: `name = blah`, which is common in other languages - // suggest `let name = blah` to introduce a new binding - fn let_binding_suggestion(&self, err: &mut Diag<'_>, ident_span: Span) -> bool { - if ident_span.from_expansion() { - return false; - } - - // only suggest when the code is a assignment without prefix code - if let Some(Expr { kind: ExprKind::Assign(lhs, ..), .. }) = self.diag_metadata.in_assignment - && let ast::ExprKind::Path(None, ref path) = lhs.kind - && self.r.tcx.sess.source_map().is_line_before_span_empty(ident_span) - { - let (span, text) = match path.segments.first() { - Some(seg) if let Some(name) = seg.ident.as_str().strip_prefix("let") => { - // a special case for #117894 - let name = name.trim_prefix('_'); - (ident_span, format!("let {name}")) - } - _ => (ident_span.shrink_to_lo(), "let ".to_string()), - }; - - err.span_suggestion_verbose( - span, - "you might have meant to introduce a new binding", - text, - Applicability::MaybeIncorrect, - ); - return true; - } - - // a special case for #133713 - // '=' maybe a typo of `:`, which is a type annotation instead of assignment - if err.code == Some(E0423) - && let Some((let_span, None, Some(val_span))) = self.diag_metadata.current_let_binding - && val_span.contains(ident_span) - && val_span.lo() == ident_span.lo() - { - err.span_suggestion_verbose( - let_span.shrink_to_hi().to(val_span.shrink_to_lo()), - "you might have meant to use `:` for type annotation", - ": ", - Applicability::MaybeIncorrect, - ); - return true; - } - false - } - - fn find_module(&self, def_id: DefId) -> Option<(Module<'ra>, ImportSuggestion)> { - let mut result = None; - let mut seen_modules = FxHashSet::default(); - let mut worklist = vec![(self.r.graph_root.to_module(), ThinVec::new(), true)]; - - while let Some((in_module, path_segments, doc_visible)) = worklist.pop() { - // abort if the module is already found - if result.is_some() { - break; - } - - in_module.for_each_child(self.r, |r, ident, orig_ident_span, _, name_binding| { - // abort if the module is already found or if name_binding is private external - if result.is_some() || !name_binding.vis().is_visible_locally() { - return; - } - if let Some(module_def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let doc_visible = doc_visible - && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); - if module_def_id == def_id { - let path = - Path { span: name_binding.span, segments: path_segments, tokens: None }; - result = Some(( - r.expect_module(module_def_id), - ImportSuggestion { - did: Some(def_id), - descr: "module", - path, - accessible: true, - doc_visible, - note: None, - via_import: false, - is_stable: true, - }, - )); - } else { - // add the module to the lookup - if seen_modules.insert(module_def_id) { - let module = r.expect_module(module_def_id); - worklist.push((module, path_segments, doc_visible)); - } - } - } - }); - } - - result - } - - fn collect_enum_ctors(&self, def_id: DefId) -> Option> { - self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| { - let mut variants = Vec::new(); - enum_module.for_each_child(self.r, |_, ident, orig_ident_span, _, name_binding| { - if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() { - let mut segms = enum_import_suggestion.path.segments.clone(); - segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms, tokens: None }; - variants.push((path, def_id, kind)); - } - }); - variants - }) - } - - /// Adds a suggestion for using an enum's variant when an enum is used instead. - fn suggest_using_enum_variant( - &self, - err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, - def_id: DefId, - span: Span, - ) { - let Some(variant_ctors) = self.collect_enum_ctors(def_id) else { - err.note("you might have meant to use one of the enum's variants"); - return; - }; - - // If the expression is a field-access or method-call, try to find a variant with the field/method name - // that could have been intended, and suggest replacing the `.` with `::`. - // Otherwise, suggest adding `::VariantName` after the enum; - // and if the expression is call-like, only suggest tuple variants. - let (suggest_path_sep_dot_span, suggest_only_tuple_variants) = match source { - // `Type(a, b)` in a pattern, only suggest adding a tuple variant after `Type`. - PathSource::TupleStruct(..) => (None, true), - PathSource::Expr(Some(expr)) => match &expr.kind { - // `Type(a, b)`, only suggest adding a tuple variant after `Type`. - ExprKind::Call(..) => (None, true), - // `Type.Foo(a, b)`, suggest replacing `.` -> `::` if variant `Foo` exists and is a tuple variant, - // otherwise suggest adding a variant after `Type`. - ExprKind::MethodCall(MethodCall { - receiver, - span, - seg: PathSegment { ident, .. }, - .. - }) => { - let dot_span = receiver.span.between(*span); - let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| { - *ctor_kind == CtorKind::Fn - && path.segments.last().is_some_and(|seg| seg.ident == *ident) - }); - (found_tuple_variant.then_some(dot_span), false) - } - // `Type.Foo`, suggest replacing `.` -> `::` if variant `Foo` exists and is a unit or tuple variant, - // otherwise suggest adding a variant after `Type`. - ExprKind::Field(base, ident) => { - let dot_span = base.span.between(ident.span); - let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| { - path.segments.last().is_some_and(|seg| seg.ident == *ident) - }); - (found_tuple_or_unit_variant.then_some(dot_span), false) - } - _ => (None, false), - }, - _ => (None, false), - }; - - if let Some(dot_span) = suggest_path_sep_dot_span { - err.span_suggestion_verbose( - dot_span, - "use the path separator to refer to a variant", - "::", - Applicability::MaybeIncorrect, - ); - } else if suggest_only_tuple_variants { - // Suggest only tuple variants regardless of whether they have fields and do not - // suggest path with added parentheses. - let mut suggestable_variants = variant_ctors - .iter() - .filter(|(.., kind)| *kind == CtorKind::Fn) - .map(|(variant, ..)| path_names_to_string(variant)) - .collect::>(); - suggestable_variants.sort(); - - let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len(); - - let source_msg = if matches!(source, PathSource::TupleStruct(..)) { - "to match against" - } else { - "to construct" - }; - - if !suggestable_variants.is_empty() { - let msg = if non_suggestable_variant_count == 0 && suggestable_variants.len() == 1 { - format!("try {source_msg} the enum's variant") - } else { - format!("try {source_msg} one of the enum's variants") - }; - - err.span_suggestions( - span, - msg, - suggestable_variants, - Applicability::MaybeIncorrect, - ); - } - - // If the enum has no tuple variants.. - if non_suggestable_variant_count == variant_ctors.len() { - err.help(format!("the enum has no tuple variants {source_msg}")); - } - - // If there are also non-tuple variants.. - if non_suggestable_variant_count == 1 { - err.help(format!("you might have meant {source_msg} the enum's non-tuple variant")); - } else if non_suggestable_variant_count >= 1 { - err.help(format!( - "you might have meant {source_msg} one of the enum's non-tuple variants" - )); - } - } else { - let needs_placeholder = |ctor_def_id: DefId, kind: CtorKind| { - let def_id = self.r.tcx.parent(ctor_def_id); - match kind { - CtorKind::Const => false, - CtorKind::Fn => { - !self.r.field_idents(def_id).is_some_and(|field_ids| field_ids.is_empty()) - } - } - }; - - let mut suggestable_variants = variant_ctors - .iter() - .filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind)) - .map(|(variant, _, kind)| (path_names_to_string(variant), kind)) - .map(|(variant, kind)| match kind { - CtorKind::Const => variant, - CtorKind::Fn => format!("({variant}())"), - }) - .collect::>(); - suggestable_variants.sort(); - let no_suggestable_variant = suggestable_variants.is_empty(); - - if !no_suggestable_variant { - let msg = if suggestable_variants.len() == 1 { - "you might have meant to use the following enum variant" - } else { - "you might have meant to use one of the following enum variants" - }; - - err.span_suggestions( - span, - msg, - suggestable_variants, - Applicability::MaybeIncorrect, - ); - } - - let mut suggestable_variants_with_placeholders = variant_ctors - .iter() - .filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind)) - .map(|(variant, _, kind)| (path_names_to_string(variant), kind)) - .filter_map(|(variant, kind)| match kind { - CtorKind::Fn => Some(format!("({variant}(/* fields */))")), - _ => None, - }) - .collect::>(); - suggestable_variants_with_placeholders.sort(); - - if !suggestable_variants_with_placeholders.is_empty() { - let msg = - match (no_suggestable_variant, suggestable_variants_with_placeholders.len()) { - (true, 1) => "the following enum variant is available", - (true, _) => "the following enum variants are available", - (false, 1) => "alternatively, the following enum variant is available", - (false, _) => { - "alternatively, the following enum variants are also available" - } - }; - - err.span_suggestions( - span, - msg, - suggestable_variants_with_placeholders, - Applicability::HasPlaceholders, - ); - } - }; - - if def_id.is_local() { - err.span_note(self.r.def_span(def_id), "the enum is defined here"); - } - } - - /// Detects missing const parameters in `impl` blocks and suggests adding them. - /// - /// When a const parameter is used in the self type of an `impl` but not declared - /// in the `impl`'s own generic parameter list, this function emits a targeted - /// diagnostic with a suggestion to add it at the correct position. - /// - /// Example: - /// - /// ```rust,ignore (suggested field is not completely correct, it should be a single suggestion) - /// struct C; - /// - /// impl Foo for C {} - /// // ^ the struct `C` in `C` is used as the self type - /// // ^ ^ ^ but A, X and P are not declared on the impl - /// - /// Suggested fix: - /// - /// impl Foo for C {} - /// - /// Current behavior (suggestions are emitted one-by-one): - /// - /// impl Foo for C {} - /// impl Foo for C {} - /// impl Foo for C {} - /// - /// Ideally the suggestion should aggregate them into a single line: - /// - /// impl Foo for C {} - /// ``` - /// - pub(crate) fn detect_and_suggest_const_parameter_error( - &mut self, - path: &[Segment], - source: PathSource<'_, 'ast, 'ra>, - ) -> Option> { - let Some(item) = self.diag_metadata.current_item else { return None }; - let ItemKind::Impl(impl_) = &item.kind else { return None }; - let self_ty = &impl_.self_ty; - - // Represents parameter to the struct whether `A`, `X` or `P` - let [current_parameter] = path else { - return None; - }; - - let target_ident = current_parameter.ident; - - // Find the parent segment i.e `C` in `C` - let visitor = ParentPathVisitor::new(self_ty, target_ident); - - let Some(parent_segment) = visitor.parent else { - return None; - }; - - let Some(args) = parent_segment.args.as_ref() else { - return None; - }; - - let GenericArgs::AngleBracketed(angle) = args.as_ref() else { - return None; - }; - - // Build map: NodeId of each usage in C -> its position - // e.g NodeId(A) -> 0, NodeId(X) -> 1, NodeId(C) -> 2 - let usage_to_pos: FxHashMap = angle - .args - .iter() - .enumerate() - .filter_map(|(pos, arg)| { - if let AngleBracketedArg::Arg(GenericArg::Type(ty)) = arg - && let TyKind::Path(_, path) = &ty.kind - && let [segment] = path.segments.as_slice() - { - Some((segment.id, pos)) - } else { - None - } - }) - .collect(); - - // Get the position of the missing param in C - // e.g for missing `B` in `C` this gives idx=1 - let Some(idx) = current_parameter.id.and_then(|id| usage_to_pos.get(&id).copied()) else { - return None; - }; - - // Now resolve the parent struct `C` to get its definition - let ns = source.namespace(); - let segment = Segment::from(parent_segment); - let segments = [segment]; - let finalize = Finalize::new(parent_segment.id, parent_segment.ident.span); - - if let Ok(Some(resolve)) = self.resolve_qpath_anywhere( - &None, - &segments, - ns, - source.defer_to_typeck(), - finalize, - source, - ) && let Some(resolve) = resolve.full_res() - && let Res::Def(_, def_id) = resolve - && def_id.is_local() - && let Some(local_def_id) = def_id.as_local() - && let Some(struct_generics) = self.r.struct_generics.get(&local_def_id) - && let Some(target_param) = &struct_generics.params.get(idx) - && let GenericParamKind::Const { ty, .. } = &target_param.kind - && let TyKind::Path(_, path) = &ty.kind - { - let full_type = path - .segments - .iter() - .map(|seg| seg.ident.to_string()) - .collect::>() - .join("::"); - - // Find the first impl param whose position in C - // is strictly greater than our missing param's index - // e.g missing B(idx=1), impl has A(pos=0) and C(pos=2) - // C has pos=2 > 1 so insert before C - let next_impl_param = impl_.generics.params.iter().find(|impl_param| { - angle - .args - .iter() - .find_map(|arg| { - if let AngleBracketedArg::Arg(GenericArg::Type(ty)) = arg - && let TyKind::Path(_, path) = &ty.kind - && let [segment] = path.segments.as_slice() - && segment.ident == impl_param.ident - { - usage_to_pos.get(&segment.id).copied() - } else { - None - } - }) - .map_or(false, |pos| pos > idx) - }); - - let (insert_span, snippet) = match next_impl_param { - Some(next_param) => { - // Insert in the middle before next_param - // e.g impl -> impl - ( - next_param.span().shrink_to_lo(), - format!("const {}: {}, ", target_ident, full_type), - ) - } - None => match impl_.generics.params.last() { - Some(last) => { - // Append after last existing param - // e.g impl -> impl - ( - last.span().shrink_to_hi(), - format!(", const {}: {}", target_ident, full_type), - ) - } - None => { - // No generics at all on impl - // e.g impl Foo for C -> impl Foo for C - ( - impl_.generics.span.shrink_to_hi(), - format!("", target_ident, full_type), - ) - } - }, - }; - - let mut err = self.r.dcx().struct_span_err( - target_ident.span, - format!("cannot find const `{}` in this scope", target_ident), - ); - - err.code(E0425); - - err.span_label(target_ident.span, "not found in this scope"); - - err.span_label( - target_param.span(), - format!("corresponding const parameter on the type defined here",), - ); - - err.subdiagnostic(diagnostics::UnexpectedMissingConstParameter { - span: insert_span, - snippet, - item_name: format!("{}", target_ident), - item_location: String::from("impl"), - }); - - return Some(err); - } - - None - } - - pub(crate) fn suggest_adding_generic_parameter( - &mut self, - path: &[Segment], - source: PathSource<'_, 'ast, 'ra>, - ) -> (Option<(Span, &'static str, String, Applicability)>, Option>) { - let (ident, span) = match path { - [segment] - if !segment.has_generic_args - && segment.ident.name != kw::SelfUpper - && segment.ident.name != kw::Dyn => - { - (segment.ident.to_string(), segment.ident.span) - } - _ => return (None, None), - }; - let mut iter = ident.chars().map(|c| c.is_uppercase()); - let single_uppercase_char = - matches!(iter.next(), Some(true)) && matches!(iter.next(), None); - if !self.diag_metadata.currently_processing_generic_args && !single_uppercase_char { - return (None, None); - } - match ( - self.diag_metadata.current_item, - single_uppercase_char, - self.diag_metadata.currently_processing_generic_args, - ) { - (Some(Item { kind: ItemKind::Fn(fn_), .. }), _, _) if fn_.ident.name == sym::main => { - // Ignore `fn main()` as we don't want to suggest `fn main()` - } - ( - Some(Item { - kind: - kind @ ItemKind::Fn(..) - | kind @ ItemKind::Enum(..) - | kind @ ItemKind::Struct(..) - | kind @ ItemKind::Union(..), - .. - }), - true, - _, - ) - // Without the 2nd `true`, we'd suggest `impl ` for `impl T` when a type `T` isn't found - | (Some(Item { kind: kind @ ItemKind::Impl(..), .. }), true, true) - | (Some(Item { kind, .. }), false, _) => { - if let Some(generics) = kind.generics() { - if span.overlaps(generics.span) { - // Avoid the following: - // error[E0405]: cannot find trait `A` in this scope - // --> $DIR/typo-suggestion-named-underscore.rs:CC:LL - // | - // L | fn foo(x: T) {} // Shouldn't suggest underscore - // | ^- help: you might be missing a type parameter: `, A` - // | | - // | not found in this scope - return (None, None); - } - - let (msg, sugg) = match source { - PathSource::Type | PathSource::PreciseCapturingArg(TypeNS) => { - if let Some(err) = - self.detect_and_suggest_const_parameter_error(path, source) - { - return (None, Some(err)); - } - ("you might be missing a type parameter", ident) - } - PathSource::Expr(_) | PathSource::PreciseCapturingArg(ValueNS) => ( - "you might be missing a const parameter", - format!("const {ident}: /* Type */"), - ), - _ => return (None, None), - }; - let (span, sugg) = if let [.., param] = &generics.params[..] { - let span = if let [.., bound] = ¶m.bounds[..] { - bound.span() - } else if let GenericParam { - kind: GenericParamKind::Const { ty, span: _, default }, - .. - } = param - { - default.as_ref().map(|def| def.value.span).unwrap_or(ty.span) - } else { - param.ident.span - }; - (span, format!(", {sugg}")) - } else { - (generics.span, format!("<{sugg}>")) - }; - // Do not suggest if this is coming from macro expansion. - if span.can_be_used_for_suggestions() { - return ( - Some((span.shrink_to_hi(), msg, sugg, Applicability::MaybeIncorrect)), - None, - ); - } - } - } - _ => {} - } - (None, None) - } - - /// Given the target `label`, search the `rib_index`th label rib for similarly named labels, - /// optionally returning the closest match and whether it is reachable. - pub(crate) fn suggestion_for_label_in_rib( - &self, - rib_index: usize, - label: Ident, - ) -> Option { - // Are ribs from this `rib_index` within scope? - let within_scope = self.is_label_valid_from_rib(rib_index); - - let rib = &self.label_ribs[rib_index]; - let names = rib - .bindings - .iter() - .filter(|(id, _)| id.span.eq_ctxt(label.span)) - .map(|(id, _)| id.name) - .collect::>(); - - find_best_match_for_name(&names, label.name, None).map(|symbol| { - // Upon finding a similar name, get the ident that it was from - the span - // contained within helps make a useful diagnostic. In addition, determine - // whether this candidate is within scope. - let (ident, _) = rib.bindings.iter().find(|(ident, _)| ident.name == symbol).unwrap(); - (*ident, within_scope) - }) - } - - pub(crate) fn maybe_report_lifetime_uses( - &mut self, - generics_span: Span, - params: &[ast::GenericParam], - ) { - for (param_index, param) in params.iter().enumerate() { - let GenericParamKind::Lifetime = param.kind else { continue }; - - let def_id = self.r.local_def_id(param.id); - - let use_set = self.lifetime_uses.remove(&def_id); - debug!( - "Use set for {:?}({:?} at {:?}) is {:?}", - def_id, param.ident, param.ident.span, use_set - ); - - let deletion_span = || { - if params.len() == 1 { - // if sole lifetime, remove the entire `<>` brackets - Some(generics_span) - } else if param_index == 0 { - // if removing within `<>` brackets, we also want to - // delete a leading or trailing comma as appropriate - match ( - param.span().find_ancestor_inside(generics_span), - params[param_index + 1].span().find_ancestor_inside(generics_span), - ) { - (Some(param_span), Some(next_param_span)) => { - Some(param_span.to(next_param_span.shrink_to_lo())) - } - _ => None, - } - } else { - // if removing within `<>` brackets, we also want to - // delete a leading or trailing comma as appropriate - match ( - param.span().find_ancestor_inside(generics_span), - params[param_index - 1].span().find_ancestor_inside(generics_span), - ) { - (Some(param_span), Some(prev_param_span)) => { - Some(prev_param_span.shrink_to_hi().to(param_span)) - } - _ => None, - } - } - }; - match use_set { - Some(LifetimeUseSet::Many) => {} - // A lifetime bound is a real use of that lifetime parameter, even - // though visiting a bound like `'b: 'a` only records a use of `'a`. - Some(LifetimeUseSet::One { .. }) if !param.bounds.is_empty() => {} - Some(LifetimeUseSet::One { use_span, use_ctxt }) => { - let param_ident = param.ident; - let deletion_span = - if param.bounds.is_empty() { deletion_span() } else { None }; - self.r.lint_buffer.dyn_buffer_lint_any( - lint::builtin::SINGLE_USE_LIFETIMES, - param.id, - param_ident.span, - move |dcx, level, sess| { - debug!(?param_ident, ?param_ident.span, ?use_span); - - let elidable = matches!(use_ctxt, LifetimeCtxt::Ref); - let suggestion = if let Some(deletion_span) = deletion_span { - let (use_span, replace_lt) = if elidable { - let use_span = sess - .downcast_ref::() - .expect("expected a `Session`") - .source_map() - .span_extend_while_whitespace(use_span); - (use_span, String::new()) - } else { - (use_span, "'_".to_owned()) - }; - debug!(?deletion_span, ?use_span); - - // issue 107998 for the case such as a wrong function pointer type - // `deletion_span` is empty and there is no need to report lifetime uses here - let deletion_span = if deletion_span.is_empty() { - None - } else { - Some(deletion_span) - }; - Some(diagnostics::SingleUseLifetimeSugg { - deletion_span, - use_span, - replace_lt, - }) - } else { - None - }; - diagnostics::SingleUseLifetime { - suggestion, - param_span: param_ident.span, - use_span, - ident: param_ident, - } - .into_diag(dcx, level) - }, - ); - } - None => { - debug!(?param.ident, ?param.ident.span); - let deletion_span = deletion_span(); - - // if the lifetime originates from expanded code, we won't be able to remove it #104432 - if deletion_span.is_some_and(|sp| !sp.in_derive_expansion()) { - self.r.lint_buffer.buffer_lint( - lint::builtin::UNUSED_LIFETIMES, - param.id, - param.ident.span, - diagnostics::UnusedLifetime { deletion_span, ident: param.ident }, - ); - } - } - } - } - } - - pub(crate) fn emit_undeclared_lifetime_error( - &self, - lifetime_ref: &ast::Lifetime, - outer_lifetime_ref: Option, - ) -> ErrorGuaranteed { - debug_assert_ne!(lifetime_ref.ident.name, kw::UnderscoreLifetime); - let mut err = if let Some(outer) = outer_lifetime_ref { - struct_span_code_err!( - self.r.dcx(), - lifetime_ref.ident.span, - E0401, - "can't use generic parameters from outer item", - ) - .with_span_label(lifetime_ref.ident.span, "use of generic parameter from outer item") - .with_span_label(outer.span, "lifetime parameter from outer item") - } else { - struct_span_code_err!( - self.r.dcx(), - lifetime_ref.ident.span, - E0261, - "use of undeclared lifetime name `{}`", - lifetime_ref.ident - ) - .with_span_label(lifetime_ref.ident.span, "undeclared lifetime") - }; - - // Check if this is a typo of `'static`. - if edit_distance(lifetime_ref.ident.name.as_str(), "'static", 2).is_some() { - err.span_suggestion_verbose( - lifetime_ref.ident.span, - "you may have misspelled the `'static` lifetime", - "'static", - Applicability::MachineApplicable, - ); - } else { - self.suggest_introducing_lifetime( - &mut err, - Some(lifetime_ref.ident), - |err, _, span, message, suggestion, span_suggs| { - err.multipart_suggestion( - message, - std::iter::once((span, suggestion)).chain(span_suggs).collect(), - Applicability::MaybeIncorrect, - ); - true - }, - ); - } - - err.emit() - } - - fn suggest_introducing_lifetime( - &self, - err: &mut Diag<'_>, - name: Option, - suggest: impl Fn( - &mut Diag<'_>, - bool, - Span, - Cow<'static, str>, - String, - Vec<(Span, String)>, - ) -> bool, - ) { - self.suggest_introducing_lifetime_filtered(err, name, |_| true, suggest); - } - - pub(crate) fn suggest_introducing_lifetime_for_assoc_ty_binding( - &self, - err: &mut Diag<'_>, - lifetime: Span, - ) { - self.suggest_introducing_lifetime_filtered( - err, - None, - |kind| { - !matches!( - kind, - LifetimeBinderKind::FnPtrType - | LifetimeBinderKind::PolyTrait - | LifetimeBinderKind::WhereBound - ) - }, - |err, _higher_ranked, span, message, intro_sugg, _| { - err.multipart_suggestion( - message, - vec![(span, intro_sugg), (lifetime.shrink_to_hi(), "'a ".to_string())], - Applicability::MaybeIncorrect, - ); - false - }, - ); - } - - fn suggest_introducing_lifetime_filtered( - &self, - err: &mut Diag<'_>, - name: Option, - mut consider: impl FnMut(LifetimeBinderKind) -> bool, - suggest: impl Fn( - &mut Diag<'_>, - bool, - Span, - Cow<'static, str>, - String, - Vec<(Span, String)>, - ) -> bool, - ) { - let mut suggest_note = true; - for rib in self.lifetime_ribs.iter().rev() { - let mut should_continue = true; - match rib.kind { - LifetimeRibKind::Generics { binder, span, kind } => { - // Avoid suggesting placing lifetime parameters on constant items unless the relevant - // feature is enabled. Suggest the parent item as a possible location if applicable. - if let LifetimeBinderKind::ConstItem = kind - && !self.r.tcx().features().generic_const_items() - { - continue; - } - if matches!(kind, LifetimeBinderKind::ImplAssocType) || !consider(kind) { - continue; - } - - if !span.can_be_used_for_suggestions() - && suggest_note - && let Some(name) = name - { - suggest_note = false; // Avoid displaying the same help multiple times. - err.span_label( - span, - format!( - "lifetime `{name}` is missing in item created through this procedural macro", - ), - ); - continue; - } - - let higher_ranked = matches!( - kind, - LifetimeBinderKind::FnPtrType - | LifetimeBinderKind::PolyTrait - | LifetimeBinderKind::WhereBound - ); - - let mut rm_inner_binders: FxIndexSet = Default::default(); - let (span, sugg) = if span.is_empty() { - let mut binder_idents: FxIndexSet = Default::default(); - binder_idents.insert(name.unwrap_or(Ident::from_str("'a"))); - - // We need to special case binders in the following situation: - // Change `T: for<'a> Trait + 'b` to `for<'a, 'b> T: Trait + 'b` - // T: for<'a> Trait + 'b - // ^^^^^^^ remove existing inner binder `for<'a>` - // for<'a, 'b> T: Trait + 'b - // ^^^^^^^^^^^ suggest outer binder `for<'a, 'b>` - if let LifetimeBinderKind::WhereBound = kind - && let Some(predicate) = self.diag_metadata.current_where_predicate - && let ast::WherePredicateKind::BoundPredicate( - ast::WhereBoundPredicate { bounded_ty, bounds, .. }, - ) = &predicate.kind - && bounded_ty.id == binder - { - for bound in bounds { - if let ast::GenericBound::Trait(poly_trait_ref) = bound - && let span = poly_trait_ref - .span - .with_hi(poly_trait_ref.trait_ref.path.span.lo()) - && !span.is_empty() - { - rm_inner_binders.insert(span); - poly_trait_ref.bound_generic_params.iter().for_each(|v| { - binder_idents.insert(v.ident); - }); - } - } - } - - let binders_sugg: String = binder_idents - .into_iter() - .map(|ident| ident.to_string()) - .intersperse(", ".to_owned()) - .collect(); - let sugg = format!( - "{}<{}>{}", - if higher_ranked { "for" } else { "" }, - binders_sugg, - if higher_ranked { " " } else { "" }, - ); - (span, sugg) - } else { - let span = self - .r - .tcx - .sess - .source_map() - .span_through_char(span, '<') - .shrink_to_hi(); - let sugg = - format!("{}, ", name.map(|i| i.to_string()).as_deref().unwrap_or("'a")); - (span, sugg) - }; - - if higher_ranked { - let message = Cow::from(format!( - "consider making the {} lifetime-generic with a new `{}` lifetime", - kind.descr(), - name.map(|i| i.to_string()).as_deref().unwrap_or("'a"), - )); - should_continue = suggest( - err, - true, - span, - message, - sugg, - if !rm_inner_binders.is_empty() { - rm_inner_binders - .into_iter() - .map(|v| (v, "".to_string())) - .collect::>() - } else { - vec![] - }, - ); - err.note_once( - "for more information on higher-ranked polymorphism, visit \ - https://doc.rust-lang.org/nomicon/hrtb.html", - ); - } else if let Some(name) = name { - let message = - Cow::from(format!("consider introducing lifetime `{name}` here")); - should_continue = suggest(err, false, span, message, sugg, vec![]); - } else { - let message = Cow::from("consider introducing a named lifetime parameter"); - should_continue = suggest(err, false, span, message, sugg, vec![]); - } - } - LifetimeRibKind::Item | LifetimeRibKind::ConstParamTy => break, - _ => {} - } - if !should_continue { - break; - } - } - } - - pub(crate) fn emit_non_static_lt_in_const_param_ty_error( - &self, - lifetime_ref: &ast::Lifetime, - ) -> ErrorGuaranteed { - self.r - .dcx() - .create_err(diagnostics::ParamInTyOfConstParam { - span: lifetime_ref.ident.span, - name: lifetime_ref.ident.name, - }) - .emit() - } - - /// Non-static lifetimes are prohibited in anonymous constants under `min_const_generics`. - /// This function will emit an error if `generic_const_exprs` is not enabled, the body identified by - /// `body_id` is an anonymous constant and `lifetime_ref` is non-static. - pub(crate) fn emit_forbidden_non_static_lifetime_error( - &self, - cause: NoConstantGenericsReason, - lifetime_ref: &ast::Lifetime, - ) -> ErrorGuaranteed { - match cause { - NoConstantGenericsReason::IsEnumDiscriminant => self - .r - .dcx() - .create_err(diagnostics::ParamInEnumDiscriminant { - span: lifetime_ref.ident.span, - name: lifetime_ref.ident.name, - param_kind: diagnostics::ParamKindInEnumDiscriminant::Lifetime, - }) - .emit(), - NoConstantGenericsReason::NonTrivialConstArg => { - assert!(!self.r.features.generic_const_exprs()); - self.r - .dcx() - .create_err(diagnostics::ParamInNonTrivialAnonConst { - span: lifetime_ref.ident.span, - name: lifetime_ref.ident.name, - param_kind: diagnostics::ParamKindInNonTrivialAnonConst::Lifetime, - help: self.r.tcx.sess.is_nightly_build(), - is_gca: self.r.features.generic_const_args(), - help_gca: self.r.features.generic_const_args(), - }) - .emit() - } - } - } - - pub(crate) fn report_missing_lifetime_specifiers<'a>( - &mut self, - lifetime_refs: impl Clone + IntoIterator, - function_param_lifetimes: Option<(Vec, Vec)>, - ) -> ErrorGuaranteed { - let num_lifetimes: usize = lifetime_refs.clone().into_iter().map(|lt| lt.count).sum(); - let spans: Vec<_> = lifetime_refs.clone().into_iter().map(|lt| lt.span).collect(); - - let mut err = struct_span_code_err!( - self.r.dcx(), - spans, - E0106, - "missing lifetime specifier{}", - pluralize!(num_lifetimes) - ); - self.add_missing_lifetime_specifiers_label( - &mut err, - lifetime_refs, - function_param_lifetimes, - ); - err.emit() - } - - fn add_missing_lifetime_specifiers_label<'a>( - &mut self, - err: &mut Diag<'_>, - lifetime_refs: impl Clone + IntoIterator, - function_param_lifetimes: Option<(Vec, Vec)>, - ) { - for < in lifetime_refs.clone() { - err.span_label( - lt.span, - format!( - "expected {} lifetime parameter{}", - if lt.count == 1 { "named".to_string() } else { lt.count.to_string() }, - pluralize!(lt.count), - ), - ); - } - - let mut in_scope_lifetimes: Vec<_> = self - .lifetime_ribs - .iter() - .rev() - .take_while(|rib| { - !matches!(rib.kind, LifetimeRibKind::Item | LifetimeRibKind::ConstParamTy) - }) - .flat_map(|rib| rib.bindings.iter()) - .map(|(&ident, &res)| (ident, res)) - .filter(|(ident, _)| ident.name != kw::UnderscoreLifetime) - .collect(); - debug!(?in_scope_lifetimes); - - let mut maybe_static = false; - debug!(?function_param_lifetimes); - if let Some((param_lifetimes, params)) = &function_param_lifetimes { - let elided_len = param_lifetimes.len(); - let num_params = params.len(); - - let mut m = String::new(); - - for (i, info) in params.iter().enumerate() { - let ElisionFnParameter { ident, index, lifetime_count, span } = *info; - debug_assert_ne!(lifetime_count, 0); - - err.span_label(span, ""); - - if i != 0 { - if i + 1 < num_params { - m.push_str(", "); - } else if num_params == 2 { - m.push_str(" or "); - } else { - m.push_str(", or "); - } - } - - let help_name = if let Some(ident) = ident { - format!("`{ident}`") - } else { - format!("argument {}", index + 1) - }; - - if lifetime_count == 1 { - m.push_str(&help_name[..]) - } else { - m.push_str(&format!("one of {help_name}'s {lifetime_count} lifetimes")[..]) - } - } - - if num_params == 0 { - err.help( - "this function's return type contains a borrowed value, but there is no value \ - for it to be borrowed from", - ); - if in_scope_lifetimes.is_empty() { - maybe_static = true; - in_scope_lifetimes = vec![( - Ident::with_dummy_span(kw::StaticLifetime), - (DUMMY_NODE_ID, LifetimeRes::Static), - )]; - } - } else if elided_len == 0 { - err.help( - "this function's return type contains a borrowed value with an elided \ - lifetime, but the lifetime cannot be derived from the arguments", - ); - if in_scope_lifetimes.is_empty() { - maybe_static = true; - in_scope_lifetimes = vec![( - Ident::with_dummy_span(kw::StaticLifetime), - (DUMMY_NODE_ID, LifetimeRes::Static), - )]; - } - } else if num_params == 1 { - err.help(format!( - "this function's return type contains a borrowed value, but the signature does \ - not say which {m} it is borrowed from", - )); - } else { - err.help(format!( - "this function's return type contains a borrowed value, but the signature does \ - not say whether it is borrowed from {m}", - )); - } - } - - #[allow(rustc::symbol_intern_string_literal)] - let existing_name = match &in_scope_lifetimes[..] { - [] => Symbol::intern("'a"), - [(existing, _)] => existing.name, - _ => Symbol::intern("'lifetime"), - }; - - let mut spans_suggs: Vec<_> = Vec::new(); - let source_map = self.r.tcx.sess.source_map(); - let build_sugg = |lt: MissingLifetime| match lt.kind { - MissingLifetimeKind::Underscore => { - debug_assert_eq!(lt.count, 1); - (lt.span, existing_name.to_string()) - } - MissingLifetimeKind::Ampersand => { - debug_assert_eq!(lt.count, 1); - (lt.span.shrink_to_hi(), format!("{existing_name} ")) - } - MissingLifetimeKind::Comma => { - let sugg: String = std::iter::repeat_n(existing_name.as_str(), lt.count) - .intersperse(", ") - .collect(); - let is_empty_brackets = source_map.span_followed_by(lt.span, ">").is_some(); - let sugg = if is_empty_brackets { sugg } else { format!("{sugg}, ") }; - (lt.span.shrink_to_hi(), sugg) - } - MissingLifetimeKind::Brackets => { - let sugg: String = std::iter::once("<") - .chain(std::iter::repeat_n(existing_name.as_str(), lt.count).intersperse(", ")) - .chain([">"]) - .collect(); - (lt.span.shrink_to_hi(), sugg) - } - }; - for < in lifetime_refs.clone() { - spans_suggs.push(build_sugg(lt)); - } - debug!(?spans_suggs); - match in_scope_lifetimes.len() { - 0 => { - if let Some((param_lifetimes, _)) = function_param_lifetimes { - for lt in param_lifetimes { - spans_suggs.push(build_sugg(lt)) - } - } - self.suggest_introducing_lifetime( - err, - None, - |err, higher_ranked, span, message, intro_sugg, _| { - err.multipart_suggestion( - message, - std::iter::once((span, intro_sugg)) - .chain(spans_suggs.clone()) - .collect(), - Applicability::MaybeIncorrect, - ); - higher_ranked - }, - ); - } - 1 => { - let post = if maybe_static { - let mut lifetime_refs = lifetime_refs.clone().into_iter(); - let owned = if let Some(lt) = lifetime_refs.next() - && lifetime_refs.next().is_none() - && lt.kind != MissingLifetimeKind::Ampersand - { - ", or if you will only have owned values" - } else { - "" - }; - format!( - ", but this is uncommon unless you're returning a borrowed value from a \ - `const` or a `static`{owned}", - ) - } else { - String::new() - }; - err.multipart_suggestion( - format!("consider using the `{existing_name}` lifetime{post}"), - spans_suggs, - Applicability::MaybeIncorrect, - ); - if maybe_static { - // FIXME: what follows are general suggestions, but we'd want to perform some - // minimal flow analysis to provide more accurate suggestions. For example, if - // we identified that the return expression references only one argument, we - // would suggest borrowing only that argument, and we'd skip the prior - // "use `'static`" suggestion entirely. - let mut lifetime_refs = lifetime_refs.clone().into_iter(); - if let Some(lt) = lifetime_refs.next() - && lifetime_refs.next().is_none() - && (lt.kind == MissingLifetimeKind::Ampersand - || lt.kind == MissingLifetimeKind::Underscore) - { - let pre = if let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind - && !sig.decl.inputs.is_empty() - && let sugg = sig - .decl - .inputs - .iter() - .filter_map(|param| { - if param.ty.span.contains(lt.span) { - // We don't want to suggest `fn elision(_: &fn() -> &i32)` - // when we have `fn elision(_: fn() -> &i32)` - None - } else if let TyKind::CVarArgs = param.ty.kind { - // Don't suggest `&...` for ffi fn with varargs - None - } else if let TyKind::ImplTrait(..) = ¶m.ty.kind { - // We handle these in the next `else if` branch. - None - } else { - Some((param.ty.span.shrink_to_lo(), "&".to_string())) - } - }) - .collect::>() - && !sugg.is_empty() - { - let (the, s) = if sig.decl.inputs.len() == 1 { - ("the", "") - } else { - ("one of the", "s") - }; - let dotdotdot = - if lt.kind == MissingLifetimeKind::Ampersand { "..." } else { "" }; - err.multipart_suggestion( - format!( - "instead, you are more likely to want to change {the} \ - argument{s} to be borrowed{dotdotdot}", - ), - sugg, - Applicability::MaybeIncorrect, - ); - "...or alternatively, you might want" - } else if (lt.kind == MissingLifetimeKind::Ampersand - || lt.kind == MissingLifetimeKind::Underscore) - && let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind - && let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output - && !sig.decl.inputs.is_empty() - && let arg_refs = sig - .decl - .inputs - .iter() - .filter_map(|param| match ¶m.ty.kind { - TyKind::ImplTrait(_, bounds) => Some(bounds), - _ => None, - }) - .flat_map(|bounds| bounds.into_iter()) - .collect::>() - && !arg_refs.is_empty() - { - // We have a situation like - // fn g(mut x: impl Iterator) -> Option<&()> - // So we look at every ref in the trait bound. If there's any, we - // suggest - // fn g<'a>(mut x: impl Iterator) -> Option<&'a ()> - let mut lt_finder = - LifetimeFinder { lifetime: lt.span, found: None, seen: vec![] }; - for bound in arg_refs { - if let ast::GenericBound::Trait(trait_ref) = bound { - lt_finder.visit_trait_ref(&trait_ref.trait_ref); - } - } - lt_finder.visit_ty(ret_ty); - let spans_suggs: Vec<_> = lt_finder - .seen - .iter() - .filter_map(|ty| match &ty.kind { - TyKind::Ref(_, mut_ty) => { - let span = ty.span.with_hi(mut_ty.ty.span.lo()); - Some((span, "&'a ".to_string())) - } - _ => None, - }) - .collect(); - self.suggest_introducing_lifetime( - err, - None, - |err, higher_ranked, span, message, intro_sugg, _| { - err.multipart_suggestion( - message, - std::iter::once((span, intro_sugg)) - .chain(spans_suggs.clone()) - .collect(), - Applicability::MaybeIncorrect, - ); - higher_ranked - }, - ); - "alternatively, you might want" - } else { - "instead, you are more likely to want" - }; - let mut owned_sugg = lt.kind == MissingLifetimeKind::Ampersand; - let mut sugg_is_str_to_string = false; - let mut sugg = vec![(lt.span, String::new())]; - if let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind - { - let mut lt_finder = - LifetimeFinder { lifetime: lt.span, found: None, seen: vec![] }; - for param in &sig.decl.inputs { - lt_finder.visit_ty(¶m.ty); - } - if let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output { - lt_finder.visit_ty(ret_ty); - let mut ret_lt_finder = - LifetimeFinder { lifetime: lt.span, found: None, seen: vec![] }; - ret_lt_finder.visit_ty(ret_ty); - if let [Ty { span, kind: TyKind::Ref(_, mut_ty), .. }] = - &ret_lt_finder.seen[..] - { - // We might have a situation like - // fn g(mut x: impl Iterator) -> Option<&'_ ()> - // but `lt.span` only points at `'_`, so to suggest `-> Option<()>` - // we need to find a more accurate span to end up with - // fn g<'a>(mut x: impl Iterator) -> Option<()> - sugg = vec![(span.with_hi(mut_ty.ty.span.lo()), String::new())]; - owned_sugg = true; - } - } - if let Some(ty) = lt_finder.found { - if let TyKind::Path(None, path) = &ty.kind { - // Check if the path being borrowed is likely to be owned. - let path: Vec<_> = Segment::from_path(path); - match self.resolve_path( - &path, - Some(TypeNS), - None, - PathSource::Type, - ) { - PathResult::Module(ModuleOrUniformRoot::Module(module)) => { - match module.res() { - Some(Res::PrimTy(PrimTy::Str)) => { - // Don't suggest `-> str`, suggest `-> String`. - sugg = vec![( - lt.span.with_hi(ty.span.hi()), - "String".to_string(), - )]; - sugg_is_str_to_string = true; - } - Some(Res::PrimTy(..)) => {} - Some(Res::Def( - DefKind::Struct - | DefKind::Union - | DefKind::Enum - | DefKind::ForeignTy - | DefKind::AssocTy - | DefKind::OpaqueTy - | DefKind::TyParam, - _, - )) => {} - _ => { - // Do not suggest in all other cases. - owned_sugg = false; - } - } - } - PathResult::NonModule(res) => { - match res.base_res() { - Res::PrimTy(PrimTy::Str) => { - // Don't suggest `-> str`, suggest `-> String`. - sugg = vec![( - lt.span.with_hi(ty.span.hi()), - "String".to_string(), - )]; - sugg_is_str_to_string = true; - } - Res::PrimTy(..) => {} - Res::Def( - DefKind::Struct - | DefKind::Union - | DefKind::Enum - | DefKind::ForeignTy - | DefKind::AssocTy - | DefKind::OpaqueTy - | DefKind::TyParam, - _, - ) => {} - _ => { - // Do not suggest in all other cases. - owned_sugg = false; - } - } - } - _ => { - // Do not suggest in all other cases. - owned_sugg = false; - } - } - } - if let TyKind::Slice(inner_ty) = &ty.kind { - // Don't suggest `-> [T]`, suggest `-> Vec`. - sugg = vec![ - (lt.span.with_hi(inner_ty.span.lo()), "Vec<".to_string()), - (ty.span.with_lo(inner_ty.span.hi()), ">".to_string()), - ]; - } - } - } - if owned_sugg { - if let Some(span) = - self.find_ref_prefix_span_for_owned_suggestion(lt.span) - && !sugg_is_str_to_string - { - sugg = vec![(span, String::new())]; - } - err.multipart_suggestion( - format!("{pre} to return an owned value"), - sugg, - Applicability::MaybeIncorrect, - ); - } - } - } - } - _ => { - let lifetime_spans: Vec<_> = - in_scope_lifetimes.iter().map(|(ident, _)| ident.span).collect(); - err.span_note(lifetime_spans, "these named lifetimes are available to use"); - - if spans_suggs.len() > 0 { - // This happens when we have `Foo` where we point at the space before `T`, - // but this can be confusing so we give a suggestion with placeholders. - err.multipart_suggestion( - "consider using one of the available lifetimes here", - spans_suggs, - Applicability::HasPlaceholders, - ); - } - } - } - } - - fn find_ref_prefix_span_for_owned_suggestion(&self, lifetime: Span) -> Option { - let mut finder = RefPrefixSpanFinder { lifetime, span: None }; - if let Some(item) = self.diag_metadata.current_item { - finder.visit_item(item); - } else if let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, ast::Fn { sig, .. }) = kind - { - for param in &sig.decl.inputs { - finder.visit_ty(¶m.ty); - } - if let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output { - finder.visit_ty(ret_ty); - } - } - finder.span - } -} - -fn mk_where_bound_predicate( - path: &Path, - poly_trait_ref: &ast::PolyTraitRef, - ty: &Ty, -) -> Option { - let modified_segments = { - let mut segments = path.segments.clone(); - let [preceding @ .., second_last, last] = segments.as_mut_slice() else { - return None; - }; - let mut segments = ThinVec::from(preceding); - - let added_constraint = ast::AngleBracketedArg::Constraint(ast::AssocItemConstraint { - id: DUMMY_NODE_ID, - ident: last.ident, - gen_args: None, - kind: ast::AssocItemConstraintKind::Equality { - term: ast::Term::Ty(Box::new(ast::Ty { - kind: ast::TyKind::Path(None, poly_trait_ref.trait_ref.path.clone()), - id: DUMMY_NODE_ID, - span: DUMMY_SP, - tokens: None, - })), - }, - span: DUMMY_SP, - }); - - match second_last.args.as_deref_mut() { - Some(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { args, .. })) => { - args.push(added_constraint); - } - Some(_) => return None, - None => { - second_last.args = - Some(Box::new(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { - args: ThinVec::from([added_constraint]), - span: DUMMY_SP, - }))); - } - } - - segments.push(second_last.clone()); - segments - }; - - let new_where_bound_predicate = ast::WhereBoundPredicate { - bound_generic_params: ThinVec::new(), - bounded_ty: Box::new(ty.clone()), - bounds: thin_vec![ast::GenericBound::Trait(ast::PolyTraitRef { - bound_generic_params: ThinVec::new(), - modifiers: ast::TraitBoundModifiers::NONE, - trait_ref: ast::TraitRef { - path: ast::Path { segments: modified_segments, span: DUMMY_SP, tokens: None }, - ref_id: DUMMY_NODE_ID, - }, - span: DUMMY_SP, - parens: ast::Parens::No, - })], - }; - - Some(new_where_bound_predicate) -} - -/// Report lifetime/lifetime shadowing as an error. -pub(super) fn signal_lifetime_shadowing( - sess: &Session, - orig: Ident, - shadower: Ident, -) -> ErrorGuaranteed { - struct_span_code_err!( - sess.dcx(), - shadower.span, - E0496, - "lifetime name `{}` shadows a lifetime name that is already in scope", - orig.name, - ) - .with_span_label(orig.span, "first declared here") - .with_span_label(shadower.span, format!("lifetime `{}` already in scope", orig.name)) - .emit() -} - -struct LifetimeFinder<'ast> { - lifetime: Span, - found: Option<&'ast Ty>, - seen: Vec<&'ast Ty>, -} - -impl<'ast> Visitor<'ast> for LifetimeFinder<'ast> { - fn visit_ty(&mut self, t: &'ast Ty) { - if let TyKind::Ref(_, mut_ty) | TyKind::PinnedRef(_, mut_ty) = &t.kind { - self.seen.push(t); - if t.span.lo() == self.lifetime.lo() { - self.found = Some(&mut_ty.ty); - } - } - walk_ty(self, t) - } -} - -struct RefPrefixSpanFinder { - lifetime: Span, - span: Option, -} - -impl<'ast> Visitor<'ast> for RefPrefixSpanFinder { - fn visit_ty(&mut self, t: &'ast Ty) { - if self.span.is_some() { - return; - } - if let TyKind::Ref(_, mut_ty) | TyKind::PinnedRef(_, mut_ty) = &t.kind - && t.span.lo() == self.lifetime.lo() - { - self.span = Some(t.span.with_hi(mut_ty.ty.span.lo())); - return; - } - walk_ty(self, t); - } -} - -/// Shadowing involving a label is only a warning for historical reasons. -//FIXME: make this a proper lint. -pub(super) fn signal_label_shadowing(sess: &Session, orig: Span, shadower: Ident) { - let name = shadower.name; - let shadower = shadower.span; - sess.dcx() - .struct_span_warn( - shadower, - format!("label name `{name}` shadows a label name that is already in scope"), - ) - .with_span_label(orig, "first declared here") - .with_span_label(shadower, format!("label `{name}` already in scope")) - .emit(); -} - -struct ParentPathVisitor<'a> { - target: Ident, - parent: Option<&'a PathSegment>, - stack: Vec<&'a Ty>, -} - -impl<'a> ParentPathVisitor<'a> { - fn new(self_ty: &'a Ty, target: Ident) -> Self { - let mut v = ParentPathVisitor { target, parent: None, stack: Vec::new() }; - - v.visit_ty(self_ty); - v - } -} - -impl<'a> Visitor<'a> for ParentPathVisitor<'a> { - fn visit_ty(&mut self, ty: &'a Ty) { - if self.parent.is_some() { - return; - } - - // push current type - self.stack.push(ty); - - if let TyKind::Path(_, path) = &ty.kind - // is this just `N`? - && let [segment] = path.segments.as_slice() - && segment.ident == self.target - // parent is previous element in stack - && let [.., parent_ty, _ty] = self.stack.as_slice() - && let TyKind::Path(_, parent_path) = &parent_ty.kind - { - self.parent = parent_path.segments.first(); - } - - walk_ty(self, ty); - - self.stack.pop(); - } -}