Skip to content

Commit 7dffd24

Browse files
committed
Tweak privacy errors to account for reachable items
Suggest publicly accessible paths for items in private mod: When encountering a path in non-import situations that are not reachable due to privacy constraints, search for any public re-exports that the user could use instead. Track whether an import suggestion is offering a re-export. When encountering a path with private segments, mention if the item at the final path segment is not publicly accessible at all. Add item visibility metadata to privacy errors from imports: On unreachable imports, record the item that was being imported in order to suggest publicly available re-exports or to be explicit that the item is not available publicly from any path. In order to allow this, we add a mode to `resolve_path` that will not add new privacy errors, nor return early if it encounters one. This way we can get the `Res` corresponding to the final item in the import, which is used in the privacy error machinery.
1 parent 717c481 commit 7dffd24

26 files changed

+276
-54
lines changed

Diff for: compiler/rustc_resolve/src/diagnostics.rs

+64-28
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub(crate) struct ImportSuggestion {
103103
pub descr: &'static str,
104104
pub path: Path,
105105
pub accessible: bool,
106+
pub via_import: bool,
106107
/// An extra note that should be issued if this item is suggested
107108
pub note: Option<String>,
108109
}
@@ -140,9 +141,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
140141
}
141142

142143
let mut reported_spans = FxHashSet::default();
143-
for error in &self.privacy_errors {
144+
for error in std::mem::take(&mut self.privacy_errors) {
144145
if reported_spans.insert(error.dedup_span) {
145-
self.report_privacy_error(error);
146+
self.report_privacy_error(&error);
146147
}
147148
}
148149
}
@@ -1256,6 +1257,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
12561257
path,
12571258
accessible: child_accessible,
12581259
note,
1260+
via_import,
12591261
});
12601262
}
12611263
}
@@ -1609,8 +1611,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
16091611
None
16101612
}
16111613

1612-
fn report_privacy_error(&self, privacy_error: &PrivacyError<'_>) {
1613-
let PrivacyError { ident, binding, .. } = *privacy_error;
1614+
fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'a>) {
1615+
let PrivacyError { ident, binding, outermost_res, parent_scope, dedup_span } =
1616+
*privacy_error;
16141617

16151618
let res = binding.res();
16161619
let ctor_fields_span = self.ctor_fields_span(binding);
@@ -1627,6 +1630,33 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
16271630
struct_span_err!(self.tcx.sess, ident.span, E0603, "{} `{}` is private", descr, ident);
16281631
err.span_label(ident.span, format!("private {}", descr));
16291632

1633+
if let Some((this_res, outer_ident)) = outermost_res {
1634+
let import_suggestions = self.lookup_import_candidates(
1635+
outer_ident,
1636+
this_res.ns().unwrap_or(Namespace::TypeNS),
1637+
&parent_scope,
1638+
&|res: Res| res == this_res,
1639+
);
1640+
let point_to_def = !show_candidates(
1641+
self.tcx,
1642+
&mut err,
1643+
Some(dedup_span.until(outer_ident.span.shrink_to_hi())),
1644+
&import_suggestions,
1645+
Instead::Yes,
1646+
FoundUse::Yes,
1647+
DiagnosticMode::Import,
1648+
vec![],
1649+
"",
1650+
);
1651+
// If we suggest importing a public re-export, don't point at the definition.
1652+
if point_to_def && ident.span != outer_ident.span {
1653+
err.span_label(
1654+
outer_ident.span,
1655+
format!("{} `{outer_ident}` is not publicly re-exported", this_res.descr()),
1656+
);
1657+
}
1658+
}
1659+
16301660
let mut non_exhaustive = None;
16311661
// If an ADT is foreign and marked as `non_exhaustive`, then that's
16321662
// probably why we have the privacy error.
@@ -2455,7 +2485,8 @@ pub(crate) fn import_candidates(
24552485

24562486
/// When an entity with a given name is not available in scope, we search for
24572487
/// entities with that name in all crates. This method allows outputting the
2458-
/// results of this search in a programmer-friendly way
2488+
/// results of this search in a programmer-friendly way. If any entities are
2489+
/// found and suggested, returns `true`, otherwise returns `false`.
24592490
fn show_candidates(
24602491
tcx: TyCtxt<'_>,
24612492
err: &mut Diagnostic,
@@ -2467,19 +2498,19 @@ fn show_candidates(
24672498
mode: DiagnosticMode,
24682499
path: Vec<Segment>,
24692500
append: &str,
2470-
) {
2501+
) -> bool {
24712502
if candidates.is_empty() {
2472-
return;
2503+
return false;
24732504
}
24742505

2475-
let mut accessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>)> =
2506+
let mut accessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>, bool)> =
24762507
Vec::new();
2477-
let mut inaccessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>)> =
2508+
let mut inaccessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>, bool)> =
24782509
Vec::new();
24792510

24802511
candidates.iter().for_each(|c| {
24812512
(if c.accessible { &mut accessible_path_strings } else { &mut inaccessible_path_strings })
2482-
.push((path_names_to_string(&c.path), c.descr, c.did, &c.note))
2513+
.push((path_names_to_string(&c.path), c.descr, c.did, &c.note, c.via_import))
24832514
});
24842515

24852516
// we want consistent results across executions, but candidates are produced
@@ -2493,20 +2524,25 @@ fn show_candidates(
24932524
}
24942525

24952526
if !accessible_path_strings.is_empty() {
2496-
let (determiner, kind, name) = if accessible_path_strings.len() == 1 {
2497-
("this", accessible_path_strings[0].1, format!(" `{}`", accessible_path_strings[0].0))
2498-
} else {
2499-
("one of these", "items", String::new())
2500-
};
2527+
let (determiner, kind, name, through) =
2528+
if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] {
2529+
(
2530+
"this",
2531+
*descr,
2532+
format!(" `{name}`"),
2533+
if *via_import { " through its public re-export" } else { "" },
2534+
)
2535+
} else {
2536+
("one of these", "items", String::new(), "")
2537+
};
25012538

25022539
let instead = if let Instead::Yes = instead { " instead" } else { "" };
25032540
let mut msg = if let DiagnosticMode::Pattern = mode {
25042541
format!(
2505-
"if you meant to match on {}{}{}, use the full path in the pattern",
2506-
kind, instead, name
2542+
"if you meant to match on {kind}{instead}{name}, use the full path in the pattern",
25072543
)
25082544
} else {
2509-
format!("consider importing {} {}{}", determiner, kind, instead)
2545+
format!("consider importing {determiner} {kind}{through}{instead}")
25102546
};
25112547

25122548
for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) {
@@ -2522,7 +2558,7 @@ fn show_candidates(
25222558
accessible_path_strings.into_iter().map(|a| a.0),
25232559
Applicability::MaybeIncorrect,
25242560
);
2525-
return;
2561+
return true;
25262562
}
25272563
DiagnosticMode::Import => ("", ""),
25282564
DiagnosticMode::Normal => ("use ", ";\n"),
@@ -2563,6 +2599,7 @@ fn show_candidates(
25632599

25642600
err.help(msg);
25652601
}
2602+
true
25662603
} else if !matches!(mode, DiagnosticMode::Import) {
25672604
assert!(!inaccessible_path_strings.is_empty());
25682605

@@ -2571,13 +2608,9 @@ fn show_candidates(
25712608
} else {
25722609
""
25732610
};
2574-
if inaccessible_path_strings.len() == 1 {
2575-
let (name, descr, def_id, note) = &inaccessible_path_strings[0];
2611+
if let [(name, descr, def_id, note, _)] = &inaccessible_path_strings[..] {
25762612
let msg = format!(
2577-
"{}{} `{}`{} exists but is inaccessible",
2578-
prefix,
2579-
descr,
2580-
name,
2613+
"{prefix}{descr} `{name}`{} exists but is inaccessible",
25812614
if let DiagnosticMode::Pattern = mode { ", which" } else { "" }
25822615
);
25832616

@@ -2594,11 +2627,11 @@ fn show_candidates(
25942627
err.note(note.to_string());
25952628
}
25962629
} else {
2597-
let (_, descr_first, _, _) = &inaccessible_path_strings[0];
2630+
let (_, descr_first, _, _, _) = &inaccessible_path_strings[0];
25982631
let descr = if inaccessible_path_strings
25992632
.iter()
26002633
.skip(1)
2601-
.all(|(_, descr, _, _)| descr == descr_first)
2634+
.all(|(_, descr, _, _, _)| descr == descr_first)
26022635
{
26032636
descr_first
26042637
} else {
@@ -2611,7 +2644,7 @@ fn show_candidates(
26112644
let mut has_colon = false;
26122645

26132646
let mut spans = Vec::new();
2614-
for (name, _, def_id, _) in &inaccessible_path_strings {
2647+
for (name, _, def_id, _, _) in &inaccessible_path_strings {
26152648
if let Some(local_def_id) = def_id.and_then(|did| did.as_local()) {
26162649
let span = tcx.source_span(local_def_id);
26172650
let span = tcx.sess.source_map().guess_head_span(span);
@@ -2637,6 +2670,9 @@ fn show_candidates(
26372670

26382671
err.span_note(multi_span, msg);
26392672
}
2673+
true
2674+
} else {
2675+
false
26402676
}
26412677
}
26422678

Diff for: compiler/rustc_resolve/src/ident.rs

+13
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
893893
ident,
894894
binding,
895895
dedup_span: path_span,
896+
outermost_res: None,
897+
parent_scope: *parent_scope,
896898
});
897899
} else {
898900
return Err((Determined, Weak::No));
@@ -1369,6 +1371,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
13691371
let mut allow_super = true;
13701372
let mut second_binding = None;
13711373

1374+
// We'll provide more context to the privacy errors later, up to `len`.
1375+
let privacy_errors_len = self.privacy_errors.len();
1376+
13721377
for (segment_idx, &Segment { ident, id, .. }) in path.iter().enumerate() {
13731378
debug!("resolve_path ident {} {:?} {:?}", segment_idx, ident, id);
13741379
let record_segment_res = |this: &mut Self, res| {
@@ -1506,6 +1511,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
15061511
second_binding = Some(binding);
15071512
}
15081513
let res = binding.res();
1514+
1515+
// Mark every privacy error in this path with the res to the last element. This allows us
1516+
// to detect the item the user cares about and either find an alternative import, or tell
1517+
// the user it is not accessible.
1518+
for error in &mut self.privacy_errors[privacy_errors_len..] {
1519+
error.outermost_res = Some((res, ident));
1520+
}
1521+
15091522
let maybe_assoc = opt_ns != Some(MacroNS) && PathSource::Type.is_expected(res);
15101523
if let Some(next_module) = binding.module() {
15111524
module = Some(ModuleOrUniformRoot::Module(next_module));

Diff for: compiler/rustc_resolve/src/imports.rs

+19
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
792792
};
793793
let prev_ambiguity_errors_len = self.ambiguity_errors.len();
794794
let finalize = Finalize::with_root_span(import.root_id, import.span, import.root_span);
795+
796+
// We'll provide more context to the privacy errors later, up to `len`.
797+
let privacy_errors_len = self.privacy_errors.len();
798+
795799
let path_res = self.resolve_path(
796800
&import.module_path,
797801
None,
@@ -931,6 +935,21 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
931935
_ => unreachable!(),
932936
};
933937

938+
if self.privacy_errors.len() != privacy_errors_len {
939+
// Get the Res for the last element, so that we can point to alternative ways of
940+
// importing it if available.
941+
let mut path = import.module_path.clone();
942+
path.push(Segment::from_ident(ident));
943+
if let PathResult::Module(ModuleOrUniformRoot::Module(module)) =
944+
self.resolve_path(&path, None, &import.parent_scope, Some(finalize), ignore_binding)
945+
{
946+
let res = module.res().map(|r| (r, ident));
947+
for error in &mut self.privacy_errors[privacy_errors_len..] {
948+
error.outermost_res = res;
949+
}
950+
}
951+
}
952+
934953
let mut all_ns_err = true;
935954
self.per_ns(|this, ns| {
936955
if !type_ns_only || ns == TypeNS {

Diff for: compiler/rustc_resolve/src/late/diagnostics.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1831,6 +1831,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
18311831
path,
18321832
accessible: true,
18331833
note: None,
1834+
via_import: false,
18341835
},
18351836
));
18361837
} else {

Diff for: compiler/rustc_resolve/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -689,10 +689,13 @@ impl<'a> NameBindingKind<'a> {
689689
}
690690
}
691691

692+
#[derive(Debug)]
692693
struct PrivacyError<'a> {
693694
ident: Ident,
694695
binding: &'a NameBinding<'a>,
695696
dedup_span: Span,
697+
outermost_res: Option<(Res, Ident)>,
698+
parent_scope: ParentScope<'a>,
696699
}
697700

698701
#[derive(Debug)]

Diff for: tests/ui/extern/extern-crate-visibility.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ note: the crate import `core` is defined here
99
|
1010
LL | extern crate core;
1111
| ^^^^^^^^^^^^^^^^^^
12+
help: consider importing this module instead
13+
|
14+
LL | use std::cell;
15+
| ~~~~~~~~~
1216

1317
error[E0603]: crate import `core` is private
1418
--> $DIR/extern-crate-visibility.rs:9:10
@@ -21,6 +25,10 @@ note: the crate import `core` is defined here
2125
|
2226
LL | extern crate core;
2327
| ^^^^^^^^^^^^^^^^^^
28+
help: consider importing this struct instead
29+
|
30+
LL | std::cell::Cell::new(0);
31+
| ~~~~~~~~~~~~~~~
2432

2533
error: aborting due to 2 previous errors
2634

Diff for: tests/ui/issues/issue-11680.stderr

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ error[E0603]: enum `Foo` is private
22
--> $DIR/issue-11680.rs:6:21
33
|
44
LL | let _b = other::Foo::Bar(1);
5-
| ^^^ private enum
5+
| ^^^ --- tuple variant `Bar` is not publicly re-exported
6+
| |
7+
| private enum
68
|
79
note: the enum `Foo` is defined here
810
--> $DIR/auxiliary/issue-11680.rs:1:1
@@ -14,7 +16,9 @@ error[E0603]: enum `Foo` is private
1416
--> $DIR/issue-11680.rs:9:27
1517
|
1618
LL | let _b = other::test::Foo::Bar(1);
17-
| ^^^ private enum
19+
| ^^^ --- tuple variant `Bar` is not publicly re-exported
20+
| |
21+
| private enum
1822
|
1923
note: the enum `Foo` is defined here
2024
--> $DIR/auxiliary/issue-11680.rs:6:5

Diff for: tests/ui/macros/issue-88228.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ error: cannot find macro `bla` in this scope
44
LL | bla!();
55
| ^^^
66
|
7-
help: consider importing this macro
7+
help: consider importing this macro through its public re-export
88
|
99
LL + use crate::hey::bla;
1010
|
@@ -23,7 +23,7 @@ error: cannot find derive macro `Bla` in this scope
2323
LL | #[derive(Bla)]
2424
| ^^^
2525
|
26-
help: consider importing this derive macro
26+
help: consider importing this derive macro through its public re-export
2727
|
2828
LL + use crate::hey::Bla;
2929
|

Diff for: tests/ui/privacy/export-tag-variant.stderr

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ error[E0603]: enum `Y` is private
22
--> $DIR/export-tag-variant.rs:7:26
33
|
44
LL | fn main() { let z = foo::Y::Y1; }
5-
| ^ private enum
5+
| ^ -- unit variant `Y1` is not publicly re-exported
6+
| |
7+
| private enum
68
|
79
note: the enum `Y` is defined here
810
--> $DIR/export-tag-variant.rs:4:5

0 commit comments

Comments
 (0)