diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index ad00003af9482..655c5e67744f7 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -319,14 +319,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { PathResult::NonModule(partial_res) => { expected_found_error(partial_res.expect_full_res()) } - PathResult::Failed { label, suggestion, message, segment, .. } => { - Err(VisResolutionError::FailedToResolve( - segment.span, - segment.name, + PathResult::Failed { label, suggestion, help, message, segment, .. } => { + Err(VisResolutionError::FailedToResolve { + span: segment.span, + segment: segment.name, label, suggestion, + help, message, - )) + }) } PathResult::Indeterminate => Err(VisResolutionError::Indeterminate(path.span)), } diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 9fa762c87ef3e..9747e1c7d1e51 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -287,6 +287,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { diag.multipart_suggestion(msg, suggestions, applicability); } + if let Some(help) = err.help { + diag.help(help); + } + if let Some(candidates) = &err.candidates { match &import.kind { ImportKind::Single { nested: false, source, target, .. } => import_candidates( @@ -1147,7 +1151,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { sub_unreachable, }) } - ResolutionError::FailedToResolve { segment, label, suggestion, module, message } => { + ResolutionError::FailedToResolve { + segment, + label, + suggestion, + help, + module, + message, + } => { let mut err = struct_span_code_err!(self.dcx(), span, E0433, "{message}"); err.span_label(span, label); @@ -1159,6 +1170,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { err.multipart_suggestion(msg, suggestions, applicability); } + if let Some(help) = help { + err.help(help); + } + let module = match module { Some(ModuleOrUniformRoot::Module(m)) if let Some(id) = m.opt_def_id() => id, _ => CRATE_DEF_ID.to_def_id(), @@ -1401,17 +1416,24 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { 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::FailedToResolve { + span, + segment, + label, + suggestion, + help, + message, + } => self.into_struct_error( + span, + ResolutionError::FailedToResolve { + segment, + label, + suggestion, + help, + module: None, + message, + }, + ), VisResolutionError::ExpectedFound(span, path_str, res) => { self.dcx().create_err(diagnostics::ExpectedModuleFound { span, res, path_str }) } @@ -2964,7 +2986,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { failed_segment_idx: usize, ident: Ident, diag_metadata: Option<&DiagMetadata<'_>>, - ) -> (String, String, Option) { + ) -> (String, String, Option, 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 { @@ -3010,6 +3032,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { String::from("a similar path exists"), Applicability::MaybeIncorrect, )), + None, ) } else if ident.name == sym::core { ( @@ -3020,14 +3043,45 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { "try using `std` instead of `core`".to_string(), Applicability::MaybeIncorrect, )), + None, ) } else if ident.name == kw::Underscore { ( "invalid crate or module name `_`".to_string(), "`_` is not a valid crate or module name".to_string(), None, + None, ) } else if self.tcx.sess.is_rust_2015() { + let crate_is_available = self.tcx.sess.opts.externs.get(ident.as_str()).is_some(); + let (suggestion_message, help) = if crate_is_available { + let edition_help = format!( + "if you're trying to use a dependency named `{ident}`, upgrade your \ + edition to be able to reference it with a `use` declaration" + ); + ( + "on Rust 2015, `extern crate` is required to specify a dependency on an \ + external crate" + .to_string(), + Some(edition_help), + ) + } else 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", + ), + None, + ) + } else { + ( + format!( + "you might be missing a crate named `{ident}`, add it to your \ + project and import it in your code", + ), + None, + ) + }; ( format!("cannot find module or crate `{ident}` in {scope}"), format!("use of unresolved module or unlinked crate `{ident}`"), @@ -3036,23 +3090,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { 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", - ) + suggestion_message, + if crate_is_available { + Applicability::MachineApplicable } else { - format!( - "you might be missing a crate named `{ident}`, add it to your \ - project and import it in your code", - ) + Applicability::MaybeIncorrect }, - Applicability::MaybeIncorrect, )), + help, ) } else { - (message, format!("could not find `{ident}` in the crate root"), None) + (message, format!("could not find `{ident}` in the crate root"), None, None) } } else if failed_segment_idx > 0 { let parent = path[failed_segment_idx - 1].ident.name; @@ -3118,18 +3166,19 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); }; } - (message, msg, None) + (message, msg, None, 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) + (message, "`Self` cannot be used in imports".to_string(), None, None) } else { ( message, "`Self` is only available in impls, traits, and type definitions".to_string(), None, + None, ) } } else if ident.name.as_str().chars().next().is_some_and(|c| c.is_ascii_uppercase()) { @@ -3191,7 +3240,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } else { format!("use of undeclared type `{ident}`") }; - (message, label, None) + (message, label, None, None) } else { let mut suggestion = None; if ident.name == sym::alloc { @@ -3223,7 +3272,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) { 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) + (message, format!("{descr} `{ident}` is not a crate or module"), suggestion, None) } else { let suggestion = if suggestion.is_some() { suggestion @@ -3241,7 +3290,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } else { Some(( vec![], - format!("you might be missing a crate named `{ident}`",), + format!("you might be missing a crate named `{ident}`"), Applicability::MaybeIncorrect, )) }; @@ -3250,6 +3299,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { message, format!("use of unresolved module or unlinked crate `{ident}`"), suggestion, + None, ) } } @@ -3259,7 +3309,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &self, ident: Ident, path: std::path::PathBuf, - ) -> Option<(Vec<(Span, String)>, String, Applicability)> { + ) -> Option { Some(( vec![(self.current_crate_outer_attr_insert_span, format!("mod {ident};\n"))], format!( diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 2a1b208f94c34..475eb41ee6e46 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1897,6 +1897,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { "this `super` would go above the crate root".to_string(), None, None, + None, ) }, ); @@ -1973,7 +1974,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { "can only be used in path start position".to_string(), ) }; - (message, label, None, None) + (message, label, None, None, None) }, ); } @@ -2130,7 +2131,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } else { None }; - (message, label, None, note) + (message, label, None, note, None) }, ); } @@ -2155,7 +2156,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module_had_parse_errors, module, || { - let (message, label, suggestion) = + let (message, label, suggestion, help) = this.get_mut().report_path_resolution_error( path, opt_ns, @@ -2168,7 +2169,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ident, diag_metadata, ); - (message, label, suggestion, None) + (message, label, suggestion, None, help) }, ); } diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 499f9ea297362..87d5e777941fd 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -373,6 +373,7 @@ pub(crate) struct UnresolvedImportError { pub(crate) label: Option, pub(crate) note: Option, pub(crate) suggestion: Option, + pub(crate) help: Option, pub(crate) candidates: Option>, pub(crate) segment: Option, /// comes from `PathRes::Failed { module }` @@ -994,6 +995,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { label: None, note: None, suggestion: None, + help: None, candidates: None, segment: None, module: None, @@ -1237,6 +1239,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { segment, label, suggestion, + help, module, error_implied_by_parse_error: _, message, @@ -1252,6 +1255,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { segment: segment.name, label, suggestion, + help, module, message, }, @@ -1264,6 +1268,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { span, label, suggestion, + help, module, segment, note, @@ -1290,6 +1295,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { String::from("a similar path exists"), Applicability::MaybeIncorrect, )), + help: None, candidates: None, segment: Some(segment), module, @@ -1300,6 +1306,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { label: Some(label), note, suggestion, + help, candidates: None, segment: Some(segment), module, @@ -1341,6 +1348,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { label: Some(String::from("cannot glob-import a module into itself")), note: None, suggestion: None, + help: None, candidates: None, segment: None, module: None, @@ -1579,6 +1587,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { label: Some(label), note, suggestion, + help: None, candidates: if !parent_suggestion.is_empty() { Some(parent_suggestion) } else { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index c21d3653a13be..3db8c7a496c21 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -4991,6 +4991,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { span, label, suggestion, + help, module, segment, error_implied_by_parse_error: _, @@ -5003,6 +5004,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { segment: segment.name, label, suggestion, + help, module, message, }, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index b7e57ad8ec37e..bdd8f3b457edb 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -272,6 +272,7 @@ enum ResolutionError<'ra> { segment: Symbol, label: String, suggestion: Option, + help: Option, module: Option>, message: String, }, @@ -337,7 +338,14 @@ enum ResolutionError<'ra> { enum VisResolutionError { Relative2018(Span, ast::Path), AncestorOnly(Span), - FailedToResolve(Span, Symbol, String, Option, String), + FailedToResolve { + span: Span, + segment: Symbol, + label: String, + suggestion: Option, + help: Option, + message: String, + }, ExpectedFound(Span, String, Res), Indeterminate(Span), ModuleOnly(Span), @@ -457,6 +465,7 @@ enum PathResult<'ra> { span: Span, label: String, suggestion: Option, + help: Option, is_error_from_last_segment: bool, /// The final module being resolved, for instance: /// @@ -492,19 +501,21 @@ impl<'ra> PathResult<'ra> { String, Option, Option, + Option, ), ) -> PathResult<'ra> { - let (message, label, suggestion, note) = if finalize { + let (message, label, suggestion, note, help) = if finalize { label_and_suggestion_and_note() } else { // FIXME: this output isn't actually present in the test suite. - (format!("cannot find `{ident}` in this scope"), String::new(), None, None) + (format!("cannot find `{ident}` in this scope"), String::new(), None, None, None) }; PathResult::Failed { span: ident.span, segment: ident, label, suggestion, + help, is_error_from_last_segment, module, error_implied_by_parse_error, diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 9747ebd4a8af8..4e08d58087aef 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -932,8 +932,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ), path_res @ (PathResult::NonModule(..) | PathResult::Failed { .. }) => { let mut suggestion = None; - let (span, message, label, module, segment) = match path_res { - PathResult::Failed { span, label, module, segment, message, .. } => { + let (span, message, label, module, segment, help) = match path_res { + PathResult::Failed { + span, label, module, segment, message, help, .. + } => { // try to suggest if it's not a macro, maybe a function if let PathResult::NonModule(partial_res) = self .cm() @@ -952,7 +954,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Applicability::MaybeIncorrect, )); } - (span, message, label, module, segment.name) + (span, message, label, module, segment.name, help) } PathResult::NonModule(partial_res) => { let found_an = partial_res.base_res().article(); @@ -986,6 +988,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }, None, path.last().map(|segment| segment.ident.name).unwrap(), + None, ) } _ => unreachable!(), @@ -996,6 +999,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { segment, label, suggestion, + help, module, message, }, diff --git a/tests/ui/editions/auxiliary/missing-extern-crate-suggestion-issue-160026.rs b/tests/ui/editions/auxiliary/missing-extern-crate-suggestion-issue-160026.rs new file mode 100644 index 0000000000000..666df22e16979 --- /dev/null +++ b/tests/ui/editions/auxiliary/missing-extern-crate-suggestion-issue-160026.rs @@ -0,0 +1,3 @@ +// Auxiliary crate that is available through `--extern` but not imported in Rust 2015. + +pub fn item() {} diff --git a/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.fixed b/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.fixed new file mode 100644 index 0000000000000..16d90ae7520b1 --- /dev/null +++ b/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.fixed @@ -0,0 +1,12 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/160026. +//! Rust 2015 should explain when an available crate only needs `extern crate`. + +//@ edition: 2015 +//@ aux-crate: dep=missing-extern-crate-suggestion-issue-160026.rs +//@ run-rustfix + +extern crate dep; +pub use dep::item; +//~^ ERROR unresolved import `dep` + +fn main() {} diff --git a/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.rs b/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.rs new file mode 100644 index 0000000000000..e0bb1267b65e9 --- /dev/null +++ b/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.rs @@ -0,0 +1,11 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/160026. +//! Rust 2015 should explain when an available crate only needs `extern crate`. + +//@ edition: 2015 +//@ aux-crate: dep=missing-extern-crate-suggestion-issue-160026.rs +//@ run-rustfix + +pub use dep::item; +//~^ ERROR unresolved import `dep` + +fn main() {} diff --git a/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.stderr b/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.stderr new file mode 100644 index 0000000000000..3509f06f5b210 --- /dev/null +++ b/tests/ui/editions/missing-extern-crate-suggestion-issue-160026.stderr @@ -0,0 +1,15 @@ +error[E0432]: unresolved import `dep` + --> $DIR/missing-extern-crate-suggestion-issue-160026.rs:8:9 + | +LL | pub use dep::item; + | ^^^ use of unresolved module or unlinked crate `dep` + | + = help: if you're trying to use a dependency named `dep`, upgrade your edition to be able to reference it with a `use` declaration +help: on Rust 2015, `extern crate` is required to specify a dependency on an external crate + | +LL + extern crate dep; + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/resolve/extern-prelude-fail.stderr b/tests/ui/resolve/extern-prelude-fail.stderr index a6192051e95a6..40778dd157668 100644 --- a/tests/ui/resolve/extern-prelude-fail.stderr +++ b/tests/ui/resolve/extern-prelude-fail.stderr @@ -4,7 +4,8 @@ error[E0432]: unresolved import `extern_prelude` LL | use extern_prelude::S; | ^^^^^^^^^^^^^^ use of unresolved module or unlinked crate `extern_prelude` | -help: you might be missing a crate named `extern_prelude`, add it to your project and import it in your code + = help: if you're trying to use a dependency named `extern_prelude`, upgrade your edition to be able to reference it with a `use` declaration +help: on Rust 2015, `extern crate` is required to specify a dependency on an external crate | LL + extern crate extern_prelude; | @@ -15,7 +16,8 @@ error[E0433]: cannot find module or crate `extern_prelude` in the crate root LL | let s = ::extern_prelude::S; | ^^^^^^^^^^^^^^ use of unresolved module or unlinked crate `extern_prelude` | -help: you might be missing a crate named `extern_prelude`, add it to your project and import it in your code + = help: if you're trying to use a dependency named `extern_prelude`, upgrade your edition to be able to reference it with a `use` declaration +help: on Rust 2015, `extern crate` is required to specify a dependency on an external crate | LL + extern crate extern_prelude; |