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/late/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs similarity index 53% rename from compiler/rustc_resolve/src/late/diagnostics.rs rename to compiler/rustc_resolve/src/diagnostics/impls.rs index 668278f13d9cf..b04065932fe48 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1,47 +1,4173 @@ // ignore-tidy-filelength use std::borrow::Cow; -use std::iter; -use std::ops::Deref; +use std::ops::{ControlFlow, Deref}; +use std::{iter, mem}; -use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty}; +use itertools::Itertools as _; +use rustc_ast::visit::{self, 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, + 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::{path_to_string, where_bound_predicate_to_string}; +use rustc_attr_parsing::AttributeParser; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; -use rustc_data_structures::unord::UnordItems; +use rustc_data_structures::unord::{UnordItems, UnordMap, UnordSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, - struct_span_code_err, + 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}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, LifetimeRes, MacroKinds, NonMacroAttrKind, PerNS}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; -use rustc_hir::{MissingLifetimeKind, PrimTy, find_attr}; -use rustc_middle::ty; +use rustc_hir::{Attribute, MissingLifetimeKind, PrimTy, Stability, StabilityLevel, find_attr}; +use rustc_middle::bug; +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_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 rustc_span::hygiene::MacroKind; +use rustc_span::source_map::SourceMap; +use rustc_span::{ + BytePos, DUMMY_SP, DesugaringKind, Ident, RemapPathScopeComponents, Span, Spanned, Symbol, + SyntaxContext, kw, sym, +}; use thin_vec::{ThinVec, thin_vec}; -use tracing::debug; +use tracing::{debug, instrument}; + +use crate::diagnostics::{ + self, AddedMacroUse, ChangeImportBinding, ChangeImportBindingSuggestion, ConsiderAddingADerive, + ExplicitUnsafeTraits, MacroDefinedLater, MacroRulesNot, MacroSuggMovePosition, + MaybeMissingMacroRulesName, +}; +use crate::hygiene::Macros20NormalizedSyntaxContext; +use crate::imports::{Import, ImportKind, UnresolvedImportError, import_path_to_string}; +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, + 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. +pub(crate) type Suggestion = (Vec<(Span, String)>, String, Applicability); + +/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a +/// similarly named label and whether or not it is reachable. +pub(crate) type LabelSuggestion = (Ident, bool); + +#[derive(Clone)] +pub(crate) struct StructCtor { + pub res: Res, + pub vis: Visibility, + pub field_visibilities: Vec>, +} + +impl StructCtor { + pub(crate) fn has_private_fields<'ra>(&self, m: Module<'ra>, r: &Resolver<'ra, '_>) -> bool { + self.field_visibilities.iter().any(|&vis| !r.is_accessible_from(vis, m)) + } +} + +#[derive(Debug)] +pub(crate) enum SuggestionTarget { + /// The target has a similar name as the name used by the programmer (probably a typo) + SimilarlyNamed, + /// The target is the only valid item that can be used in the corresponding context + SingleItem, +} + +#[derive(Debug)] +pub(crate) struct TypoSuggestion { + pub candidate: Symbol, + /// The source location where the name is defined; None if the name is not defined + /// in source e.g. primitives + pub span: Option, + pub res: Res, + pub target: SuggestionTarget, +} + +impl TypoSuggestion { + pub(crate) fn new(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { + Self { candidate, span: Some(span), res, target: SuggestionTarget::SimilarlyNamed } + } + pub(crate) fn typo_from_name(candidate: Symbol, res: Res) -> TypoSuggestion { + Self { candidate, span: None, res, target: SuggestionTarget::SimilarlyNamed } + } + pub(crate) fn single_item(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { + Self { candidate, span: Some(span), res, target: SuggestionTarget::SingleItem } + } +} + +/// A free importable items suggested in case of resolution failure. +#[derive(Debug)] +pub(crate) struct ImportSuggestion { + pub did: Option, + pub descr: &'static str, + pub path: Path, + pub accessible: bool, + // false if the path traverses a foreign `#[doc(hidden)]` item. + pub doc_visible: bool, + pub via_import: bool, + /// An extra note that should be issued if this item is suggested + pub note: Option, + pub is_stable: bool, +} + +/// Adjust the impl span so that just the `impl` keyword is taken by removing +/// everything after `<` (`"impl Iterator for A {}" -> "impl"`) and +/// everything after the first whitespace (`"impl Iterator for A" -> "impl"`). +/// +/// *Attention*: the method used is very fragile since it essentially duplicates the work of the +/// parser. If you need to use this function or something similar, please consider updating the +/// `source_map` functions and this function to something more robust. +fn reduce_impl_span_to_impl_keyword(sm: &SourceMap, impl_span: Span) -> Span { + let impl_span = sm.span_until_char(impl_span, '<'); + sm.span_until_whitespace(impl_span) +} + +impl<'ra, 'tcx> Resolver<'ra, 'tcx> { + /// Reports unresolved imports. + /// + /// Multiple unresolved import errors within the same use tree are combined into a single + /// diagnostic. + pub(crate) fn throw_unresolved_import_error( + &mut self, + mut errors: Vec<(Import<'_>, UnresolvedImportError)>, + glob_error: bool, + ) { + errors.retain(|(_import, err)| match err.module { + // Skip `use` errors for `use foo::Bar;` if `foo.rs` has unrecovered parse errors. + Some(def_id) if self.mods_with_parse_errors.contains(&def_id) => false, + // If we've encountered something like `use _;`, we've already emitted an error stating + // that `_` is not a valid identifier, so we ignore that resolve error. + _ => err.segment.map(|s| s.name) != Some(kw::Underscore), + }); + if errors.is_empty() { + self.tcx.dcx().delayed_bug("expected a parse or \"`_` can't be an identifier\" error"); + return; + } + + let span = MultiSpan::from_spans(errors.iter().map(|(_, err)| err.span).collect()); + + let paths = errors + .iter() + .map(|(import, err)| { + let path = import_path_to_string( + &import.module_path.iter().map(|seg| seg.ident).collect::>(), + &import.kind, + err.span, + ); + format!("`{path}`") + }) + .collect::>(); + let default_message = + format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); + + // Process `import` use of the `#[diagnostic::on_unknown]` attribute. + // + // We don't need to check feature gates here; that happens on initialization of the + // `on_unknown_attr` fields. + let (mut message, label, mut notes) = + if let Some(directive) = errors[0].1.on_unknown_attr.as_ref().map(|a| &a.directive) { + let this = errors + .iter() + .map(|(_import, err)| { + // Is this unwrap_or reachable? + err.segment.map(|s| s.name).unwrap_or(kw::Underscore) + }) + .join(", "); + + let args = FormatArgs { unresolved: this.clone(), this, .. }; + + let CustomDiagnostic { message, label, notes, parent_label: _dead } = + directive.eval(None, &args); + + (message, label, notes) + } else { + (None, None, Vec::new()) + }; + + // `module` use of the `#[diagnostic::on_unknown]` attribute. + // We assume that someone who put the attribute on the import has more information than + // the person who put it on the module, so we choose to prioritize the import attribute. + let mut mod_diagnostics: Vec = errors + .iter() + .map(|(import, import_error)| { + if let Some(ModuleOrUniformRoot::Module(module_data)) = import.imported_module.get() + && let ModuleKind::Def(DefKind::Mod, def_id, _, name) = module_data.kind + { + let Some(directive) = self.on_unknown_data(def_id) else { + return CustomDiagnostic::default(); + }; + + let this = if let Some(name) = name { + name.to_string() + } else if let Some(crate_name) = &self.tcx.sess.opts.crate_name { + crate_name.to_string() + } else { + "".to_string() + }; + let unresolved = import_error.segment.map(|s| s.name).unwrap_or(kw::Underscore); + let args = FormatArgs { this, unresolved: unresolved.to_string(), .. }; + + directive.eval(None, &args) + } else { + CustomDiagnostic::default() + } + }) + .collect(); + + // If there is no import attribute with a message, + // but all mod messages are the same, use that. + let mod_message = + mod_diagnostics.iter_mut().flat_map(|d| d.message.take()).all_equal_value(); + if message.is_none() + && let Ok(mod_msg) = mod_message + { + message = Some(mod_msg); + } + + let mut diag = if let Some(message) = message { + struct_span_code_err!(self.dcx(), span, E0432, "{message}").with_note(default_message) + } else { + struct_span_code_err!(self.dcx(), span, E0432, "{default_message}") + }; + + for mod_diag in mod_diagnostics.iter_mut() { + for mod_note in mod_diag.notes.drain(..) { + if !notes.contains(&mod_note) { + notes.push(mod_note); + } + } + } + + if !notes.is_empty() { + for note in notes { + diag.note(note); + } + } else if let Some((_, UnresolvedImportError { note: Some(note), .. })) = + errors.iter().last() + { + diag.note(note.clone()); + } + + /// Upper limit on the number of `span_label` messages. + const MAX_LABEL_COUNT: usize = 10; + let mod_labels = mod_diagnostics.into_iter().map(|cd| cd.label); + + for ((import, err), mod_label) in errors.into_iter().zip(mod_labels).take(MAX_LABEL_COUNT) { + let label_span = match err.segment { + Some(segment) => segment.span, + None => err.span, + }; + if let Some(label) = &label { + diag.span_label(label_span, label.clone()); + } else if let Some(label) = mod_label { + diag.span_label(label_span, label); + } else if let Some(label) = &err.label { + diag.span_label(label_span, label.clone()); + } + + if let Some((suggestions, msg, applicability)) = err.suggestion { + if suggestions.is_empty() { + diag.help(msg); + continue; + } + diag.multipart_suggestion(msg, suggestions, applicability); + } + + if let Some(candidates) = &err.candidates { + match &import.kind { + ImportKind::Single { nested: false, source, target, .. } => import_candidates( + self.tcx, + &mut diag, + Some(err.span), + candidates, + DiagMode::Import { append: false, unresolved_import: true }, + (source != target) + .then(|| format!(" as {target}")) + .as_deref() + .unwrap_or(""), + ), + ImportKind::Single { nested: true, source, target, .. } => { + import_candidates( + self.tcx, + &mut diag, + None, + candidates, + DiagMode::Normal, + (source != target) + .then(|| format!(" as {target}")) + .as_deref() + .unwrap_or(""), + ); + } + _ => {} + } + } + + if matches!(import.kind, ImportKind::Single { .. }) + && let Some(segment) = err.segment + && let Some(module) = err.module + { + self.find_cfg_stripped(&mut diag, &segment.name, module) + } + } + + let guar = diag.emit(); + if glob_error { + self.glob_error = Some(guar); + } + } + + pub(crate) fn dcx(&self) -> DiagCtxtHandle<'tcx> { + self.tcx.dcx() + } + + pub(crate) fn report_errors(&mut self, krate: &Crate) { + self.report_delayed_vis_resolution_errors(); + self.report_with_use_injections(krate); + + for &(span_use, span_def) in &self.macro_expanded_macro_export_errors { + self.lint_buffer.buffer_lint( + MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, + CRATE_NODE_ID, + span_use, + diagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths { + definition: span_def, + }, + ); + } + + for ambiguity_error in &self.ambiguity_errors { + let mut diag = self.ambiguity_diagnostic(ambiguity_error); + + if let Some(ambiguity_warning) = ambiguity_error.warning { + let node_id = match ambiguity_error.b1.0.kind { + DeclKind::Import { import, .. } => import.root_id, + DeclKind::Def(_) => CRATE_NODE_ID, + }; + + let lint = match ambiguity_warning { + _ if ambiguity_error.ambig_vis.is_some() => AMBIGUOUS_IMPORT_VISIBILITIES, + AmbiguityWarning::GlobImport => AMBIGUOUS_GLOB_IMPORTS, + AmbiguityWarning::PanicImport => AMBIGUOUS_PANIC_IMPORTS, + }; + + self.lint_buffer.buffer_lint(lint, node_id, diag.ident.span, diag); + } else { + diag.is_error = true; + self.dcx().emit_err(diag); + } + } + + let mut reported_spans = FxHashSet::default(); + for error in mem::take(&mut self.privacy_errors) { + if reported_spans.insert(error.dedup_span) { + self.report_privacy_error(&error); + } + } + } + + fn report_delayed_vis_resolution_errors(&mut self) { + for DelayedVisResolutionError { vis, parent_scope, error } in + mem::take(&mut self.delayed_vis_resolution_errors) + { + match self.try_resolve_visibility(&parent_scope, &vis, true) { + Ok(_) => self.report_vis_error(error), + Err(error) => self.report_vis_error(error), + }; + } + } + + fn report_with_use_injections(&mut self, krate: &Crate) { + for UseError { mut err, candidates, node_id, instead, suggestion, path, is_call } in + mem::take(&mut self.use_injections) + { + let (span, found_use) = if node_id != DUMMY_NODE_ID { + UsePlacementFinder::check(krate, node_id) + } else { + (None, FoundUse::No) + }; + + if !candidates.is_empty() { + show_candidates( + self.tcx, + &mut err, + span, + &candidates, + if instead { Instead::Yes } else { Instead::No }, + found_use, + DiagMode::Normal, + path, + "", + ); + err.emit(); + } else if let Some((span, msg, sugg, appl)) = suggestion { + err.span_suggestion_verbose(span, msg, sugg, appl); + err.emit(); + } else if let [segment] = path.as_slice() + && is_call + { + err.stash(segment.ident.span, rustc_errors::StashKey::CallIntoMethod); + } else { + err.emit(); + } + } + } + + pub(crate) fn report_conflict( + &mut self, + ident: IdentKey, + ns: Namespace, + old_binding: Decl<'ra>, + new_binding: Decl<'ra>, + ) { + // Error on the second of two conflicting names + if old_binding.span.lo() > new_binding.span.lo() { + return self.report_conflict(ident, ns, new_binding, old_binding); + } + + let container = match old_binding.parent_module.unwrap().expect_local().kind { + // Avoid using TyCtxt::def_kind_descr in the resolver, because it + // indirectly *calls* the resolver, and would cause a query cycle. + ModuleKind::Def(kind, def_id, _, _) => kind.descr(def_id), + ModuleKind::Block => "block", + }; + + let (name, span) = + (ident.name, self.tcx.sess.source_map().guess_head_span(new_binding.span)); + + if self.name_already_seen.get(&name) == Some(&span) { + return; + } + + let old_kind = match (ns, old_binding.res()) { + (ValueNS, _) => "value", + (MacroNS, _) => "macro", + (TypeNS, _) if old_binding.is_extern_crate() => "extern crate", + (TypeNS, Res::Def(DefKind::Mod, _)) => "module", + (TypeNS, Res::Def(DefKind::Trait, _)) => "trait", + (TypeNS, _) => "type", + }; + + let code = match (old_binding.is_extern_crate(), new_binding.is_extern_crate()) { + (true, true) => E0259, + (true, _) | (_, true) => match new_binding.is_import() && old_binding.is_import() { + true => E0254, + false => E0260, + }, + _ => match (old_binding.is_import_user_facing(), new_binding.is_import_user_facing()) { + (false, false) => E0428, + (true, true) => E0252, + _ => E0255, + }, + }; + + let label = match new_binding.is_import_user_facing() { + true => diagnostics::NameDefinedMultipleTimeLabel::Reimported { span, name }, + false => diagnostics::NameDefinedMultipleTimeLabel::Redefined { span, name }, + }; + + let old_binding_label = + (!old_binding.span.is_dummy() && old_binding.span != span).then(|| { + let span = self.tcx.sess.source_map().guess_head_span(old_binding.span); + match old_binding.is_import_user_facing() { + true => diagnostics::NameDefinedMultipleTimeOldBindingLabel::Import { + span, + old_kind, + name, + }, + false => diagnostics::NameDefinedMultipleTimeOldBindingLabel::Definition { + span, + old_kind, + name, + }, + } + }); + + let mut err = self + .dcx() + .create_err(diagnostics::NameDefinedMultipleTime { + span, + name, + descr: ns.descr(), + container, + label, + old_binding_label, + }) + .with_code(code); + + // See https://github.com/rust-lang/rust/issues/32354 + use DeclKind::Import; + let can_suggest = |binding: Decl<'_>, import: self::Import<'_>| { + !binding.span.is_dummy() + && !matches!(import.kind, ImportKind::MacroUse { .. } | ImportKind::MacroExport) + }; + let import = match (&new_binding.kind, &old_binding.kind) { + // If there are two imports where one or both have attributes then prefer removing the + // import without attributes. + (Import { import: new, .. }, Import { import: old, .. }) + if { + (new.has_attributes || old.has_attributes) + && can_suggest(old_binding, *old) + && can_suggest(new_binding, *new) + } => + { + if old.has_attributes { + Some((*new, new_binding.span, true)) + } else { + Some((*old, old_binding.span, true)) + } + } + // Otherwise prioritize the new binding. + (Import { import, .. }, other) if can_suggest(new_binding, *import) => { + Some((*import, new_binding.span, other.is_import())) + } + (other, Import { import, .. }) if can_suggest(old_binding, *import) => { + Some((*import, old_binding.span, other.is_import())) + } + _ => None, + }; + + // Check if the target of the use for both bindings is the same. + let duplicate = new_binding.res().opt_def_id() == old_binding.res().opt_def_id(); + let has_dummy_span = new_binding.span.is_dummy() || old_binding.span.is_dummy(); + let from_item = + self.extern_prelude.get(&ident).is_none_or(|entry| entry.introduced_by_item()); + // Only suggest removing an import if both bindings are to the same def, if both spans + // aren't dummy spans. Further, if both bindings are imports, then the ident must have + // been introduced by an item. + let should_remove_import = duplicate + && !has_dummy_span + && ((new_binding.is_extern_crate() || old_binding.is_extern_crate()) || from_item); + + match import { + Some((import, span, true)) if should_remove_import && import.is_nested() => { + self.add_suggestion_for_duplicate_nested_use(&mut err, import, span); + } + Some((import, _, true)) if should_remove_import && !import.is_glob() => { + // Simple case - remove the entire import. Due to the above match arm, this can + // only be a single use so just remove it entirely. + err.subdiagnostic(diagnostics::ToolOnlyRemoveUnnecessaryImport { + span: import.use_span_with_attributes, + }); + } + Some((import, span, _)) => { + self.add_suggestion_for_rename_of_use(&mut err, name, import, span); + } + _ => {} + } + + err.emit(); + self.name_already_seen.insert(name, span); + } + + /// This function adds a suggestion to change the binding name of a new import that conflicts + /// with an existing import. + /// + /// ```text,ignore (diagnostic) + /// help: you can use `as` to change the binding name of the import + /// | + /// LL | use foo::bar as other_bar; + /// | ^^^^^^^^^^^^^^^^^^^^^ + /// ``` + fn add_suggestion_for_rename_of_use( + &self, + err: &mut Diag<'_>, + name: Symbol, + import: Import<'_>, + binding_span: Span, + ) { + let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() { + format!("Other{name}") + } else { + format!("other_{name}") + }; + + let mut suggestion = None; + let mut span = binding_span; + match import.kind { + ImportKind::Single { source, .. } => { + if let Some(pos) = source.span.hi().0.checked_sub(binding_span.lo().0) + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(binding_span) + && pos as usize <= snippet.len() + { + span = binding_span.with_lo(binding_span.lo() + BytePos(pos)).with_hi( + binding_span.hi() - BytePos(if snippet.ends_with(';') { 1 } else { 0 }), + ); + suggestion = Some(format!(" as {suggested_name}")); + } + } + ImportKind::ExternCrate { source, target, .. } => { + suggestion = Some(format!( + "extern crate {} as {};", + source.unwrap_or(target.name), + suggested_name, + )) + } + _ => unreachable!(), + } + + if let Some(suggestion) = suggestion { + err.subdiagnostic(ChangeImportBindingSuggestion { span, suggestion }); + } else { + err.subdiagnostic(ChangeImportBinding { span }); + } + } + + /// This function adds a suggestion to remove an unnecessary binding from an import that is + /// nested. In the following example, this function will be invoked to remove the `a` binding + /// in the second use statement: + /// + /// ```ignore (diagnostic) + /// use issue_52891::a; + /// use issue_52891::{d, a, e}; + /// ``` + /// + /// The following suggestion will be added: + /// + /// ```ignore (diagnostic) + /// use issue_52891::{d, a, e}; + /// ^-- help: remove unnecessary import + /// ``` + /// + /// If the nested use contains only one import then the suggestion will remove the entire + /// line. + /// + /// It is expected that the provided import is nested - this isn't checked by the + /// function. If this invariant is not upheld, this function's behaviour will be unexpected + /// as characters expected by span manipulations won't be present. + fn add_suggestion_for_duplicate_nested_use( + &self, + err: &mut Diag<'_>, + import: Import<'_>, + binding_span: Span, + ) { + assert!(import.is_nested()); + + // Two examples will be used to illustrate the span manipulations we're doing: + // + // - Given `use issue_52891::{d, a, e};` where `a` is a duplicate then `binding_span` is + // `a` and `import.use_span` is `issue_52891::{d, a, e};`. + // - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is + // `a` and `import.use_span` is `issue_52891::{d, e, a};`. + + let (found_closing_brace, span) = + find_span_of_binding_until_next_binding(self.tcx.sess, binding_span, import.use_span); + + // If there was a closing brace then identify the span to remove any trailing commas from + // previous imports. + if found_closing_brace { + if let Some(span) = extend_span_to_previous_binding(self.tcx.sess, span) { + err.subdiagnostic(diagnostics::ToolOnlyRemoveUnnecessaryImport { span }); + } else { + // Remove the entire line if we cannot extend the span back, this indicates an + // `issue_52891::{self}` case. + err.subdiagnostic(diagnostics::RemoveUnnecessaryImport { + span: import.use_span_with_attributes, + }); + } + + return; + } + + err.subdiagnostic(diagnostics::RemoveUnnecessaryImport { span }); + } + + pub(crate) fn lint_if_path_starts_with_module( + &mut self, + finalize: Finalize, + path: &[Segment], + second_binding: Option>, + ) { + let Finalize { node_id, root_span, .. } = finalize; + + let first_name = match path.get(0) { + // In the 2018 edition this lint is a hard error, so nothing to do + Some(seg) if seg.ident.span.is_rust_2015() && self.tcx.sess.is_rust_2015() => { + seg.ident.name + } + _ => return, + }; + + // We're only interested in `use` paths which should start with + // `{{root}}` currently. + if first_name != kw::PathRoot { + return; + } + + match path.get(1) { + // If this import looks like `crate::...` it's already good + Some(Segment { ident, .. }) if ident.name == kw::Crate => return, + // Otherwise go below to see if it's an extern crate + Some(_) => {} + // If the path has length one (and it's `PathRoot` most likely) + // then we don't know whether we're gonna be importing a crate or an + // item in our crate. Defer this lint to elsewhere + None => return, + } + + // If the first element of our path was actually resolved to an + // `ExternCrate` (also used for `crate::...`) then no need to issue a + // warning, this looks all good! + if let Some(binding) = second_binding + && let DeclKind::Import { import, .. } = binding.kind + // Careful: we still want to rewrite paths from renamed extern crates. + && let ImportKind::ExternCrate { source: None, .. } = import.kind + { + return; + } + + self.lint_buffer.dyn_buffer_lint_any( + ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, + node_id, + root_span, + move |dcx, level, sess| { + let (replacement, applicability) = match sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_to_snippet(root_span) + { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), + }; + diagnostics::AbsPathWithModule { + sugg: diagnostics::AbsPathWithModuleSugg { + span: root_span, + applicability, + replacement, + }, + } + .into_diag(dcx, level) + }, + ); + } + + pub(crate) fn add_module_candidates( + &self, + module: Module<'ra>, + names: &mut Vec, + filter_fn: &impl Fn(Res) -> bool, + ctxt: Option, + ) { + module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { + let res = binding.res(); + if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { + names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); + } + }); + } + + /// Combines an error with provided span and emits it. + /// + /// This takes the error provided, combines it with the span and any additional spans inside the + /// error and emits it. + pub(crate) fn report_error( + &mut self, + span: Span, + resolution_error: ResolutionError<'ra>, + ) -> ErrorGuaranteed { + self.into_struct_error(span, resolution_error).emit() + } + + pub(crate) fn into_struct_error( + &mut self, + span: Span, + resolution_error: ResolutionError<'ra>, + ) -> Diag<'_> { + match resolution_error { + ResolutionError::GenericParamsFromOuterItem { + outer_res, + has_generic_params, + def_kind, + inner_item, + current_self_ty, + } => { + use diagnostics::GenericParamsFromOuterItemLabel as Label; + let static_or_const = match def_kind { + DefKind::Static { .. } => { + Some(diagnostics::GenericParamsFromOuterItemStaticOrConst::Static) + } + DefKind::Const { .. } => { + Some(diagnostics::GenericParamsFromOuterItemStaticOrConst::Const) + } + _ => None, + }; + let is_self = + matches!(outer_res, Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }); + let mut err = diagnostics::GenericParamsFromOuterItem { + span, + label: None, + refer_to_type_directly: None, + use_let: None, + sugg: None, + static_or_const, + is_self, + item: inner_item.as_ref().map(|(label_span, _, kind)| { + diagnostics::GenericParamsFromOuterItemInnerItem { + span: *label_span, + descr: kind.descr().to_string(), + is_self, + } + }), + }; + + let sm = self.tcx.sess.source_map(); + // Note: do not early return for missing def_id here, + // we still want to provide suggestions for `Res::SelfTyParam` and `Res::SelfTyAlias`. + let def_id = match outer_res { + Res::SelfTyParam { .. } => { + err.label = Some(Label::SelfTyParam(span)); + None + } + Res::SelfTyAlias { alias_to: def_id, .. } => { + err.label = Some(Label::SelfTyAlias(reduce_impl_span_to_impl_keyword( + sm, + self.def_span(def_id), + ))); + err.refer_to_type_directly = current_self_ty + .map(|snippet| diagnostics::UseTypeDirectly { span, snippet }); + None + } + Res::Def(DefKind::TyParam, def_id) => { + err.label = Some(Label::TyParam(self.def_span(def_id))); + Some(def_id) + } + Res::Def(DefKind::ConstParam, def_id) => { + err.label = Some(Label::ConstParam(self.def_span(def_id))); + Some(def_id) + } + _ => { + bug!( + "GenericParamsFromOuterItem should only be used with \ + Res::SelfTyParam, Res::SelfTyAlias, DefKind::TyParam or \ + DefKind::ConstParam" + ); + } + }; + + if let Some((_, item_span, ItemKind::Const(_))) = inner_item.as_ref() { + err.use_let = Some(diagnostics::GenericParamsFromOuterItemUseLet { + span: sm.span_until_whitespace(*item_span), + }); + } + + if let Some(def_id) = def_id + && let HasGenericParams::Yes(span) = has_generic_params + && !matches!(inner_item, Some((_, _, ItemKind::Delegation(..)))) + { + let name = self.tcx.item_name(def_id); + let (span, snippet) = if span.is_empty() { + let snippet = format!("<{name}>"); + (span, snippet) + } else { + let span = sm.span_through_char(span, '<').shrink_to_hi(); + let snippet = format!("{name}, "); + (span, snippet) + }; + err.sugg = Some(diagnostics::GenericParamsFromOuterItemSugg { span, snippet }); + } + + self.dcx().create_err(err) + } + ResolutionError::NameAlreadyUsedInParameterList(name, first_use_span) => { + self.dcx().create_err(diagnostics::NameAlreadyUsedInParameterList { + span, + first_use_span, + name, + }) + } + ResolutionError::MethodNotMemberOfTrait(method, trait_, candidate) => { + self.dcx().create_err(diagnostics::MethodNotMemberOfTrait { + span, + method, + trait_, + sub: candidate.map(|c| diagnostics::AssociatedFnWithSimilarNameExists { + span: method.span, + candidate: c, + }), + }) + } + ResolutionError::TypeNotMemberOfTrait(type_, trait_, candidate) => { + self.dcx().create_err(diagnostics::TypeNotMemberOfTrait { + span, + type_, + trait_, + sub: candidate.map(|c| diagnostics::AssociatedTypeWithSimilarNameExists { + span: type_.span, + candidate: c, + }), + }) + } + ResolutionError::ConstNotMemberOfTrait(const_, trait_, candidate) => { + self.dcx().create_err(diagnostics::ConstNotMemberOfTrait { + span, + const_, + trait_, + sub: candidate.map(|c| diagnostics::AssociatedConstWithSimilarNameExists { + span: const_.span, + candidate: c, + }), + }) + } + ResolutionError::VariableNotBoundInPattern(binding_error, parent_scope) => { + let BindingError { name, target, origin, could_be_path } = binding_error; + + let mut target_sp = target.iter().map(|pat| pat.span).collect::>(); + target_sp.sort(); + target_sp.dedup(); + let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::>(); + origin_sp.sort(); + origin_sp.dedup(); + + let msp = MultiSpan::from_spans(target_sp.clone()); + let mut err = self.dcx().create_err(diagnostics::VariableIsNotBoundInAllPatterns { + multispan: msp, + name, + }); + for sp in target_sp { + err.subdiagnostic(diagnostics::PatternDoesntBindName { span: sp, name }); + } + for sp in &origin_sp { + err.subdiagnostic(diagnostics::VariableNotInAllPatterns { span: *sp }); + } + let mut suggested_typo = false; + if !target.iter().all(|pat| matches!(pat.kind, ast::PatKind::Ident(..))) + && !origin.iter().all(|(_, pat)| matches!(pat.kind, ast::PatKind::Ident(..))) + { + // The check above is so that when we encounter `match foo { (a | b) => {} }`, + // we don't suggest `(a | a) => {}`, which would never be what the user wants. + let mut target_visitor = BindingVisitor::default(); + for pat in &target { + target_visitor.visit_pat(pat); + } + target_visitor.identifiers.sort(); + target_visitor.identifiers.dedup(); + let mut origin_visitor = BindingVisitor::default(); + for (_, pat) in &origin { + origin_visitor.visit_pat(pat); + } + origin_visitor.identifiers.sort(); + origin_visitor.identifiers.dedup(); + // Find if the binding could have been a typo + if let Some(typo) = + find_best_match_for_name(&target_visitor.identifiers, name.name, None) + && !origin_visitor.identifiers.contains(&typo) + { + err.subdiagnostic(diagnostics::PatternBindingTypo { + spans: origin_sp, + typo, + }); + suggested_typo = true; + } + } + if could_be_path { + let import_suggestions = self.lookup_import_candidates( + name, + Namespace::ValueNS, + &parent_scope, + &|res: Res| { + matches!( + res, + Res::Def( + DefKind::Ctor(CtorOf::Variant, CtorKind::Const) + | DefKind::Ctor(CtorOf::Struct, CtorKind::Const) + | DefKind::Const { .. } + | DefKind::AssocConst { .. }, + _, + ) + ) + }, + ); + + if import_suggestions.is_empty() && !suggested_typo { + let kind_matches: [fn(DefKind) -> bool; 4] = [ + |kind| matches!(kind, DefKind::Ctor(CtorOf::Variant, CtorKind::Const)), + |kind| matches!(kind, DefKind::Ctor(CtorOf::Struct, CtorKind::Const)), + |kind| matches!(kind, DefKind::Const { .. }), + |kind| matches!(kind, DefKind::AssocConst { .. }), + ]; + let mut local_names = vec![]; + self.add_module_candidates( + parent_scope.module, + &mut local_names, + &|res| matches!(res, Res::Def(_, _)), + None, + ); + let local_names: FxHashSet<_> = local_names + .into_iter() + .filter_map(|s| match s.res { + Res::Def(_, def_id) => Some(def_id), + _ => None, + }) + .collect(); + + let mut local_suggestions = vec![]; + let mut suggestions = vec![]; + for matches_kind in kind_matches { + if let Some(suggestion) = self.early_lookup_typo_candidate( + ScopeSet::All(Namespace::ValueNS), + &parent_scope, + name, + &|res: Res| match res { + Res::Def(k, _) => matches_kind(k), + _ => false, + }, + ) && let Res::Def(kind, mut def_id) = suggestion.res + { + if let DefKind::Ctor(_, _) = kind { + def_id = self.tcx.parent(def_id); + } + let kind = kind.descr(def_id); + if local_names.contains(&def_id) { + // The item is available in the current scope. Very likely to + // be a typo. Don't use the full path. + local_suggestions.push(( + suggestion.candidate, + suggestion.candidate.to_string(), + kind, + )); + } else { + suggestions.push(( + suggestion.candidate, + self.def_path_str(def_id), + kind, + )); + } + } + } + let suggestions = if !local_suggestions.is_empty() { + // There is at least one item available in the current scope that is a + // likely typo. We only show those. + local_suggestions + } else { + suggestions + }; + for (name, sugg, kind) in suggestions { + err.span_suggestion_verbose( + span, + format!( + "you might have meant to use the similarly named {kind} `{name}`", + ), + sugg, + Applicability::MaybeIncorrect, + ); + suggested_typo = true; + } + } + if import_suggestions.is_empty() && !suggested_typo { + let help_msg = format!( + "if you meant to match on a unit struct, unit variant or a `const` \ + item, consider making the path in the pattern qualified: \ + `path::to::ModOrType::{name}`", + ); + err.span_help(span, help_msg); + } + show_candidates( + self.tcx, + &mut err, + Some(span), + &import_suggestions, + Instead::No, + FoundUse::Yes, + DiagMode::Pattern, + vec![], + "", + ); + } + err + } + ResolutionError::VariableBoundWithDifferentMode(variable_name, first_binding_span) => { + self.dcx().create_err(diagnostics::VariableBoundWithDifferentMode { + span, + first_binding_span, + variable_name, + }) + } + ResolutionError::IdentifierBoundMoreThanOnceInParameterList(identifier) => { + self.dcx().create_err(diagnostics::IdentifierBoundMoreThanOnceInParameterList { + span, + identifier, + }) + } + ResolutionError::IdentifierBoundMoreThanOnceInSamePattern(identifier) => { + self.dcx().create_err(diagnostics::IdentifierBoundMoreThanOnceInSamePattern { + span, + identifier, + }) + } + ResolutionError::UndeclaredLabel { name, suggestion } => { + let ((sub_reachable, sub_reachable_suggestion), sub_unreachable) = match suggestion + { + // A reachable label with a similar name exists. + Some((ident, true)) => ( + ( + Some(diagnostics::LabelWithSimilarNameReachable(ident.span)), + Some(diagnostics::TryUsingSimilarlyNamedLabel { + span, + ident_name: ident.name, + }), + ), + None, + ), + // An unreachable label with a similar name exists. + Some((ident, false)) => ( + (None, None), + Some(diagnostics::UnreachableLabelWithSimilarNameExists { + ident_span: ident.span, + }), + ), + // No similarly-named labels exist. + None => ((None, None), None), + }; + self.dcx().create_err(diagnostics::UndeclaredLabel { + span, + name, + sub_reachable, + sub_reachable_suggestion, + sub_unreachable, + }) + } + ResolutionError::FailedToResolve { segment, label, suggestion, module, message } => { + let mut err = struct_span_code_err!(self.dcx(), span, E0433, "{message}"); + err.span_label(span, label); + + if let Some((suggestions, msg, applicability)) = suggestion { + if suggestions.is_empty() { + err.help(msg); + return err; + } + err.multipart_suggestion(msg, suggestions, applicability); + } + + let module = match module { + Some(ModuleOrUniformRoot::Module(m)) if let Some(id) = m.opt_def_id() => id, + _ => CRATE_DEF_ID.to_def_id(), + }; + self.find_cfg_stripped(&mut err, &segment, module); + + err + } + ResolutionError::CannotCaptureDynamicEnvironmentInFnItem => { + self.dcx().create_err(diagnostics::CannotCaptureDynamicEnvironmentInFnItem { span }) + } + ResolutionError::AttemptToUseNonConstantValueInConstant { + ident, + suggestion, + current, + type_span, + } => { + // let foo =... + // ^^^ given this Span + // ------- get this Span to have an applicable suggestion + + // edit: + // only do this if the const and usage of the non-constant value are on the same line + // the further the two are apart, the higher the chance of the suggestion being wrong + + let sp = self + .tcx + .sess + .source_map() + .span_extend_to_prev_str(ident.span, current, true, false); + + let (with, with_label, without) = match sp { + Some(sp) if !self.tcx.sess.source_map().is_multiline(sp) => { + let sp = sp + .with_lo(BytePos(sp.lo().0 - (current.len() as u32))) + .until(ident.span); + + // Only suggest replacing the binding keyword if this is a simple + // binding. + // + // Note: this approach still incorrectly suggests for irrefutable + // patterns like `if let x = 1 { const { x } }`, since the text + // between `let` and the identifier is just whitespace. + // See tests/ui/consts/non-const-value-in-const-irrefutable-pat-binding.rs + let is_simple_binding = + self.tcx.sess.source_map().span_to_snippet(sp).is_ok_and(|snippet| { + let after_keyword = snippet[current.len()..].trim(); + after_keyword.is_empty() || after_keyword == "mut" + }); + + if is_simple_binding { + ( + Some(diagnostics::AttemptToUseNonConstantValueInConstantWithSuggestion { + span: sp, + suggestion, + current, + type_span, + }), + Some(diagnostics::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), + None, + ) + } else { + ( + None, + Some(diagnostics::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), + None, + ) + } + } + _ => ( + None, + None, + Some( + diagnostics::AttemptToUseNonConstantValueInConstantWithoutSuggestion { + ident_span: ident.span, + suggestion, + }, + ), + ), + }; + + self.dcx().create_err(diagnostics::AttemptToUseNonConstantValueInConstant { + span, + with, + with_label, + without, + }) + } + ResolutionError::BindingShadowsSomethingUnacceptable { + shadowing_binding, + name, + participle, + article, + shadowed_binding, + shadowed_binding_span, + } => self.dcx().create_err(diagnostics::BindingShadowsSomethingUnacceptable { + span, + shadowing_binding, + shadowed_binding, + article, + sub_suggestion: match (shadowing_binding, shadowed_binding) { + ( + PatternSource::Match, + Res::Def(DefKind::Ctor(CtorOf::Variant | CtorOf::Struct, CtorKind::Fn), _), + ) => Some(diagnostics::BindingShadowsSomethingUnacceptableSuggestion { + span, + name, + }), + _ => None, + }, + shadowed_binding_span, + participle, + name, + }), + ResolutionError::ForwardDeclaredGenericParam(param, reason) => match reason { + ForwardGenericParamBanReason::Default => { + self.dcx().create_err(diagnostics::ForwardDeclaredGenericParam { param, span }) + } + ForwardGenericParamBanReason::ConstParamTy => self + .dcx() + .create_err(diagnostics::ForwardDeclaredGenericInConstParamTy { param, span }), + }, + ResolutionError::ParamInTyOfConstParam { name } => { + self.dcx().create_err(diagnostics::ParamInTyOfConstParam { span, name }) + } + ResolutionError::ParamInNonTrivialAnonConst { is_gca, name, param_kind: is_type } => { + self.dcx().create_err(diagnostics::ParamInNonTrivialAnonConst { + span, + name, + param_kind: is_type, + help: self.tcx.sess.is_nightly_build(), + is_gca, + help_gca: is_gca, + }) + } + ResolutionError::ParamInEnumDiscriminant { name, param_kind: is_type } => { + self.dcx().create_err(diagnostics::ParamInEnumDiscriminant { + span, + name, + param_kind: is_type, + }) + } + ResolutionError::ForwardDeclaredSelf(reason) => match reason { + ForwardGenericParamBanReason::Default => { + self.dcx().create_err(diagnostics::SelfInGenericParamDefault { span }) + } + ForwardGenericParamBanReason::ConstParamTy => { + self.dcx().create_err(diagnostics::SelfInConstGenericTy { span }) + } + }, + ResolutionError::UnreachableLabel { name, definition_span, suggestion } => { + let ((sub_suggestion_label, sub_suggestion), sub_unreachable_label) = + match suggestion { + // A reachable label with a similar name exists. + Some((ident, true)) => ( + ( + Some(diagnostics::UnreachableLabelSubLabel { + ident_span: ident.span, + }), + Some(diagnostics::UnreachableLabelSubSuggestion { + span, + // intentionally taking 'ident.name' instead of 'ident' itself, as this + // could be used in suggestion context + ident_name: ident.name, + }), + ), + None, + ), + // An unreachable label with a similar name exists. + Some((ident, false)) => ( + (None, None), + Some(diagnostics::UnreachableLabelSubLabelUnreachable { + ident_span: ident.span, + }), + ), + // No similarly-named labels exist. + None => ((None, None), None), + }; + self.dcx().create_err(diagnostics::UnreachableLabel { + span, + name, + definition_span, + sub_suggestion, + sub_suggestion_label, + sub_unreachable_label, + }) + } + ResolutionError::TraitImplMismatch { + name, + kind, + code, + trait_item_span, + trait_path, + } => self + .dcx() + .create_err(diagnostics::TraitImplMismatch { + span, + name, + kind, + trait_path, + trait_item_span, + }) + .with_code(code), + ResolutionError::TraitImplDuplicate { name, trait_item_span, old_span } => { + self.dcx().create_err(diagnostics::TraitImplDuplicate { + span, + name, + trait_item_span, + old_span, + }) + } + ResolutionError::InvalidAsmSym => { + self.dcx().create_err(diagnostics::InvalidAsmSym { span }) + } + ResolutionError::LowercaseSelf => { + self.dcx().create_err(diagnostics::LowercaseSelf { span }) + } + ResolutionError::BindingInNeverPattern => { + self.dcx().create_err(diagnostics::BindingInNeverPattern { span }) + } + } + } + + pub(crate) fn report_vis_error( + &mut self, + vis_resolution_error: VisResolutionError, + ) -> ErrorGuaranteed { + match vis_resolution_error { + VisResolutionError::Relative2018(span, path) => { + self.dcx().create_err(diagnostics::Relative2018 { + span, + path_span: path.span, + // intentionally converting to String, as the text would also be used as + // in suggestion context + path_str: path_to_string(&path), + }) + } + VisResolutionError::AncestorOnly(span) => { + self.dcx().create_err(diagnostics::AncestorOnly(span)) + } + VisResolutionError::FailedToResolve(span, segment, label, suggestion, message) => self + .into_struct_error( + span, + ResolutionError::FailedToResolve { + segment, + label, + suggestion, + module: None, + message, + }, + ), + VisResolutionError::ExpectedFound(span, path_str, res) => { + self.dcx().create_err(diagnostics::ExpectedModuleFound { span, res, path_str }) + } + VisResolutionError::Indeterminate(span) => { + self.dcx().create_err(diagnostics::Indeterminate(span)) + } + VisResolutionError::ModuleOnly(span) => { + self.dcx().create_err(diagnostics::ModuleOnly(span)) + } + } + .emit() + } + + pub(crate) fn def_path_str(&self, mut def_id: DefId) -> String { + // We can't use `def_path_str` in resolve. + let mut path = vec![def_id]; + while let Some(parent) = self.tcx.opt_parent(def_id) { + def_id = parent; + path.push(def_id); + if def_id.is_top_level_module() { + break; + } + } + // We will only suggest importing directly if it is accessible through that path. + path.into_iter() + .rev() + .map(|def_id| { + self.tcx + .opt_item_name(def_id) + .map(|name| { + match ( + def_id.is_top_level_module(), + def_id.is_local(), + self.tcx.sess.edition(), + ) { + (true, true, Edition::Edition2015) => String::new(), + (true, true, _) => kw::Crate.to_string(), + (true, false, _) | (false, _, _) => name.to_string(), + } + }) + .unwrap_or_else(|| "_".to_string()) + }) + .collect::>() + .join("::") + } + + pub(crate) fn add_scope_set_candidates( + &mut self, + suggestions: &mut Vec, + scope_set: ScopeSet<'ra>, + ps: &ParentScope<'ra>, + sp: Span, + filter_fn: &impl Fn(Res) -> bool, + ) { + let ctxt = Macros20NormalizedSyntaxContext::new(sp.ctxt()); + self.cm().visit_scopes(scope_set, ps, ctxt, sp, None, |this, scope, use_prelude, _| { + match scope { + Scope::DeriveHelpers(expn_id) => { + let res = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); + if filter_fn(res) { + suggestions.extend(this.helper_attrs.get(&expn_id).into_flat_iter().map( + |&(ident, orig_ident_span, _)| { + TypoSuggestion::new(ident.name, orig_ident_span, res) + }, + )); + } + } + Scope::DeriveHelpersCompat => { + // Never recommend deprecated helper attributes. + } + Scope::MacroRules(macro_rules_scope) => { + if let MacroRulesScope::Def(macro_rules_def) = macro_rules_scope.get() { + let res = macro_rules_def.decl.res(); + if filter_fn(res) { + suggestions.push(TypoSuggestion::new( + macro_rules_def.ident.name, + macro_rules_def.orig_ident_span, + res, + )) + } + } + } + Scope::ModuleNonGlobs(module, _) => { + this.add_module_candidates(module, suggestions, filter_fn, None); + } + Scope::ModuleGlobs(..) => { + // Already handled in `ModuleNonGlobs`. + } + Scope::MacroUsePrelude => { + suggestions.extend(this.macro_use_prelude.iter().filter_map( + |(name, binding)| { + let res = binding.res(); + filter_fn(res).then_some(TypoSuggestion::typo_from_name(*name, res)) + }, + )); + } + Scope::BuiltinAttrs => { + let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin(sym::dummy)); + if filter_fn(res) { + suggestions.extend( + BUILTIN_ATTRIBUTES + .iter() + // These trace attributes are compiler-generated and have + // deliberately invalid names. + .filter(|attr| { + !matches!(**attr, sym::cfg_trace | sym::cfg_attr_trace) + }) + .map(|attr| TypoSuggestion::typo_from_name(*attr, res)), + ); + } + } + Scope::ExternPreludeItems => { + // Add idents from both item and flag scopes. + suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, entry)| { + let res = Res::Def(DefKind::Mod, CRATE_DEF_ID.to_def_id()); + filter_fn(res).then_some(TypoSuggestion::new(ident.name, entry.span(), res)) + })); + } + Scope::ExternPreludeFlags => {} + Scope::ToolPrelude => { + let res = Res::NonMacroAttr(NonMacroAttrKind::Tool); + suggestions.extend( + this.registered_tools + .iter() + .map(|ident| TypoSuggestion::new(ident.name, ident.span, res)), + ); + } + Scope::StdLibPrelude => { + if let Some(prelude) = this.prelude { + let mut tmp_suggestions = Vec::new(); + this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); + suggestions.extend( + tmp_suggestions + .into_iter() + .filter(|s| use_prelude.into() || this.is_builtin_macro(s.res)), + ); + } + } + Scope::BuiltinTypes => { + suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| { + let res = Res::PrimTy(*prim_ty); + filter_fn(res) + .then_some(TypoSuggestion::typo_from_name(prim_ty.name(), res)) + })) + } + } + + ControlFlow::<()>::Continue(()) + }); + } + + /// Lookup typo candidate in scope for a macro or import. + fn early_lookup_typo_candidate( + &mut self, + scope_set: ScopeSet<'ra>, + parent_scope: &ParentScope<'ra>, + ident: Ident, + filter_fn: &impl Fn(Res) -> bool, + ) -> Option { + let mut suggestions = Vec::new(); + self.add_scope_set_candidates( + &mut suggestions, + scope_set, + parent_scope, + ident.span, + filter_fn, + ); + + // Make sure error reporting is deterministic. + suggestions.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); + + match find_best_match_for_name( + &suggestions.iter().map(|suggestion| suggestion.candidate).collect::>(), + ident.name, + None, + ) { + Some(found) if found != ident.name => { + suggestions.into_iter().find(|suggestion| suggestion.candidate == found) + } + _ => None, + } + } + + fn lookup_import_candidates_from_module( + &self, + lookup_ident: Ident, + namespace: Namespace, + parent_scope: &ParentScope<'ra>, + start_module: Module<'ra>, + crate_path: ThinVec, + filter_fn: FilterFn, + ) -> Vec + where + FilterFn: Fn(Res) -> bool, + { + let mut candidates = Vec::new(); + let mut seen_modules = FxHashSet::default(); + let start_did = start_module.def_id(); + let mut worklist = vec![( + start_module, + ThinVec::::new(), + true, + start_did.is_local() || !self.tcx.is_doc_hidden(start_did), + true, + )]; + let mut worklist_via_import = vec![]; + + while let Some((in_module, path_segments, accessible, doc_visible, is_stable)) = + match worklist.pop() { + None => worklist_via_import.pop(), + Some(x) => Some(x), + } + { + let in_module_is_extern = !in_module.def_id().is_local(); + in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { + // Avoid non-importable candidates. + if name_binding.is_assoc_item() + && !this.features.import_trait_associated_functions() + { + return; + } + + if ident.name == kw::Underscore { + return; + } + + let child_accessible = + accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); + + // do not venture inside inaccessible items of other crates + if in_module_is_extern && !child_accessible { + return; + } + + let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); + + // There is an assumption elsewhere that paths of variants are in the enum's + // declaration and not imported. With this assumption, the variant component is + // chopped and the rest of the path is assumed to be the enum's own path. For + // errors where a variant is used as the type instead of the enum, this causes + // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. + if via_import && name_binding.is_possibly_imported_variant() { + return; + } + + // #90113: Do not count an inaccessible reexported item as a candidate. + if let DeclKind::Import { source_decl, .. } = name_binding.kind + && this.is_accessible_from(source_decl.vis(), parent_scope.module) + && !this.is_accessible_from(name_binding.vis(), parent_scope.module) + { + return; + } + + let res = name_binding.res(); + let did = match res { + Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), + _ => res.opt_def_id(), + }; + let child_doc_visible = doc_visible + && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); + + // collect results based on the filter function + // avoid suggesting anything from the same module in which we are resolving + // avoid suggesting anything with a hygienic name + if ident.name == lookup_ident.name + && ns == namespace + && in_module != parent_scope.module + && ident.ctxt.is_root() + && filter_fn(res) + { + // create the path + let mut segms = if lookup_ident.span.at_least_rust_2018() { + // crate-local absolute paths start with `crate::` in edition 2018 + // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) + crate_path.clone() + } else { + ThinVec::new() + }; + segms.append(&mut 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 }; + + if child_accessible + // Remove invisible match if exists + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.accessible) + { + candidates.remove(idx); + } + + let is_stable = if is_stable + && let Some(did) = did + && this.is_stable(did, path.span) + { + true + } else { + false + }; + + // Rreplace unstable suggestions if we meet a new stable one, + // and do nothing if any other situation. For example, if we + // meet `std::ops::Range` after `std::range::legacy::Range`, + // we will remove the latter and then insert the former. + if is_stable + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) + { + candidates.remove(idx); + } + + if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { + // See if we're recommending TryFrom, TryInto, or FromIterator and add + // a note about editions + let note = if let Some(did) = did { + let requires_note = !did.is_local() + && find_attr!( + this.tcx, + did, + RustcDiagnosticItem( + sym::TryInto | sym::TryFrom | sym::FromIterator + ) + ); + requires_note.then(|| { + format!( + "'{}' is included in the prelude starting in Edition 2021", + path_names_to_string(&path) + ) + }) + } else { + None + }; + + candidates.push(ImportSuggestion { + did, + descr: res.descr(), + path, + accessible: child_accessible, + doc_visible: child_doc_visible, + note, + via_import, + is_stable, + }); + } + } + + // collect submodules to explore + if let Some(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 alias_import = if let DeclKind::Import { import, .. } = name_binding.kind + && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind + && import.parent_scope.expansion == parent_scope.expansion + { + true + } else { + false + }; + + let is_extern_crate_that_also_appears_in_prelude = + name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); + + if !is_extern_crate_that_also_appears_in_prelude || alias_import { + // add the module to the lookup + if seen_modules.insert(def_id) { + if via_import { &mut worklist_via_import } else { &mut worklist }.push( + ( + this.expect_module(def_id), + path_segments, + child_accessible, + child_doc_visible, + is_stable && this.is_stable(def_id, name_binding.span), + ), + ); + } + } + } + }) + } + + candidates + } + + fn is_stable(&self, did: DefId, span: Span) -> bool { + if did.is_local() { + return true; + } + + match self.tcx.lookup_stability(did) { + Some(Stability { + level: StabilityLevel::Unstable { implied_by, .. }, feature, .. + }) => { + if span.allows_unstable(feature) { + true + } else if self.features.enabled(feature) { + true + } else if let Some(implied_by) = implied_by + && self.features.enabled(implied_by) + { + true + } else { + false + } + } + Some(_) => true, + None => false, + } + } + + /// When name resolution fails, this method can be used to look up candidate + /// entities with the expected name. It allows filtering them using the + /// supplied predicate (which should be used to only accept the types of + /// definitions expected, e.g., traits). The lookup spans across all crates. + /// + /// N.B., the method does not look into imports, but this is not a problem, + /// since we report the definitions (thus, the de-aliased imports). + pub(crate) fn lookup_import_candidates( + &mut self, + lookup_ident: Ident, + namespace: Namespace, + parent_scope: &ParentScope<'ra>, + filter_fn: FilterFn, + ) -> Vec + where + FilterFn: Fn(Res) -> bool, + { + let crate_path = thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(kw::Crate))]; + let mut suggestions = self.lookup_import_candidates_from_module( + lookup_ident, + namespace, + parent_scope, + self.graph_root.to_module(), + crate_path, + &filter_fn, + ); + + if lookup_ident.span.at_least_rust_2018() { + for (ident, entry) in &self.extern_prelude { + if entry.span().from_expansion() { + // Idents are adjusted to the root context before being + // resolved in the extern prelude, so reporting this to the + // user is no help. This skips the injected + // `extern crate std` in the 2018 edition, which would + // otherwise cause duplicate suggestions. + continue; + } + let Some(crate_id) = + self.cstore_mut().maybe_process_path_extern(self.tcx, ident.name) + else { + continue; + }; + + let crate_def_id = crate_id.as_def_id(); + let crate_root = self.expect_module(crate_def_id); + + // Check if there's already an item in scope with the same name as the crate. + // If so, we have to disambiguate the potential import suggestions by making + // the paths *global* (i.e., by prefixing them with `::`). + let needs_disambiguation = + self.resolutions(parent_scope.module).borrow().iter().any( + |(key, name_resolution)| { + if key.ns == TypeNS + && key.ident == *ident + && let Some(decl) = name_resolution.borrow().best_decl() + { + match decl.res() { + // No disambiguation needed if the identically named item we + // found in scope actually refers to the crate in question. + Res::Def(_, def_id) => def_id != crate_def_id, + Res::PrimTy(_) => true, + _ => false, + } + } else { + false + } + }, + ); + let mut crate_path = ThinVec::new(); + if needs_disambiguation { + crate_path.push(ast::PathSegment::path_root(rustc_span::DUMMY_SP)); + } + crate_path.push(ast::PathSegment::from_ident(ident.orig(entry.span()))); + + suggestions.extend(self.lookup_import_candidates_from_module( + lookup_ident, + namespace, + parent_scope, + crate_root, + crate_path, + &filter_fn, + )); + } + } + + suggestions.retain(|suggestion| suggestion.is_stable || self.tcx.sess.is_nightly_build()); + suggestions + } + + pub(crate) fn unresolved_macro_suggestions( + &mut self, + err: &mut Diag<'_>, + macro_kind: MacroKind, + parent_scope: &ParentScope<'ra>, + ident: Ident, + krate: &Crate, + sugg_span: Option, + ) { + // Bring all unused `derive` macros into `macro_map` so we ensure they can be used for + // suggestions. + self.register_macros_for_all_crates(); + + let is_expected = + &|res: Res| res.macro_kinds().is_some_and(|k| k.contains(macro_kind.into())); + let suggestion = self.early_lookup_typo_candidate( + ScopeSet::Macro(macro_kind), + parent_scope, + ident, + is_expected, + ); + if !self.add_typo_suggestion(err, suggestion, ident.span) { + self.detect_derive_attribute(err, ident, parent_scope, sugg_span); + } + + let import_suggestions = + self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected); + let (span, found_use) = match parent_scope.module.nearest_parent_mod_node_id() { + DUMMY_NODE_ID => (None, FoundUse::No), + node_id => UsePlacementFinder::check(krate, node_id), + }; + show_candidates( + self.tcx, + err, + span, + &import_suggestions, + Instead::No, + found_use, + DiagMode::Normal, + vec![], + "", + ); + + if macro_kind == MacroKind::Bang && ident.name == sym::macro_rules { + let label_span = ident.span.shrink_to_hi(); + let mut spans = MultiSpan::from_span(label_span); + spans.push_span_label(label_span, "put a macro name here"); + err.subdiagnostic(MaybeMissingMacroRulesName { spans }); + return; + } + + if macro_kind == MacroKind::Derive && (ident.name == sym::Send || ident.name == sym::Sync) { + err.subdiagnostic(ExplicitUnsafeTraits { span: ident.span, ident }); + return; + } + + let unused_macro = self.unused_macros.iter().find_map(|(def_id, (_, unused_ident))| { + if unused_ident.name == ident.name { Some((def_id, unused_ident)) } else { None } + }); + + if let Some((def_id, unused_ident)) = unused_macro { + let scope = self.local_macro_def_scopes[&def_id]; + let parent_nearest = parent_scope.module.nearest_parent_mod(); + let unused_macro_kinds = self.local_macro_map[def_id].macro_kinds(); + if !unused_macro_kinds.contains(macro_kind.into()) { + match macro_kind { + MacroKind::Bang => { + err.subdiagnostic(MacroRulesNot::Func { span: unused_ident.span, ident }); + } + MacroKind::Attr => { + err.subdiagnostic(MacroRulesNot::Attr { span: unused_ident.span, ident }); + } + MacroKind::Derive => { + err.subdiagnostic(MacroRulesNot::Derive { span: unused_ident.span, ident }); + } + } + return; + } + if Some(parent_nearest) == scope.opt_def_id() { + err.subdiagnostic(MacroDefinedLater { span: unused_ident.span }); + err.subdiagnostic(MacroSuggMovePosition { span: ident.span, ident }); + return; + } + } + + if ident.name == kw::Default + && let ModuleKind::Def(DefKind::Enum, def_id, _, _) = parent_scope.module.kind + { + let span = self.def_span(def_id); + let source_map = self.tcx.sess.source_map(); + let head_span = source_map.guess_head_span(span); + err.subdiagnostic(ConsiderAddingADerive { + span: head_span.shrink_to_lo(), + suggestion: "#[derive(Default)]\n".to_string(), + }); + } + for ns in [Namespace::MacroNS, Namespace::TypeNS, Namespace::ValueNS] { + let Ok(binding) = self.cm().resolve_ident_in_scope_set( + ident, + ScopeSet::All(ns), + parent_scope, + None, + None, + None, + ) else { + continue; + }; + + let desc = match binding.res() { + Res::Def(DefKind::Macro(MacroKinds::BANG), _) => { + "a function-like macro".to_string() + } + Res::Def(DefKind::Macro(MacroKinds::ATTR), _) | Res::NonMacroAttr(..) => { + format!("an attribute: `#[{ident}]`") + } + Res::Def(DefKind::Macro(MacroKinds::DERIVE), _) => { + format!("a derive macro: `#[derive({ident})]`") + } + Res::Def(DefKind::Macro(kinds), _) => { + format!("{} {}", kinds.article(), kinds.descr()) + } + Res::ToolMod | Res::OpenMod(..) => { + // Don't confuse the user with tool modules or open modules. + continue; + } + Res::Def(DefKind::Trait, _) if macro_kind == MacroKind::Derive => { + "only a trait, without a derive macro".to_string() + } + res => format!( + "{} {}, not {} {}", + res.article(), + res.descr(), + macro_kind.article(), + macro_kind.descr_expected(), + ), + }; + if let crate::DeclKind::Import { import, .. } = binding.kind + && !import.span.is_dummy() + { + let note = diagnostics::IdentImporterHereButItIsDesc { + span: import.span, + imported_ident: ident, + imported_ident_desc: &desc, + }; + err.subdiagnostic(note); + // Silence the 'unused import' warning we might get, + // since this diagnostic already covers that import. + self.record_use(ident, binding, Used::Other); + return; + } + let note = diagnostics::IdentInScopeButItIsDesc { + imported_ident: ident, + imported_ident_desc: &desc, + }; + err.subdiagnostic(note); + return; + } + + if self.macro_names.contains(&IdentKey::new(ident)) { + err.subdiagnostic(AddedMacroUse); + return; + } + } + + /// Given an attribute macro that failed to be resolved, look for `derive` macros that could + /// provide it, either as-is or with small typos. + fn detect_derive_attribute( + &self, + err: &mut Diag<'_>, + ident: Ident, + parent_scope: &ParentScope<'ra>, + sugg_span: Option, + ) { + // Find all of the `derive`s in scope and collect their corresponding declared + // attributes. + // FIXME: this only works if the crate that owns the macro that has the helper_attr + // has already been imported. + let mut derives = vec![]; + let mut all_attrs: UnordMap> = UnordMap::default(); + // We're collecting these in a hashmap, and handle ordering the output further down. + #[allow(rustc::potential_query_instability)] + for (def_id, ext) in self + .local_macro_map + .iter() + .map(|(local_id, ext)| (local_id.to_def_id(), ext)) + .chain(self.extern_macro_map.borrow().iter().map(|(id, d)| (*id, d))) + { + for helper_attr in &ext.helper_attrs { + let item_name = self.tcx.item_name(def_id); + all_attrs.entry(*helper_attr).or_default().push(item_name); + if helper_attr == &ident.name { + derives.push(item_name); + } + } + } + let kind = MacroKind::Derive.descr(); + if !derives.is_empty() { + // We found an exact match for the missing attribute in a `derive` macro. Suggest it. + let mut derives: Vec = derives.into_iter().map(|d| d.to_string()).collect(); + derives.sort(); + derives.dedup(); + let msg = match &derives[..] { + [derive] => format!(" `{derive}`"), + [start @ .., last] => format!( + "s {} and `{last}`", + start.iter().map(|d| format!("`{d}`")).collect::>().join(", ") + ), + [] => unreachable!("we checked for this to be non-empty 10 lines above!?"), + }; + let msg = format!( + "`{}` is an attribute that can be used by the {kind}{msg}, you might be \ + missing a `derive` attribute", + ident.name, + ); + let sugg_span = + if let ModuleKind::Def(DefKind::Enum, id, _, _) = parent_scope.module.kind { + let span = self.def_span(id); + if span.from_expansion() { + None + } else { + // For enum variants sugg_span is empty but we can get the enum's Span. + Some(span.shrink_to_lo()) + } + } else { + // For items this `Span` will be populated, everything else it'll be None. + sugg_span + }; + match sugg_span { + Some(span) => { + err.span_suggestion_verbose( + span, + msg, + format!("#[derive({})]\n", derives.join(", ")), + Applicability::MaybeIncorrect, + ); + } + None => { + err.note(msg); + } + } + } else { + // We didn't find an exact match. Look for close matches. If any, suggest fixing typo. + let all_attr_names = all_attrs.keys().map(|s| *s).into_sorted_stable_ord(); + if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None) + && let Some(macros) = all_attrs.get(&best_match) + { + let mut macros: Vec = macros.into_iter().map(|d| d.to_string()).collect(); + macros.sort(); + macros.dedup(); + let msg = match ¯os[..] { + [] => return, + [name] => format!(" `{name}` accepts"), + [start @ .., end] => format!( + "s {} and `{end}` accept", + start.iter().map(|m| format!("`{m}`")).collect::>().join(", "), + ), + }; + let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute"); + err.span_suggestion_verbose( + ident.span, + msg, + best_match, + Applicability::MaybeIncorrect, + ); + } + } + } + + pub(crate) fn add_typo_suggestion( + &self, + err: &mut Diag<'_>, + suggestion: Option, + span: Span, + ) -> bool { + let suggestion = match suggestion { + None => return false, + // We shouldn't suggest underscore. + Some(suggestion) if suggestion.candidate == kw::Underscore => return false, + Some(suggestion) => suggestion, + }; + + let mut did_label_def_span = false; + + if let Some(def_span) = suggestion.res.opt_def_id().map(|def_id| self.def_span(def_id)) { + if span.overlaps(def_span) { + // Don't suggest typo suggestion for itself like in the following: + // error[E0423]: expected function, tuple struct or tuple variant, found struct `X` + // --> $DIR/unicode-string-literal-syntax-error-64792.rs:4:14 + // | + // LL | struct X {} + // | ----------- `X` defined here + // LL | + // LL | const Y: X = X("ö"); + // | -------------^^^^^^- similarly named constant `Y` defined here + // | + // help: use struct literal syntax instead + // | + // LL | const Y: X = X {}; + // | ^^^^ + // help: a constant with a similar name exists + // | + // LL | const Y: X = Y("ö"); + // | ^ + return false; + } + let span = self.tcx.sess.source_map().guess_head_span(def_span); + let candidate_descr = suggestion.res.descr(); + let candidate = suggestion.candidate; + let label = match suggestion.target { + SuggestionTarget::SimilarlyNamed => { + diagnostics::DefinedHere::SimilarlyNamed { span, candidate_descr, candidate } + } + SuggestionTarget::SingleItem => { + diagnostics::DefinedHere::SingleItem { span, candidate_descr, candidate } + } + }; + did_label_def_span = true; + err.subdiagnostic(label); + } + + let (span, msg, sugg) = if let SuggestionTarget::SimilarlyNamed = suggestion.target + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + && let Some(span) = suggestion.span + && let Some(candidate) = suggestion.candidate.as_str().strip_prefix('_') + && snippet == candidate + { + let candidate = suggestion.candidate; + // When the suggested binding change would be from `x` to `_x`, suggest changing the + // original binding definition instead. (#60164) + let msg = format!( + "the leading underscore in `{candidate}` marks it as unused, consider renaming it to `{snippet}`" + ); + if !did_label_def_span { + err.span_label(span, format!("`{candidate}` defined here")); + } + (span, msg, snippet) + } else { + let msg = match suggestion.target { + SuggestionTarget::SimilarlyNamed => format!( + "{} {} with a similar name exists", + suggestion.res.article(), + suggestion.res.descr() + ), + SuggestionTarget::SingleItem => { + format!("maybe you meant this {}", suggestion.res.descr()) + } + }; + (span, msg, suggestion.candidate.to_ident_string()) + }; + err.span_suggestion_verbose(span, msg, sugg, Applicability::MaybeIncorrect); + true + } + + fn decl_description(&self, b: Decl<'_>, ident: Ident, scope: Scope<'_>) -> String { + let res = b.res(); + if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) { + let (built_in, from) = match scope { + Scope::StdLibPrelude | Scope::MacroUsePrelude => ("", " from prelude"), + Scope::ExternPreludeFlags + if self.tcx.sess.opts.externs.get(ident.as_str()).is_some() + || matches!(res, Res::OpenMod(..)) => + { + ("", " passed with `--extern`") + } + _ => { + if matches!(res, Res::NonMacroAttr(..) | Res::PrimTy(..) | Res::ToolMod) { + // These already contain the "built-in" prefix or look bad with it. + ("", "") + } else { + (" built-in", "") + } + } + }; + + let a = if built_in.is_empty() { res.article() } else { "a" }; + format!("{a}{built_in} {thing}{from}", thing = res.descr()) + } else { + let introduced = if b.is_import_user_facing() { "imported" } else { "defined" }; + format!("the {thing} {introduced} here", thing = res.descr()) + } + } + + fn ambiguity_diagnostic( + &self, + ambiguity_error: &AmbiguityError<'ra>, + ) -> diagnostics::Ambiguity { + let AmbiguityError { kind, ambig_vis, ident, b1, b2, scope1, scope2, .. } = + *ambiguity_error; + let extern_prelude_ambiguity = || { + // Note: b1 may come from a module scope, as an extern crate item in module. + matches!(scope2, Scope::ExternPreludeFlags) + && self + .extern_prelude + .get(&IdentKey::new(ident)) + .is_some_and(|entry| entry.item_decl.map(|(b, ..)| b) == Some(b1)) + }; + let (b1, b2, scope1, scope2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() { + // We have to print the span-less alternative first, otherwise formatting looks bad. + (b2, b1, scope2, scope1, true) + } else { + (b1, b2, scope1, scope2, false) + }; + + let could_refer_to = |b: Decl<'_>, scope: Scope<'ra>, also: &str| { + let what = self.decl_description(b, ident, scope); + let note_msg = format!("`{ident}` could{also} refer to {what}"); + + let thing = b.res().descr(); + let mut help_msgs = Vec::new(); + if b.is_glob_import() + && (kind == AmbiguityKind::GlobVsGlob + || kind == AmbiguityKind::GlobVsExpanded + || kind == AmbiguityKind::GlobVsOuter && swapped != also.is_empty()) + { + help_msgs.push(format!( + "consider adding an explicit import of `{ident}` to disambiguate" + )) + } + if b.is_extern_crate() && ident.span.at_least_rust_2018() && !extern_prelude_ambiguity() + { + help_msgs.push(format!("use `::{ident}` to refer to this {thing} unambiguously")) + } + + if kind != AmbiguityKind::GlobVsGlob { + if let Scope::ModuleNonGlobs(module, _) | Scope::ModuleGlobs(module, _) = scope { + if module == self.graph_root.to_module() { + help_msgs.push(format!( + "use `crate::{ident}` to refer to this {thing} unambiguously" + )); + } else if module.is_normal() { + help_msgs.push(format!( + "use `self::{ident}` to refer to this {thing} unambiguously" + )); + } + } + } + + ( + Spanned { node: note_msg, span: b.span }, + help_msgs + .iter() + .enumerate() + .map(|(i, help_msg)| { + let or = if i == 0 { "" } else { "or " }; + format!("{or}{help_msg}") + }) + .collect::>(), + ) + }; + let (b1_note, b1_help_msgs) = could_refer_to(b1, scope1, ""); + let (b2_note, b2_help_msgs) = could_refer_to(b2, scope2, " also"); + let help = if kind == AmbiguityKind::GlobVsGlob + && b1 + .parent_module + .and_then(|m| m.opt_def_id()) + .map(|d| !d.is_local()) + .unwrap_or_default() + { + Some(&[ + "consider updating this dependency to resolve this error", + "if updating the dependency does not resolve the problem report the problem to the author of the relevant crate", + ] as &[_]) + } else { + None + }; + + let ambig_vis = ambig_vis.map(|(vis1, vis2)| { + format!( + "{} or {}", + vis1.to_string(CRATE_DEF_ID, self.tcx), + vis2.to_string(CRATE_DEF_ID, self.tcx) + ) + }); + + diagnostics::Ambiguity { + ident, + help, + ambig_vis, + kind: kind.descr(), + b1_note, + b1_help_msgs, + b2_note, + b2_help_msgs, + is_error: false, + } + } + + /// If the binding refers to a tuple struct constructor with fields, + /// returns the span of its fields. + fn ctor_fields_span(&self, decl: Decl<'_>) -> Option { + let DeclKind::Def(Res::Def(DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), ctor_def_id)) = + decl.kind + else { + return None; + }; + + let def_id = self.tcx.parent(ctor_def_id); + self.field_idents(def_id)?.iter().map(|&f| f.span).reduce(Span::to) // None for `struct Foo()` + } + + /// Returns the path segments (as symbols) of a module, including `kw::Crate` at the start. + /// For example, for `crate::foo::bar`, returns `[Crate, foo, bar]`. + /// Returns `None` for block modules that don't have a `DefId`. + fn module_path_names(&self, module: Module<'ra>) -> Option> { + let mut path = Vec::new(); + let mut def_id = module.opt_def_id()?; + while let Some(parent) = self.tcx.opt_parent(def_id) { + if let Some(name) = self.tcx.opt_item_name(def_id) { + path.push(name); + } + if parent.is_top_level_module() { + break; + } + def_id = parent; + } + path.reverse(); + path.insert(0, kw::Crate); + Some(path) + } + + /// Shortens a candidate import path to use `super::` (up to 1 level) or `self::` (same module) + /// relative to the current scope, if possible. Only applies to crate-local items and + /// only when the resulting path is actually shorter than the original. + fn shorten_candidate_path( + &self, + suggestion: &mut ImportSuggestion, + current_module: Module<'ra>, + ) { + const MAX_SUPER_PATH_ITEMS_IN_SUGGESTION: usize = 1; + + // Only shorten local items. + if suggestion.did.is_none_or(|did| !did.is_local()) { + return; + } + + // Build current module path: [Crate, foo, bar, ...]. + let Some(current_mod_path) = self.module_path_names(current_module) else { + return; + }; + + // Normalise candidate path: filter out `PathRoot` (`::`), and if the path + // doesn't start with `Crate`, prepend it (edition 2015 paths are relative + // to the crate root without an explicit `crate::` prefix). + let candidate_names = { + let filtered_segments: Vec<_> = suggestion + .path + .segments + .iter() + .filter(|segment| segment.ident.name != kw::PathRoot) + .collect(); + + let mut candidate_names: Vec = + filtered_segments.iter().map(|segment| segment.ident.name).collect(); + if candidate_names.first() != Some(&kw::Crate) { + candidate_names.insert(0, kw::Crate); + } + if candidate_names.len() < 2 { + return; + } + candidate_names + }; + + // The candidate's module path is everything except the last segment (the item name). + let candidate_mod_names = &candidate_names[..candidate_names.len() - 1]; + + // Find the longest common prefix between the current module and candidate module paths. + let common_prefix_length = current_mod_path + .iter() + .zip(candidate_mod_names.iter()) + .take_while(|(current, candidate)| current == candidate) + .count(); + + // Non-crate-local item; keep the full absolute path. + if common_prefix_length == 0 { + return; + } + + let super_count = current_mod_path.len() - common_prefix_length; + + // At the crate root, `use` paths resolve from the crate root anyway, so we can + // drop the `crate::` prefix entirely instead of replacing it with `self::`. + let at_crate_root = current_mod_path.len() == 1; + + let mut new_segments = if super_count == 0 && at_crate_root { + ThinVec::new() + } else { + let prefix_keyword = match super_count { + 0 => kw::SelfLower, + 1..=MAX_SUPER_PATH_ITEMS_IN_SUGGESTION => kw::Super, + _ => return, // Too many `super` levels; keep the full absolute path. + }; + thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(prefix_keyword),)] + }; + for &name in &candidate_names[common_prefix_length..] { + new_segments.push(ast::PathSegment::from_ident(Ident::with_dummy_span(name))); + } + + // Only apply if the result is strictly shorter than the original path. + if new_segments.len() >= suggestion.path.segments.len() { + return; + } + + suggestion.path = Path { span: suggestion.path.span, segments: new_segments, tokens: None }; + } + + fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { + let PrivacyError { + ident, + decl, + outermost_res, + parent_scope, + single_nested, + dedup_span, + ref source, + } = *privacy_error; + + let res = decl.res(); + let ctor_fields_span = self.ctor_fields_span(decl); + let plain_descr = res.descr().to_string(); + let nonimport_descr = + if ctor_fields_span.is_some() { plain_descr + " constructor" } else { plain_descr }; + let import_descr = nonimport_descr.clone() + " import"; + let get_descr = |b: Decl<'_>| if b.is_import() { &import_descr } else { &nonimport_descr }; + + // Print the primary message. + let ident_descr = get_descr(decl); + let mut err = + self.dcx().create_err(diagnostics::IsPrivate { span: ident.span, ident_descr, ident }); + + self.mention_default_field_values(source, ident, &mut err); + + let shown_candidates = if let Some((this_res, outer_ident)) = outermost_res { + let mut import_suggestions = self.lookup_import_candidates( + outer_ident, + this_res.ns().unwrap_or(Namespace::TypeNS), + &parent_scope, + &|res: Res| res == this_res, + ); + // Shorten candidate paths using `super::` or `self::` when possible. + for suggestion in &mut import_suggestions { + self.shorten_candidate_path(suggestion, parent_scope.module); + } + let point_to_def = !show_candidates( + self.tcx, + &mut err, + Some(dedup_span.until(outer_ident.span.shrink_to_hi())), + &import_suggestions, + Instead::Yes, + FoundUse::Yes, + DiagMode::Import { append: single_nested, unresolved_import: false }, + vec![], + "", + ); + // If we suggest importing a public re-export, don't point at the definition. + if point_to_def && ident.span != outer_ident.span { + let label = diagnostics::OuterIdentIsNotPubliclyReexported { + span: outer_ident.span, + outer_ident_descr: this_res.descr(), + outer_ident, + }; + err.subdiagnostic(label); + } + !point_to_def + } else { + false + }; + + let mut non_exhaustive = None; + // If an ADT is foreign and marked as `non_exhaustive`, then that's + // probably why we have the privacy error. + // Otherwise, point out if the struct has any private fields. + if let Some(def_id) = res.opt_def_id() + && !def_id.is_local() + && let Some(attr_span) = find_attr!(self.tcx, def_id, NonExhaustive(span) => *span) + { + non_exhaustive = Some(attr_span); + } else if let Some(span) = ctor_fields_span { + let label = diagnostics::ConstructorPrivateIfAnyFieldPrivate { span }; + err.subdiagnostic(label); + if let Res::Def(_, d) = res + && let Some(fields) = self.field_visibility_spans.get(&d) + { + let spans = fields.iter().map(|span| *span).collect(); + let sugg = diagnostics::ConsiderMakingTheFieldPublic { + spans, + number_of_fields: fields.len(), + }; + err.subdiagnostic(sugg); + } + } + + let mut sugg_paths: Vec<(Vec, bool)> = vec![]; + if let Some(mut def_id) = res.opt_def_id() { + // We can't use `def_path_str` in resolve. + let mut path = vec![def_id]; + while let Some(parent) = self.tcx.opt_parent(def_id) { + def_id = parent; + if !def_id.is_top_level_module() { + path.push(def_id); + } else { + break; + } + } + // We will only suggest importing directly if it is accessible through that path. + let path_names: Option> = path + .iter() + .rev() + .map(|def_id| { + self.tcx.opt_item_name(*def_id).map(|name| { + Ident::with_dummy_span(if def_id.is_top_level_module() { + kw::Crate + } else { + name + }) + }) + }) + .collect(); + if let Some(&def_id) = path.get(0) + && let Some(path) = path_names + { + if let Some(def_id) = def_id.as_local() { + if self.effective_visibilities.is_directly_public(def_id) { + sugg_paths.push((path, false)); + } + } else if self.is_accessible_from(self.tcx.visibility(def_id), parent_scope.module) + { + sugg_paths.push((path, false)); + } + } + } + + // Print the whole import chain to make it easier to see what happens. + let first_binding = decl; + let mut next_binding = Some(decl); + let mut next_ident = ident; + while let Some(binding) = next_binding { + let name = next_ident; + next_binding = match binding.kind { + _ if res == Res::Err => None, + DeclKind::Import { source_decl, import, .. } => match import.kind { + _ if source_decl.span.is_dummy() => None, + ImportKind::Single { source, .. } => { + next_ident = source; + Some(source_decl) + } + ImportKind::Glob { .. } + | ImportKind::MacroUse { .. } + | ImportKind::MacroExport => Some(source_decl), + ImportKind::ExternCrate { .. } => None, + }, + _ => None, + }; + + match binding.kind { + DeclKind::Import { source_decl, import, .. } => { + // Don't include `{{root}}` in suggestions - it's an internal symbol + // that should never be shown to users. + let path = import + .module_path + .iter() + .filter(|seg| seg.ident.name != kw::PathRoot) + .map(|seg| seg.ident.clone()) + .chain(std::iter::once(ident)) + .collect::>(); + let through_reexport = !matches!(source_decl.kind, DeclKind::Def(_)); + sugg_paths.push((path, through_reexport)); + } + DeclKind::Def(_) => {} + } + let first = binding == first_binding; + let def_span = self.tcx.sess.source_map().guess_head_span(binding.span); + let mut note_span = MultiSpan::from_span(def_span); + if !first && binding.vis().is_public() { + let desc = match binding.kind { + DeclKind::Import { .. } => "re-export", + _ => "directly", + }; + note_span.push_span_label(def_span, format!("you could import this {desc}")); + } + // Final step in the import chain, point out if the ADT is `non_exhaustive` + // which is probably why this privacy violation occurred. + if next_binding.is_none() + && let Some(span) = non_exhaustive + { + note_span.push_span_label( + span, + "cannot be constructed because it is `#[non_exhaustive]`", + ); + } + let note = diagnostics::NoteAndRefersToTheItemDefinedHere { + span: note_span, + binding_descr: get_descr(binding), + binding_name: name, + first, + dots: next_binding.is_some(), + }; + err.subdiagnostic(note); + } + // The suggestion replaces `dedup_span` with a path reaching the failing ident. + // That's valid only when + // 1) the failing ident is the imported leaf, otherwise `as` renames and trailing segments + // get dropped, and + // 2) the use isn't nested, otherwise `dedup_span` is one ident in `{...}`. + // + // See issue #156060. + let can_replace_use = !shown_candidates + && !single_nested + && !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span); + if can_replace_use { + // We prioritize shorter paths, non-core imports and direct imports over the + // alternatives. + sugg_paths.sort_by_key(|(p, reexport)| (p.len(), p[0].name == sym::core, *reexport)); + for (sugg, reexport) in sugg_paths { + if sugg.len() <= 1 { + // A single path segment suggestion is wrong. This happens on circular + // imports. `tests/ui/imports/issue-55884-2.rs` + continue; + } + let path = join_path_idents(sugg); + let sugg = if reexport { + diagnostics::ImportIdent::ThroughReExport { span: dedup_span, ident, path } + } else { + diagnostics::ImportIdent::Directly { span: dedup_span, ident, path } + }; + err.subdiagnostic(sugg); + break; + } + } + + err.emit(); + } + + /// When a private field is being set that has a default field value, we suggest using `..` and + /// setting the value of that field implicitly with its default. + /// + /// If we encounter code like + /// ```text + /// struct Priv; + /// pub struct S { + /// pub field: Priv = Priv, + /// } + /// ``` + /// which is used from a place where `Priv` isn't accessible + /// ```text + /// let _ = S { field: m::Priv1 {} }; + /// // ^^^^^ private struct + /// ``` + /// we will suggest instead using the `default_field_values` syntax instead: + /// ```text + /// let _ = S { .. }; + /// ``` + fn mention_default_field_values( + &self, + source: &Option, + ident: Ident, + err: &mut Diag<'_>, + ) { + let Some(expr) = source else { return }; + let ast::ExprKind::Struct(struct_expr) = &expr.kind else { return }; + // We don't have to handle type-relative paths because they're forbidden in ADT + // expressions, but that would change with `#[feature(more_qualified_paths)]`. + let Some(segment) = struct_expr.path.segments.last() else { return }; + let Some(partial_res) = self.partial_res_map.get(&segment.id) else { return }; + let Some(Res::Def(_, def_id)) = partial_res.full_res() else { + return; + }; + let Some(default_fields) = self.field_defaults(def_id) else { return }; + if struct_expr.fields.is_empty() { + return; + } + let last_span = struct_expr.fields.iter().last().unwrap().span; + let mut iter = struct_expr.fields.iter().peekable(); + let mut prev: Option = None; + while let Some(field) = iter.next() { + if field.expr.span.overlaps(ident.span) { + err.span_label(field.ident.span, "while setting this field"); + if default_fields.contains(&field.ident.name) { + let sugg = if last_span == field.span { + vec![(field.span, "..".to_string())] + } else { + vec![ + ( + // Account for trailing commas and ensure we remove them. + match (prev, iter.peek()) { + (_, Some(next)) => field.span.with_hi(next.span.lo()), + (Some(prev), _) => field.span.with_lo(prev.hi()), + (None, None) => field.span, + }, + String::new(), + ), + (last_span.shrink_to_hi(), ", ..".to_string()), + ] + }; + err.multipart_suggestion( + format!( + "the type `{ident}` of field `{}` is private, but you can construct \ + the default value defined for it in `{}` using `..` in the struct \ + initializer expression", + field.ident, + self.tcx.item_name(def_id), + ), + sugg, + Applicability::MachineApplicable, + ); + break; + } + } + prev = Some(field.span); + } + } + + pub(crate) fn find_similarly_named_module_or_crate( + &self, + ident: Symbol, + current_module: Module<'ra>, + ) -> Option { + let mut candidates = self + .extern_prelude + .keys() + .map(|ident| ident.name) + .chain( + self.local_module_map + .iter() + .filter(|(_, module)| { + let module = module.to_module(); + current_module.is_ancestor_of(module) && current_module != module + }) + .flat_map(|(_, module)| module.name()), + ) + .chain( + self.extern_module_map + .borrow() + .iter() + .filter(|(_, module)| { + let module = module.to_module(); + current_module.is_ancestor_of(module) && current_module != module + }) + .flat_map(|(_, module)| module.name()), + ) + .filter(|c| !c.to_string().is_empty()) + .collect::>(); + candidates.sort(); + candidates.dedup(); + find_best_match_for_name(&candidates, ident, None).filter(|sugg| *sugg != ident) + } + + pub(crate) fn report_path_resolution_error( + &mut self, + path: &[Segment], + opt_ns: Option, // `None` indicates a module path in import + parent_scope: &ParentScope<'ra>, + ribs: Option<&PerNS>>>, + ignore_decl: Option>, + ignore_import: Option>, + module: Option>, + failed_segment_idx: usize, + ident: Ident, + diag_metadata: Option<&DiagMetadata<'_>>, + ) -> (String, String, Option) { + let is_last = failed_segment_idx == path.len() - 1; + let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS }; + let module_def_id = match module { + Some(ModuleOrUniformRoot::Module(module)) => module.opt_def_id(), + _ => None, + }; + let scope = match &path[..failed_segment_idx] { + [.., prev] => { + if prev.ident.name == kw::PathRoot { + format!("the crate root") + } else { + format!("`{}`", prev.ident) + } + } + _ => format!("this scope"), + }; + let message = format!("cannot find `{ident}` in {scope}"); + + 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(), path_to_string(&c.path))); + if let Some(candidate) = candidates.get(0) { + let path = { + // remove the possible common prefix of the path + let len = candidate.path.segments.len(); + let start_index = (0..=failed_segment_idx.min(len - 1)) + .find(|&i| path[i].ident.name != candidate.path.segments[i].ident.name) + .unwrap_or_default(); + let segments = + (start_index..len).map(|s| candidate.path.segments[s].clone()).collect(); + Path { segments, span: Span::default(), tokens: None } + }; + ( + message, + String::from("unresolved import"), + Some(( + vec![(ident.span, path_to_string(&path))], + String::from("a similar path exists"), + Applicability::MaybeIncorrect, + )), + ) + } else if ident.name == sym::core { + ( + message, + format!("you might be missing crate `{ident}`"), + Some(( + vec![(ident.span, "std".to_string())], + "try using `std` instead of `core`".to_string(), + Applicability::MaybeIncorrect, + )), + ) + } else if ident.name == kw::Underscore { + ( + "invalid crate or module name `_`".to_string(), + "`_` is not a valid crate or module name".to_string(), + None, + ) + } else if self.tcx.sess.is_rust_2015() { + ( + format!("cannot find module or crate `{ident}` in {scope}"), + format!("use of unresolved module or unlinked crate `{ident}`"), + Some(( + vec![( + self.current_crate_outer_attr_insert_span, + format!("extern crate {ident};\n"), + )], + if was_invoked_from_cargo() { + format!( + "if you wanted to use a crate named `{ident}`, use `cargo add \ + {ident}` to add it to your `Cargo.toml` and import it in your \ + code", + ) + } else { + format!( + "you might be missing a crate named `{ident}`, add it to your \ + project and import it in your code", + ) + }, + Applicability::MaybeIncorrect, + )), + ) + } else { + (message, format!("could not find `{ident}` in the crate root"), None) + } + } else if failed_segment_idx > 0 { + let parent = path[failed_segment_idx - 1].ident.name; + let parent = match parent { + // ::foo is mounted at the crate root for 2015, and is the extern + // prelude for 2018+ + kw::PathRoot if self.tcx.sess.edition() > Edition::Edition2015 => { + "the list of imported crates".to_owned() + } + kw::PathRoot | kw::Crate => "the crate root".to_owned(), + _ => format!("`{parent}`"), + }; + + let mut msg = format!("could not find `{ident}` in {parent}"); + if ns == TypeNS || ns == ValueNS { + let ns_to_try = if ns == TypeNS { ValueNS } else { TypeNS }; + let binding = if let Some(module) = module { + self.cm() + .resolve_ident_in_module( + module, + ident, + ns_to_try, + parent_scope, + None, + ignore_decl, + ignore_import, + ) + .ok() + } else if let Some(ribs) = ribs + && let Some(TypeNS | ValueNS) = opt_ns + { + assert!(ignore_import.is_none()); + match self.resolve_ident_in_lexical_scope( + ident, + ns_to_try, + parent_scope, + None, + &ribs[ns_to_try], + ignore_decl, + diag_metadata, + ) { + // we found a locally-imported or available item/module + Some(LateDecl::Decl(binding)) => Some(binding), + _ => None, + } + } else { + self.cm() + .resolve_ident_in_scope_set( + ident, + ScopeSet::All(ns_to_try), + parent_scope, + None, + ignore_decl, + ignore_import, + ) + .ok() + }; + if let Some(binding) = binding { + msg = format!( + "expected {}, found {} `{ident}` in {parent}", + ns.descr(), + binding.res().descr(), + ); + }; + } + (message, msg, None) + } else if ident.name == kw::SelfUpper { + // As mentioned above, `opt_ns` being `None` indicates a module path in import. + // We can use this to improve a confusing error for, e.g. `use Self::Variant` in an + // impl + if opt_ns.is_none() { + (message, "`Self` cannot be used in imports".to_string(), None) + } else { + ( + message, + "`Self` is only available in impls, traits, and type definitions".to_string(), + None, + ) + } + } else if ident.name.as_str().chars().next().is_some_and(|c| c.is_ascii_uppercase()) { + // Check whether the name refers to an item in the value namespace. + let binding = if let Some(ribs) = ribs { + assert!(ignore_import.is_none()); + self.resolve_ident_in_lexical_scope( + ident, + ValueNS, + parent_scope, + None, + &ribs[ValueNS], + ignore_decl, + diag_metadata, + ) + } else { + None + }; + let match_span = match binding { + // Name matches a local variable. For example: + // ``` + // fn f() { + // let Foo: &str = ""; + // println!("{}", Foo::Bar); // Name refers to local + // // variable `Foo`. + // } + // ``` + Some(LateDecl::RibDef(Res::Local(id))) => { + Some((*self.pat_span_map.get(&id).unwrap(), "a", "local binding")) + } + // Name matches item from a local name binding + // created by `use` declaration. For example: + // ``` + // pub const Foo: &str = ""; + // + // mod submod { + // use super::Foo; + // println!("{}", Foo::Bar); // Name refers to local + // // binding `Foo`. + // } + // ``` + Some(LateDecl::Decl(name_binding)) => Some(( + name_binding.span, + name_binding.res().article(), + name_binding.res().descr(), + )), + _ => None, + }; + + let message = format!("cannot find type `{ident}` in {scope}"); + let label = if let Some((span, article, descr)) = match_span { + format!( + "`{ident}` is declared as {article} {descr} at `{}`, not a type", + self.tcx + .sess + .source_map() + .span_to_short_string(span, RemapPathScopeComponents::DIAGNOSTICS) + ) + } else { + format!("use of undeclared type `{ident}`") + }; + (message, label, None) + } else { + let mut suggestion = None; + if ident.name == sym::alloc { + suggestion = Some(( + vec![], + String::from("add `extern crate alloc` to use the `alloc` crate"), + Applicability::MaybeIncorrect, + )) + } + + suggestion = suggestion.or_else(|| { + self.find_similarly_named_module_or_crate(ident.name, parent_scope.module).map( + |sugg| { + ( + vec![(ident.span, sugg.to_string())], + String::from("there is a crate or module with a similar name"), + Applicability::MaybeIncorrect, + ) + }, + ) + }); + if let Ok(binding) = self.cm().resolve_ident_in_scope_set( + ident, + ScopeSet::All(ValueNS), + parent_scope, + None, + ignore_decl, + ignore_import, + ) { + let descr = binding.res().descr(); + let message = format!("cannot find module or crate `{ident}` in {scope}"); + (message, format!("{descr} `{ident}` is not a crate or module"), suggestion) + } else { + let suggestion = if suggestion.is_some() { + suggestion + } else if let Some(m) = self.undeclared_module_exists(ident) { + self.undeclared_module_suggest_declare(ident, m) + } else if was_invoked_from_cargo() { + Some(( + vec![], + format!( + "if you wanted to use a crate named `{ident}`, use `cargo add {ident}` \ + to add it to your `Cargo.toml`", + ), + Applicability::MaybeIncorrect, + )) + } else { + Some(( + vec![], + format!("you might be missing a crate named `{ident}`",), + Applicability::MaybeIncorrect, + )) + }; + let message = format!("cannot find module or crate `{ident}` in {scope}"); + ( + message, + format!("use of unresolved module or unlinked crate `{ident}`"), + suggestion, + ) + } + } + } + + fn undeclared_module_suggest_declare( + &self, + ident: Ident, + path: std::path::PathBuf, + ) -> Option<(Vec<(Span, String)>, String, Applicability)> { + Some(( + vec![(self.current_crate_outer_attr_insert_span, format!("mod {ident};\n"))], + format!( + "to make use of source file {}, use `mod {ident}` \ + in this file to declare the module", + path.display() + ), + Applicability::MaybeIncorrect, + )) + } + + fn undeclared_module_exists(&self, ident: Ident) -> Option { + let map = self.tcx.sess.source_map(); + + let src = map.span_to_filename(ident.span).into_local_path()?; + let i = ident.as_str(); + // FIXME: add case where non parent using undeclared module (hard?) + let dir = src.parent()?; + let src = src.file_stem()?.to_str()?; + for file in [ + // …/x.rs + dir.join(i).with_extension("rs"), + // …/x/mod.rs + dir.join(i).join("mod.rs"), + ] { + if file.exists() { + return Some(file); + } + } + if !matches!(src, "main" | "lib" | "mod") { + for file in [ + // …/x/y.rs + dir.join(src).join(i).with_extension("rs"), + // …/x/y/mod.rs + dir.join(src).join(i).join("mod.rs"), + ] { + if file.exists() { + return Some(file); + } + } + } + None + } + + /// Adds suggestions for a path that cannot be resolved. + #[instrument(level = "debug", skip(self, parent_scope))] + pub(crate) fn make_path_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + match path[..] { + // `{{root}}::ident::...` on both editions. + // On 2015 `{{root}}` is usually added implicitly. + [first, second, ..] + if first.ident.name == kw::PathRoot && !second.ident.is_path_segment_keyword() => {} + // `ident::...` on 2018. + [first, ..] + if first.ident.span.at_least_rust_2018() + && !first.ident.is_path_segment_keyword() => + { + // Insert a placeholder that's later replaced by `self`/`super`/etc. + path.insert(0, Segment::from_ident(Ident::dummy())); + } + _ => return None, + } + + self.make_missing_self_suggestion(path.clone(), parent_scope) + .or_else(|| self.make_missing_crate_suggestion(path.clone(), parent_scope)) + .or_else(|| self.make_missing_super_suggestion(path.clone(), parent_scope)) + .or_else(|| self.make_external_crate_suggestion(path, parent_scope)) + } + + /// Suggest a missing `self::` if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foo::Bar; + /// | ^^^ did you mean `self::foo`? + /// ``` + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_missing_self_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + // Replace first ident with `self` and check if that is valid. + path[0].ident.name = kw::SelfLower; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?result); + if let PathResult::Module(..) = result { Some((path, None)) } else { None } + } + + /// Suggests a missing `crate::` if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foo::Bar; + /// | ^^^ did you mean `crate::foo`? + /// ``` + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_missing_crate_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + // Replace first ident with `crate` and check if that is valid. + path[0].ident.name = kw::Crate; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?result); + if let PathResult::Module(..) = result { + Some(( + path, + Some( + "`use` statements changed in Rust 2018; read more at \ + " + .to_string(), + ), + )) + } else { + None + } + } + + /// Suggests a missing `super::` if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foo::Bar; + /// | ^^^ did you mean `super::foo`? + /// ``` + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_missing_super_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + // Replace first ident with `crate` and check if that is valid. + path[0].ident.name = kw::Super; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?result); + if let PathResult::Module(..) = result { Some((path, None)) } else { None } + } -use super::NoConstantGenericsReason; -use crate::error_helper::{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, -}; + /// Suggests a missing external crate name if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foobar::Baz; + /// | ^^^^^^ did you mean `baz::foobar`? + /// ``` + /// + /// Used when importing a submodule of an external crate but missing that crate's + /// name as the first part of path. + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_external_crate_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + if path[1].ident.span.is_rust_2015() { + return None; + } + + // Sort extern crate names in *reverse* order to get + // 1) some consistent ordering for emitted diagnostics, and + // 2) `std` suggestions before `core` suggestions. + let mut extern_crate_names = + self.extern_prelude.keys().map(|ident| ident.name).collect::>(); + extern_crate_names.sort_by(|a, b| b.as_str().cmp(a.as_str())); + + for name in extern_crate_names.into_iter() { + // Replace first ident with a crate name and check if that is valid. + path[0].ident.name = name; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?name, ?result); + if let PathResult::Module(..) = result { + return Some((path, None)); + } + } + + None + } + + /// Suggests importing a macro from the root of the crate rather than a module within + /// the crate. + /// + /// ```text + /// help: a macro with this name exists at the root of the crate + /// | + /// LL | use issue_59764::makro; + /// | ^^^^^^^^^^^^^^^^^^ + /// | + /// = note: this could be because a macro annotated with `#[macro_export]` will be exported + /// at the root of the crate instead of the module where it is defined + /// ``` + pub(crate) fn check_for_module_export_macro( + &mut self, + import: Import<'ra>, + module: ModuleOrUniformRoot<'ra>, + ident: Ident, + ) -> Option<(Option, Option)> { + let ModuleOrUniformRoot::Module(mut crate_module) = module else { + return None; + }; + + while let Some(parent) = crate_module.parent { + crate_module = parent; + } + + if module == ModuleOrUniformRoot::Module(crate_module) { + // Don't make a suggestion if the import was already from the root of the crate. + return None; + } + + let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); + let binding = self.resolution(crate_module, binding_key)?.best_decl()?; + let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { + return None; + }; + if !kinds.contains(MacroKinds::BANG) { + return None; + } + let module_name = crate_module.name().unwrap_or(kw::Crate); + let import_snippet = match import.kind { + ImportKind::Single { source, target, .. } if source != target => { + format!("{source} as {target}") + } + _ => format!("{ident}"), + }; + + let mut corrections: Vec<(Span, String)> = Vec::new(); + if !import.is_nested() { + // Assume this is the easy case of `use issue_59764::foo::makro;` and just remove + // intermediate segments. + corrections.push((import.span, format!("{module_name}::{import_snippet}"))); + } else { + // Find the binding span (and any trailing commas and spaces). + // i.e. `use a::b::{c, d, e};` + // ^^^ + let (found_closing_brace, binding_span) = find_span_of_binding_until_next_binding( + self.tcx.sess, + import.span, + import.use_span, + ); + debug!(found_closing_brace, ?binding_span); + + let mut removal_span = binding_span; + + // If the binding span ended with a closing brace, as in the below example: + // i.e. `use a::b::{c, d};` + // ^ + // Then expand the span of characters to remove to include the previous + // binding's trailing comma. + // i.e. `use a::b::{c, d};` + // ^^^ + if found_closing_brace + && let Some(previous_span) = + extend_span_to_previous_binding(self.tcx.sess, binding_span) + { + debug!(?previous_span); + removal_span = removal_span.with_lo(previous_span.lo()); + } + debug!(?removal_span); + + // Remove the `removal_span`. + corrections.push((removal_span, "".to_string())); + + // Find the span after the crate name and if it has nested imports immediately + // after the crate name already. + // i.e. `use a::b::{c, d};` + // ^^^^^^^^^ + // or `use a::{b, c, d}};` + // ^^^^^^^^^^^ + let (has_nested, after_crate_name) = + find_span_immediately_after_crate_name(self.tcx.sess, import.use_span); + debug!(has_nested, ?after_crate_name); + + let source_map = self.tcx.sess.source_map(); + + // Make sure this is actually crate-relative. + let is_definitely_crate = import + .module_path + .first() + .is_some_and(|f| f.ident.name != kw::SelfLower && f.ident.name != kw::Super); + + // Add the import to the start, with a `{` if required. + let start_point = source_map.start_point(after_crate_name); + if is_definitely_crate + && let Ok(start_snippet) = source_map.span_to_snippet(start_point) + { + corrections.push(( + start_point, + if has_nested { + // In this case, `start_snippet` must equal '{'. + format!("{start_snippet}{import_snippet}, ") + } else { + // In this case, add a `{`, then the moved import, then whatever + // was there before. + format!("{{{import_snippet}, {start_snippet}") + }, + )); + + // Add a `};` to the end if nested, matching the `{` added at the start. + if !has_nested { + corrections.push((source_map.end_point(after_crate_name), "};".to_string())); + } + } else { + // If the root import is module-relative, add the import separately + corrections.push(( + import.use_span.shrink_to_lo(), + format!("use {module_name}::{import_snippet};\n"), + )); + } + } + + let suggestion = Some(( + corrections, + String::from("a macro with this name exists at the root of the crate"), + Applicability::MaybeIncorrect, + )); + Some(( + suggestion, + Some( + "this could be because a macro annotated with `#[macro_export]` will be exported \ + at the root of the crate instead of the module where it is defined" + .to_string(), + ), + )) + } + + /// Finds a cfg-ed out item inside `module` with the matching name. + pub(crate) fn find_cfg_stripped(&self, err: &mut Diag<'_>, segment: &Symbol, module: DefId) { + let local_items; + let symbols = if module.is_local() { + local_items = self + .stripped_cfg_items + .iter() + .filter_map(|item| { + let parent_scope = self.local_modules.iter().find_map(|m| match m.kind { + ModuleKind::Def(_, def_id, node_id, _) if node_id == item.parent_scope => { + Some(def_id) + } + _ => None, + })?; + Some(StrippedCfgItem { parent_scope, ident: item.ident, cfg: item.cfg.clone() }) + }) + .collect::>(); + local_items.as_slice() + } else { + self.tcx.stripped_cfg_items(module.krate) + }; + + for &StrippedCfgItem { parent_scope, ident, ref cfg } in symbols { + if ident.name != *segment { + continue; + } + + let parent_module = self.get_nearest_non_block_module(parent_scope).def_id(); + + fn comes_from_same_module_for_glob( + r: &Resolver<'_, '_>, + parent_module: DefId, + module: DefId, + visited: &mut FxHashMap, + ) -> bool { + if let Some(&cached) = visited.get(&parent_module) { + // this branch is prevent from being called recursively infinity, + // because there has some cycles in globs imports, + // see more spec case at `tests/ui/cfg/diagnostics-reexport-2.rs#reexport32` + return cached; + } + visited.insert(parent_module, false); + let mut res = false; + let m = r.expect_module(parent_module); + if m.is_local() { + for importer in m.glob_importers.borrow().iter() { + if let Some(next_parent_module) = importer.parent_scope.module.opt_def_id() + { + if next_parent_module == module + || comes_from_same_module_for_glob( + r, + next_parent_module, + module, + visited, + ) + { + res = true; + break; + } + } + } + } + visited.insert(parent_module, res); + res + } + + let comes_from_same_module = parent_module == module + || comes_from_same_module_for_glob( + self, + parent_module, + module, + &mut Default::default(), + ); + if !comes_from_same_module { + continue; + } + + let item_was = if let CfgEntry::NameValue { value: Some(feature), .. } = cfg.0 { + diagnostics::ItemWas::BehindFeature { feature, span: cfg.1 } + } else { + diagnostics::ItemWas::CfgOut { span: cfg.1 } + }; + let note = diagnostics::FoundItemConfigureOut { span: ident.span, item_was }; + err.subdiagnostic(note); + } + } + + pub(crate) fn struct_ctor(&self, def_id: DefId) -> Option { + match def_id.as_local() { + Some(def_id) => self.struct_ctors.get(&def_id).cloned(), + None => { + self.cstore().ctor_untracked(self.tcx, def_id).map(|(ctor_kind, ctor_def_id)| { + let res = Res::Def(DefKind::Ctor(CtorOf::Struct, ctor_kind), ctor_def_id); + let vis = self.tcx.visibility(ctor_def_id); + let field_visibilities = self + .tcx + .associated_item_def_ids(def_id) + .iter() + .map(|&field_id| self.tcx.visibility(field_id)) + .collect(); + StructCtor { res, vis, field_visibilities } + }) + } + } + } + + /// Gets the `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. + fn on_unknown_data(&self, def_id: DefId) -> Option<&Directive> { + match def_id.as_local() { + Some(local) => Some(self.on_unknown_data.get(&local)?.directive.as_ref()), + None => find_attr!(self.tcx, def_id, OnUnknown{ directive } => directive)?.as_deref(), + } + } +} + +/// Given a `binding_span` of a binding within a use statement: +/// +/// ```ignore (illustrative) +/// use foo::{a, b, c}; +/// // ^ +/// ``` +/// +/// then return the span until the next binding or the end of the statement: +/// +/// ```ignore (illustrative) +/// use foo::{a, b, c}; +/// // ^^^ +/// ``` +fn find_span_of_binding_until_next_binding( + sess: &Session, + binding_span: Span, + use_span: Span, +) -> (bool, Span) { + let source_map = sess.source_map(); + + // Find the span of everything after the binding. + // i.e. `a, e};` or `a};` + let binding_until_end = binding_span.with_hi(use_span.hi()); + + // Find everything after the binding but not including the binding. + // i.e. `, e};` or `};` + let after_binding_until_end = binding_until_end.with_lo(binding_span.hi()); + + // Keep characters in the span until we encounter something that isn't a comma or + // whitespace. + // i.e. `, ` or ``. + // + // Also note whether a closing brace character was encountered. If there + // was, then later go backwards to remove any trailing commas that are left. + let mut found_closing_brace = false; + let after_binding_until_next_binding = + source_map.span_take_while(after_binding_until_end, |&ch| { + if ch == '}' { + found_closing_brace = true; + } + ch == ' ' || ch == ',' + }); + + // Combine the two spans. + // i.e. `a, ` or `a`. + // + // Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };` + let span = binding_span.with_hi(after_binding_until_next_binding.hi()); + + (found_closing_brace, span) +} + +/// Given a `binding_span`, return the span through to the comma or opening brace of the previous +/// binding. +/// +/// ```ignore (illustrative) +/// use foo::a::{a, b, c}; +/// // ^^--- binding span +/// // | +/// // returned span +/// +/// use foo::{a, b, c}; +/// // --- binding span +/// ``` +fn extend_span_to_previous_binding(sess: &Session, binding_span: Span) -> Option { + let source_map = sess.source_map(); + + // `prev_source` will contain all of the source that came before the span. + // Then split based on a command and take the first (i.e. closest to our span) + // snippet. In the example, this is a space. + let prev_source = source_map.span_to_prev_source(binding_span).ok()?; + + let prev_comma = prev_source.rsplit(',').collect::>(); + let prev_starting_brace = prev_source.rsplit('{').collect::>(); + if prev_comma.len() <= 1 || prev_starting_brace.len() <= 1 { + return None; + } + + let prev_comma = prev_comma.first().unwrap(); + let prev_starting_brace = prev_starting_brace.first().unwrap(); + + // If the amount of source code before the comma is greater than + // the amount of source code before the starting brace then we've only + // got one item in the nested item (eg. `issue_52891::{self}`). + if prev_comma.len() > prev_starting_brace.len() { + return None; + } + + Some(binding_span.with_lo(BytePos( + // Take away the number of bytes for the characters we've found and an + // extra for the comma. + binding_span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1, + ))) +} + +/// Given a `use_span` of a binding within a use statement, returns the highlighted span and if +/// it is a nested use tree. +/// +/// ```ignore (illustrative) +/// use foo::a::{b, c}; +/// // ^^^^^^^^^^ -- false +/// +/// use foo::{a, b, c}; +/// // ^^^^^^^^^^ -- true +/// +/// use foo::{a, b::{c, d}}; +/// // ^^^^^^^^^^^^^^^ -- true +/// ``` +#[instrument(level = "debug", skip(sess))] +fn find_span_immediately_after_crate_name(sess: &Session, use_span: Span) -> (bool, Span) { + let source_map = sess.source_map(); + + // Using `use issue_59764::foo::{baz, makro};` as an example throughout.. + let mut num_colons = 0; + // Find second colon.. `use issue_59764:` + let until_second_colon = source_map.span_take_while(use_span, |c| { + if *c == ':' { + num_colons += 1; + } + !matches!(c, ':' if num_colons == 2) + }); + // Find everything after the second colon.. `foo::{baz, makro};` + let from_second_colon = use_span.with_lo(until_second_colon.hi() + BytePos(1)); + + let mut found_a_non_whitespace_character = false; + // Find the first non-whitespace character in `from_second_colon`.. `f` + let after_second_colon = source_map.span_take_while(from_second_colon, |c| { + if found_a_non_whitespace_character { + return false; + } + if !c.is_whitespace() { + found_a_non_whitespace_character = true; + } + true + }); + + // Find the first `{` in from_second_colon.. `foo::{` + let next_left_bracket = source_map.span_through_char(from_second_colon, '{'); + + (next_left_bracket == after_second_colon, from_second_colon) +} + +/// A suggestion has already been emitted, change the wording slightly to clarify that both are +/// independent options. +enum Instead { + Yes, + No, +} + +/// Whether an existing place with an `use` item was found. +enum FoundUse { + Yes, + No, +} + +/// Whether a binding is part of a pattern or a use statement. Used for diagnostics. +pub(crate) enum DiagMode { + Normal, + /// The binding is part of a pattern + Pattern, + /// The binding is part of a use statement + Import { + /// `true` means diagnostics is for unresolved import + unresolved_import: bool, + /// `true` mean add the tips afterward for case `use a::{b,c}`, + /// rather than replacing within. + append: bool, + }, +} + +pub(crate) fn import_candidates( + tcx: TyCtxt<'_>, + err: &mut Diag<'_>, + // This is `None` if all placement locations are inside expansions + use_placement_span: Option, + candidates: &[ImportSuggestion], + mode: DiagMode, + append: &str, +) { + show_candidates( + tcx, + err, + use_placement_span, + candidates, + Instead::Yes, + FoundUse::Yes, + mode, + vec![], + append, + ); +} + +type PathString<'a> = (String, &'a str, Option, &'a Option, bool); + +/// When an entity with a given name is not available in scope, we search for +/// entities with that name in all crates. This method allows outputting the +/// results of this search in a programmer-friendly way. If any entities are +/// found and suggested, returns `true`, otherwise returns `false`. +fn show_candidates( + tcx: TyCtxt<'_>, + err: &mut Diag<'_>, + // This is `None` if all placement locations are inside expansions + use_placement_span: Option, + candidates: &[ImportSuggestion], + instead: Instead, + found_use: FoundUse, + mode: DiagMode, + path: Vec, + append: &str, +) -> bool { + if candidates.is_empty() { + return false; + } + + let mut showed = false; + let mut accessible_path_strings: Vec> = Vec::new(); + let mut inaccessible_path_strings: Vec> = Vec::new(); + + candidates.iter().for_each(|c| { + if c.accessible { + // Don't suggest `#[doc(hidden)]` items from other crates + if c.doc_visible { + accessible_path_strings.push(( + path_to_string(&c.path), + c.descr, + c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), + &c.note, + c.via_import, + )) + } + } else { + inaccessible_path_strings.push(( + path_to_string(&c.path), + c.descr, + c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), + &c.note, + c.via_import, + )) + } + }); + + // we want consistent results across executions, but candidates are produced + // by iterating through a hash map, so make sure they are ordered: + for path_strings in [&mut accessible_path_strings, &mut inaccessible_path_strings] { + path_strings.sort_by(|a, b| a.0.cmp(&b.0)); + path_strings.dedup_by(|a, b| a.0 == b.0); + let core_path_strings = + path_strings.extract_if(.., |p| p.0.starts_with("core::")).collect::>(); + let std_path_strings = + path_strings.extract_if(.., |p| p.0.starts_with("std::")).collect::>(); + let foreign_crate_path_strings = + path_strings.extract_if(.., |p| !p.0.starts_with("crate::")).collect::>(); + + // We list the `crate` local paths first. + // Then we list the `std`/`core` paths. + if std_path_strings.len() == core_path_strings.len() { + // Do not list `core::` paths if we are already listing the `std::` ones. + path_strings.extend(std_path_strings); + } else { + path_strings.extend(std_path_strings); + path_strings.extend(core_path_strings); + } + // List all paths from foreign crates last. + path_strings.extend(foreign_crate_path_strings); + } + + if !accessible_path_strings.is_empty() { + let (determiner, kind, s, name, through) = + if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] { + ( + "this", + *descr, + "", + format!(" `{name}`"), + if *via_import { " through its public re-export" } else { "" }, + ) + } else { + // Get the unique item kinds and if there's only one, we use the right kind name + // instead of the more generic "items". + let kinds = accessible_path_strings + .iter() + .map(|(_, descr, _, _, _)| *descr) + .collect::>(); + let kind = if let Some(kind) = kinds.get_only() { kind } else { "item" }; + let s = if kind.ends_with('s') { "es" } else { "s" }; + + ("one of these", kind, s, String::new(), "") + }; + + let instead = if let Instead::Yes = instead { " instead" } else { "" }; + let mut msg = if let DiagMode::Pattern = mode { + format!( + "if you meant to match on {kind}{s}{instead}{name}, use the full path in the \ + pattern", + ) + } else { + format!("consider importing {determiner} {kind}{s}{through}{instead}") + }; + + for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { + err.note(note.clone()); + } + + let append_candidates = |msg: &mut String, accessible_path_strings: Vec>| { + msg.push(':'); + + for candidate in accessible_path_strings { + msg.push('\n'); + msg.push_str(&candidate.0); + } + }; + + if let Some(span) = use_placement_span { + let (add_use, trailing) = match mode { + DiagMode::Pattern => { + err.span_suggestions( + span, + msg, + accessible_path_strings.into_iter().map(|a| a.0), + Applicability::MaybeIncorrect, + ); + return true; + } + DiagMode::Import { .. } => ("", ""), + DiagMode::Normal => ("use ", ";\n"), + }; + for candidate in &mut accessible_path_strings { + // produce an additional newline to separate the new use statement + // from the directly following item. + let additional_newline = if let FoundUse::No = found_use + && let DiagMode::Normal = mode + { + "\n" + } else { + "" + }; + candidate.0 = + format!("{add_use}{}{append}{trailing}{additional_newline}", candidate.0); + } + + match mode { + DiagMode::Import { append: true, .. } => { + append_candidates(&mut msg, accessible_path_strings); + err.span_help(span, msg); + } + _ => { + err.span_suggestions_with_style( + span, + msg, + accessible_path_strings.into_iter().map(|a| a.0), + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + } + } + + if let [first, .., last] = &path[..] { + let sp = first.ident.span.until(last.ident.span); + // Our suggestion is empty, so make sure the span is not empty (or we'd ICE). + // Can happen for derive-generated spans. + if sp.can_be_used_for_suggestions() && !sp.is_empty() { + err.span_suggestion_verbose( + sp, + format!("if you import `{}`, refer to it directly", last.ident), + "", + Applicability::Unspecified, + ); + } + } + } else { + append_candidates(&mut msg, accessible_path_strings); + err.help(msg); + } + showed = true; + } + if !inaccessible_path_strings.is_empty() + && (!matches!(mode, DiagMode::Import { unresolved_import: false, .. })) + { + let prefix = + if let DiagMode::Pattern = mode { "you might have meant to match on " } else { "" }; + if let [(name, descr, source_span, note, _)] = &inaccessible_path_strings[..] { + let msg = format!( + "{prefix}{descr} `{name}`{} exists but is inaccessible", + if let DiagMode::Pattern = mode { ", which" } else { "" } + ); + + if let Some(source_span) = source_span { + let span = tcx.sess.source_map().guess_head_span(*source_span); + let mut multi_span = MultiSpan::from_span(span); + multi_span.push_span_label(span, "not accessible"); + err.span_note(multi_span, msg); + } else { + err.note(msg); + } + if let Some(note) = (*note).as_deref() { + err.note(note.to_string()); + } + } else { + let descr = inaccessible_path_strings + .iter() + .map(|&(_, descr, _, _, _)| descr) + .all_equal_value() + .unwrap_or("item"); + let plural_descr = + if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") }; + + let mut msg = format!("{prefix}these {plural_descr} exist but are inaccessible"); + let mut has_colon = false; + + let mut spans = Vec::new(); + for (name, _, source_span, _, _) in &inaccessible_path_strings { + if let Some(source_span) = source_span { + let span = tcx.sess.source_map().guess_head_span(*source_span); + spans.push((name, span)); + } else { + if !has_colon { + msg.push(':'); + has_colon = true; + } + msg.push('\n'); + msg.push_str(name); + } + } + + let mut multi_span = MultiSpan::from_spans(spans.iter().map(|(_, sp)| *sp).collect()); + for (name, span) in spans { + multi_span.push_span_label(span, format!("`{name}`: not accessible")); + } + + for note in inaccessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { + err.note(note.clone()); + } + + err.span_note(multi_span, msg); + } + showed = true; + } + showed +} + +#[derive(Debug)] +struct UsePlacementFinder { + target_module: NodeId, + first_legal_span: Option, + first_use_span: Option, +} + +impl UsePlacementFinder { + fn check(krate: &Crate, target_module: NodeId) -> (Option, FoundUse) { + let mut finder = + UsePlacementFinder { target_module, first_legal_span: None, first_use_span: None }; + finder.visit_crate(krate); + if let Some(use_span) = finder.first_use_span { + (Some(use_span), FoundUse::Yes) + } else { + (finder.first_legal_span, FoundUse::No) + } + } +} + +impl<'tcx> Visitor<'tcx> for UsePlacementFinder { + fn visit_crate(&mut self, c: &Crate) { + if self.target_module == CRATE_NODE_ID { + let inject = c.spans.inject_use_span; + if is_span_suitable_for_use_injection(inject) { + self.first_legal_span = Some(inject); + } + self.first_use_span = search_for_any_use_in_items(&c.items); + } else { + visit::walk_crate(self, c); + } + } + + fn visit_item(&mut self, item: &'tcx ast::Item) { + if self.target_module == item.id { + if let ItemKind::Mod(_, _, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind { + let inject = mod_spans.inject_use_span; + if is_span_suitable_for_use_injection(inject) { + self.first_legal_span = Some(inject); + } + self.first_use_span = search_for_any_use_in_items(items); + } + } else { + visit::walk_item(self, item); + } + } +} + +#[derive(Default)] +struct BindingVisitor { + identifiers: Vec, + spans: FxHashMap>, +} + +impl<'tcx> Visitor<'tcx> for BindingVisitor { + fn visit_pat(&mut self, pat: &ast::Pat) { + if let ast::PatKind::Ident(_, ident, _) = pat.kind { + self.identifiers.push(ident.name); + self.spans.entry(ident.name).or_default().push(ident.span); + } + visit::walk_pat(self, pat); + } +} + +fn search_for_any_use_in_items(items: &[Box]) -> Option { + for item in items { + if let ItemKind::Use(..) = item.kind + && is_span_suitable_for_use_injection(item.span) + { + let mut lo = item.span.lo(); + for attr in &item.attrs { + if attr.span.eq_ctxt(item.span) { + lo = std::cmp::min(lo, attr.span.lo()); + } + } + return Some(Span::new(lo, lo, item.span.ctxt(), item.span.parent())); + } + } + None +} + +fn is_span_suitable_for_use_injection(s: Span) -> bool { + // don't suggest placing a use before the prelude + // import or other generated ones + !s.from_expansion() +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct OnUnknownData { + pub(crate) directive: Box, +} + +impl OnUnknownData { + pub(crate) fn from_attrs( + r: &Resolver<'_, '_>, + attrs: &[ast::Attribute], + ) -> Option { + if r.features.diagnostic_on_unknown() + && let Some(Attribute::Parsed(AttributeKind::OnUnknown { directive, .. })) = + AttributeParser::parse_limited( + r.tcx.sess, + attrs, + &[sym::diagnostic, sym::on_unknown], + ) + { + Some(Self { directive: directive? }) + } else { + None + } + } +} /// A field or associated item from self type suggested in case of resolution failure. enum AssocSuggestion { @@ -113,7 +4239,7 @@ fn import_candidate_to_enum_paths(suggestion: &ImportSuggestion) -> (String, Str /// Description of an elided lifetime. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(super) struct MissingLifetime { +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, @@ -134,7 +4260,7 @@ pub(super) struct MissingLifetime { /// 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 { +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. @@ -148,7 +4274,7 @@ pub(super) struct ElisionFnParameter { /// 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 { +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), @@ -3876,11 +8002,11 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } } - let binders_sugg: String = binder_idents - .into_iter() - .map(|ident| ident.to_string()) - .intersperse(", ".to_owned()) - .collect(); + 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 { "" }, @@ -4138,16 +8264,21 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { (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 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::repeat_n(existing_name.as_str(), lt.count).intersperse(", ")) + .chain(std::iter::Iterator::intersperse( + std::iter::repeat_n(existing_name.as_str(), lt.count), + ", ", + )) .chain([">"]) .collect(); (lt.span.shrink_to_hi(), sugg) @@ -4538,7 +8669,7 @@ fn mk_where_bound_predicate( } /// Report lifetime/lifetime shadowing as an error. -pub(super) fn signal_lifetime_shadowing( +pub(crate) fn signal_lifetime_shadowing( sess: &Session, orig: Ident, shadower: Ident, @@ -4595,7 +8726,7 @@ impl<'ast> Visitor<'ast> for RefPrefixSpanFinder { /// 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) { +pub(crate) fn signal_label_shadowing(sess: &Session, orig: Span, shadower: Ident) { let name = shadower.name; let shadower = shadower.span; sess.dcx() 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/error_helper.rs b/compiler/rustc_resolve/src/error_helper.rs deleted file mode 100644 index 8ab193afe0eee..0000000000000 --- a/compiler/rustc_resolve/src/error_helper.rs +++ /dev/null @@ -1,4162 +0,0 @@ -// ignore-tidy-filelength -use std::mem; -use std::ops::ControlFlow; - -use itertools::Itertools as _; -use rustc_ast::visit::{self, Visitor}; -use rustc_ast::{ - self as ast, CRATE_NODE_ID, Crate, DUMMY_NODE_ID, ItemKind, ModKind, NodeId, Path, - join_path_idents, -}; -use rustc_ast_pretty::pprust; -use rustc_attr_parsing::AttributeParser; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::unord::{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::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_id::{CRATE_DEF_ID, DefId}; -use rustc_hir::{Attribute, PrimTy, Stability, StabilityLevel, find_attr}; -use rustc_middle::bug; -use rustc_middle::ty::{TyCtxt, Visibility}; -use rustc_session::Session; -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_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, -}; -use thin_vec::{ThinVec, thin_vec}; -use tracing::{debug, instrument}; - -use crate::diagnostics::{ - self, AddedMacroUse, ChangeImportBinding, ChangeImportBindingSuggestion, ConsiderAddingADerive, - ExplicitUnsafeTraits, MacroDefinedLater, MacroRulesNot, MacroSuggMovePosition, - MaybeMissingMacroRulesName, -}; -use crate::hygiene::Macros20NormalizedSyntaxContext; -use crate::imports::{Import, ImportKind, UnresolvedImportError, import_path_to_string}; -use crate::late::{DiagMetadata, PatternSource, Rib}; -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, -}; - -/// A vector of spans and replacements, a message and applicability. -pub(crate) type Suggestion = (Vec<(Span, String)>, String, Applicability); - -/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a -/// similarly named label and whether or not it is reachable. -pub(crate) type LabelSuggestion = (Ident, bool); - -#[derive(Clone)] -pub(crate) struct StructCtor { - pub res: Res, - pub vis: Visibility, - pub field_visibilities: Vec>, -} - -impl StructCtor { - pub(crate) fn has_private_fields<'ra>(&self, m: Module<'ra>, r: &Resolver<'ra, '_>) -> bool { - self.field_visibilities.iter().any(|&vis| !r.is_accessible_from(vis, m)) - } -} - -#[derive(Debug)] -pub(crate) enum SuggestionTarget { - /// The target has a similar name as the name used by the programmer (probably a typo) - SimilarlyNamed, - /// The target is the only valid item that can be used in the corresponding context - SingleItem, -} - -#[derive(Debug)] -pub(crate) struct TypoSuggestion { - pub candidate: Symbol, - /// The source location where the name is defined; None if the name is not defined - /// in source e.g. primitives - pub span: Option, - pub res: Res, - pub target: SuggestionTarget, -} - -impl TypoSuggestion { - pub(crate) fn new(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { - Self { candidate, span: Some(span), res, target: SuggestionTarget::SimilarlyNamed } - } - pub(crate) fn typo_from_name(candidate: Symbol, res: Res) -> TypoSuggestion { - Self { candidate, span: None, res, target: SuggestionTarget::SimilarlyNamed } - } - pub(crate) fn single_item(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { - Self { candidate, span: Some(span), res, target: SuggestionTarget::SingleItem } - } -} - -/// A free importable items suggested in case of resolution failure. -#[derive(Debug)] -pub(crate) struct ImportSuggestion { - pub did: Option, - pub descr: &'static str, - pub path: Path, - pub accessible: bool, - // false if the path traverses a foreign `#[doc(hidden)]` item. - pub doc_visible: bool, - pub via_import: bool, - /// An extra note that should be issued if this item is suggested - pub note: Option, - pub is_stable: bool, -} - -/// Adjust the impl span so that just the `impl` keyword is taken by removing -/// everything after `<` (`"impl Iterator for A {}" -> "impl"`) and -/// everything after the first whitespace (`"impl Iterator for A" -> "impl"`). -/// -/// *Attention*: the method used is very fragile since it essentially duplicates the work of the -/// parser. If you need to use this function or something similar, please consider updating the -/// `source_map` functions and this function to something more robust. -fn reduce_impl_span_to_impl_keyword(sm: &SourceMap, impl_span: Span) -> Span { - let impl_span = sm.span_until_char(impl_span, '<'); - sm.span_until_whitespace(impl_span) -} - -impl<'ra, 'tcx> Resolver<'ra, 'tcx> { - /// Reports unresolved imports. - /// - /// Multiple unresolved import errors within the same use tree are combined into a single - /// diagnostic. - pub(crate) fn throw_unresolved_import_error( - &mut self, - mut errors: Vec<(Import<'_>, UnresolvedImportError)>, - glob_error: bool, - ) { - errors.retain(|(_import, err)| match err.module { - // Skip `use` errors for `use foo::Bar;` if `foo.rs` has unrecovered parse errors. - Some(def_id) if self.mods_with_parse_errors.contains(&def_id) => false, - // If we've encountered something like `use _;`, we've already emitted an error stating - // that `_` is not a valid identifier, so we ignore that resolve error. - _ => err.segment.map(|s| s.name) != Some(kw::Underscore), - }); - if errors.is_empty() { - self.tcx.dcx().delayed_bug("expected a parse or \"`_` can't be an identifier\" error"); - return; - } - - let span = MultiSpan::from_spans(errors.iter().map(|(_, err)| err.span).collect()); - - let paths = errors - .iter() - .map(|(import, err)| { - let path = import_path_to_string( - &import.module_path.iter().map(|seg| seg.ident).collect::>(), - &import.kind, - err.span, - ); - format!("`{path}`") - }) - .collect::>(); - let default_message = - format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); - - // Process `import` use of the `#[diagnostic::on_unknown]` attribute. - // - // We don't need to check feature gates here; that happens on initialization of the - // `on_unknown_attr` fields. - let (mut message, label, mut notes) = - if let Some(directive) = errors[0].1.on_unknown_attr.as_ref().map(|a| &a.directive) { - let this = errors - .iter() - .map(|(_import, err)| { - // Is this unwrap_or reachable? - err.segment.map(|s| s.name).unwrap_or(kw::Underscore) - }) - .join(", "); - - let args = FormatArgs { unresolved: this.clone(), this, .. }; - - let CustomDiagnostic { message, label, notes, parent_label: _dead } = - directive.eval(None, &args); - - (message, label, notes) - } else { - (None, None, Vec::new()) - }; - - // `module` use of the `#[diagnostic::on_unknown]` attribute. - // We assume that someone who put the attribute on the import has more information than - // the person who put it on the module, so we choose to prioritize the import attribute. - let mut mod_diagnostics: Vec = errors - .iter() - .map(|(import, import_error)| { - if let Some(ModuleOrUniformRoot::Module(module_data)) = import.imported_module.get() - && let ModuleKind::Def(DefKind::Mod, def_id, _, name) = module_data.kind - { - let Some(directive) = self.on_unknown_data(def_id) else { - return CustomDiagnostic::default(); - }; - - let this = if let Some(name) = name { - name.to_string() - } else if let Some(crate_name) = &self.tcx.sess.opts.crate_name { - crate_name.to_string() - } else { - "".to_string() - }; - let unresolved = import_error.segment.map(|s| s.name).unwrap_or(kw::Underscore); - let args = FormatArgs { this, unresolved: unresolved.to_string(), .. }; - - directive.eval(None, &args) - } else { - CustomDiagnostic::default() - } - }) - .collect(); - - // If there is no import attribute with a message, - // but all mod messages are the same, use that. - let mod_message = - mod_diagnostics.iter_mut().flat_map(|d| d.message.take()).all_equal_value(); - if message.is_none() - && let Ok(mod_msg) = mod_message - { - message = Some(mod_msg); - } - - let mut diag = if let Some(message) = message { - struct_span_code_err!(self.dcx(), span, E0432, "{message}").with_note(default_message) - } else { - struct_span_code_err!(self.dcx(), span, E0432, "{default_message}") - }; - - for mod_diag in mod_diagnostics.iter_mut() { - for mod_note in mod_diag.notes.drain(..) { - if !notes.contains(&mod_note) { - notes.push(mod_note); - } - } - } - - if !notes.is_empty() { - for note in notes { - diag.note(note); - } - } else if let Some((_, UnresolvedImportError { note: Some(note), .. })) = - errors.iter().last() - { - diag.note(note.clone()); - } - - /// Upper limit on the number of `span_label` messages. - const MAX_LABEL_COUNT: usize = 10; - let mod_labels = mod_diagnostics.into_iter().map(|cd| cd.label); - - for ((import, err), mod_label) in errors.into_iter().zip(mod_labels).take(MAX_LABEL_COUNT) { - let label_span = match err.segment { - Some(segment) => segment.span, - None => err.span, - }; - if let Some(label) = &label { - diag.span_label(label_span, label.clone()); - } else if let Some(label) = mod_label { - diag.span_label(label_span, label); - } else if let Some(label) = &err.label { - diag.span_label(label_span, label.clone()); - } - - if let Some((suggestions, msg, applicability)) = err.suggestion { - if suggestions.is_empty() { - diag.help(msg); - continue; - } - diag.multipart_suggestion(msg, suggestions, applicability); - } - - if let Some(candidates) = &err.candidates { - match &import.kind { - ImportKind::Single { nested: false, source, target, .. } => import_candidates( - self.tcx, - &mut diag, - Some(err.span), - candidates, - DiagMode::Import { append: false, unresolved_import: true }, - (source != target) - .then(|| format!(" as {target}")) - .as_deref() - .unwrap_or(""), - ), - ImportKind::Single { nested: true, source, target, .. } => { - import_candidates( - self.tcx, - &mut diag, - None, - candidates, - DiagMode::Normal, - (source != target) - .then(|| format!(" as {target}")) - .as_deref() - .unwrap_or(""), - ); - } - _ => {} - } - } - - if matches!(import.kind, ImportKind::Single { .. }) - && let Some(segment) = err.segment - && let Some(module) = err.module - { - self.find_cfg_stripped(&mut diag, &segment.name, module) - } - } - - let guar = diag.emit(); - if glob_error { - self.glob_error = Some(guar); - } - } - - pub(crate) fn dcx(&self) -> DiagCtxtHandle<'tcx> { - self.tcx.dcx() - } - - pub(crate) fn report_errors(&mut self, krate: &Crate) { - self.report_delayed_vis_resolution_errors(); - self.report_with_use_injections(krate); - - for &(span_use, span_def) in &self.macro_expanded_macro_export_errors { - self.lint_buffer.buffer_lint( - MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, - CRATE_NODE_ID, - span_use, - diagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths { - definition: span_def, - }, - ); - } - - for ambiguity_error in &self.ambiguity_errors { - let mut diag = self.ambiguity_diagnostic(ambiguity_error); - - if let Some(ambiguity_warning) = ambiguity_error.warning { - let node_id = match ambiguity_error.b1.0.kind { - DeclKind::Import { import, .. } => import.root_id, - DeclKind::Def(_) => CRATE_NODE_ID, - }; - - let lint = match ambiguity_warning { - _ if ambiguity_error.ambig_vis.is_some() => AMBIGUOUS_IMPORT_VISIBILITIES, - AmbiguityWarning::GlobImport => AMBIGUOUS_GLOB_IMPORTS, - AmbiguityWarning::PanicImport => AMBIGUOUS_PANIC_IMPORTS, - }; - - self.lint_buffer.buffer_lint(lint, node_id, diag.ident.span, diag); - } else { - diag.is_error = true; - self.dcx().emit_err(diag); - } - } - - let mut reported_spans = FxHashSet::default(); - for error in mem::take(&mut self.privacy_errors) { - if reported_spans.insert(error.dedup_span) { - self.report_privacy_error(&error); - } - } - } - - fn report_delayed_vis_resolution_errors(&mut self) { - for DelayedVisResolutionError { vis, parent_scope, error } in - mem::take(&mut self.delayed_vis_resolution_errors) - { - match self.try_resolve_visibility(&parent_scope, &vis, true) { - Ok(_) => self.report_vis_error(error), - Err(error) => self.report_vis_error(error), - }; - } - } - - fn report_with_use_injections(&mut self, krate: &Crate) { - for UseError { mut err, candidates, node_id, instead, suggestion, path, is_call } in - mem::take(&mut self.use_injections) - { - let (span, found_use) = if node_id != DUMMY_NODE_ID { - UsePlacementFinder::check(krate, node_id) - } else { - (None, FoundUse::No) - }; - - if !candidates.is_empty() { - show_candidates( - self.tcx, - &mut err, - span, - &candidates, - if instead { Instead::Yes } else { Instead::No }, - found_use, - DiagMode::Normal, - path, - "", - ); - err.emit(); - } else if let Some((span, msg, sugg, appl)) = suggestion { - err.span_suggestion_verbose(span, msg, sugg, appl); - err.emit(); - } else if let [segment] = path.as_slice() - && is_call - { - err.stash(segment.ident.span, rustc_errors::StashKey::CallIntoMethod); - } else { - err.emit(); - } - } - } - - pub(crate) fn report_conflict( - &mut self, - ident: IdentKey, - ns: Namespace, - old_binding: Decl<'ra>, - new_binding: Decl<'ra>, - ) { - // Error on the second of two conflicting names - if old_binding.span.lo() > new_binding.span.lo() { - return self.report_conflict(ident, ns, new_binding, old_binding); - } - - let container = match old_binding.parent_module.unwrap().expect_local().kind { - // Avoid using TyCtxt::def_kind_descr in the resolver, because it - // indirectly *calls* the resolver, and would cause a query cycle. - ModuleKind::Def(kind, def_id, _, _) => kind.descr(def_id), - ModuleKind::Block => "block", - }; - - let (name, span) = - (ident.name, self.tcx.sess.source_map().guess_head_span(new_binding.span)); - - if self.name_already_seen.get(&name) == Some(&span) { - return; - } - - let old_kind = match (ns, old_binding.res()) { - (ValueNS, _) => "value", - (MacroNS, _) => "macro", - (TypeNS, _) if old_binding.is_extern_crate() => "extern crate", - (TypeNS, Res::Def(DefKind::Mod, _)) => "module", - (TypeNS, Res::Def(DefKind::Trait, _)) => "trait", - (TypeNS, _) => "type", - }; - - let code = match (old_binding.is_extern_crate(), new_binding.is_extern_crate()) { - (true, true) => E0259, - (true, _) | (_, true) => match new_binding.is_import() && old_binding.is_import() { - true => E0254, - false => E0260, - }, - _ => match (old_binding.is_import_user_facing(), new_binding.is_import_user_facing()) { - (false, false) => E0428, - (true, true) => E0252, - _ => E0255, - }, - }; - - let label = match new_binding.is_import_user_facing() { - true => diagnostics::NameDefinedMultipleTimeLabel::Reimported { span, name }, - false => diagnostics::NameDefinedMultipleTimeLabel::Redefined { span, name }, - }; - - let old_binding_label = - (!old_binding.span.is_dummy() && old_binding.span != span).then(|| { - let span = self.tcx.sess.source_map().guess_head_span(old_binding.span); - match old_binding.is_import_user_facing() { - true => diagnostics::NameDefinedMultipleTimeOldBindingLabel::Import { - span, - old_kind, - name, - }, - false => diagnostics::NameDefinedMultipleTimeOldBindingLabel::Definition { - span, - old_kind, - name, - }, - } - }); - - let mut err = self - .dcx() - .create_err(diagnostics::NameDefinedMultipleTime { - span, - name, - descr: ns.descr(), - container, - label, - old_binding_label, - }) - .with_code(code); - - // See https://github.com/rust-lang/rust/issues/32354 - use DeclKind::Import; - let can_suggest = |binding: Decl<'_>, import: self::Import<'_>| { - !binding.span.is_dummy() - && !matches!(import.kind, ImportKind::MacroUse { .. } | ImportKind::MacroExport) - }; - let import = match (&new_binding.kind, &old_binding.kind) { - // If there are two imports where one or both have attributes then prefer removing the - // import without attributes. - (Import { import: new, .. }, Import { import: old, .. }) - if { - (new.has_attributes || old.has_attributes) - && can_suggest(old_binding, *old) - && can_suggest(new_binding, *new) - } => - { - if old.has_attributes { - Some((*new, new_binding.span, true)) - } else { - Some((*old, old_binding.span, true)) - } - } - // Otherwise prioritize the new binding. - (Import { import, .. }, other) if can_suggest(new_binding, *import) => { - Some((*import, new_binding.span, other.is_import())) - } - (other, Import { import, .. }) if can_suggest(old_binding, *import) => { - Some((*import, old_binding.span, other.is_import())) - } - _ => None, - }; - - // Check if the target of the use for both bindings is the same. - let duplicate = new_binding.res().opt_def_id() == old_binding.res().opt_def_id(); - let has_dummy_span = new_binding.span.is_dummy() || old_binding.span.is_dummy(); - let from_item = - self.extern_prelude.get(&ident).is_none_or(|entry| entry.introduced_by_item()); - // Only suggest removing an import if both bindings are to the same def, if both spans - // aren't dummy spans. Further, if both bindings are imports, then the ident must have - // been introduced by an item. - let should_remove_import = duplicate - && !has_dummy_span - && ((new_binding.is_extern_crate() || old_binding.is_extern_crate()) || from_item); - - match import { - Some((import, span, true)) if should_remove_import && import.is_nested() => { - self.add_suggestion_for_duplicate_nested_use(&mut err, import, span); - } - Some((import, _, true)) if should_remove_import && !import.is_glob() => { - // Simple case - remove the entire import. Due to the above match arm, this can - // only be a single use so just remove it entirely. - err.subdiagnostic(diagnostics::ToolOnlyRemoveUnnecessaryImport { - span: import.use_span_with_attributes, - }); - } - Some((import, span, _)) => { - self.add_suggestion_for_rename_of_use(&mut err, name, import, span); - } - _ => {} - } - - err.emit(); - self.name_already_seen.insert(name, span); - } - - /// This function adds a suggestion to change the binding name of a new import that conflicts - /// with an existing import. - /// - /// ```text,ignore (diagnostic) - /// help: you can use `as` to change the binding name of the import - /// | - /// LL | use foo::bar as other_bar; - /// | ^^^^^^^^^^^^^^^^^^^^^ - /// ``` - fn add_suggestion_for_rename_of_use( - &self, - err: &mut Diag<'_>, - name: Symbol, - import: Import<'_>, - binding_span: Span, - ) { - let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() { - format!("Other{name}") - } else { - format!("other_{name}") - }; - - let mut suggestion = None; - let mut span = binding_span; - match import.kind { - ImportKind::Single { source, .. } => { - if let Some(pos) = source.span.hi().0.checked_sub(binding_span.lo().0) - && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(binding_span) - && pos as usize <= snippet.len() - { - span = binding_span.with_lo(binding_span.lo() + BytePos(pos)).with_hi( - binding_span.hi() - BytePos(if snippet.ends_with(';') { 1 } else { 0 }), - ); - suggestion = Some(format!(" as {suggested_name}")); - } - } - ImportKind::ExternCrate { source, target, .. } => { - suggestion = Some(format!( - "extern crate {} as {};", - source.unwrap_or(target.name), - suggested_name, - )) - } - _ => unreachable!(), - } - - if let Some(suggestion) = suggestion { - err.subdiagnostic(ChangeImportBindingSuggestion { span, suggestion }); - } else { - err.subdiagnostic(ChangeImportBinding { span }); - } - } - - /// This function adds a suggestion to remove an unnecessary binding from an import that is - /// nested. In the following example, this function will be invoked to remove the `a` binding - /// in the second use statement: - /// - /// ```ignore (diagnostic) - /// use issue_52891::a; - /// use issue_52891::{d, a, e}; - /// ``` - /// - /// The following suggestion will be added: - /// - /// ```ignore (diagnostic) - /// use issue_52891::{d, a, e}; - /// ^-- help: remove unnecessary import - /// ``` - /// - /// If the nested use contains only one import then the suggestion will remove the entire - /// line. - /// - /// It is expected that the provided import is nested - this isn't checked by the - /// function. If this invariant is not upheld, this function's behaviour will be unexpected - /// as characters expected by span manipulations won't be present. - fn add_suggestion_for_duplicate_nested_use( - &self, - err: &mut Diag<'_>, - import: Import<'_>, - binding_span: Span, - ) { - assert!(import.is_nested()); - - // Two examples will be used to illustrate the span manipulations we're doing: - // - // - Given `use issue_52891::{d, a, e};` where `a` is a duplicate then `binding_span` is - // `a` and `import.use_span` is `issue_52891::{d, a, e};`. - // - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is - // `a` and `import.use_span` is `issue_52891::{d, e, a};`. - - let (found_closing_brace, span) = - find_span_of_binding_until_next_binding(self.tcx.sess, binding_span, import.use_span); - - // If there was a closing brace then identify the span to remove any trailing commas from - // previous imports. - if found_closing_brace { - if let Some(span) = extend_span_to_previous_binding(self.tcx.sess, span) { - err.subdiagnostic(diagnostics::ToolOnlyRemoveUnnecessaryImport { span }); - } else { - // Remove the entire line if we cannot extend the span back, this indicates an - // `issue_52891::{self}` case. - err.subdiagnostic(diagnostics::RemoveUnnecessaryImport { - span: import.use_span_with_attributes, - }); - } - - return; - } - - err.subdiagnostic(diagnostics::RemoveUnnecessaryImport { span }); - } - - pub(crate) fn lint_if_path_starts_with_module( - &mut self, - finalize: Finalize, - path: &[Segment], - second_binding: Option>, - ) { - let Finalize { node_id, root_span, .. } = finalize; - - let first_name = match path.get(0) { - // In the 2018 edition this lint is a hard error, so nothing to do - Some(seg) if seg.ident.span.is_rust_2015() && self.tcx.sess.is_rust_2015() => { - seg.ident.name - } - _ => return, - }; - - // We're only interested in `use` paths which should start with - // `{{root}}` currently. - if first_name != kw::PathRoot { - return; - } - - match path.get(1) { - // If this import looks like `crate::...` it's already good - Some(Segment { ident, .. }) if ident.name == kw::Crate => return, - // Otherwise go below to see if it's an extern crate - Some(_) => {} - // If the path has length one (and it's `PathRoot` most likely) - // then we don't know whether we're gonna be importing a crate or an - // item in our crate. Defer this lint to elsewhere - None => return, - } - - // If the first element of our path was actually resolved to an - // `ExternCrate` (also used for `crate::...`) then no need to issue a - // warning, this looks all good! - if let Some(binding) = second_binding - && let DeclKind::Import { import, .. } = binding.kind - // Careful: we still want to rewrite paths from renamed extern crates. - && let ImportKind::ExternCrate { source: None, .. } = import.kind - { - return; - } - - self.lint_buffer.dyn_buffer_lint_any( - ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, - node_id, - root_span, - move |dcx, level, sess| { - let (replacement, applicability) = match sess - .downcast_ref::() - .expect("expected a `Session`") - .source_map() - .span_to_snippet(root_span) - { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), - }; - diagnostics::AbsPathWithModule { - sugg: diagnostics::AbsPathWithModuleSugg { - span: root_span, - applicability, - replacement, - }, - } - .into_diag(dcx, level) - }, - ); - } - - pub(crate) fn add_module_candidates( - &self, - module: Module<'ra>, - names: &mut Vec, - filter_fn: &impl Fn(Res) -> bool, - ctxt: Option, - ) { - module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { - let res = binding.res(); - if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { - names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); - } - }); - } - - /// Combines an error with provided span and emits it. - /// - /// This takes the error provided, combines it with the span and any additional spans inside the - /// error and emits it. - pub(crate) fn report_error( - &mut self, - span: Span, - resolution_error: ResolutionError<'ra>, - ) -> ErrorGuaranteed { - self.into_struct_error(span, resolution_error).emit() - } - - pub(crate) fn into_struct_error( - &mut self, - span: Span, - resolution_error: ResolutionError<'ra>, - ) -> Diag<'_> { - match resolution_error { - ResolutionError::GenericParamsFromOuterItem { - outer_res, - has_generic_params, - def_kind, - inner_item, - current_self_ty, - } => { - use diagnostics::GenericParamsFromOuterItemLabel as Label; - let static_or_const = match def_kind { - DefKind::Static { .. } => { - Some(diagnostics::GenericParamsFromOuterItemStaticOrConst::Static) - } - DefKind::Const { .. } => { - Some(diagnostics::GenericParamsFromOuterItemStaticOrConst::Const) - } - _ => None, - }; - let is_self = - matches!(outer_res, Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }); - let mut err = diagnostics::GenericParamsFromOuterItem { - span, - label: None, - refer_to_type_directly: None, - use_let: None, - sugg: None, - static_or_const, - is_self, - item: inner_item.as_ref().map(|(label_span, _, kind)| { - diagnostics::GenericParamsFromOuterItemInnerItem { - span: *label_span, - descr: kind.descr().to_string(), - is_self, - } - }), - }; - - let sm = self.tcx.sess.source_map(); - // Note: do not early return for missing def_id here, - // we still want to provide suggestions for `Res::SelfTyParam` and `Res::SelfTyAlias`. - let def_id = match outer_res { - Res::SelfTyParam { .. } => { - err.label = Some(Label::SelfTyParam(span)); - None - } - Res::SelfTyAlias { alias_to: def_id, .. } => { - err.label = Some(Label::SelfTyAlias(reduce_impl_span_to_impl_keyword( - sm, - self.def_span(def_id), - ))); - err.refer_to_type_directly = current_self_ty - .map(|snippet| diagnostics::UseTypeDirectly { span, snippet }); - None - } - Res::Def(DefKind::TyParam, def_id) => { - err.label = Some(Label::TyParam(self.def_span(def_id))); - Some(def_id) - } - Res::Def(DefKind::ConstParam, def_id) => { - err.label = Some(Label::ConstParam(self.def_span(def_id))); - Some(def_id) - } - _ => { - bug!( - "GenericParamsFromOuterItem should only be used with \ - Res::SelfTyParam, Res::SelfTyAlias, DefKind::TyParam or \ - DefKind::ConstParam" - ); - } - }; - - if let Some((_, item_span, ItemKind::Const(_))) = inner_item.as_ref() { - err.use_let = Some(diagnostics::GenericParamsFromOuterItemUseLet { - span: sm.span_until_whitespace(*item_span), - }); - } - - if let Some(def_id) = def_id - && let HasGenericParams::Yes(span) = has_generic_params - && !matches!(inner_item, Some((_, _, ItemKind::Delegation(..)))) - { - let name = self.tcx.item_name(def_id); - let (span, snippet) = if span.is_empty() { - let snippet = format!("<{name}>"); - (span, snippet) - } else { - let span = sm.span_through_char(span, '<').shrink_to_hi(); - let snippet = format!("{name}, "); - (span, snippet) - }; - err.sugg = Some(diagnostics::GenericParamsFromOuterItemSugg { span, snippet }); - } - - self.dcx().create_err(err) - } - ResolutionError::NameAlreadyUsedInParameterList(name, first_use_span) => { - self.dcx().create_err(diagnostics::NameAlreadyUsedInParameterList { - span, - first_use_span, - name, - }) - } - ResolutionError::MethodNotMemberOfTrait(method, trait_, candidate) => { - self.dcx().create_err(diagnostics::MethodNotMemberOfTrait { - span, - method, - trait_, - sub: candidate.map(|c| diagnostics::AssociatedFnWithSimilarNameExists { - span: method.span, - candidate: c, - }), - }) - } - ResolutionError::TypeNotMemberOfTrait(type_, trait_, candidate) => { - self.dcx().create_err(diagnostics::TypeNotMemberOfTrait { - span, - type_, - trait_, - sub: candidate.map(|c| diagnostics::AssociatedTypeWithSimilarNameExists { - span: type_.span, - candidate: c, - }), - }) - } - ResolutionError::ConstNotMemberOfTrait(const_, trait_, candidate) => { - self.dcx().create_err(diagnostics::ConstNotMemberOfTrait { - span, - const_, - trait_, - sub: candidate.map(|c| diagnostics::AssociatedConstWithSimilarNameExists { - span: const_.span, - candidate: c, - }), - }) - } - ResolutionError::VariableNotBoundInPattern(binding_error, parent_scope) => { - let BindingError { name, target, origin, could_be_path } = binding_error; - - let mut target_sp = target.iter().map(|pat| pat.span).collect::>(); - target_sp.sort(); - target_sp.dedup(); - let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::>(); - origin_sp.sort(); - origin_sp.dedup(); - - let msp = MultiSpan::from_spans(target_sp.clone()); - let mut err = self.dcx().create_err(diagnostics::VariableIsNotBoundInAllPatterns { - multispan: msp, - name, - }); - for sp in target_sp { - err.subdiagnostic(diagnostics::PatternDoesntBindName { span: sp, name }); - } - for sp in &origin_sp { - err.subdiagnostic(diagnostics::VariableNotInAllPatterns { span: *sp }); - } - let mut suggested_typo = false; - if !target.iter().all(|pat| matches!(pat.kind, ast::PatKind::Ident(..))) - && !origin.iter().all(|(_, pat)| matches!(pat.kind, ast::PatKind::Ident(..))) - { - // The check above is so that when we encounter `match foo { (a | b) => {} }`, - // we don't suggest `(a | a) => {}`, which would never be what the user wants. - let mut target_visitor = BindingVisitor::default(); - for pat in &target { - target_visitor.visit_pat(pat); - } - target_visitor.identifiers.sort(); - target_visitor.identifiers.dedup(); - let mut origin_visitor = BindingVisitor::default(); - for (_, pat) in &origin { - origin_visitor.visit_pat(pat); - } - origin_visitor.identifiers.sort(); - origin_visitor.identifiers.dedup(); - // Find if the binding could have been a typo - if let Some(typo) = - find_best_match_for_name(&target_visitor.identifiers, name.name, None) - && !origin_visitor.identifiers.contains(&typo) - { - err.subdiagnostic(diagnostics::PatternBindingTypo { - spans: origin_sp, - typo, - }); - suggested_typo = true; - } - } - if could_be_path { - let import_suggestions = self.lookup_import_candidates( - name, - Namespace::ValueNS, - &parent_scope, - &|res: Res| { - matches!( - res, - Res::Def( - DefKind::Ctor(CtorOf::Variant, CtorKind::Const) - | DefKind::Ctor(CtorOf::Struct, CtorKind::Const) - | DefKind::Const { .. } - | DefKind::AssocConst { .. }, - _, - ) - ) - }, - ); - - if import_suggestions.is_empty() && !suggested_typo { - let kind_matches: [fn(DefKind) -> bool; 4] = [ - |kind| matches!(kind, DefKind::Ctor(CtorOf::Variant, CtorKind::Const)), - |kind| matches!(kind, DefKind::Ctor(CtorOf::Struct, CtorKind::Const)), - |kind| matches!(kind, DefKind::Const { .. }), - |kind| matches!(kind, DefKind::AssocConst { .. }), - ]; - let mut local_names = vec![]; - self.add_module_candidates( - parent_scope.module, - &mut local_names, - &|res| matches!(res, Res::Def(_, _)), - None, - ); - let local_names: FxHashSet<_> = local_names - .into_iter() - .filter_map(|s| match s.res { - Res::Def(_, def_id) => Some(def_id), - _ => None, - }) - .collect(); - - let mut local_suggestions = vec![]; - let mut suggestions = vec![]; - for matches_kind in kind_matches { - if let Some(suggestion) = self.early_lookup_typo_candidate( - ScopeSet::All(Namespace::ValueNS), - &parent_scope, - name, - &|res: Res| match res { - Res::Def(k, _) => matches_kind(k), - _ => false, - }, - ) && let Res::Def(kind, mut def_id) = suggestion.res - { - if let DefKind::Ctor(_, _) = kind { - def_id = self.tcx.parent(def_id); - } - let kind = kind.descr(def_id); - if local_names.contains(&def_id) { - // The item is available in the current scope. Very likely to - // be a typo. Don't use the full path. - local_suggestions.push(( - suggestion.candidate, - suggestion.candidate.to_string(), - kind, - )); - } else { - suggestions.push(( - suggestion.candidate, - self.def_path_str(def_id), - kind, - )); - } - } - } - let suggestions = if !local_suggestions.is_empty() { - // There is at least one item available in the current scope that is a - // likely typo. We only show those. - local_suggestions - } else { - suggestions - }; - for (name, sugg, kind) in suggestions { - err.span_suggestion_verbose( - span, - format!( - "you might have meant to use the similarly named {kind} `{name}`", - ), - sugg, - Applicability::MaybeIncorrect, - ); - suggested_typo = true; - } - } - if import_suggestions.is_empty() && !suggested_typo { - let help_msg = format!( - "if you meant to match on a unit struct, unit variant or a `const` \ - item, consider making the path in the pattern qualified: \ - `path::to::ModOrType::{name}`", - ); - err.span_help(span, help_msg); - } - show_candidates( - self.tcx, - &mut err, - Some(span), - &import_suggestions, - Instead::No, - FoundUse::Yes, - DiagMode::Pattern, - vec![], - "", - ); - } - err - } - ResolutionError::VariableBoundWithDifferentMode(variable_name, first_binding_span) => { - self.dcx().create_err(diagnostics::VariableBoundWithDifferentMode { - span, - first_binding_span, - variable_name, - }) - } - ResolutionError::IdentifierBoundMoreThanOnceInParameterList(identifier) => { - self.dcx().create_err(diagnostics::IdentifierBoundMoreThanOnceInParameterList { - span, - identifier, - }) - } - ResolutionError::IdentifierBoundMoreThanOnceInSamePattern(identifier) => { - self.dcx().create_err(diagnostics::IdentifierBoundMoreThanOnceInSamePattern { - span, - identifier, - }) - } - ResolutionError::UndeclaredLabel { name, suggestion } => { - let ((sub_reachable, sub_reachable_suggestion), sub_unreachable) = match suggestion - { - // A reachable label with a similar name exists. - Some((ident, true)) => ( - ( - Some(diagnostics::LabelWithSimilarNameReachable(ident.span)), - Some(diagnostics::TryUsingSimilarlyNamedLabel { - span, - ident_name: ident.name, - }), - ), - None, - ), - // An unreachable label with a similar name exists. - Some((ident, false)) => ( - (None, None), - Some(diagnostics::UnreachableLabelWithSimilarNameExists { - ident_span: ident.span, - }), - ), - // No similarly-named labels exist. - None => ((None, None), None), - }; - self.dcx().create_err(diagnostics::UndeclaredLabel { - span, - name, - sub_reachable, - sub_reachable_suggestion, - sub_unreachable, - }) - } - ResolutionError::FailedToResolve { segment, label, suggestion, module, message } => { - let mut err = struct_span_code_err!(self.dcx(), span, E0433, "{message}"); - err.span_label(span, label); - - if let Some((suggestions, msg, applicability)) = suggestion { - if suggestions.is_empty() { - err.help(msg); - return err; - } - err.multipart_suggestion(msg, suggestions, applicability); - } - - let module = match module { - Some(ModuleOrUniformRoot::Module(m)) if let Some(id) = m.opt_def_id() => id, - _ => CRATE_DEF_ID.to_def_id(), - }; - self.find_cfg_stripped(&mut err, &segment, module); - - err - } - ResolutionError::CannotCaptureDynamicEnvironmentInFnItem => { - self.dcx().create_err(diagnostics::CannotCaptureDynamicEnvironmentInFnItem { span }) - } - ResolutionError::AttemptToUseNonConstantValueInConstant { - ident, - suggestion, - current, - type_span, - } => { - // let foo =... - // ^^^ given this Span - // ------- get this Span to have an applicable suggestion - - // edit: - // only do this if the const and usage of the non-constant value are on the same line - // the further the two are apart, the higher the chance of the suggestion being wrong - - let sp = self - .tcx - .sess - .source_map() - .span_extend_to_prev_str(ident.span, current, true, false); - - let (with, with_label, without) = match sp { - Some(sp) if !self.tcx.sess.source_map().is_multiline(sp) => { - let sp = sp - .with_lo(BytePos(sp.lo().0 - (current.len() as u32))) - .until(ident.span); - - // Only suggest replacing the binding keyword if this is a simple - // binding. - // - // Note: this approach still incorrectly suggests for irrefutable - // patterns like `if let x = 1 { const { x } }`, since the text - // between `let` and the identifier is just whitespace. - // See tests/ui/consts/non-const-value-in-const-irrefutable-pat-binding.rs - let is_simple_binding = - self.tcx.sess.source_map().span_to_snippet(sp).is_ok_and(|snippet| { - let after_keyword = snippet[current.len()..].trim(); - after_keyword.is_empty() || after_keyword == "mut" - }); - - if is_simple_binding { - ( - Some(diagnostics::AttemptToUseNonConstantValueInConstantWithSuggestion { - span: sp, - suggestion, - current, - type_span, - }), - Some(diagnostics::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), - None, - ) - } else { - ( - None, - Some(diagnostics::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), - None, - ) - } - } - _ => ( - None, - None, - Some( - diagnostics::AttemptToUseNonConstantValueInConstantWithoutSuggestion { - ident_span: ident.span, - suggestion, - }, - ), - ), - }; - - self.dcx().create_err(diagnostics::AttemptToUseNonConstantValueInConstant { - span, - with, - with_label, - without, - }) - } - ResolutionError::BindingShadowsSomethingUnacceptable { - shadowing_binding, - name, - participle, - article, - shadowed_binding, - shadowed_binding_span, - } => self.dcx().create_err(diagnostics::BindingShadowsSomethingUnacceptable { - span, - shadowing_binding, - shadowed_binding, - article, - sub_suggestion: match (shadowing_binding, shadowed_binding) { - ( - PatternSource::Match, - Res::Def(DefKind::Ctor(CtorOf::Variant | CtorOf::Struct, CtorKind::Fn), _), - ) => Some(diagnostics::BindingShadowsSomethingUnacceptableSuggestion { - span, - name, - }), - _ => None, - }, - shadowed_binding_span, - participle, - name, - }), - ResolutionError::ForwardDeclaredGenericParam(param, reason) => match reason { - ForwardGenericParamBanReason::Default => { - self.dcx().create_err(diagnostics::ForwardDeclaredGenericParam { param, span }) - } - ForwardGenericParamBanReason::ConstParamTy => self - .dcx() - .create_err(diagnostics::ForwardDeclaredGenericInConstParamTy { param, span }), - }, - ResolutionError::ParamInTyOfConstParam { name } => { - self.dcx().create_err(diagnostics::ParamInTyOfConstParam { span, name }) - } - ResolutionError::ParamInNonTrivialAnonConst { is_gca, name, param_kind: is_type } => { - self.dcx().create_err(diagnostics::ParamInNonTrivialAnonConst { - span, - name, - param_kind: is_type, - help: self.tcx.sess.is_nightly_build(), - is_gca, - help_gca: is_gca, - }) - } - ResolutionError::ParamInEnumDiscriminant { name, param_kind: is_type } => { - self.dcx().create_err(diagnostics::ParamInEnumDiscriminant { - span, - name, - param_kind: is_type, - }) - } - ResolutionError::ForwardDeclaredSelf(reason) => match reason { - ForwardGenericParamBanReason::Default => { - self.dcx().create_err(diagnostics::SelfInGenericParamDefault { span }) - } - ForwardGenericParamBanReason::ConstParamTy => { - self.dcx().create_err(diagnostics::SelfInConstGenericTy { span }) - } - }, - ResolutionError::UnreachableLabel { name, definition_span, suggestion } => { - let ((sub_suggestion_label, sub_suggestion), sub_unreachable_label) = - match suggestion { - // A reachable label with a similar name exists. - Some((ident, true)) => ( - ( - Some(diagnostics::UnreachableLabelSubLabel { - ident_span: ident.span, - }), - Some(diagnostics::UnreachableLabelSubSuggestion { - span, - // intentionally taking 'ident.name' instead of 'ident' itself, as this - // could be used in suggestion context - ident_name: ident.name, - }), - ), - None, - ), - // An unreachable label with a similar name exists. - Some((ident, false)) => ( - (None, None), - Some(diagnostics::UnreachableLabelSubLabelUnreachable { - ident_span: ident.span, - }), - ), - // No similarly-named labels exist. - None => ((None, None), None), - }; - self.dcx().create_err(diagnostics::UnreachableLabel { - span, - name, - definition_span, - sub_suggestion, - sub_suggestion_label, - sub_unreachable_label, - }) - } - ResolutionError::TraitImplMismatch { - name, - kind, - code, - trait_item_span, - trait_path, - } => self - .dcx() - .create_err(diagnostics::TraitImplMismatch { - span, - name, - kind, - trait_path, - trait_item_span, - }) - .with_code(code), - ResolutionError::TraitImplDuplicate { name, trait_item_span, old_span } => { - self.dcx().create_err(diagnostics::TraitImplDuplicate { - span, - name, - trait_item_span, - old_span, - }) - } - ResolutionError::InvalidAsmSym => { - self.dcx().create_err(diagnostics::InvalidAsmSym { span }) - } - ResolutionError::LowercaseSelf => { - self.dcx().create_err(diagnostics::LowercaseSelf { span }) - } - ResolutionError::BindingInNeverPattern => { - self.dcx().create_err(diagnostics::BindingInNeverPattern { span }) - } - } - } - - pub(crate) fn report_vis_error( - &mut self, - vis_resolution_error: VisResolutionError, - ) -> ErrorGuaranteed { - match vis_resolution_error { - VisResolutionError::Relative2018(span, path) => { - self.dcx().create_err(diagnostics::Relative2018 { - span, - 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), - }) - } - VisResolutionError::AncestorOnly(span) => { - self.dcx().create_err(diagnostics::AncestorOnly(span)) - } - VisResolutionError::FailedToResolve(span, segment, label, suggestion, message) => self - .into_struct_error( - span, - ResolutionError::FailedToResolve { - segment, - label, - suggestion, - module: None, - message, - }, - ), - VisResolutionError::ExpectedFound(span, path_str, res) => { - self.dcx().create_err(diagnostics::ExpectedModuleFound { span, res, path_str }) - } - VisResolutionError::Indeterminate(span) => { - self.dcx().create_err(diagnostics::Indeterminate(span)) - } - VisResolutionError::ModuleOnly(span) => { - self.dcx().create_err(diagnostics::ModuleOnly(span)) - } - } - .emit() - } - - pub(crate) fn def_path_str(&self, mut def_id: DefId) -> String { - // We can't use `def_path_str` in resolve. - let mut path = vec![def_id]; - while let Some(parent) = self.tcx.opt_parent(def_id) { - def_id = parent; - path.push(def_id); - if def_id.is_top_level_module() { - break; - } - } - // We will only suggest importing directly if it is accessible through that path. - path.into_iter() - .rev() - .map(|def_id| { - self.tcx - .opt_item_name(def_id) - .map(|name| { - match ( - def_id.is_top_level_module(), - def_id.is_local(), - self.tcx.sess.edition(), - ) { - (true, true, Edition::Edition2015) => String::new(), - (true, true, _) => kw::Crate.to_string(), - (true, false, _) | (false, _, _) => name.to_string(), - } - }) - .unwrap_or_else(|| "_".to_string()) - }) - .collect::>() - .join("::") - } - - pub(crate) fn add_scope_set_candidates( - &mut self, - suggestions: &mut Vec, - scope_set: ScopeSet<'ra>, - ps: &ParentScope<'ra>, - sp: Span, - filter_fn: &impl Fn(Res) -> bool, - ) { - let ctxt = Macros20NormalizedSyntaxContext::new(sp.ctxt()); - self.cm().visit_scopes(scope_set, ps, ctxt, sp, None, |this, scope, use_prelude, _| { - match scope { - Scope::DeriveHelpers(expn_id) => { - let res = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); - if filter_fn(res) { - suggestions.extend(this.helper_attrs.get(&expn_id).into_flat_iter().map( - |&(ident, orig_ident_span, _)| { - TypoSuggestion::new(ident.name, orig_ident_span, res) - }, - )); - } - } - Scope::DeriveHelpersCompat => { - // Never recommend deprecated helper attributes. - } - Scope::MacroRules(macro_rules_scope) => { - if let MacroRulesScope::Def(macro_rules_def) = macro_rules_scope.get() { - let res = macro_rules_def.decl.res(); - if filter_fn(res) { - suggestions.push(TypoSuggestion::new( - macro_rules_def.ident.name, - macro_rules_def.orig_ident_span, - res, - )) - } - } - } - Scope::ModuleNonGlobs(module, _) => { - this.add_module_candidates(module, suggestions, filter_fn, None); - } - Scope::ModuleGlobs(..) => { - // Already handled in `ModuleNonGlobs`. - } - Scope::MacroUsePrelude => { - suggestions.extend(this.macro_use_prelude.iter().filter_map( - |(name, binding)| { - let res = binding.res(); - filter_fn(res).then_some(TypoSuggestion::typo_from_name(*name, res)) - }, - )); - } - Scope::BuiltinAttrs => { - let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin(sym::dummy)); - if filter_fn(res) { - suggestions.extend( - BUILTIN_ATTRIBUTES - .iter() - // These trace attributes are compiler-generated and have - // deliberately invalid names. - .filter(|attr| { - !matches!(**attr, sym::cfg_trace | sym::cfg_attr_trace) - }) - .map(|attr| TypoSuggestion::typo_from_name(*attr, res)), - ); - } - } - Scope::ExternPreludeItems => { - // Add idents from both item and flag scopes. - suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, entry)| { - let res = Res::Def(DefKind::Mod, CRATE_DEF_ID.to_def_id()); - filter_fn(res).then_some(TypoSuggestion::new(ident.name, entry.span(), res)) - })); - } - Scope::ExternPreludeFlags => {} - Scope::ToolPrelude => { - let res = Res::NonMacroAttr(NonMacroAttrKind::Tool); - suggestions.extend( - this.registered_tools - .iter() - .map(|ident| TypoSuggestion::new(ident.name, ident.span, res)), - ); - } - Scope::StdLibPrelude => { - if let Some(prelude) = this.prelude { - let mut tmp_suggestions = Vec::new(); - this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); - suggestions.extend( - tmp_suggestions - .into_iter() - .filter(|s| use_prelude.into() || this.is_builtin_macro(s.res)), - ); - } - } - Scope::BuiltinTypes => { - suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| { - let res = Res::PrimTy(*prim_ty); - filter_fn(res) - .then_some(TypoSuggestion::typo_from_name(prim_ty.name(), res)) - })) - } - } - - ControlFlow::<()>::Continue(()) - }); - } - - /// Lookup typo candidate in scope for a macro or import. - fn early_lookup_typo_candidate( - &mut self, - scope_set: ScopeSet<'ra>, - parent_scope: &ParentScope<'ra>, - ident: Ident, - filter_fn: &impl Fn(Res) -> bool, - ) -> Option { - let mut suggestions = Vec::new(); - self.add_scope_set_candidates( - &mut suggestions, - scope_set, - parent_scope, - ident.span, - filter_fn, - ); - - // Make sure error reporting is deterministic. - suggestions.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); - - match find_best_match_for_name( - &suggestions.iter().map(|suggestion| suggestion.candidate).collect::>(), - ident.name, - None, - ) { - Some(found) if found != ident.name => { - suggestions.into_iter().find(|suggestion| suggestion.candidate == found) - } - _ => None, - } - } - - fn lookup_import_candidates_from_module( - &self, - lookup_ident: Ident, - namespace: Namespace, - parent_scope: &ParentScope<'ra>, - start_module: Module<'ra>, - crate_path: ThinVec, - filter_fn: FilterFn, - ) -> Vec - where - FilterFn: Fn(Res) -> bool, - { - let mut candidates = Vec::new(); - let mut seen_modules = FxHashSet::default(); - let start_did = start_module.def_id(); - let mut worklist = vec![( - start_module, - ThinVec::::new(), - true, - start_did.is_local() || !self.tcx.is_doc_hidden(start_did), - true, - )]; - let mut worklist_via_import = vec![]; - - while let Some((in_module, path_segments, accessible, doc_visible, is_stable)) = - match worklist.pop() { - None => worklist_via_import.pop(), - Some(x) => Some(x), - } - { - let in_module_is_extern = !in_module.def_id().is_local(); - in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { - // Avoid non-importable candidates. - if name_binding.is_assoc_item() - && !this.features.import_trait_associated_functions() - { - return; - } - - if ident.name == kw::Underscore { - return; - } - - let child_accessible = - accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); - - // do not venture inside inaccessible items of other crates - if in_module_is_extern && !child_accessible { - return; - } - - let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); - - // There is an assumption elsewhere that paths of variants are in the enum's - // declaration and not imported. With this assumption, the variant component is - // chopped and the rest of the path is assumed to be the enum's own path. For - // errors where a variant is used as the type instead of the enum, this causes - // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. - if via_import && name_binding.is_possibly_imported_variant() { - return; - } - - // #90113: Do not count an inaccessible reexported item as a candidate. - if let DeclKind::Import { source_decl, .. } = name_binding.kind - && this.is_accessible_from(source_decl.vis(), parent_scope.module) - && !this.is_accessible_from(name_binding.vis(), parent_scope.module) - { - return; - } - - let res = name_binding.res(); - let did = match res { - Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), - _ => res.opt_def_id(), - }; - let child_doc_visible = doc_visible - && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); - - // collect results based on the filter function - // avoid suggesting anything from the same module in which we are resolving - // avoid suggesting anything with a hygienic name - if ident.name == lookup_ident.name - && ns == namespace - && in_module != parent_scope.module - && ident.ctxt.is_root() - && filter_fn(res) - { - // create the path - let mut segms = if lookup_ident.span.at_least_rust_2018() { - // crate-local absolute paths start with `crate::` in edition 2018 - // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) - crate_path.clone() - } else { - ThinVec::new() - }; - segms.append(&mut 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 }; - - if child_accessible - // Remove invisible match if exists - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.accessible) - { - candidates.remove(idx); - } - - let is_stable = if is_stable - && let Some(did) = did - && this.is_stable(did, path.span) - { - true - } else { - false - }; - - // Rreplace unstable suggestions if we meet a new stable one, - // and do nothing if any other situation. For example, if we - // meet `std::ops::Range` after `std::range::legacy::Range`, - // we will remove the latter and then insert the former. - if is_stable - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) - { - candidates.remove(idx); - } - - if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { - // See if we're recommending TryFrom, TryInto, or FromIterator and add - // a note about editions - let note = if let Some(did) = did { - let requires_note = !did.is_local() - && find_attr!( - this.tcx, - did, - RustcDiagnosticItem( - sym::TryInto | sym::TryFrom | sym::FromIterator - ) - ); - requires_note.then(|| { - format!( - "'{}' is included in the prelude starting in Edition 2021", - path_names_to_string(&path) - ) - }) - } else { - None - }; - - candidates.push(ImportSuggestion { - did, - descr: res.descr(), - path, - accessible: child_accessible, - doc_visible: child_doc_visible, - note, - via_import, - is_stable, - }); - } - } - - // collect submodules to explore - if let Some(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 alias_import = if let DeclKind::Import { import, .. } = name_binding.kind - && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind - && import.parent_scope.expansion == parent_scope.expansion - { - true - } else { - false - }; - - let is_extern_crate_that_also_appears_in_prelude = - name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); - - if !is_extern_crate_that_also_appears_in_prelude || alias_import { - // add the module to the lookup - if seen_modules.insert(def_id) { - if via_import { &mut worklist_via_import } else { &mut worklist }.push( - ( - this.expect_module(def_id), - path_segments, - child_accessible, - child_doc_visible, - is_stable && this.is_stable(def_id, name_binding.span), - ), - ); - } - } - } - }) - } - - candidates - } - - fn is_stable(&self, did: DefId, span: Span) -> bool { - if did.is_local() { - return true; - } - - match self.tcx.lookup_stability(did) { - Some(Stability { - level: StabilityLevel::Unstable { implied_by, .. }, feature, .. - }) => { - if span.allows_unstable(feature) { - true - } else if self.features.enabled(feature) { - true - } else if let Some(implied_by) = implied_by - && self.features.enabled(implied_by) - { - true - } else { - false - } - } - Some(_) => true, - None => false, - } - } - - /// When name resolution fails, this method can be used to look up candidate - /// entities with the expected name. It allows filtering them using the - /// supplied predicate (which should be used to only accept the types of - /// definitions expected, e.g., traits). The lookup spans across all crates. - /// - /// N.B., the method does not look into imports, but this is not a problem, - /// since we report the definitions (thus, the de-aliased imports). - pub(crate) fn lookup_import_candidates( - &mut self, - lookup_ident: Ident, - namespace: Namespace, - parent_scope: &ParentScope<'ra>, - filter_fn: FilterFn, - ) -> Vec - where - FilterFn: Fn(Res) -> bool, - { - let crate_path = thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(kw::Crate))]; - let mut suggestions = self.lookup_import_candidates_from_module( - lookup_ident, - namespace, - parent_scope, - self.graph_root.to_module(), - crate_path, - &filter_fn, - ); - - if lookup_ident.span.at_least_rust_2018() { - for (ident, entry) in &self.extern_prelude { - if entry.span().from_expansion() { - // Idents are adjusted to the root context before being - // resolved in the extern prelude, so reporting this to the - // user is no help. This skips the injected - // `extern crate std` in the 2018 edition, which would - // otherwise cause duplicate suggestions. - continue; - } - let Some(crate_id) = - self.cstore_mut().maybe_process_path_extern(self.tcx, ident.name) - else { - continue; - }; - - let crate_def_id = crate_id.as_def_id(); - let crate_root = self.expect_module(crate_def_id); - - // Check if there's already an item in scope with the same name as the crate. - // If so, we have to disambiguate the potential import suggestions by making - // the paths *global* (i.e., by prefixing them with `::`). - let needs_disambiguation = - self.resolutions(parent_scope.module).borrow().iter().any( - |(key, name_resolution)| { - if key.ns == TypeNS - && key.ident == *ident - && let Some(decl) = name_resolution.borrow().best_decl() - { - match decl.res() { - // No disambiguation needed if the identically named item we - // found in scope actually refers to the crate in question. - Res::Def(_, def_id) => def_id != crate_def_id, - Res::PrimTy(_) => true, - _ => false, - } - } else { - false - } - }, - ); - let mut crate_path = ThinVec::new(); - if needs_disambiguation { - crate_path.push(ast::PathSegment::path_root(rustc_span::DUMMY_SP)); - } - crate_path.push(ast::PathSegment::from_ident(ident.orig(entry.span()))); - - suggestions.extend(self.lookup_import_candidates_from_module( - lookup_ident, - namespace, - parent_scope, - crate_root, - crate_path, - &filter_fn, - )); - } - } - - suggestions.retain(|suggestion| suggestion.is_stable || self.tcx.sess.is_nightly_build()); - suggestions - } - - pub(crate) fn unresolved_macro_suggestions( - &mut self, - err: &mut Diag<'_>, - macro_kind: MacroKind, - parent_scope: &ParentScope<'ra>, - ident: Ident, - krate: &Crate, - sugg_span: Option, - ) { - // Bring all unused `derive` macros into `macro_map` so we ensure they can be used for - // suggestions. - self.register_macros_for_all_crates(); - - let is_expected = - &|res: Res| res.macro_kinds().is_some_and(|k| k.contains(macro_kind.into())); - let suggestion = self.early_lookup_typo_candidate( - ScopeSet::Macro(macro_kind), - parent_scope, - ident, - is_expected, - ); - if !self.add_typo_suggestion(err, suggestion, ident.span) { - self.detect_derive_attribute(err, ident, parent_scope, sugg_span); - } - - let import_suggestions = - self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected); - let (span, found_use) = match parent_scope.module.nearest_parent_mod_node_id() { - DUMMY_NODE_ID => (None, FoundUse::No), - node_id => UsePlacementFinder::check(krate, node_id), - }; - show_candidates( - self.tcx, - err, - span, - &import_suggestions, - Instead::No, - found_use, - DiagMode::Normal, - vec![], - "", - ); - - if macro_kind == MacroKind::Bang && ident.name == sym::macro_rules { - let label_span = ident.span.shrink_to_hi(); - let mut spans = MultiSpan::from_span(label_span); - spans.push_span_label(label_span, "put a macro name here"); - err.subdiagnostic(MaybeMissingMacroRulesName { spans }); - return; - } - - if macro_kind == MacroKind::Derive && (ident.name == sym::Send || ident.name == sym::Sync) { - err.subdiagnostic(ExplicitUnsafeTraits { span: ident.span, ident }); - return; - } - - let unused_macro = self.unused_macros.iter().find_map(|(def_id, (_, unused_ident))| { - if unused_ident.name == ident.name { Some((def_id, unused_ident)) } else { None } - }); - - if let Some((def_id, unused_ident)) = unused_macro { - let scope = self.local_macro_def_scopes[&def_id]; - let parent_nearest = parent_scope.module.nearest_parent_mod(); - let unused_macro_kinds = self.local_macro_map[def_id].macro_kinds(); - if !unused_macro_kinds.contains(macro_kind.into()) { - match macro_kind { - MacroKind::Bang => { - err.subdiagnostic(MacroRulesNot::Func { span: unused_ident.span, ident }); - } - MacroKind::Attr => { - err.subdiagnostic(MacroRulesNot::Attr { span: unused_ident.span, ident }); - } - MacroKind::Derive => { - err.subdiagnostic(MacroRulesNot::Derive { span: unused_ident.span, ident }); - } - } - return; - } - if Some(parent_nearest) == scope.opt_def_id() { - err.subdiagnostic(MacroDefinedLater { span: unused_ident.span }); - err.subdiagnostic(MacroSuggMovePosition { span: ident.span, ident }); - return; - } - } - - if ident.name == kw::Default - && let ModuleKind::Def(DefKind::Enum, def_id, _, _) = parent_scope.module.kind - { - let span = self.def_span(def_id); - let source_map = self.tcx.sess.source_map(); - let head_span = source_map.guess_head_span(span); - err.subdiagnostic(ConsiderAddingADerive { - span: head_span.shrink_to_lo(), - suggestion: "#[derive(Default)]\n".to_string(), - }); - } - for ns in [Namespace::MacroNS, Namespace::TypeNS, Namespace::ValueNS] { - let Ok(binding) = self.cm().resolve_ident_in_scope_set( - ident, - ScopeSet::All(ns), - parent_scope, - None, - None, - None, - ) else { - continue; - }; - - let desc = match binding.res() { - Res::Def(DefKind::Macro(MacroKinds::BANG), _) => { - "a function-like macro".to_string() - } - Res::Def(DefKind::Macro(MacroKinds::ATTR), _) | Res::NonMacroAttr(..) => { - format!("an attribute: `#[{ident}]`") - } - Res::Def(DefKind::Macro(MacroKinds::DERIVE), _) => { - format!("a derive macro: `#[derive({ident})]`") - } - Res::Def(DefKind::Macro(kinds), _) => { - format!("{} {}", kinds.article(), kinds.descr()) - } - Res::ToolMod | Res::OpenMod(..) => { - // Don't confuse the user with tool modules or open modules. - continue; - } - Res::Def(DefKind::Trait, _) if macro_kind == MacroKind::Derive => { - "only a trait, without a derive macro".to_string() - } - res => format!( - "{} {}, not {} {}", - res.article(), - res.descr(), - macro_kind.article(), - macro_kind.descr_expected(), - ), - }; - if let crate::DeclKind::Import { import, .. } = binding.kind - && !import.span.is_dummy() - { - let note = diagnostics::IdentImporterHereButItIsDesc { - span: import.span, - imported_ident: ident, - imported_ident_desc: &desc, - }; - err.subdiagnostic(note); - // Silence the 'unused import' warning we might get, - // since this diagnostic already covers that import. - self.record_use(ident, binding, Used::Other); - return; - } - let note = diagnostics::IdentInScopeButItIsDesc { - imported_ident: ident, - imported_ident_desc: &desc, - }; - err.subdiagnostic(note); - return; - } - - if self.macro_names.contains(&IdentKey::new(ident)) { - err.subdiagnostic(AddedMacroUse); - return; - } - } - - /// Given an attribute macro that failed to be resolved, look for `derive` macros that could - /// provide it, either as-is or with small typos. - fn detect_derive_attribute( - &self, - err: &mut Diag<'_>, - ident: Ident, - parent_scope: &ParentScope<'ra>, - sugg_span: Option, - ) { - // Find all of the `derive`s in scope and collect their corresponding declared - // attributes. - // FIXME: this only works if the crate that owns the macro that has the helper_attr - // has already been imported. - let mut derives = vec![]; - let mut all_attrs: UnordMap> = UnordMap::default(); - // We're collecting these in a hashmap, and handle ordering the output further down. - #[allow(rustc::potential_query_instability)] - for (def_id, ext) in self - .local_macro_map - .iter() - .map(|(local_id, ext)| (local_id.to_def_id(), ext)) - .chain(self.extern_macro_map.borrow().iter().map(|(id, d)| (*id, d))) - { - for helper_attr in &ext.helper_attrs { - let item_name = self.tcx.item_name(def_id); - all_attrs.entry(*helper_attr).or_default().push(item_name); - if helper_attr == &ident.name { - derives.push(item_name); - } - } - } - let kind = MacroKind::Derive.descr(); - if !derives.is_empty() { - // We found an exact match for the missing attribute in a `derive` macro. Suggest it. - let mut derives: Vec = derives.into_iter().map(|d| d.to_string()).collect(); - derives.sort(); - derives.dedup(); - let msg = match &derives[..] { - [derive] => format!(" `{derive}`"), - [start @ .., last] => format!( - "s {} and `{last}`", - start.iter().map(|d| format!("`{d}`")).collect::>().join(", ") - ), - [] => unreachable!("we checked for this to be non-empty 10 lines above!?"), - }; - let msg = format!( - "`{}` is an attribute that can be used by the {kind}{msg}, you might be \ - missing a `derive` attribute", - ident.name, - ); - let sugg_span = - if let ModuleKind::Def(DefKind::Enum, id, _, _) = parent_scope.module.kind { - let span = self.def_span(id); - if span.from_expansion() { - None - } else { - // For enum variants sugg_span is empty but we can get the enum's Span. - Some(span.shrink_to_lo()) - } - } else { - // For items this `Span` will be populated, everything else it'll be None. - sugg_span - }; - match sugg_span { - Some(span) => { - err.span_suggestion_verbose( - span, - msg, - format!("#[derive({})]\n", derives.join(", ")), - Applicability::MaybeIncorrect, - ); - } - None => { - err.note(msg); - } - } - } else { - // We didn't find an exact match. Look for close matches. If any, suggest fixing typo. - let all_attr_names = all_attrs.keys().map(|s| *s).into_sorted_stable_ord(); - if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None) - && let Some(macros) = all_attrs.get(&best_match) - { - let mut macros: Vec = macros.into_iter().map(|d| d.to_string()).collect(); - macros.sort(); - macros.dedup(); - let msg = match ¯os[..] { - [] => return, - [name] => format!(" `{name}` accepts"), - [start @ .., end] => format!( - "s {} and `{end}` accept", - start.iter().map(|m| format!("`{m}`")).collect::>().join(", "), - ), - }; - let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute"); - err.span_suggestion_verbose( - ident.span, - msg, - best_match, - Applicability::MaybeIncorrect, - ); - } - } - } - - pub(crate) fn add_typo_suggestion( - &self, - err: &mut Diag<'_>, - suggestion: Option, - span: Span, - ) -> bool { - let suggestion = match suggestion { - None => return false, - // We shouldn't suggest underscore. - Some(suggestion) if suggestion.candidate == kw::Underscore => return false, - Some(suggestion) => suggestion, - }; - - let mut did_label_def_span = false; - - if let Some(def_span) = suggestion.res.opt_def_id().map(|def_id| self.def_span(def_id)) { - if span.overlaps(def_span) { - // Don't suggest typo suggestion for itself like in the following: - // error[E0423]: expected function, tuple struct or tuple variant, found struct `X` - // --> $DIR/unicode-string-literal-syntax-error-64792.rs:4:14 - // | - // LL | struct X {} - // | ----------- `X` defined here - // LL | - // LL | const Y: X = X("ö"); - // | -------------^^^^^^- similarly named constant `Y` defined here - // | - // help: use struct literal syntax instead - // | - // LL | const Y: X = X {}; - // | ^^^^ - // help: a constant with a similar name exists - // | - // LL | const Y: X = Y("ö"); - // | ^ - return false; - } - let span = self.tcx.sess.source_map().guess_head_span(def_span); - let candidate_descr = suggestion.res.descr(); - let candidate = suggestion.candidate; - let label = match suggestion.target { - SuggestionTarget::SimilarlyNamed => { - diagnostics::DefinedHere::SimilarlyNamed { span, candidate_descr, candidate } - } - SuggestionTarget::SingleItem => { - diagnostics::DefinedHere::SingleItem { span, candidate_descr, candidate } - } - }; - did_label_def_span = true; - err.subdiagnostic(label); - } - - let (span, msg, sugg) = if let SuggestionTarget::SimilarlyNamed = suggestion.target - && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && let Some(span) = suggestion.span - && let Some(candidate) = suggestion.candidate.as_str().strip_prefix('_') - && snippet == candidate - { - let candidate = suggestion.candidate; - // When the suggested binding change would be from `x` to `_x`, suggest changing the - // original binding definition instead. (#60164) - let msg = format!( - "the leading underscore in `{candidate}` marks it as unused, consider renaming it to `{snippet}`" - ); - if !did_label_def_span { - err.span_label(span, format!("`{candidate}` defined here")); - } - (span, msg, snippet) - } else { - let msg = match suggestion.target { - SuggestionTarget::SimilarlyNamed => format!( - "{} {} with a similar name exists", - suggestion.res.article(), - suggestion.res.descr() - ), - SuggestionTarget::SingleItem => { - format!("maybe you meant this {}", suggestion.res.descr()) - } - }; - (span, msg, suggestion.candidate.to_ident_string()) - }; - err.span_suggestion_verbose(span, msg, sugg, Applicability::MaybeIncorrect); - true - } - - fn decl_description(&self, b: Decl<'_>, ident: Ident, scope: Scope<'_>) -> String { - let res = b.res(); - if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) { - let (built_in, from) = match scope { - Scope::StdLibPrelude | Scope::MacroUsePrelude => ("", " from prelude"), - Scope::ExternPreludeFlags - if self.tcx.sess.opts.externs.get(ident.as_str()).is_some() - || matches!(res, Res::OpenMod(..)) => - { - ("", " passed with `--extern`") - } - _ => { - if matches!(res, Res::NonMacroAttr(..) | Res::PrimTy(..) | Res::ToolMod) { - // These already contain the "built-in" prefix or look bad with it. - ("", "") - } else { - (" built-in", "") - } - } - }; - - let a = if built_in.is_empty() { res.article() } else { "a" }; - format!("{a}{built_in} {thing}{from}", thing = res.descr()) - } else { - let introduced = if b.is_import_user_facing() { "imported" } else { "defined" }; - format!("the {thing} {introduced} here", thing = res.descr()) - } - } - - fn ambiguity_diagnostic( - &self, - ambiguity_error: &AmbiguityError<'ra>, - ) -> diagnostics::Ambiguity { - let AmbiguityError { kind, ambig_vis, ident, b1, b2, scope1, scope2, .. } = - *ambiguity_error; - let extern_prelude_ambiguity = || { - // Note: b1 may come from a module scope, as an extern crate item in module. - matches!(scope2, Scope::ExternPreludeFlags) - && self - .extern_prelude - .get(&IdentKey::new(ident)) - .is_some_and(|entry| entry.item_decl.map(|(b, ..)| b) == Some(b1)) - }; - let (b1, b2, scope1, scope2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() { - // We have to print the span-less alternative first, otherwise formatting looks bad. - (b2, b1, scope2, scope1, true) - } else { - (b1, b2, scope1, scope2, false) - }; - - let could_refer_to = |b: Decl<'_>, scope: Scope<'ra>, also: &str| { - let what = self.decl_description(b, ident, scope); - let note_msg = format!("`{ident}` could{also} refer to {what}"); - - let thing = b.res().descr(); - let mut help_msgs = Vec::new(); - if b.is_glob_import() - && (kind == AmbiguityKind::GlobVsGlob - || kind == AmbiguityKind::GlobVsExpanded - || kind == AmbiguityKind::GlobVsOuter && swapped != also.is_empty()) - { - help_msgs.push(format!( - "consider adding an explicit import of `{ident}` to disambiguate" - )) - } - if b.is_extern_crate() && ident.span.at_least_rust_2018() && !extern_prelude_ambiguity() - { - help_msgs.push(format!("use `::{ident}` to refer to this {thing} unambiguously")) - } - - if kind != AmbiguityKind::GlobVsGlob { - if let Scope::ModuleNonGlobs(module, _) | Scope::ModuleGlobs(module, _) = scope { - if module == self.graph_root.to_module() { - help_msgs.push(format!( - "use `crate::{ident}` to refer to this {thing} unambiguously" - )); - } else if module.is_normal() { - help_msgs.push(format!( - "use `self::{ident}` to refer to this {thing} unambiguously" - )); - } - } - } - - ( - Spanned { node: note_msg, span: b.span }, - help_msgs - .iter() - .enumerate() - .map(|(i, help_msg)| { - let or = if i == 0 { "" } else { "or " }; - format!("{or}{help_msg}") - }) - .collect::>(), - ) - }; - let (b1_note, b1_help_msgs) = could_refer_to(b1, scope1, ""); - let (b2_note, b2_help_msgs) = could_refer_to(b2, scope2, " also"); - let help = if kind == AmbiguityKind::GlobVsGlob - && b1 - .parent_module - .and_then(|m| m.opt_def_id()) - .map(|d| !d.is_local()) - .unwrap_or_default() - { - Some(&[ - "consider updating this dependency to resolve this error", - "if updating the dependency does not resolve the problem report the problem to the author of the relevant crate", - ] as &[_]) - } else { - None - }; - - let ambig_vis = ambig_vis.map(|(vis1, vis2)| { - format!( - "{} or {}", - vis1.to_string(CRATE_DEF_ID, self.tcx), - vis2.to_string(CRATE_DEF_ID, self.tcx) - ) - }); - - diagnostics::Ambiguity { - ident, - help, - ambig_vis, - kind: kind.descr(), - b1_note, - b1_help_msgs, - b2_note, - b2_help_msgs, - is_error: false, - } - } - - /// If the binding refers to a tuple struct constructor with fields, - /// returns the span of its fields. - fn ctor_fields_span(&self, decl: Decl<'_>) -> Option { - let DeclKind::Def(Res::Def(DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), ctor_def_id)) = - decl.kind - else { - return None; - }; - - let def_id = self.tcx.parent(ctor_def_id); - self.field_idents(def_id)?.iter().map(|&f| f.span).reduce(Span::to) // None for `struct Foo()` - } - - /// Returns the path segments (as symbols) of a module, including `kw::Crate` at the start. - /// For example, for `crate::foo::bar`, returns `[Crate, foo, bar]`. - /// Returns `None` for block modules that don't have a `DefId`. - fn module_path_names(&self, module: Module<'ra>) -> Option> { - let mut path = Vec::new(); - let mut def_id = module.opt_def_id()?; - while let Some(parent) = self.tcx.opt_parent(def_id) { - if let Some(name) = self.tcx.opt_item_name(def_id) { - path.push(name); - } - if parent.is_top_level_module() { - break; - } - def_id = parent; - } - path.reverse(); - path.insert(0, kw::Crate); - Some(path) - } - - /// Shortens a candidate import path to use `super::` (up to 1 level) or `self::` (same module) - /// relative to the current scope, if possible. Only applies to crate-local items and - /// only when the resulting path is actually shorter than the original. - fn shorten_candidate_path( - &self, - suggestion: &mut ImportSuggestion, - current_module: Module<'ra>, - ) { - const MAX_SUPER_PATH_ITEMS_IN_SUGGESTION: usize = 1; - - // Only shorten local items. - if suggestion.did.is_none_or(|did| !did.is_local()) { - return; - } - - // Build current module path: [Crate, foo, bar, ...]. - let Some(current_mod_path) = self.module_path_names(current_module) else { - return; - }; - - // Normalise candidate path: filter out `PathRoot` (`::`), and if the path - // doesn't start with `Crate`, prepend it (edition 2015 paths are relative - // to the crate root without an explicit `crate::` prefix). - let candidate_names = { - let filtered_segments: Vec<_> = suggestion - .path - .segments - .iter() - .filter(|segment| segment.ident.name != kw::PathRoot) - .collect(); - - let mut candidate_names: Vec = - filtered_segments.iter().map(|segment| segment.ident.name).collect(); - if candidate_names.first() != Some(&kw::Crate) { - candidate_names.insert(0, kw::Crate); - } - if candidate_names.len() < 2 { - return; - } - candidate_names - }; - - // The candidate's module path is everything except the last segment (the item name). - let candidate_mod_names = &candidate_names[..candidate_names.len() - 1]; - - // Find the longest common prefix between the current module and candidate module paths. - let common_prefix_length = current_mod_path - .iter() - .zip(candidate_mod_names.iter()) - .take_while(|(current, candidate)| current == candidate) - .count(); - - // Non-crate-local item; keep the full absolute path. - if common_prefix_length == 0 { - return; - } - - let super_count = current_mod_path.len() - common_prefix_length; - - // At the crate root, `use` paths resolve from the crate root anyway, so we can - // drop the `crate::` prefix entirely instead of replacing it with `self::`. - let at_crate_root = current_mod_path.len() == 1; - - let mut new_segments = if super_count == 0 && at_crate_root { - ThinVec::new() - } else { - let prefix_keyword = match super_count { - 0 => kw::SelfLower, - 1..=MAX_SUPER_PATH_ITEMS_IN_SUGGESTION => kw::Super, - _ => return, // Too many `super` levels; keep the full absolute path. - }; - thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(prefix_keyword),)] - }; - for &name in &candidate_names[common_prefix_length..] { - new_segments.push(ast::PathSegment::from_ident(Ident::with_dummy_span(name))); - } - - // Only apply if the result is strictly shorter than the original path. - if new_segments.len() >= suggestion.path.segments.len() { - return; - } - - suggestion.path = Path { span: suggestion.path.span, segments: new_segments, tokens: None }; - } - - fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { - let PrivacyError { - ident, - decl, - outermost_res, - parent_scope, - single_nested, - dedup_span, - ref source, - } = *privacy_error; - - let res = decl.res(); - let ctor_fields_span = self.ctor_fields_span(decl); - let plain_descr = res.descr().to_string(); - let nonimport_descr = - if ctor_fields_span.is_some() { plain_descr + " constructor" } else { plain_descr }; - let import_descr = nonimport_descr.clone() + " import"; - let get_descr = |b: Decl<'_>| if b.is_import() { &import_descr } else { &nonimport_descr }; - - // Print the primary message. - let ident_descr = get_descr(decl); - let mut err = - self.dcx().create_err(diagnostics::IsPrivate { span: ident.span, ident_descr, ident }); - - self.mention_default_field_values(source, ident, &mut err); - - let shown_candidates = if let Some((this_res, outer_ident)) = outermost_res { - let mut import_suggestions = self.lookup_import_candidates( - outer_ident, - this_res.ns().unwrap_or(Namespace::TypeNS), - &parent_scope, - &|res: Res| res == this_res, - ); - // Shorten candidate paths using `super::` or `self::` when possible. - for suggestion in &mut import_suggestions { - self.shorten_candidate_path(suggestion, parent_scope.module); - } - let point_to_def = !show_candidates( - self.tcx, - &mut err, - Some(dedup_span.until(outer_ident.span.shrink_to_hi())), - &import_suggestions, - Instead::Yes, - FoundUse::Yes, - DiagMode::Import { append: single_nested, unresolved_import: false }, - vec![], - "", - ); - // If we suggest importing a public re-export, don't point at the definition. - if point_to_def && ident.span != outer_ident.span { - let label = diagnostics::OuterIdentIsNotPubliclyReexported { - span: outer_ident.span, - outer_ident_descr: this_res.descr(), - outer_ident, - }; - err.subdiagnostic(label); - } - !point_to_def - } else { - false - }; - - let mut non_exhaustive = None; - // If an ADT is foreign and marked as `non_exhaustive`, then that's - // probably why we have the privacy error. - // Otherwise, point out if the struct has any private fields. - if let Some(def_id) = res.opt_def_id() - && !def_id.is_local() - && let Some(attr_span) = find_attr!(self.tcx, def_id, NonExhaustive(span) => *span) - { - non_exhaustive = Some(attr_span); - } else if let Some(span) = ctor_fields_span { - let label = diagnostics::ConstructorPrivateIfAnyFieldPrivate { span }; - err.subdiagnostic(label); - if let Res::Def(_, d) = res - && let Some(fields) = self.field_visibility_spans.get(&d) - { - let spans = fields.iter().map(|span| *span).collect(); - let sugg = diagnostics::ConsiderMakingTheFieldPublic { - spans, - number_of_fields: fields.len(), - }; - err.subdiagnostic(sugg); - } - } - - let mut sugg_paths: Vec<(Vec, bool)> = vec![]; - if let Some(mut def_id) = res.opt_def_id() { - // We can't use `def_path_str` in resolve. - let mut path = vec![def_id]; - while let Some(parent) = self.tcx.opt_parent(def_id) { - def_id = parent; - if !def_id.is_top_level_module() { - path.push(def_id); - } else { - break; - } - } - // We will only suggest importing directly if it is accessible through that path. - let path_names: Option> = path - .iter() - .rev() - .map(|def_id| { - self.tcx.opt_item_name(*def_id).map(|name| { - Ident::with_dummy_span(if def_id.is_top_level_module() { - kw::Crate - } else { - name - }) - }) - }) - .collect(); - if let Some(&def_id) = path.get(0) - && let Some(path) = path_names - { - if let Some(def_id) = def_id.as_local() { - if self.effective_visibilities.is_directly_public(def_id) { - sugg_paths.push((path, false)); - } - } else if self.is_accessible_from(self.tcx.visibility(def_id), parent_scope.module) - { - sugg_paths.push((path, false)); - } - } - } - - // Print the whole import chain to make it easier to see what happens. - let first_binding = decl; - let mut next_binding = Some(decl); - let mut next_ident = ident; - while let Some(binding) = next_binding { - let name = next_ident; - next_binding = match binding.kind { - _ if res == Res::Err => None, - DeclKind::Import { source_decl, import, .. } => match import.kind { - _ if source_decl.span.is_dummy() => None, - ImportKind::Single { source, .. } => { - next_ident = source; - Some(source_decl) - } - ImportKind::Glob { .. } - | ImportKind::MacroUse { .. } - | ImportKind::MacroExport => Some(source_decl), - ImportKind::ExternCrate { .. } => None, - }, - _ => None, - }; - - match binding.kind { - DeclKind::Import { source_decl, import, .. } => { - // Don't include `{{root}}` in suggestions - it's an internal symbol - // that should never be shown to users. - let path = import - .module_path - .iter() - .filter(|seg| seg.ident.name != kw::PathRoot) - .map(|seg| seg.ident.clone()) - .chain(std::iter::once(ident)) - .collect::>(); - let through_reexport = !matches!(source_decl.kind, DeclKind::Def(_)); - sugg_paths.push((path, through_reexport)); - } - DeclKind::Def(_) => {} - } - let first = binding == first_binding; - let def_span = self.tcx.sess.source_map().guess_head_span(binding.span); - let mut note_span = MultiSpan::from_span(def_span); - if !first && binding.vis().is_public() { - let desc = match binding.kind { - DeclKind::Import { .. } => "re-export", - _ => "directly", - }; - note_span.push_span_label(def_span, format!("you could import this {desc}")); - } - // Final step in the import chain, point out if the ADT is `non_exhaustive` - // which is probably why this privacy violation occurred. - if next_binding.is_none() - && let Some(span) = non_exhaustive - { - note_span.push_span_label( - span, - "cannot be constructed because it is `#[non_exhaustive]`", - ); - } - let note = diagnostics::NoteAndRefersToTheItemDefinedHere { - span: note_span, - binding_descr: get_descr(binding), - binding_name: name, - first, - dots: next_binding.is_some(), - }; - err.subdiagnostic(note); - } - // The suggestion replaces `dedup_span` with a path reaching the failing ident. - // That's valid only when - // 1) the failing ident is the imported leaf, otherwise `as` renames and trailing segments - // get dropped, and - // 2) the use isn't nested, otherwise `dedup_span` is one ident in `{...}`. - // - // See issue #156060. - let can_replace_use = !shown_candidates - && !single_nested - && !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span); - if can_replace_use { - // We prioritize shorter paths, non-core imports and direct imports over the - // alternatives. - sugg_paths.sort_by_key(|(p, reexport)| (p.len(), p[0].name == sym::core, *reexport)); - for (sugg, reexport) in sugg_paths { - if sugg.len() <= 1 { - // A single path segment suggestion is wrong. This happens on circular - // imports. `tests/ui/imports/issue-55884-2.rs` - continue; - } - let path = join_path_idents(sugg); - let sugg = if reexport { - diagnostics::ImportIdent::ThroughReExport { span: dedup_span, ident, path } - } else { - diagnostics::ImportIdent::Directly { span: dedup_span, ident, path } - }; - err.subdiagnostic(sugg); - break; - } - } - - err.emit(); - } - - /// When a private field is being set that has a default field value, we suggest using `..` and - /// setting the value of that field implicitly with its default. - /// - /// If we encounter code like - /// ```text - /// struct Priv; - /// pub struct S { - /// pub field: Priv = Priv, - /// } - /// ``` - /// which is used from a place where `Priv` isn't accessible - /// ```text - /// let _ = S { field: m::Priv1 {} }; - /// // ^^^^^ private struct - /// ``` - /// we will suggest instead using the `default_field_values` syntax instead: - /// ```text - /// let _ = S { .. }; - /// ``` - fn mention_default_field_values( - &self, - source: &Option, - ident: Ident, - err: &mut Diag<'_>, - ) { - let Some(expr) = source else { return }; - let ast::ExprKind::Struct(struct_expr) = &expr.kind else { return }; - // We don't have to handle type-relative paths because they're forbidden in ADT - // expressions, but that would change with `#[feature(more_qualified_paths)]`. - let Some(segment) = struct_expr.path.segments.last() else { return }; - let Some(partial_res) = self.partial_res_map.get(&segment.id) else { return }; - let Some(Res::Def(_, def_id)) = partial_res.full_res() else { - return; - }; - let Some(default_fields) = self.field_defaults(def_id) else { return }; - if struct_expr.fields.is_empty() { - return; - } - let last_span = struct_expr.fields.iter().last().unwrap().span; - let mut iter = struct_expr.fields.iter().peekable(); - let mut prev: Option = None; - while let Some(field) = iter.next() { - if field.expr.span.overlaps(ident.span) { - err.span_label(field.ident.span, "while setting this field"); - if default_fields.contains(&field.ident.name) { - let sugg = if last_span == field.span { - vec![(field.span, "..".to_string())] - } else { - vec![ - ( - // Account for trailing commas and ensure we remove them. - match (prev, iter.peek()) { - (_, Some(next)) => field.span.with_hi(next.span.lo()), - (Some(prev), _) => field.span.with_lo(prev.hi()), - (None, None) => field.span, - }, - String::new(), - ), - (last_span.shrink_to_hi(), ", ..".to_string()), - ] - }; - err.multipart_suggestion( - format!( - "the type `{ident}` of field `{}` is private, but you can construct \ - the default value defined for it in `{}` using `..` in the struct \ - initializer expression", - field.ident, - self.tcx.item_name(def_id), - ), - sugg, - Applicability::MachineApplicable, - ); - break; - } - } - prev = Some(field.span); - } - } - - pub(crate) fn find_similarly_named_module_or_crate( - &self, - ident: Symbol, - current_module: Module<'ra>, - ) -> Option { - let mut candidates = self - .extern_prelude - .keys() - .map(|ident| ident.name) - .chain( - self.local_module_map - .iter() - .filter(|(_, module)| { - let module = module.to_module(); - current_module.is_ancestor_of(module) && current_module != module - }) - .flat_map(|(_, module)| module.name()), - ) - .chain( - self.extern_module_map - .borrow() - .iter() - .filter(|(_, module)| { - let module = module.to_module(); - current_module.is_ancestor_of(module) && current_module != module - }) - .flat_map(|(_, module)| module.name()), - ) - .filter(|c| !c.to_string().is_empty()) - .collect::>(); - candidates.sort(); - candidates.dedup(); - find_best_match_for_name(&candidates, ident, None).filter(|sugg| *sugg != ident) - } - - pub(crate) fn report_path_resolution_error( - &mut self, - path: &[Segment], - opt_ns: Option, // `None` indicates a module path in import - parent_scope: &ParentScope<'ra>, - ribs: Option<&PerNS>>>, - ignore_decl: Option>, - ignore_import: Option>, - module: Option>, - failed_segment_idx: usize, - ident: Ident, - diag_metadata: Option<&DiagMetadata<'_>>, - ) -> (String, String, Option) { - let is_last = failed_segment_idx == path.len() - 1; - let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS }; - let module_def_id = match module { - Some(ModuleOrUniformRoot::Module(module)) => module.opt_def_id(), - _ => None, - }; - let scope = match &path[..failed_segment_idx] { - [.., prev] => { - if prev.ident.name == kw::PathRoot { - format!("the crate root") - } else { - format!("`{}`", prev.ident) - } - } - _ => format!("this scope"), - }; - let message = format!("cannot find `{ident}` in {scope}"); - - 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))); - if let Some(candidate) = candidates.get(0) { - let path = { - // remove the possible common prefix of the path - let len = candidate.path.segments.len(); - let start_index = (0..=failed_segment_idx.min(len - 1)) - .find(|&i| path[i].ident.name != candidate.path.segments[i].ident.name) - .unwrap_or_default(); - let segments = - (start_index..len).map(|s| candidate.path.segments[s].clone()).collect(); - Path { segments, span: Span::default(), tokens: None } - }; - ( - message, - String::from("unresolved import"), - Some(( - vec![(ident.span, pprust::path_to_string(&path))], - String::from("a similar path exists"), - Applicability::MaybeIncorrect, - )), - ) - } else if ident.name == sym::core { - ( - message, - format!("you might be missing crate `{ident}`"), - Some(( - vec![(ident.span, "std".to_string())], - "try using `std` instead of `core`".to_string(), - Applicability::MaybeIncorrect, - )), - ) - } else if ident.name == kw::Underscore { - ( - "invalid crate or module name `_`".to_string(), - "`_` is not a valid crate or module name".to_string(), - None, - ) - } else if self.tcx.sess.is_rust_2015() { - ( - format!("cannot find module or crate `{ident}` in {scope}"), - format!("use of unresolved module or unlinked crate `{ident}`"), - Some(( - vec![( - self.current_crate_outer_attr_insert_span, - format!("extern crate {ident};\n"), - )], - if was_invoked_from_cargo() { - format!( - "if you wanted to use a crate named `{ident}`, use `cargo add \ - {ident}` to add it to your `Cargo.toml` and import it in your \ - code", - ) - } else { - format!( - "you might be missing a crate named `{ident}`, add it to your \ - project and import it in your code", - ) - }, - Applicability::MaybeIncorrect, - )), - ) - } else { - (message, format!("could not find `{ident}` in the crate root"), None) - } - } else if failed_segment_idx > 0 { - let parent = path[failed_segment_idx - 1].ident.name; - let parent = match parent { - // ::foo is mounted at the crate root for 2015, and is the extern - // prelude for 2018+ - kw::PathRoot if self.tcx.sess.edition() > Edition::Edition2015 => { - "the list of imported crates".to_owned() - } - kw::PathRoot | kw::Crate => "the crate root".to_owned(), - _ => format!("`{parent}`"), - }; - - let mut msg = format!("could not find `{ident}` in {parent}"); - if ns == TypeNS || ns == ValueNS { - let ns_to_try = if ns == TypeNS { ValueNS } else { TypeNS }; - let binding = if let Some(module) = module { - self.cm() - .resolve_ident_in_module( - module, - ident, - ns_to_try, - parent_scope, - None, - ignore_decl, - ignore_import, - ) - .ok() - } else if let Some(ribs) = ribs - && let Some(TypeNS | ValueNS) = opt_ns - { - assert!(ignore_import.is_none()); - match self.resolve_ident_in_lexical_scope( - ident, - ns_to_try, - parent_scope, - None, - &ribs[ns_to_try], - ignore_decl, - diag_metadata, - ) { - // we found a locally-imported or available item/module - Some(LateDecl::Decl(binding)) => Some(binding), - _ => None, - } - } else { - self.cm() - .resolve_ident_in_scope_set( - ident, - ScopeSet::All(ns_to_try), - parent_scope, - None, - ignore_decl, - ignore_import, - ) - .ok() - }; - if let Some(binding) = binding { - msg = format!( - "expected {}, found {} `{ident}` in {parent}", - ns.descr(), - binding.res().descr(), - ); - }; - } - (message, msg, None) - } else if ident.name == kw::SelfUpper { - // As mentioned above, `opt_ns` being `None` indicates a module path in import. - // We can use this to improve a confusing error for, e.g. `use Self::Variant` in an - // impl - if opt_ns.is_none() { - (message, "`Self` cannot be used in imports".to_string(), None) - } else { - ( - message, - "`Self` is only available in impls, traits, and type definitions".to_string(), - None, - ) - } - } else if ident.name.as_str().chars().next().is_some_and(|c| c.is_ascii_uppercase()) { - // Check whether the name refers to an item in the value namespace. - let binding = if let Some(ribs) = ribs { - assert!(ignore_import.is_none()); - self.resolve_ident_in_lexical_scope( - ident, - ValueNS, - parent_scope, - None, - &ribs[ValueNS], - ignore_decl, - diag_metadata, - ) - } else { - None - }; - let match_span = match binding { - // Name matches a local variable. For example: - // ``` - // fn f() { - // let Foo: &str = ""; - // println!("{}", Foo::Bar); // Name refers to local - // // variable `Foo`. - // } - // ``` - Some(LateDecl::RibDef(Res::Local(id))) => { - Some((*self.pat_span_map.get(&id).unwrap(), "a", "local binding")) - } - // Name matches item from a local name binding - // created by `use` declaration. For example: - // ``` - // pub const Foo: &str = ""; - // - // mod submod { - // use super::Foo; - // println!("{}", Foo::Bar); // Name refers to local - // // binding `Foo`. - // } - // ``` - Some(LateDecl::Decl(name_binding)) => Some(( - name_binding.span, - name_binding.res().article(), - name_binding.res().descr(), - )), - _ => None, - }; - - let message = format!("cannot find type `{ident}` in {scope}"); - let label = if let Some((span, article, descr)) = match_span { - format!( - "`{ident}` is declared as {article} {descr} at `{}`, not a type", - self.tcx - .sess - .source_map() - .span_to_short_string(span, RemapPathScopeComponents::DIAGNOSTICS) - ) - } else { - format!("use of undeclared type `{ident}`") - }; - (message, label, None) - } else { - let mut suggestion = None; - if ident.name == sym::alloc { - suggestion = Some(( - vec![], - String::from("add `extern crate alloc` to use the `alloc` crate"), - Applicability::MaybeIncorrect, - )) - } - - suggestion = suggestion.or_else(|| { - self.find_similarly_named_module_or_crate(ident.name, parent_scope.module).map( - |sugg| { - ( - vec![(ident.span, sugg.to_string())], - String::from("there is a crate or module with a similar name"), - Applicability::MaybeIncorrect, - ) - }, - ) - }); - if let Ok(binding) = self.cm().resolve_ident_in_scope_set( - ident, - ScopeSet::All(ValueNS), - parent_scope, - None, - ignore_decl, - ignore_import, - ) { - let descr = binding.res().descr(); - let message = format!("cannot find module or crate `{ident}` in {scope}"); - (message, format!("{descr} `{ident}` is not a crate or module"), suggestion) - } else { - let suggestion = if suggestion.is_some() { - suggestion - } else if let Some(m) = self.undeclared_module_exists(ident) { - self.undeclared_module_suggest_declare(ident, m) - } else if was_invoked_from_cargo() { - Some(( - vec![], - format!( - "if you wanted to use a crate named `{ident}`, use `cargo add {ident}` \ - to add it to your `Cargo.toml`", - ), - Applicability::MaybeIncorrect, - )) - } else { - Some(( - vec![], - format!("you might be missing a crate named `{ident}`",), - Applicability::MaybeIncorrect, - )) - }; - let message = format!("cannot find module or crate `{ident}` in {scope}"); - ( - message, - format!("use of unresolved module or unlinked crate `{ident}`"), - suggestion, - ) - } - } - } - - fn undeclared_module_suggest_declare( - &self, - ident: Ident, - path: std::path::PathBuf, - ) -> Option<(Vec<(Span, String)>, String, Applicability)> { - Some(( - vec![(self.current_crate_outer_attr_insert_span, format!("mod {ident};\n"))], - format!( - "to make use of source file {}, use `mod {ident}` \ - in this file to declare the module", - path.display() - ), - Applicability::MaybeIncorrect, - )) - } - - fn undeclared_module_exists(&self, ident: Ident) -> Option { - let map = self.tcx.sess.source_map(); - - let src = map.span_to_filename(ident.span).into_local_path()?; - let i = ident.as_str(); - // FIXME: add case where non parent using undeclared module (hard?) - let dir = src.parent()?; - let src = src.file_stem()?.to_str()?; - for file in [ - // …/x.rs - dir.join(i).with_extension("rs"), - // …/x/mod.rs - dir.join(i).join("mod.rs"), - ] { - if file.exists() { - return Some(file); - } - } - if !matches!(src, "main" | "lib" | "mod") { - for file in [ - // …/x/y.rs - dir.join(src).join(i).with_extension("rs"), - // …/x/y/mod.rs - dir.join(src).join(i).join("mod.rs"), - ] { - if file.exists() { - return Some(file); - } - } - } - None - } - - /// Adds suggestions for a path that cannot be resolved. - #[instrument(level = "debug", skip(self, parent_scope))] - pub(crate) fn make_path_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - match path[..] { - // `{{root}}::ident::...` on both editions. - // On 2015 `{{root}}` is usually added implicitly. - [first, second, ..] - if first.ident.name == kw::PathRoot && !second.ident.is_path_segment_keyword() => {} - // `ident::...` on 2018. - [first, ..] - if first.ident.span.at_least_rust_2018() - && !first.ident.is_path_segment_keyword() => - { - // Insert a placeholder that's later replaced by `self`/`super`/etc. - path.insert(0, Segment::from_ident(Ident::dummy())); - } - _ => return None, - } - - self.make_missing_self_suggestion(path.clone(), parent_scope) - .or_else(|| self.make_missing_crate_suggestion(path.clone(), parent_scope)) - .or_else(|| self.make_missing_super_suggestion(path.clone(), parent_scope)) - .or_else(|| self.make_external_crate_suggestion(path, parent_scope)) - } - - /// Suggest a missing `self::` if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foo::Bar; - /// | ^^^ did you mean `self::foo`? - /// ``` - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_missing_self_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - // Replace first ident with `self` and check if that is valid. - path[0].ident.name = kw::SelfLower; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?result); - if let PathResult::Module(..) = result { Some((path, None)) } else { None } - } - - /// Suggests a missing `crate::` if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foo::Bar; - /// | ^^^ did you mean `crate::foo`? - /// ``` - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_missing_crate_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - // Replace first ident with `crate` and check if that is valid. - path[0].ident.name = kw::Crate; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?result); - if let PathResult::Module(..) = result { - Some(( - path, - Some( - "`use` statements changed in Rust 2018; read more at \ - " - .to_string(), - ), - )) - } else { - None - } - } - - /// Suggests a missing `super::` if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foo::Bar; - /// | ^^^ did you mean `super::foo`? - /// ``` - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_missing_super_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - // Replace first ident with `crate` and check if that is valid. - path[0].ident.name = kw::Super; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?result); - if let PathResult::Module(..) = result { Some((path, None)) } else { None } - } - - /// Suggests a missing external crate name if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foobar::Baz; - /// | ^^^^^^ did you mean `baz::foobar`? - /// ``` - /// - /// Used when importing a submodule of an external crate but missing that crate's - /// name as the first part of path. - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_external_crate_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - if path[1].ident.span.is_rust_2015() { - return None; - } - - // Sort extern crate names in *reverse* order to get - // 1) some consistent ordering for emitted diagnostics, and - // 2) `std` suggestions before `core` suggestions. - let mut extern_crate_names = - self.extern_prelude.keys().map(|ident| ident.name).collect::>(); - extern_crate_names.sort_by(|a, b| b.as_str().cmp(a.as_str())); - - for name in extern_crate_names.into_iter() { - // Replace first ident with a crate name and check if that is valid. - path[0].ident.name = name; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?name, ?result); - if let PathResult::Module(..) = result { - return Some((path, None)); - } - } - - None - } - - /// Suggests importing a macro from the root of the crate rather than a module within - /// the crate. - /// - /// ```text - /// help: a macro with this name exists at the root of the crate - /// | - /// LL | use issue_59764::makro; - /// | ^^^^^^^^^^^^^^^^^^ - /// | - /// = note: this could be because a macro annotated with `#[macro_export]` will be exported - /// at the root of the crate instead of the module where it is defined - /// ``` - pub(crate) fn check_for_module_export_macro( - &mut self, - import: Import<'ra>, - module: ModuleOrUniformRoot<'ra>, - ident: Ident, - ) -> Option<(Option, Option)> { - let ModuleOrUniformRoot::Module(mut crate_module) = module else { - return None; - }; - - while let Some(parent) = crate_module.parent { - crate_module = parent; - } - - if module == ModuleOrUniformRoot::Module(crate_module) { - // Don't make a suggestion if the import was already from the root of the crate. - return None; - } - - let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); - let binding = self.resolution(crate_module, binding_key)?.best_decl()?; - let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { - return None; - }; - if !kinds.contains(MacroKinds::BANG) { - return None; - } - let module_name = crate_module.name().unwrap_or(kw::Crate); - let import_snippet = match import.kind { - ImportKind::Single { source, target, .. } if source != target => { - format!("{source} as {target}") - } - _ => format!("{ident}"), - }; - - let mut corrections: Vec<(Span, String)> = Vec::new(); - if !import.is_nested() { - // Assume this is the easy case of `use issue_59764::foo::makro;` and just remove - // intermediate segments. - corrections.push((import.span, format!("{module_name}::{import_snippet}"))); - } else { - // Find the binding span (and any trailing commas and spaces). - // i.e. `use a::b::{c, d, e};` - // ^^^ - let (found_closing_brace, binding_span) = find_span_of_binding_until_next_binding( - self.tcx.sess, - import.span, - import.use_span, - ); - debug!(found_closing_brace, ?binding_span); - - let mut removal_span = binding_span; - - // If the binding span ended with a closing brace, as in the below example: - // i.e. `use a::b::{c, d};` - // ^ - // Then expand the span of characters to remove to include the previous - // binding's trailing comma. - // i.e. `use a::b::{c, d};` - // ^^^ - if found_closing_brace - && let Some(previous_span) = - extend_span_to_previous_binding(self.tcx.sess, binding_span) - { - debug!(?previous_span); - removal_span = removal_span.with_lo(previous_span.lo()); - } - debug!(?removal_span); - - // Remove the `removal_span`. - corrections.push((removal_span, "".to_string())); - - // Find the span after the crate name and if it has nested imports immediately - // after the crate name already. - // i.e. `use a::b::{c, d};` - // ^^^^^^^^^ - // or `use a::{b, c, d}};` - // ^^^^^^^^^^^ - let (has_nested, after_crate_name) = - find_span_immediately_after_crate_name(self.tcx.sess, import.use_span); - debug!(has_nested, ?after_crate_name); - - let source_map = self.tcx.sess.source_map(); - - // Make sure this is actually crate-relative. - let is_definitely_crate = import - .module_path - .first() - .is_some_and(|f| f.ident.name != kw::SelfLower && f.ident.name != kw::Super); - - // Add the import to the start, with a `{` if required. - let start_point = source_map.start_point(after_crate_name); - if is_definitely_crate - && let Ok(start_snippet) = source_map.span_to_snippet(start_point) - { - corrections.push(( - start_point, - if has_nested { - // In this case, `start_snippet` must equal '{'. - format!("{start_snippet}{import_snippet}, ") - } else { - // In this case, add a `{`, then the moved import, then whatever - // was there before. - format!("{{{import_snippet}, {start_snippet}") - }, - )); - - // Add a `};` to the end if nested, matching the `{` added at the start. - if !has_nested { - corrections.push((source_map.end_point(after_crate_name), "};".to_string())); - } - } else { - // If the root import is module-relative, add the import separately - corrections.push(( - import.use_span.shrink_to_lo(), - format!("use {module_name}::{import_snippet};\n"), - )); - } - } - - let suggestion = Some(( - corrections, - String::from("a macro with this name exists at the root of the crate"), - Applicability::MaybeIncorrect, - )); - Some(( - suggestion, - Some( - "this could be because a macro annotated with `#[macro_export]` will be exported \ - at the root of the crate instead of the module where it is defined" - .to_string(), - ), - )) - } - - /// Finds a cfg-ed out item inside `module` with the matching name. - pub(crate) fn find_cfg_stripped(&self, err: &mut Diag<'_>, segment: &Symbol, module: DefId) { - let local_items; - let symbols = if module.is_local() { - local_items = self - .stripped_cfg_items - .iter() - .filter_map(|item| { - let parent_scope = self.local_modules.iter().find_map(|m| match m.kind { - ModuleKind::Def(_, def_id, node_id, _) if node_id == item.parent_scope => { - Some(def_id) - } - _ => None, - })?; - Some(StrippedCfgItem { parent_scope, ident: item.ident, cfg: item.cfg.clone() }) - }) - .collect::>(); - local_items.as_slice() - } else { - self.tcx.stripped_cfg_items(module.krate) - }; - - for &StrippedCfgItem { parent_scope, ident, ref cfg } in symbols { - if ident.name != *segment { - continue; - } - - let parent_module = self.get_nearest_non_block_module(parent_scope).def_id(); - - fn comes_from_same_module_for_glob( - r: &Resolver<'_, '_>, - parent_module: DefId, - module: DefId, - visited: &mut FxHashMap, - ) -> bool { - if let Some(&cached) = visited.get(&parent_module) { - // this branch is prevent from being called recursively infinity, - // because there has some cycles in globs imports, - // see more spec case at `tests/ui/cfg/diagnostics-reexport-2.rs#reexport32` - return cached; - } - visited.insert(parent_module, false); - let mut res = false; - let m = r.expect_module(parent_module); - if m.is_local() { - for importer in m.glob_importers.borrow().iter() { - if let Some(next_parent_module) = importer.parent_scope.module.opt_def_id() - { - if next_parent_module == module - || comes_from_same_module_for_glob( - r, - next_parent_module, - module, - visited, - ) - { - res = true; - break; - } - } - } - } - visited.insert(parent_module, res); - res - } - - let comes_from_same_module = parent_module == module - || comes_from_same_module_for_glob( - self, - parent_module, - module, - &mut Default::default(), - ); - if !comes_from_same_module { - continue; - } - - let item_was = if let CfgEntry::NameValue { value: Some(feature), .. } = cfg.0 { - diagnostics::ItemWas::BehindFeature { feature, span: cfg.1 } - } else { - diagnostics::ItemWas::CfgOut { span: cfg.1 } - }; - let note = diagnostics::FoundItemConfigureOut { span: ident.span, item_was }; - err.subdiagnostic(note); - } - } - - pub(crate) fn struct_ctor(&self, def_id: DefId) -> Option { - match def_id.as_local() { - Some(def_id) => self.struct_ctors.get(&def_id).cloned(), - None => { - self.cstore().ctor_untracked(self.tcx, def_id).map(|(ctor_kind, ctor_def_id)| { - let res = Res::Def(DefKind::Ctor(CtorOf::Struct, ctor_kind), ctor_def_id); - let vis = self.tcx.visibility(ctor_def_id); - let field_visibilities = self - .tcx - .associated_item_def_ids(def_id) - .iter() - .map(|&field_id| self.tcx.visibility(field_id)) - .collect(); - StructCtor { res, vis, field_visibilities } - }) - } - } - } - - /// Gets the `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. - fn on_unknown_data(&self, def_id: DefId) -> Option<&Directive> { - match def_id.as_local() { - Some(local) => Some(self.on_unknown_data.get(&local)?.directive.as_ref()), - None => find_attr!(self.tcx, def_id, OnUnknown{ directive } => directive)?.as_deref(), - } - } -} - -/// Given a `binding_span` of a binding within a use statement: -/// -/// ```ignore (illustrative) -/// use foo::{a, b, c}; -/// // ^ -/// ``` -/// -/// then return the span until the next binding or the end of the statement: -/// -/// ```ignore (illustrative) -/// use foo::{a, b, c}; -/// // ^^^ -/// ``` -fn find_span_of_binding_until_next_binding( - sess: &Session, - binding_span: Span, - use_span: Span, -) -> (bool, Span) { - let source_map = sess.source_map(); - - // Find the span of everything after the binding. - // i.e. `a, e};` or `a};` - let binding_until_end = binding_span.with_hi(use_span.hi()); - - // Find everything after the binding but not including the binding. - // i.e. `, e};` or `};` - let after_binding_until_end = binding_until_end.with_lo(binding_span.hi()); - - // Keep characters in the span until we encounter something that isn't a comma or - // whitespace. - // i.e. `, ` or ``. - // - // Also note whether a closing brace character was encountered. If there - // was, then later go backwards to remove any trailing commas that are left. - let mut found_closing_brace = false; - let after_binding_until_next_binding = - source_map.span_take_while(after_binding_until_end, |&ch| { - if ch == '}' { - found_closing_brace = true; - } - ch == ' ' || ch == ',' - }); - - // Combine the two spans. - // i.e. `a, ` or `a`. - // - // Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };` - let span = binding_span.with_hi(after_binding_until_next_binding.hi()); - - (found_closing_brace, span) -} - -/// Given a `binding_span`, return the span through to the comma or opening brace of the previous -/// binding. -/// -/// ```ignore (illustrative) -/// use foo::a::{a, b, c}; -/// // ^^--- binding span -/// // | -/// // returned span -/// -/// use foo::{a, b, c}; -/// // --- binding span -/// ``` -fn extend_span_to_previous_binding(sess: &Session, binding_span: Span) -> Option { - let source_map = sess.source_map(); - - // `prev_source` will contain all of the source that came before the span. - // Then split based on a command and take the first (i.e. closest to our span) - // snippet. In the example, this is a space. - let prev_source = source_map.span_to_prev_source(binding_span).ok()?; - - let prev_comma = prev_source.rsplit(',').collect::>(); - let prev_starting_brace = prev_source.rsplit('{').collect::>(); - if prev_comma.len() <= 1 || prev_starting_brace.len() <= 1 { - return None; - } - - let prev_comma = prev_comma.first().unwrap(); - let prev_starting_brace = prev_starting_brace.first().unwrap(); - - // If the amount of source code before the comma is greater than - // the amount of source code before the starting brace then we've only - // got one item in the nested item (eg. `issue_52891::{self}`). - if prev_comma.len() > prev_starting_brace.len() { - return None; - } - - Some(binding_span.with_lo(BytePos( - // Take away the number of bytes for the characters we've found and an - // extra for the comma. - binding_span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1, - ))) -} - -/// Given a `use_span` of a binding within a use statement, returns the highlighted span and if -/// it is a nested use tree. -/// -/// ```ignore (illustrative) -/// use foo::a::{b, c}; -/// // ^^^^^^^^^^ -- false -/// -/// use foo::{a, b, c}; -/// // ^^^^^^^^^^ -- true -/// -/// use foo::{a, b::{c, d}}; -/// // ^^^^^^^^^^^^^^^ -- true -/// ``` -#[instrument(level = "debug", skip(sess))] -fn find_span_immediately_after_crate_name(sess: &Session, use_span: Span) -> (bool, Span) { - let source_map = sess.source_map(); - - // Using `use issue_59764::foo::{baz, makro};` as an example throughout.. - let mut num_colons = 0; - // Find second colon.. `use issue_59764:` - let until_second_colon = source_map.span_take_while(use_span, |c| { - if *c == ':' { - num_colons += 1; - } - !matches!(c, ':' if num_colons == 2) - }); - // Find everything after the second colon.. `foo::{baz, makro};` - let from_second_colon = use_span.with_lo(until_second_colon.hi() + BytePos(1)); - - let mut found_a_non_whitespace_character = false; - // Find the first non-whitespace character in `from_second_colon`.. `f` - let after_second_colon = source_map.span_take_while(from_second_colon, |c| { - if found_a_non_whitespace_character { - return false; - } - if !c.is_whitespace() { - found_a_non_whitespace_character = true; - } - true - }); - - // Find the first `{` in from_second_colon.. `foo::{` - let next_left_bracket = source_map.span_through_char(from_second_colon, '{'); - - (next_left_bracket == after_second_colon, from_second_colon) -} - -/// A suggestion has already been emitted, change the wording slightly to clarify that both are -/// independent options. -enum Instead { - Yes, - No, -} - -/// Whether an existing place with an `use` item was found. -enum FoundUse { - Yes, - No, -} - -/// Whether a binding is part of a pattern or a use statement. Used for diagnostics. -pub(crate) enum DiagMode { - Normal, - /// The binding is part of a pattern - Pattern, - /// The binding is part of a use statement - Import { - /// `true` means diagnostics is for unresolved import - unresolved_import: bool, - /// `true` mean add the tips afterward for case `use a::{b,c}`, - /// rather than replacing within. - append: bool, - }, -} - -pub(crate) fn import_candidates( - tcx: TyCtxt<'_>, - err: &mut Diag<'_>, - // This is `None` if all placement locations are inside expansions - use_placement_span: Option, - candidates: &[ImportSuggestion], - mode: DiagMode, - append: &str, -) { - show_candidates( - tcx, - err, - use_placement_span, - candidates, - Instead::Yes, - FoundUse::Yes, - mode, - vec![], - append, - ); -} - -type PathString<'a> = (String, &'a str, Option, &'a Option, bool); - -/// When an entity with a given name is not available in scope, we search for -/// entities with that name in all crates. This method allows outputting the -/// results of this search in a programmer-friendly way. If any entities are -/// found and suggested, returns `true`, otherwise returns `false`. -fn show_candidates( - tcx: TyCtxt<'_>, - err: &mut Diag<'_>, - // This is `None` if all placement locations are inside expansions - use_placement_span: Option, - candidates: &[ImportSuggestion], - instead: Instead, - found_use: FoundUse, - mode: DiagMode, - path: Vec, - append: &str, -) -> bool { - if candidates.is_empty() { - return false; - } - - let mut showed = false; - let mut accessible_path_strings: Vec> = Vec::new(); - let mut inaccessible_path_strings: Vec> = Vec::new(); - - candidates.iter().for_each(|c| { - if c.accessible { - // Don't suggest `#[doc(hidden)]` items from other crates - if c.doc_visible { - accessible_path_strings.push(( - pprust::path_to_string(&c.path), - c.descr, - c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), - &c.note, - c.via_import, - )) - } - } else { - inaccessible_path_strings.push(( - pprust::path_to_string(&c.path), - c.descr, - c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), - &c.note, - c.via_import, - )) - } - }); - - // we want consistent results across executions, but candidates are produced - // by iterating through a hash map, so make sure they are ordered: - for path_strings in [&mut accessible_path_strings, &mut inaccessible_path_strings] { - path_strings.sort_by(|a, b| a.0.cmp(&b.0)); - path_strings.dedup_by(|a, b| a.0 == b.0); - let core_path_strings = - path_strings.extract_if(.., |p| p.0.starts_with("core::")).collect::>(); - let std_path_strings = - path_strings.extract_if(.., |p| p.0.starts_with("std::")).collect::>(); - let foreign_crate_path_strings = - path_strings.extract_if(.., |p| !p.0.starts_with("crate::")).collect::>(); - - // We list the `crate` local paths first. - // Then we list the `std`/`core` paths. - if std_path_strings.len() == core_path_strings.len() { - // Do not list `core::` paths if we are already listing the `std::` ones. - path_strings.extend(std_path_strings); - } else { - path_strings.extend(std_path_strings); - path_strings.extend(core_path_strings); - } - // List all paths from foreign crates last. - path_strings.extend(foreign_crate_path_strings); - } - - if !accessible_path_strings.is_empty() { - let (determiner, kind, s, name, through) = - if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] { - ( - "this", - *descr, - "", - format!(" `{name}`"), - if *via_import { " through its public re-export" } else { "" }, - ) - } else { - // Get the unique item kinds and if there's only one, we use the right kind name - // instead of the more generic "items". - let kinds = accessible_path_strings - .iter() - .map(|(_, descr, _, _, _)| *descr) - .collect::>(); - let kind = if let Some(kind) = kinds.get_only() { kind } else { "item" }; - let s = if kind.ends_with('s') { "es" } else { "s" }; - - ("one of these", kind, s, String::new(), "") - }; - - let instead = if let Instead::Yes = instead { " instead" } else { "" }; - let mut msg = if let DiagMode::Pattern = mode { - format!( - "if you meant to match on {kind}{s}{instead}{name}, use the full path in the \ - pattern", - ) - } else { - format!("consider importing {determiner} {kind}{s}{through}{instead}") - }; - - for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { - err.note(note.clone()); - } - - let append_candidates = |msg: &mut String, accessible_path_strings: Vec>| { - msg.push(':'); - - for candidate in accessible_path_strings { - msg.push('\n'); - msg.push_str(&candidate.0); - } - }; - - if let Some(span) = use_placement_span { - let (add_use, trailing) = match mode { - DiagMode::Pattern => { - err.span_suggestions( - span, - msg, - accessible_path_strings.into_iter().map(|a| a.0), - Applicability::MaybeIncorrect, - ); - return true; - } - DiagMode::Import { .. } => ("", ""), - DiagMode::Normal => ("use ", ";\n"), - }; - for candidate in &mut accessible_path_strings { - // produce an additional newline to separate the new use statement - // from the directly following item. - let additional_newline = if let FoundUse::No = found_use - && let DiagMode::Normal = mode - { - "\n" - } else { - "" - }; - candidate.0 = - format!("{add_use}{}{append}{trailing}{additional_newline}", candidate.0); - } - - match mode { - DiagMode::Import { append: true, .. } => { - append_candidates(&mut msg, accessible_path_strings); - err.span_help(span, msg); - } - _ => { - err.span_suggestions_with_style( - span, - msg, - accessible_path_strings.into_iter().map(|a| a.0), - Applicability::MaybeIncorrect, - SuggestionStyle::ShowAlways, - ); - } - } - - if let [first, .., last] = &path[..] { - let sp = first.ident.span.until(last.ident.span); - // Our suggestion is empty, so make sure the span is not empty (or we'd ICE). - // Can happen for derive-generated spans. - if sp.can_be_used_for_suggestions() && !sp.is_empty() { - err.span_suggestion_verbose( - sp, - format!("if you import `{}`, refer to it directly", last.ident), - "", - Applicability::Unspecified, - ); - } - } - } else { - append_candidates(&mut msg, accessible_path_strings); - err.help(msg); - } - showed = true; - } - if !inaccessible_path_strings.is_empty() - && (!matches!(mode, DiagMode::Import { unresolved_import: false, .. })) - { - let prefix = - if let DiagMode::Pattern = mode { "you might have meant to match on " } else { "" }; - if let [(name, descr, source_span, note, _)] = &inaccessible_path_strings[..] { - let msg = format!( - "{prefix}{descr} `{name}`{} exists but is inaccessible", - if let DiagMode::Pattern = mode { ", which" } else { "" } - ); - - if let Some(source_span) = source_span { - let span = tcx.sess.source_map().guess_head_span(*source_span); - let mut multi_span = MultiSpan::from_span(span); - multi_span.push_span_label(span, "not accessible"); - err.span_note(multi_span, msg); - } else { - err.note(msg); - } - if let Some(note) = (*note).as_deref() { - err.note(note.to_string()); - } - } else { - let descr = inaccessible_path_strings - .iter() - .map(|&(_, descr, _, _, _)| descr) - .all_equal_value() - .unwrap_or("item"); - let plural_descr = - if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") }; - - let mut msg = format!("{prefix}these {plural_descr} exist but are inaccessible"); - let mut has_colon = false; - - let mut spans = Vec::new(); - for (name, _, source_span, _, _) in &inaccessible_path_strings { - if let Some(source_span) = source_span { - let span = tcx.sess.source_map().guess_head_span(*source_span); - spans.push((name, span)); - } else { - if !has_colon { - msg.push(':'); - has_colon = true; - } - msg.push('\n'); - msg.push_str(name); - } - } - - let mut multi_span = MultiSpan::from_spans(spans.iter().map(|(_, sp)| *sp).collect()); - for (name, span) in spans { - multi_span.push_span_label(span, format!("`{name}`: not accessible")); - } - - for note in inaccessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { - err.note(note.clone()); - } - - err.span_note(multi_span, msg); - } - showed = true; - } - showed -} - -#[derive(Debug)] -struct UsePlacementFinder { - target_module: NodeId, - first_legal_span: Option, - first_use_span: Option, -} - -impl UsePlacementFinder { - fn check(krate: &Crate, target_module: NodeId) -> (Option, FoundUse) { - let mut finder = - UsePlacementFinder { target_module, first_legal_span: None, first_use_span: None }; - finder.visit_crate(krate); - if let Some(use_span) = finder.first_use_span { - (Some(use_span), FoundUse::Yes) - } else { - (finder.first_legal_span, FoundUse::No) - } - } -} - -impl<'tcx> Visitor<'tcx> for UsePlacementFinder { - fn visit_crate(&mut self, c: &Crate) { - if self.target_module == CRATE_NODE_ID { - let inject = c.spans.inject_use_span; - if is_span_suitable_for_use_injection(inject) { - self.first_legal_span = Some(inject); - } - self.first_use_span = search_for_any_use_in_items(&c.items); - } else { - visit::walk_crate(self, c); - } - } - - fn visit_item(&mut self, item: &'tcx ast::Item) { - if self.target_module == item.id { - if let ItemKind::Mod(_, _, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind { - let inject = mod_spans.inject_use_span; - if is_span_suitable_for_use_injection(inject) { - self.first_legal_span = Some(inject); - } - self.first_use_span = search_for_any_use_in_items(items); - } - } else { - visit::walk_item(self, item); - } - } -} - -#[derive(Default)] -struct BindingVisitor { - identifiers: Vec, - spans: FxHashMap>, -} - -impl<'tcx> Visitor<'tcx> for BindingVisitor { - fn visit_pat(&mut self, pat: &ast::Pat) { - if let ast::PatKind::Ident(_, ident, _) = pat.kind { - self.identifiers.push(ident.name); - self.spans.entry(ident.name).or_default().push(ident.span); - } - visit::walk_pat(self, pat); - } -} - -fn search_for_any_use_in_items(items: &[Box]) -> Option { - for item in items { - if let ItemKind::Use(..) = item.kind - && is_span_suitable_for_use_injection(item.span) - { - let mut lo = item.span.lo(); - for attr in &item.attrs { - if attr.span.eq_ctxt(item.span) { - lo = std::cmp::min(lo, attr.span.lo()); - } - } - return Some(Span::new(lo, lo, item.span.ctxt(), item.span.parent())); - } - } - None -} - -fn is_span_suitable_for_use_injection(s: Span) -> bool { - // don't suggest placing a use before the prelude - // import or other generated ones - !s.from_expansion() -} - -#[derive(Debug, Clone, Default)] -pub(crate) struct OnUnknownData { - pub(crate) directive: Box, -} - -impl OnUnknownData { - pub(crate) fn from_attrs( - r: &Resolver<'_, '_>, - attrs: &[ast::Attribute], - ) -> Option { - if r.features.diagnostic_on_unknown() - && let Some(Attribute::Parsed(AttributeKind::OnUnknown { directive, .. })) = - AttributeParser::parse_limited( - r.tcx.sess, - attrs, - &[sym::diagnostic, sym::on_unknown], - ) - { - Some(Self { directive: directive? }) - } else { - None - } - } -} 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.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/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;