From 7bfc78162c77f9c0860f97cac169e8bcd6a148da Mon Sep 17 00:00:00 2001 From: Raushan kumar Date: Fri, 26 Jun 2026 09:37:12 +0000 Subject: [PATCH 1/2] test(imports): add baseline test for E0603 grouped import suggestion --- ...rivate-import-grouped-suggestion-157453.rs | 14 +++++++++++++ ...te-import-grouped-suggestion-157453.stderr | 20 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/ui/imports/private-import-grouped-suggestion-157453.rs create mode 100644 tests/ui/imports/private-import-grouped-suggestion-157453.stderr diff --git a/tests/ui/imports/private-import-grouped-suggestion-157453.rs b/tests/ui/imports/private-import-grouped-suggestion-157453.rs new file mode 100644 index 0000000000000..8e4585b21abb2 --- /dev/null +++ b/tests/ui/imports/private-import-grouped-suggestion-157453.rs @@ -0,0 +1,14 @@ +mod one { + pub struct One(); +} + +mod two { + use crate::one::One; + pub struct Two(); +} + +mod test_grouped { + use crate::two::{One, Two}; //~ ERROR struct import `One` is private [E0603] +} + +fn main() {} diff --git a/tests/ui/imports/private-import-grouped-suggestion-157453.stderr b/tests/ui/imports/private-import-grouped-suggestion-157453.stderr new file mode 100644 index 0000000000000..a268ed5c65447 --- /dev/null +++ b/tests/ui/imports/private-import-grouped-suggestion-157453.stderr @@ -0,0 +1,20 @@ +error[E0603]: struct import `One` is private + --> $DIR/private-import-grouped-suggestion-157453.rs:11:22 + | +LL | use crate::two::{One, Two}; + | ^^^ private struct import + | +note: the struct import `One` is defined here... + --> $DIR/private-import-grouped-suggestion-157453.rs:6:9 + | +LL | use crate::one::One; + | ^^^^^^^^^^^^^^^ +note: ...and refers to the struct `One` which is defined here + --> $DIR/private-import-grouped-suggestion-157453.rs:2:5 + | +LL | pub struct One(); + | ^^^^^^^^^^^^^^^^^ you could import this directly + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0603`. From 601c41c129a029e5938e43656edad33c6c0dc0f9 Mon Sep 17 00:00:00 2001 From: Raushan kumar Date: Sat, 25 Jul 2026 12:49:53 +0000 Subject: [PATCH 2/2] fix(resolve): improve E0603 suggestions for grouped imports Suggest direct imports for private items inside grouped imports. Split grouped imports when necessary and replace single-item groups with direct imports. --- .../rustc_resolve/src/diagnostics/impls.rs | 81 +++++++++++++++---- compiler/rustc_resolve/src/ident.rs | 2 +- compiler/rustc_resolve/src/lib.rs | 7 +- ...rivate-import-grouped-suggestion-157453.rs | 18 +++++ ...te-import-grouped-suggestion-157453.stderr | 51 +++++++++++- ...ate-import-nested-suggestion-156060.stderr | 5 ++ ...suggestion-path-156244.edition_2015.stderr | 10 +++ ...suggestion-path-156244.edition_2018.stderr | 10 +++ 8 files changed, 163 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 2ea2f52c696ba..bda35698cbdde 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -2604,11 +2604,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { decl, outermost_res, parent_scope, - single_nested, + root_span, dedup_span, ref source, } = *privacy_error; + let single_nested = dedup_span != root_span; + let res = decl.res(); let ctor_fields_span = self.ctor_fields_span(decl); let plain_descr = res.descr().to_string(); @@ -2854,19 +2856,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; 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 + // We only offer this suggestion when no other candidates have already been shown to the user. + let can_suggest = !shown_candidates + // Don't suggest if the outermost resolution points somewhere else already. && !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. + + if can_suggest { + // 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 { @@ -2875,12 +2871,63 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { continue; } let path = join_path_idents(sugg); - let sugg = if reexport { - diagnostics::ImportIdent::ThroughReExport { span: dedup_span, ident, path } + + // Replacing `dedup_span` assumes `ident` is the leaf, not an `as`-renamed binding. + if !single_nested { + 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; + } + + // For a grouped import, suggest a standalone `use` for the correct path + // and remove the failing item from the existing group. + let (found_closing_brace, span_to_remove) = + find_span_of_binding_until_next_binding(self.tcx.sess, ident.span, root_span); + + let msg = if reexport { + format!("import `{ident}` through the re-export") } else { - diagnostics::ImportIdent::Directly { span: dedup_span, ident, path } + format!("import `{ident}` directly") }; - err.subdiagnostic(sugg); + + let span_to_remove = if found_closing_brace { + match extend_span_to_previous_binding(self.tcx.sess, span_to_remove) { + Some(prev) => prev, + None => { + // Replace the entire statement rather than leaving an empty group. + err.multipart_suggestion( + msg, + vec![(root_span, format!("{path}"))], + Applicability::MachineApplicable, + ); + break; + } + } + } else { + span_to_remove + }; + + let indentation = + self.tcx.sess.source_map().indentation_before(root_span).unwrap_or_default(); + + // We intentionally insert at `root_span.shrink_to_lo()` instead of a line-level + // span. This preserves formatting and surrounding tokens if the `use` statement + // is on the same line as other items (e.g. `{ use foo::{bar, baz}; }`). + let spans = vec![ + (root_span.shrink_to_lo(), format!("{path};\n{indentation}use ")), + (span_to_remove, String::new()), + ]; + + // Braces are left in place intentionally (e.g. `use foo::{Bar};`). `rustfmt` will + // normalize them to `use foo::Bar;` later, and computing byte offsets to strip + // them here risks ICEs on multibyte source. + + // Insert before `root_span` to reuse the existing `use`. + err.multipart_suggestion(msg, spans, Applicability::MachineApplicable); break; } } diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index d743093aae92f..40f529ee0f63d 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1378,10 +1378,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ident, decl: binding, dedup_span: path_span, + root_span, outermost_res: None, source: None, parent_scope: *parent_scope, - single_nested: path_span != root_span, }); } else { return Err(ControlFlow::Break(Determined)); diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 0a96becf993e9..5cace430adc01 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1057,11 +1057,14 @@ impl<'ra> DeclKind<'ra> { struct PrivacyError<'ra> { ident: Ident, decl: Decl<'ra>, + /// Span of the specific item being imported (e.g. `bar` in `use foo::{bar, baz}`). + /// Used for deduplication and single-item suggestion replacement. dedup_span: Span, + /// Span of the entire `use` path, excluding the leading `use` keyword. + /// Needed for grouped-import suggestions. + root_span: Span, outermost_res: Option<(Res, Ident)>, parent_scope: ParentScope<'ra>, - /// Is the format `use a::{b,c}`? - single_nested: bool, source: Option, } diff --git a/tests/ui/imports/private-import-grouped-suggestion-157453.rs b/tests/ui/imports/private-import-grouped-suggestion-157453.rs index 8e4585b21abb2..24a1f1b9aca28 100644 --- a/tests/ui/imports/private-import-grouped-suggestion-157453.rs +++ b/tests/ui/imports/private-import-grouped-suggestion-157453.rs @@ -11,4 +11,22 @@ mod test_grouped { use crate::two::{One, Two}; //~ ERROR struct import `One` is private [E0603] } +mod test_single_item { + use crate::two::{One}; //~ ERROR struct import `One` is private [E0603] +} + +mod outer { + pub mod inner { + pub struct MyPath; + } +} + +mod reexport { + use crate::outer::inner::MyPath; +} + +mod test_std_style { + use crate::reexport::{MyPath}; //~ ERROR struct import `MyPath` is private [E0603] +} + fn main() {} diff --git a/tests/ui/imports/private-import-grouped-suggestion-157453.stderr b/tests/ui/imports/private-import-grouped-suggestion-157453.stderr index a268ed5c65447..2ab39c3f76ce9 100644 --- a/tests/ui/imports/private-import-grouped-suggestion-157453.stderr +++ b/tests/ui/imports/private-import-grouped-suggestion-157453.stderr @@ -14,7 +14,56 @@ note: ...and refers to the struct `One` which is defined here | LL | pub struct One(); | ^^^^^^^^^^^^^^^^^ you could import this directly +help: import `One` directly + | +LL ~ use crate::one::One; +LL ~ use crate::two::{Two}; + | + +error[E0603]: struct import `One` is private + --> $DIR/private-import-grouped-suggestion-157453.rs:15:22 + | +LL | use crate::two::{One}; + | ^^^ private struct import + | +note: the struct import `One` is defined here... + --> $DIR/private-import-grouped-suggestion-157453.rs:6:9 + | +LL | use crate::one::One; + | ^^^^^^^^^^^^^^^ +note: ...and refers to the struct `One` which is defined here + --> $DIR/private-import-grouped-suggestion-157453.rs:2:5 + | +LL | pub struct One(); + | ^^^^^^^^^^^^^^^^^ you could import this directly +help: import `One` directly + | +LL - use crate::two::{One}; +LL + use crate::one::One; + | + +error[E0603]: struct import `MyPath` is private + --> $DIR/private-import-grouped-suggestion-157453.rs:29:27 + | +LL | use crate::reexport::{MyPath}; + | ^^^^^^ private struct import + | +note: the struct import `MyPath` is defined here... + --> $DIR/private-import-grouped-suggestion-157453.rs:25:9 + | +LL | use crate::outer::inner::MyPath; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: ...and refers to the struct `MyPath` which is defined here + --> $DIR/private-import-grouped-suggestion-157453.rs:20:9 + | +LL | pub struct MyPath; + | ^^^^^^^^^^^^^^^^^^ you could import this directly +help: import `MyPath` directly + | +LL - use crate::reexport::{MyPath}; +LL + use crate::outer::inner::MyPath; + | -error: aborting due to 1 previous error +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0603`. diff --git a/tests/ui/imports/private-import-nested-suggestion-156060.stderr b/tests/ui/imports/private-import-nested-suggestion-156060.stderr index 09d5391bd58df..62b8d0413903a 100644 --- a/tests/ui/imports/private-import-nested-suggestion-156060.stderr +++ b/tests/ui/imports/private-import-nested-suggestion-156060.stderr @@ -14,6 +14,11 @@ note: ...and refers to the struct `One` which is defined here | LL | pub struct One(); | ^^^^^^^^^^^^^^^^^ you could import this directly +help: import `One` directly + | +LL ~ use crate::one::One; +LL ~ use crate::two::{Two}; + | error: aborting due to 1 previous error diff --git a/tests/ui/imports/private-import-suggestion-path-156244.edition_2015.stderr b/tests/ui/imports/private-import-suggestion-path-156244.edition_2015.stderr index 2a8795b6ab93c..e1a0121286cbc 100644 --- a/tests/ui/imports/private-import-suggestion-path-156244.edition_2015.stderr +++ b/tests/ui/imports/private-import-suggestion-path-156244.edition_2015.stderr @@ -36,6 +36,11 @@ note: ...and refers to the struct `One` which is defined here | LL | pub struct One; | ^^^^^^^^^^^^^^^ you could import this directly +help: import `One` directly + | +LL ~ use crate::a::One; +LL ~ use crate::b::{Two}; + | error[E0603]: struct import `Two` is private --> $DIR/private-import-suggestion-path-156244.rs:35:25 @@ -53,6 +58,11 @@ note: ...and refers to the struct `Two` which is defined here | LL | pub struct Two; | ^^^^^^^^^^^^^^^ you could import this directly +help: import `Two` directly + | +LL ~ use crate::a::Two; +LL ~ use crate::b::{One}; + | error[E0603]: module import `inner` is private --> $DIR/private-import-suggestion-path-156244.rs:38:24 diff --git a/tests/ui/imports/private-import-suggestion-path-156244.edition_2018.stderr b/tests/ui/imports/private-import-suggestion-path-156244.edition_2018.stderr index 9f112fe4e7551..01ec6c84dfeff 100644 --- a/tests/ui/imports/private-import-suggestion-path-156244.edition_2018.stderr +++ b/tests/ui/imports/private-import-suggestion-path-156244.edition_2018.stderr @@ -36,6 +36,11 @@ note: ...and refers to the struct `One` which is defined here | LL | pub struct One; | ^^^^^^^^^^^^^^^ you could import this directly +help: import `One` directly + | +LL ~ use crate::a::One; +LL ~ use crate::b::{Two}; + | error[E0603]: struct import `Two` is private --> $DIR/private-import-suggestion-path-156244.rs:35:25 @@ -53,6 +58,11 @@ note: ...and refers to the struct `Two` which is defined here | LL | pub struct Two; | ^^^^^^^^^^^^^^^ you could import this directly +help: import `Two` directly + | +LL ~ use crate::a::Two; +LL ~ use crate::b::{One}; + | error[E0603]: module import `inner` is private --> $DIR/private-import-suggestion-path-156244.rs:38:24