diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 98982240af2..9a3eac2f866 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -70,6 +70,7 @@ impl TypoSuggestion { } /// A free importable items suggested in case of resolution failure. +#[derive(Debug, Clone)] pub(crate) struct ImportSuggestion { pub did: Option, pub descr: &'static str, @@ -139,6 +140,7 @@ impl<'a> Resolver<'a> { if instead { Instead::Yes } else { Instead::No }, found_use, IsPattern::No, + IsImport::No, path, ); err.emit(); @@ -698,6 +700,7 @@ impl<'a> Resolver<'a> { Instead::No, FoundUse::Yes, IsPattern::Yes, + IsImport::No, vec![], ); } @@ -1481,6 +1484,7 @@ impl<'a> Resolver<'a> { Instead::No, FoundUse::Yes, IsPattern::No, + IsImport::No, vec![], ); @@ -2449,6 +2453,34 @@ enum IsPattern { No, } +/// Whether a binding is part of a use statement. Used for diagnostics. +enum IsImport { + Yes, + No, +} + +pub(crate) fn import_candidates( + session: &Session, + source_span: &IndexVec, + err: &mut Diagnostic, + // This is `None` if all placement locations are inside expansions + use_placement_span: Option, + candidates: &[ImportSuggestion], +) { + show_candidates( + session, + source_span, + err, + use_placement_span, + candidates, + Instead::Yes, + FoundUse::Yes, + IsPattern::No, + IsImport::Yes, + vec![], + ); +} + /// 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 @@ -2462,6 +2494,7 @@ fn show_candidates( instead: Instead, found_use: FoundUse, is_pattern: IsPattern, + is_import: IsImport, path: Vec, ) { if candidates.is_empty() { @@ -2521,7 +2554,8 @@ fn show_candidates( // produce an additional newline to separate the new use statement // from the directly following item. let additional_newline = if let FoundUse::Yes = found_use { "" } else { "\n" }; - candidate.0 = format!("use {};\n{}", &candidate.0, additional_newline); + let add_use = if let IsImport::Yes = is_import { "" } else { "use " }; + candidate.0 = format!("{}{};\n{}", add_use, &candidate.0, additional_newline); } err.span_suggestions( @@ -2551,7 +2585,7 @@ fn show_candidates( err.note(&msg); } - } else { + } else if matches!(is_import, IsImport::No) { assert!(!inaccessible_path_strings.is_empty()); let prefix = diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 5bdb4274781..9e2234ae4a5 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1,9 +1,9 @@ //! A bunch of methods and structures more or less related to resolving imports. -use crate::diagnostics::Suggestion; +use crate::diagnostics::{import_candidates, Suggestion}; use crate::Determinacy::{self, *}; use crate::Namespace::{self, *}; -use crate::{module_to_string, names_to_string}; +use crate::{module_to_string, names_to_string, ImportSuggestion}; use crate::{AmbiguityKind, BindingKey, ModuleKind, ResolutionError, Resolver, Segment}; use crate::{Finalize, Module, ModuleOrUniformRoot, ParentScope, PerNS, ScopeSet}; use crate::{NameBinding, NameBindingKind, PathResult}; @@ -406,6 +406,7 @@ struct UnresolvedImportError { label: Option, note: Option, suggestion: Option, + candidate: Option>, } pub struct ImportResolver<'a, 'b> { @@ -497,6 +498,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> { label: None, note: None, suggestion: None, + candidate: None, }; if path.contains("::") { errors.push((path, err)) @@ -547,6 +549,16 @@ impl<'a, 'b> ImportResolver<'a, 'b> { } diag.multipart_suggestion(&msg, suggestions, applicability); } + + if let Some(candidate) = &err.candidate { + import_candidates( + self.r.session, + &self.r.source_span, + &mut diag, + Some(err.span), + &candidate, + ) + } } diag.emit(); @@ -664,6 +676,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> { Some(finalize), ignore_binding, ); + let no_ambiguity = self.r.ambiguity_errors.len() == prev_ambiguity_errors_len; import.vis.set(orig_vis); let module = match path_res { @@ -706,12 +719,14 @@ impl<'a, 'b> ImportResolver<'a, 'b> { String::from("a similar path exists"), Applicability::MaybeIncorrect, )), + candidate: None, }, None => UnresolvedImportError { span, label: Some(label), note: None, suggestion, + candidate: None, }, }; return Some(err); @@ -754,6 +769,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> { label: Some(String::from("cannot glob-import a module into itself")), note: None, suggestion: None, + candidate: None, }); } } @@ -919,11 +935,19 @@ impl<'a, 'b> ImportResolver<'a, 'b> { } }; + let parent_suggestion = + self.r.lookup_import_candidates(ident, TypeNS, &import.parent_scope, |_| true); + Some(UnresolvedImportError { span: import.span, label: Some(label), note, suggestion, + candidate: if !parent_suggestion.is_empty() { + Some(parent_suggestion) + } else { + None + }, }) } else { // `resolve_ident_in_module` reported a privacy error. diff --git a/src/test/ui/extenv/issue-55897.stderr b/src/test/ui/extenv/issue-55897.stderr index e2afe6f34c1..63797d4a71b 100644 --- a/src/test/ui/extenv/issue-55897.stderr +++ b/src/test/ui/extenv/issue-55897.stderr @@ -26,6 +26,11 @@ error[E0432]: unresolved import `env` | LL | use env; | ^^^ no `env` in the root + | +help: consider importing this module instead + | +LL | use std::env; + | ~~~~~~~~~ error: cannot determine resolution for the macro `env` --> $DIR/issue-55897.rs:6:22 diff --git a/src/test/ui/imports/issue-56125.stderr b/src/test/ui/imports/issue-56125.stderr index 2e4ba862376..059ca96808d 100644 --- a/src/test/ui/imports/issue-56125.stderr +++ b/src/test/ui/imports/issue-56125.stderr @@ -3,6 +3,18 @@ error[E0432]: unresolved import `empty::issue_56125` | LL | use empty::issue_56125; | ^^^^^^^^^^^^^^^^^^ no `issue_56125` in `m3::empty` + | +help: consider importing one of these items instead + | +LL | use crate::m3::last_segment::issue_56125; + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LL | use crate::m3::non_last_segment::non_last_segment::issue_56125; + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LL | use issue_56125::issue_56125; + | ~~~~~~~~~~~~~~~~~~~~~~~~~ +LL | use issue_56125::last_segment::issue_56125; + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + and 1 other candidate error[E0659]: `issue_56125` is ambiguous --> $DIR/issue-56125.rs:6:9 diff --git a/src/test/ui/imports/issue-57015.stderr b/src/test/ui/imports/issue-57015.stderr index d200d23ab28..3b72d57fee4 100644 --- a/src/test/ui/imports/issue-57015.stderr +++ b/src/test/ui/imports/issue-57015.stderr @@ -3,6 +3,11 @@ error[E0432]: unresolved import `single_err::something` | LL | use single_err::something; | ^^^^^^^^^^^^^^^^^^^^^ no `something` in `single_err` + | +help: consider importing this module instead + | +LL | use glob_ok::something; + | ~~~~~~~~~~~~~~~~~~~ error: aborting due to previous error diff --git a/src/test/ui/rfc-2126-extern-absolute-paths/not-allowed.stderr b/src/test/ui/rfc-2126-extern-absolute-paths/not-allowed.stderr index a66330ccc46..761089cd387 100644 --- a/src/test/ui/rfc-2126-extern-absolute-paths/not-allowed.stderr +++ b/src/test/ui/rfc-2126-extern-absolute-paths/not-allowed.stderr @@ -3,6 +3,13 @@ error[E0432]: unresolved import `alloc` | LL | use alloc; | ^^^^^ no external crate `alloc` + | +help: consider importing one of these items instead + | +LL | use core::alloc; + | ~~~~~~~~~~~~ +LL | use std::alloc; + | ~~~~~~~~~~~ error: aborting due to previous error diff --git a/src/test/ui/simd/portable-intrinsics-arent-exposed.stderr b/src/test/ui/simd/portable-intrinsics-arent-exposed.stderr index 870f4064de4..8881ede0dbc 100644 --- a/src/test/ui/simd/portable-intrinsics-arent-exposed.stderr +++ b/src/test/ui/simd/portable-intrinsics-arent-exposed.stderr @@ -11,6 +11,11 @@ error[E0432]: unresolved import `std::simd::intrinsics` | LL | use std::simd::intrinsics; | ^^^^^^^^^^^^^^^^^^^^^ no `intrinsics` in `simd` + | +help: consider importing this module instead + | +LL | use std::intrinsics; + | ~~~~~~~~~~~~~~~~ error: aborting due to 2 previous errors diff --git a/src/test/ui/test-attrs/inaccessible-test-modules.stderr b/src/test/ui/test-attrs/inaccessible-test-modules.stderr index a94ea1e79bc..0c16ecd4c86 100644 --- a/src/test/ui/test-attrs/inaccessible-test-modules.stderr +++ b/src/test/ui/test-attrs/inaccessible-test-modules.stderr @@ -11,10 +11,16 @@ error[E0432]: unresolved import `test` --> $DIR/inaccessible-test-modules.rs:6:5 | LL | use test as y; - | ----^^^^^ - | | - | no `test` in the root - | help: a similar name exists in the module: `test` + | ^^^^^^^^^ no `test` in the root + | +help: a similar name exists in the module + | +LL | use test as y; + | ~~~~ +help: consider importing this module instead + | +LL | use test::test; + | ~~~~~~~~~~~ error: aborting due to 2 previous errors diff --git a/src/test/ui/unresolved/unresolved-candidates.rs b/src/test/ui/unresolved/unresolved-candidates.rs new file mode 100644 index 00000000000..38b227f609b --- /dev/null +++ b/src/test/ui/unresolved/unresolved-candidates.rs @@ -0,0 +1,13 @@ +mod a { + pub trait Trait {} +} + +mod b { + use Trait; //~ ERROR unresolved import `Trait` +} + +mod c { + impl Trait for () {} //~ ERROR cannot find trait `Trait` in this scope +} + +fn main() {} diff --git a/src/test/ui/unresolved/unresolved-candidates.stderr b/src/test/ui/unresolved/unresolved-candidates.stderr new file mode 100644 index 00000000000..bbd3eec2a54 --- /dev/null +++ b/src/test/ui/unresolved/unresolved-candidates.stderr @@ -0,0 +1,26 @@ +error[E0432]: unresolved import `Trait` + --> $DIR/unresolved-candidates.rs:6:9 + | +LL | use Trait; + | ^^^^^ no `Trait` in the root + | +help: consider importing this trait instead + | +LL | use a::Trait; + | ~~~~~~~~~ + +error[E0405]: cannot find trait `Trait` in this scope + --> $DIR/unresolved-candidates.rs:10:10 + | +LL | impl Trait for () {} + | ^^^^^ not found in this scope + | +help: consider importing this trait + | +LL | use a::Trait; + | + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0405, E0432. +For more information about an error, try `rustc --explain E0405`.