From 40638087681e726be9b60e6d887a76aed0626a01 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 fb597c361138293c6b393329fae26f4f193dc789 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 | 97 ++++++++++++++++--- 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, 185 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index ad6c9cea18126..47c4719fcc715 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -2554,11 +2554,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(); @@ -2811,12 +2813,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // 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. + let can_suggest = + !shown_candidates && !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span); + + 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 { @@ -2825,12 +2826,86 @@ 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 } + + 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 mut spans = vec![ + (root_span.shrink_to_lo(), format!("{path};\n{indentation}use ")), + (span_to_remove, String::new()), + ]; + + // Strip braces if only one item remains (e.g. `foo::{Bar}` -> `foo::Bar`). + if let Ok(Some(extra_spans)) = + self.tcx.sess.source_map().span_to_source(root_span, |src, start, end| { + let src = &src[start..end]; + let lo = (span_to_remove.lo() - root_span.lo()).0 as usize; + let hi = (span_to_remove.hi() - root_span.lo()).0 as usize; + if let (Some(open), Some(close)) = (src.find('{'), src.rfind('}')) { + // No other commas means exactly one item remains. + if !src[open + 1..lo].contains(',') && !src[hi..close].contains(',') { + let remove_char = |pos: usize| { + let lo = root_span.lo() + BytePos(pos as u32); + ( + Span::new(lo, lo + BytePos(1), root_span.ctxt(), None), + String::new(), + ) + }; + return Ok::<_, rustc_span::SpanSnippetError>(Some(vec![ + remove_char(open), + remove_char(close), + ])); + } + } + Ok::<_, rustc_span::SpanSnippetError>(None) + }) + { + spans.extend(extra_spans); + } + + // 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 3f34af1d01d83..df213f194bffa 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1362,10 +1362,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 a3c804e56ee22..af754098f3faf 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1043,11 +1043,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..47ff51e4246cd 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..372fb4781c128 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..14a196f0ee13b 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..988d3374ea78c 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