Skip to content

Commit 9358642

Browse files
committed
Auto merge of rust-lang#118066 - estebank:structured-use-suggestion, r=b-naber
Structured `use` suggestion on privacy error When encoutering a privacy error on an item through a re-export that is accessible in an alternative path, provide a structured suggestion with that path. ``` error[E0603]: module import `mem` is private --> $DIR/private-std-reexport-suggest-public.rs:4:14 | LL | use foo::mem; | ^^^ private module import | note: the module import `mem` is defined here... --> $DIR/private-std-reexport-suggest-public.rs:8:9 | LL | use std::mem; | ^^^^^^^^ note: ...and refers to the module `mem` which is defined here --> $SRC_DIR/std/src/lib.rs:LL:COL | = note: you could import this help: import `mem` through the re-export | LL | use std::mem; | ~~~~~~~~ ``` Fix rust-lang#42909.
2 parents 317d14a + beaf315 commit 9358642

7 files changed

+140
-6
lines changed

compiler/rustc_resolve/src/diagnostics.rs

+86-1
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
16971697
struct_span_err!(self.tcx.sess, ident.span, E0603, "{} `{}` is private", descr, ident);
16981698
err.span_label(ident.span, format!("private {descr}"));
16991699

1700+
let mut not_publicly_reexported = false;
17001701
if let Some((this_res, outer_ident)) = outermost_res {
17011702
let import_suggestions = self.lookup_import_candidates(
17021703
outer_ident,
@@ -1717,6 +1718,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17171718
);
17181719
// If we suggest importing a public re-export, don't point at the definition.
17191720
if point_to_def && ident.span != outer_ident.span {
1721+
not_publicly_reexported = true;
17201722
err.span_label(
17211723
outer_ident.span,
17221724
format!("{} `{outer_ident}` is not publicly re-exported", this_res.descr()),
@@ -1749,10 +1751,51 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17491751
}
17501752
}
17511753

1754+
let mut sugg_paths = vec![];
1755+
if let Some(mut def_id) = res.opt_def_id() {
1756+
// We can't use `def_path_str` in resolve.
1757+
let mut path = vec![def_id];
1758+
while let Some(parent) = self.tcx.opt_parent(def_id) {
1759+
def_id = parent;
1760+
if !def_id.is_top_level_module() {
1761+
path.push(def_id);
1762+
} else {
1763+
break;
1764+
}
1765+
}
1766+
// We will only suggest importing directly if it is accessible through that path.
1767+
let path_names: Option<Vec<String>> = path
1768+
.iter()
1769+
.rev()
1770+
.map(|def_id| {
1771+
self.tcx.opt_item_name(*def_id).map(|n| {
1772+
if def_id.is_top_level_module() {
1773+
"crate".to_string()
1774+
} else {
1775+
n.to_string()
1776+
}
1777+
})
1778+
})
1779+
.collect();
1780+
if let Some(def_id) = path.get(0)
1781+
&& let Some(path) = path_names
1782+
{
1783+
if let Some(def_id) = def_id.as_local() {
1784+
if self.effective_visibilities.is_directly_public(def_id) {
1785+
sugg_paths.push((path, false));
1786+
}
1787+
} else if self.is_accessible_from(self.tcx.visibility(def_id), parent_scope.module)
1788+
{
1789+
sugg_paths.push((path, false));
1790+
}
1791+
}
1792+
}
1793+
17521794
// Print the whole import chain to make it easier to see what happens.
17531795
let first_binding = binding;
17541796
let mut next_binding = Some(binding);
17551797
let mut next_ident = ident;
1798+
let mut path = vec![];
17561799
while let Some(binding) = next_binding {
17571800
let name = next_ident;
17581801
next_binding = match binding.kind {
@@ -1771,6 +1814,21 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17711814
_ => None,
17721815
};
17731816

1817+
match binding.kind {
1818+
NameBindingKind::Import { import, .. } => {
1819+
for segment in import.module_path.iter().skip(1) {
1820+
path.push(segment.ident.to_string());
1821+
}
1822+
sugg_paths.push((
1823+
path.iter()
1824+
.cloned()
1825+
.chain(vec![ident.to_string()].into_iter())
1826+
.collect::<Vec<_>>(),
1827+
true, // re-export
1828+
));
1829+
}
1830+
NameBindingKind::Res(_) | NameBindingKind::Module(_) => {}
1831+
}
17741832
let first = binding == first_binding;
17751833
let msg = format!(
17761834
"{and_refers_to}the {item} `{name}`{which} is defined here{dots}",
@@ -1782,7 +1840,11 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17821840
let def_span = self.tcx.sess.source_map().guess_head_span(binding.span);
17831841
let mut note_span = MultiSpan::from_span(def_span);
17841842
if !first && binding.vis.is_public() {
1785-
note_span.push_span_label(def_span, "consider importing it directly");
1843+
let desc = match binding.kind {
1844+
NameBindingKind::Import { .. } => "re-export",
1845+
_ => "directly",
1846+
};
1847+
note_span.push_span_label(def_span, format!("you could import this {desc}"));
17861848
}
17871849
// Final step in the import chain, point out if the ADT is `non_exhaustive`
17881850
// which is probably why this privacy violation occurred.
@@ -1796,6 +1858,29 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17961858
}
17971859
err.span_note(note_span, msg);
17981860
}
1861+
// We prioritize shorter paths, non-core imports and direct imports over the alternatives.
1862+
sugg_paths.sort_by_key(|(p, reexport)| (p.len(), p[0] == "core", *reexport));
1863+
for (sugg, reexport) in sugg_paths {
1864+
if not_publicly_reexported {
1865+
break;
1866+
}
1867+
if sugg.len() <= 1 {
1868+
// A single path segment suggestion is wrong. This happens on circular imports.
1869+
// `tests/ui/imports/issue-55884-2.rs`
1870+
continue;
1871+
}
1872+
let path = sugg.join("::");
1873+
err.span_suggestion_verbose(
1874+
dedup_span,
1875+
format!(
1876+
"import `{ident}` {}",
1877+
if reexport { "through the re-export" } else { "directly" }
1878+
),
1879+
path,
1880+
Applicability::MachineApplicable,
1881+
);
1882+
break;
1883+
}
17991884

18001885
err.emit();
18011886
}

tests/ui/imports/issue-55884-2.stderr

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ note: ...and refers to the struct import `ParseOptions` which is defined here...
1313
--> $DIR/issue-55884-2.rs:13:9
1414
|
1515
LL | pub use parser::ParseOptions;
16-
| ^^^^^^^^^^^^^^^^^^^^ consider importing it directly
16+
| ^^^^^^^^^^^^^^^^^^^^ you could import this re-export
1717
note: ...and refers to the struct import `ParseOptions` which is defined here...
1818
--> $DIR/issue-55884-2.rs:6:13
1919
|
2020
LL | pub use options::*;
21-
| ^^^^^^^^^^ consider importing it directly
21+
| ^^^^^^^^^^ you could import this re-export
2222
note: ...and refers to the struct `ParseOptions` which is defined here
2323
--> $DIR/issue-55884-2.rs:2:5
2424
|
2525
LL | pub struct ParseOptions {}
26-
| ^^^^^^^^^^^^^^^^^^^^^^^ consider importing it directly
26+
| ^^^^^^^^^^^^^^^^^^^^^^^ you could import this directly
27+
help: import `ParseOptions` through the re-export
28+
|
29+
LL | pub use parser::ParseOptions;
30+
| ~~~~~~~~~~~~~~~~~~~~
2731

2832
error: aborting due to 1 previous error
2933

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// run-rustfix
2+
#![allow(unused_imports)]
3+
fn main() {
4+
use std::mem; //~ ERROR module import `mem` is private
5+
}
6+
7+
pub mod foo {
8+
use std::mem;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// run-rustfix
2+
#![allow(unused_imports)]
3+
fn main() {
4+
use foo::mem; //~ ERROR module import `mem` is private
5+
}
6+
7+
pub mod foo {
8+
use std::mem;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0603]: module import `mem` is private
2+
--> $DIR/private-std-reexport-suggest-public.rs:4:14
3+
|
4+
LL | use foo::mem;
5+
| ^^^ private module import
6+
|
7+
note: the module import `mem` is defined here...
8+
--> $DIR/private-std-reexport-suggest-public.rs:8:9
9+
|
10+
LL | use std::mem;
11+
| ^^^^^^^^
12+
note: ...and refers to the module `mem` which is defined here
13+
--> $SRC_DIR/std/src/lib.rs:LL:COL
14+
|
15+
= note: you could import this directly
16+
help: import `mem` through the re-export
17+
|
18+
LL | use std::mem;
19+
| ~~~~~~~~
20+
21+
error: aborting due to 1 previous error
22+
23+
For more information about this error, try `rustc --explain E0603`.

tests/ui/privacy/privacy2.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ note: ...and refers to the function `foo` which is defined here
1919
--> $DIR/privacy2.rs:16:1
2020
|
2121
LL | pub fn foo() {}
22-
| ^^^^^^^^^^^^ consider importing it directly
22+
| ^^^^^^^^^^^^ you could import this directly
2323

2424
error: requires `sized` lang_item
2525

tests/ui/proc-macro/disappearing-resolution.stderr

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ note: ...and refers to the derive macro `Empty` which is defined here
1919
--> $DIR/auxiliary/test-macros.rs:25:1
2020
|
2121
LL | pub fn empty_derive(_: TokenStream) -> TokenStream {
22-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ consider importing it directly
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ you could import this directly
23+
help: import `Empty` directly
24+
|
25+
LL | use test_macros::Empty;
26+
| ~~~~~~~~~~~~~~~~~~
2327

2428
error: aborting due to 2 previous errors
2529

0 commit comments

Comments
 (0)