Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 86 additions & 11 deletions compiler/rustc_resolve/src/diagnostics/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}

@cjgillot cjgillot Aug 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't try to be smart with braces. The user has rustfmt.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure I understood this comment my intention wasn't just formatting but to produce a cleaner suggestion (use foo::Bar instead of use foo::{Bar}) when only one item remains. does rustfmt rewrite use foo::{Bar} into use foo::Bar, or is the idea that we shouldn't try to simplify the syntax in diagnostics?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't try to simplify the syntax in diagnostics?

i dont thing the UI should change regardless of what we do in the code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does rustfmt rewrite use foo::{Bar} into use foo::Bar, or is the idea that we shouldn't try to simplify the syntax in diagnostics?

Both.

Simplifying the syntax is ok when it makes rustc code simpler too. If you need to count BytePos for span arithmetic, it's not worth it. (Span arithmetic is known to cause ICEs when the user uses multibyte characters.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far i can tell report_privacy_error only has root_span and span_to_remove not the UseTree AST, so I couldn't find existing helper that would simplify {Bar} to Bar without doing manual BytePos-based span arithmetic. check_unused.rs can do this because it still has the UseTree spans available.

i wanted the diagnostic to present the import in the form users would normally write use foo::Bar rather than use foo::{Bar} especially for a single remaining item. That said, if the extra span arithmetic isn't considered worth the complexity, i'm happy to drop brace simplification but, personally i dont want we should suggest {Bar} instead of Bar for a single item.


// Insert before `root_span` to reuse the existing `use`.
err.multipart_suggestion(msg, spans, Applicability::MachineApplicable);
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Comment thread
raushan728 marked this conversation as resolved.
outermost_res: Option<(Res, Ident)>,
parent_scope: ParentScope<'ra>,
/// Is the format `use a::{b,c}`?
single_nested: bool,
source: Option<ast::Expr>,
}

Expand Down
32 changes: 32 additions & 0 deletions tests/ui/imports/private-import-grouped-suggestion-157453.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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]
}

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() {}
69 changes: 69 additions & 0 deletions tests/ui/imports/private-import-grouped-suggestion-157453.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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
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 3 previous errors

For more information about this error, try `rustc --explain E0603`.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading