Skip to content

Commit

Permalink
Auto merge of rust-lang#112686 - estebank:sealed-traits, r=petrochenkov
Browse files Browse the repository at this point in the history
Account for sealed traits in privacy and trait bound errors

On trait bound errors caused by super-traits, identify if the super-trait is publicly accessibly and if not, explain "sealed traits".

```
error[E0277]: the trait bound `S: Hidden` is not satisfied
  --> $DIR/sealed-trait-local.rs:17:20
   |
LL | impl a::Sealed for S {}
   |                    ^ the trait `Hidden` is not implemented for `S`
   |
note: required by a bound in `Sealed`
  --> $DIR/sealed-trait-local.rs:3:23
   |
LL |     pub trait Sealed: self::b::Hidden {
   |                       ^^^^^^^^^^^^^^^ required by this bound in `Sealed`
   = note: `Sealed` is a "sealed trait", because to implement it you also need to implelement `a::b::Hidden`, which is not accessible; this is usually done to force you to use one of the provided types that already implement it
```

Deduplicate privacy errors that point to the same path segment even if their deduplication span are different.

When encountering a path that is not reachable due to privacy constraints path segments other than the last, keep metadata for the last path segment's `Res` in order to look for alternative import paths for that item to suggest. If there are none, be explicit that the item is not accessible.

```
error[E0603]: module `b` is private
  --> $DIR/re-exported-trait.rs:11:9
   |
LL | impl a::b::Trait for S {}
   |         ^ private module
   |
note: the module `b` is defined here
  --> $DIR/re-exported-trait.rs:5:5
   |
LL |     mod b {
   |     ^^^^^
help: consider importing this trait through its public re-export instead
   |
LL | impl a::Trait for S {}
   |      ~~~~~~~~
```

```
error[E0603]: module `b` is private
  --> $DIR/private-trait.rs:8:9
   |
LL | impl a::b::Hidden for S {}
   |         ^  ------ trait `b` is not publicly reachable
   |         |
   |         private module
   |
note: the module `b` is defined here
  --> $DIR/private-trait.rs:2:5
   |
LL |     mod b {
   |     ^^^^^
```
  • Loading branch information
bors committed Jun 22, 2023
2 parents 2efe091 + 7dffd24 commit 04075b3
Show file tree
Hide file tree
Showing 29 changed files with 337 additions and 54 deletions.
92 changes: 64 additions & 28 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub(crate) struct ImportSuggestion {
pub descr: &'static str,
pub path: Path,
pub accessible: bool,
pub via_import: bool,
/// An extra note that should be issued if this item is suggested
pub note: Option<String>,
}
Expand Down Expand Up @@ -140,9 +141,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
}

let mut reported_spans = FxHashSet::default();
for error in &self.privacy_errors {
for error in std::mem::take(&mut self.privacy_errors) {
if reported_spans.insert(error.dedup_span) {
self.report_privacy_error(error);
self.report_privacy_error(&error);
}
}
}
Expand Down Expand Up @@ -1256,6 +1257,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
path,
accessible: child_accessible,
note,
via_import,
});
}
}
Expand Down Expand Up @@ -1609,8 +1611,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
None
}

fn report_privacy_error(&self, privacy_error: &PrivacyError<'_>) {
let PrivacyError { ident, binding, .. } = *privacy_error;
fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'a>) {
let PrivacyError { ident, binding, outermost_res, parent_scope, dedup_span } =
*privacy_error;

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

if let Some((this_res, outer_ident)) = outermost_res {
let import_suggestions = self.lookup_import_candidates(
outer_ident,
this_res.ns().unwrap_or(Namespace::TypeNS),
&parent_scope,
&|res: Res| res == this_res,
);
let point_to_def = !show_candidates(
self.tcx,
&mut err,
Some(dedup_span.until(outer_ident.span.shrink_to_hi())),
&import_suggestions,
Instead::Yes,
FoundUse::Yes,
DiagnosticMode::Import,
vec![],
"",
);
// If we suggest importing a public re-export, don't point at the definition.
if point_to_def && ident.span != outer_ident.span {
err.span_label(
outer_ident.span,
format!("{} `{outer_ident}` is not publicly re-exported", this_res.descr()),
);
}
}

let mut non_exhaustive = None;
// If an ADT is foreign and marked as `non_exhaustive`, then that's
// probably why we have the privacy error.
Expand Down Expand Up @@ -2455,7 +2485,8 @@ pub(crate) fn import_candidates(

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

let mut accessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>)> =
let mut accessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>, bool)> =
Vec::new();
let mut inaccessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>)> =
let mut inaccessible_path_strings: Vec<(String, &str, Option<DefId>, &Option<String>, bool)> =
Vec::new();

candidates.iter().for_each(|c| {
(if c.accessible { &mut accessible_path_strings } else { &mut inaccessible_path_strings })
.push((path_names_to_string(&c.path), c.descr, c.did, &c.note))
.push((path_names_to_string(&c.path), c.descr, c.did, &c.note, c.via_import))
});

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

if !accessible_path_strings.is_empty() {
let (determiner, kind, name) = if accessible_path_strings.len() == 1 {
("this", accessible_path_strings[0].1, format!(" `{}`", accessible_path_strings[0].0))
} else {
("one of these", "items", String::new())
};
let (determiner, kind, name, through) =
if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] {
(
"this",
*descr,
format!(" `{name}`"),
if *via_import { " through its public re-export" } else { "" },
)
} else {
("one of these", "items", String::new(), "")
};

let instead = if let Instead::Yes = instead { " instead" } else { "" };
let mut msg = if let DiagnosticMode::Pattern = mode {
format!(
"if you meant to match on {}{}{}, use the full path in the pattern",
kind, instead, name
"if you meant to match on {kind}{instead}{name}, use the full path in the pattern",
)
} else {
format!("consider importing {} {}{}", determiner, kind, instead)
format!("consider importing {determiner} {kind}{through}{instead}")
};

for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) {
Expand All @@ -2522,7 +2558,7 @@ fn show_candidates(
accessible_path_strings.into_iter().map(|a| a.0),
Applicability::MaybeIncorrect,
);
return;
return true;
}
DiagnosticMode::Import => ("", ""),
DiagnosticMode::Normal => ("use ", ";\n"),
Expand Down Expand Up @@ -2563,6 +2599,7 @@ fn show_candidates(

err.help(msg);
}
true
} else if !matches!(mode, DiagnosticMode::Import) {
assert!(!inaccessible_path_strings.is_empty());

Expand All @@ -2571,13 +2608,9 @@ fn show_candidates(
} else {
""
};
if inaccessible_path_strings.len() == 1 {
let (name, descr, def_id, note) = &inaccessible_path_strings[0];
if let [(name, descr, def_id, note, _)] = &inaccessible_path_strings[..] {
let msg = format!(
"{}{} `{}`{} exists but is inaccessible",
prefix,
descr,
name,
"{prefix}{descr} `{name}`{} exists but is inaccessible",
if let DiagnosticMode::Pattern = mode { ", which" } else { "" }
);

Expand All @@ -2594,11 +2627,11 @@ fn show_candidates(
err.note(note.to_string());
}
} else {
let (_, descr_first, _, _) = &inaccessible_path_strings[0];
let (_, descr_first, _, _, _) = &inaccessible_path_strings[0];
let descr = if inaccessible_path_strings
.iter()
.skip(1)
.all(|(_, descr, _, _)| descr == descr_first)
.all(|(_, descr, _, _, _)| descr == descr_first)
{
descr_first
} else {
Expand All @@ -2611,7 +2644,7 @@ fn show_candidates(
let mut has_colon = false;

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

err.span_note(multi_span, msg);
}
true
} else {
false
}
}

Expand Down
13 changes: 13 additions & 0 deletions compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
ident,
binding,
dedup_span: path_span,
outermost_res: None,
parent_scope: *parent_scope,
});
} else {
return Err((Determined, Weak::No));
Expand Down Expand Up @@ -1369,6 +1371,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
let mut allow_super = true;
let mut second_binding = None;

// We'll provide more context to the privacy errors later, up to `len`.
let privacy_errors_len = self.privacy_errors.len();

for (segment_idx, &Segment { ident, id, .. }) in path.iter().enumerate() {
debug!("resolve_path ident {} {:?} {:?}", segment_idx, ident, id);
let record_segment_res = |this: &mut Self, res| {
Expand Down Expand Up @@ -1506,6 +1511,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
second_binding = Some(binding);
}
let res = binding.res();

// Mark every privacy error in this path with the res to the last element. This allows us
// to detect the item the user cares about and either find an alternative import, or tell
// the user it is not accessible.
for error in &mut self.privacy_errors[privacy_errors_len..] {
error.outermost_res = Some((res, ident));
}

let maybe_assoc = opt_ns != Some(MacroNS) && PathSource::Type.is_expected(res);
if let Some(next_module) = binding.module() {
module = Some(ModuleOrUniformRoot::Module(next_module));
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_resolve/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
};
let prev_ambiguity_errors_len = self.ambiguity_errors.len();
let finalize = Finalize::with_root_span(import.root_id, import.span, import.root_span);

// We'll provide more context to the privacy errors later, up to `len`.
let privacy_errors_len = self.privacy_errors.len();

let path_res = self.resolve_path(
&import.module_path,
None,
Expand Down Expand Up @@ -931,6 +935,21 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
_ => unreachable!(),
};

if self.privacy_errors.len() != privacy_errors_len {
// Get the Res for the last element, so that we can point to alternative ways of
// importing it if available.
let mut path = import.module_path.clone();
path.push(Segment::from_ident(ident));
if let PathResult::Module(ModuleOrUniformRoot::Module(module)) =
self.resolve_path(&path, None, &import.parent_scope, Some(finalize), ignore_binding)
{
let res = module.res().map(|r| (r, ident));
for error in &mut self.privacy_errors[privacy_errors_len..] {
error.outermost_res = res;
}
}
}

let mut all_ns_err = true;
self.per_ns(|this, ns| {
if !type_ns_only || ns == TypeNS {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
path,
accessible: true,
note: None,
via_import: false,
},
));
} else {
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,10 +689,13 @@ impl<'a> NameBindingKind<'a> {
}
}

#[derive(Debug)]
struct PrivacyError<'a> {
ident: Ident,
binding: &'a NameBinding<'a>,
dedup_span: Span,
outermost_res: Option<(Res, Ident)>,
parent_scope: ParentScope<'a>,
}

#[derive(Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2724,6 +2724,32 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
let msg = format!("required by this bound in `{short_item_name}`");
multispan.push_span_label(span, msg);
err.span_note(multispan, descr);
if let ty::PredicateKind::Clause(clause) = predicate.kind().skip_binder()
&& let ty::ClauseKind::Trait(trait_pred) = clause
{
let def_id = trait_pred.def_id();
let visible_item = if let Some(local) = def_id.as_local() {
// Check for local traits being reachable.
let vis = &self.tcx.resolutions(()).effective_visibilities;
// Account for non-`pub` traits in the root of the local crate.
let is_locally_reachable = self.tcx.parent(def_id).is_crate_root();
vis.is_reachable(local) || is_locally_reachable
} else {
// Check for foreign traits being reachable.
self.tcx.visible_parent_map(()).get(&def_id).is_some()
};
if let DefKind::Trait = tcx.def_kind(item_def_id) && !visible_item {
// FIXME(estebank): extend this to search for all the types that do
// implement this trait and list them.
err.note(format!(
"`{short_item_name}` is a \"sealed trait\", because to implement \
it you also need to implelement `{}`, which is not accessible; \
this is usually done to force you to use one of the provided \
types that already implement it",
with_no_trimmed_paths!(tcx.def_path_str(def_id)),
));
}
}
} else {
err.span_note(tcx.def_span(item_def_id), descr);
}
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/extern/extern-crate-visibility.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ note: the crate import `core` is defined here
|
LL | extern crate core;
| ^^^^^^^^^^^^^^^^^^
help: consider importing this module instead
|
LL | use std::cell;
| ~~~~~~~~~

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

error: aborting due to 2 previous errors

Expand Down
8 changes: 6 additions & 2 deletions tests/ui/issues/issue-11680.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ error[E0603]: enum `Foo` is private
--> $DIR/issue-11680.rs:6:21
|
LL | let _b = other::Foo::Bar(1);
| ^^^ private enum
| ^^^ --- tuple variant `Bar` is not publicly re-exported
| |
| private enum
|
note: the enum `Foo` is defined here
--> $DIR/auxiliary/issue-11680.rs:1:1
Expand All @@ -14,7 +16,9 @@ error[E0603]: enum `Foo` is private
--> $DIR/issue-11680.rs:9:27
|
LL | let _b = other::test::Foo::Bar(1);
| ^^^ private enum
| ^^^ --- tuple variant `Bar` is not publicly re-exported
| |
| private enum
|
note: the enum `Foo` is defined here
--> $DIR/auxiliary/issue-11680.rs:6:5
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/macros/issue-88228.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error: cannot find macro `bla` in this scope
LL | bla!();
| ^^^
|
help: consider importing this macro
help: consider importing this macro through its public re-export
|
LL + use crate::hey::bla;
|
Expand All @@ -23,7 +23,7 @@ error: cannot find derive macro `Bla` in this scope
LL | #[derive(Bla)]
| ^^^
|
help: consider importing this derive macro
help: consider importing this derive macro through its public re-export
|
LL + use crate::hey::Bla;
|
Expand Down
Loading

0 comments on commit 04075b3

Please sign in to comment.