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
120 changes: 119 additions & 1 deletion compiler/rustc_resolve/src/diagnostics/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2519,17 +2519,57 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
*path = Path { span: path.span, segments: new_segments };
}

fn expand_span_to_surrounding_comma(
source_text: &str,
ident_start: usize,
ident_end: usize,
) -> (usize, usize) {
// A trailing comma is consumed to keep the remaining group valid.
let after_ident = &source_text[ident_end..];
let trimmed = after_ident.trim_start();
if trimmed.starts_with(',') {
let comma_pos = ident_end + (after_ident.len() - trimmed.len());
let after_comma = &source_text[comma_pos + 1..];
let next_non_ws =
after_comma.find(|c: char| !c.is_whitespace()).unwrap_or(after_comma.len());
return (ident_start, comma_pos + 1 + next_non_ws);
}

// If there is no trailing comma, this must be the last item in the group,
// so we consume the leading comma instead.
let before_ident = &source_text[..ident_start];
let trimmed = before_ident.trim_end();
if trimmed.ends_with(',') {
let comma_pos = trimmed.len() - 1;
return (comma_pos, ident_end);
}

(ident_start, ident_end)
}

fn get_indentation(sm: &SourceMap, span: Span) -> String {
let line_span = sm.span_extend_to_line(span.shrink_to_lo());
sm.span_to_snippet(line_span)
.map(|line| {
let non_ws = line.find(|c: char| !c.is_whitespace()).unwrap_or(line.len());
line[..non_ws].to_string()
})
.unwrap_or_default()
}

fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) {
let PrivacyError {
ident,
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 @@ -2804,6 +2844,84 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
err.subdiagnostic(sugg);
break;
}
} else if single_nested
&& !shown_candidates
&& !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span)
{
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 {
continue;
}
let path = join_path_idents(sugg);
let Ok(source_text) = self.tcx.sess.source_map().span_to_snippet(root_span) else {
continue;
};

let lo_offset = (ident.span.lo() - root_span.lo()).0 as usize;
let hi_offset = (ident.span.hi() - root_span.lo()).0 as usize;

if lo_offset > source_text.len() || hi_offset > source_text.len() {
continue;
}

let (start, end) =
Self::expand_span_to_surrounding_comma(&source_text, lo_offset, hi_offset);

let mut replacement = String::new();
replacement.push_str(&source_text[..start]);
replacement.push_str(&source_text[end..]);

// An empty group like `foo::{}` is invalid syntax.
if replacement.contains("{}") {
let msg = if reexport {
format!("import `{ident}` through the re-export")
} else {
format!("import `{ident}` directly")
};

let line_span = self.tcx.sess.source_map().span_extend_to_line(root_span);
let indentation = Self::get_indentation(self.tcx.sess.source_map(), root_span);
let suggestion_text = format!("{indentation}use {path};");
err.multipart_suggestion(
msg,
vec![(line_span, suggestion_text)],
Applicability::MachineApplicable,
);
break;
}

// Braces are not needed when only one item remains in the group.
if let Some(open) = replacement.find('{') {
if let Some(close) = replacement.rfind('}') {
let inner = replacement[open + 1..close].trim();
if !inner.contains(',') && !inner.is_empty() {
replacement = format!("{}{}", replacement[..open].trim_end(), inner);
}
}
}

let msg = if reexport {
format!("import `{ident}` through the re-export")
} else {
format!("import `{ident}` directly")
};

// The new import is placed on its own line, so we match the current indentation.
let indentation = Self::get_indentation(self.tcx.sess.source_map(), root_span);

// `root_span` doesn't include the `use` keyword, so inserting just before it
// avoids duplicating the keyword for the new import.
err.multipart_suggestion(
msg,
vec![
(root_span.shrink_to_lo(), format!("{path};\n{indentation}use ")),
(root_span, replacement),
],
Applicability::MachineApplicable,
);
break;
}
}

err.emit();
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
3 changes: 1 addition & 2 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,10 +1031,9 @@ struct PrivacyError<'ra> {
ident: Ident,
decl: Decl<'ra>,
dedup_span: Span,
root_span: Span,
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