Skip to content

Detect tuple structs that are unconstructable due to re-export #133477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
32 changes: 32 additions & 0 deletions compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,38 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}

self.record_use(ident, binding, used);

// If we encounter a re-export for a type with private fields, it will not be able to
// be constructed through this re-export. We track that case here to expand later
// privacy errors with appropriate information.
if let Res::Def(_, def_id) = binding.res() {
let struct_ctor = match def_id.as_local() {
Some(def_id) => self.struct_constructors.get(&def_id).cloned(),
None => {
let ctor = self.cstore().ctor_untracked(def_id);
ctor.map(|(ctor_kind, ctor_def_id)| {
let ctor_res = Res::Def(
DefKind::Ctor(rustc_hir::def::CtorOf::Struct, ctor_kind),
ctor_def_id,
);
let ctor_vis = self.tcx.visibility(ctor_def_id);
let field_visibilities = self
.tcx
.associated_item_def_ids(def_id)
.iter()
.map(|field_id| self.tcx.visibility(field_id))
.collect();
(ctor_res, ctor_vis, field_visibilities)
})
}
};
if let Some((_, _, fields)) = struct_ctor
&& fields.iter().any(|vis| !self.is_accessible_from(*vis, module))
{
self.inaccessible_ctor_reexport.insert(path_span, binding.span);
}
}

return Ok(binding);
}

Expand Down
99 changes: 66 additions & 33 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1738,44 +1738,77 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
return true;
};

let update_message =
|this: &mut Self, err: &mut Diag<'_>, source: &PathSource<'_>| {
match source {
// e.g. `if let Enum::TupleVariant(field1, field2) = _`
PathSource::TupleStruct(_, pattern_spans) => {
err.primary_message(
"cannot match against a tuple struct which contains private fields",
);

// Use spans of the tuple struct pattern.
Some(Vec::from(*pattern_spans))
}
// e.g. `let _ = Enum::TupleVariant(field1, field2);`
PathSource::Expr(Some(Expr {
kind: ExprKind::Call(path, ref args),
span: call_span,
..
})) => {
err.primary_message(
"cannot initialize a tuple struct which contains private fields",
);
this.suggest_alternative_construction_methods(
def_id,
err,
path.span,
*call_span,
&args[..],
);
// Use spans of the tuple struct definition.
this.r
.field_idents(def_id)
.map(|fields| fields.iter().map(|f| f.span).collect::<Vec<_>>())
}
_ => None,
}
};
let is_accessible = self.r.is_accessible_from(ctor_vis, self.parent_scope.module);
if let Some(use_span) = self.r.inaccessible_ctor_reexport.get(&span)
&& is_accessible
{
err.span_note(
*use_span,
"the type is accessed through this re-export, but the type's constructor \
is not visible in this import's scope due to private fields",
);
if is_accessible
&& fields
.iter()
.all(|vis| self.r.is_accessible_from(*vis, self.parent_scope.module))
{
err.span_suggestion_verbose(
span,
"the type can be constructed directly, because its fields are \
available from the current scope",
// Using `tcx.def_path_str` causes the compiler to hang.
// We don't need to handle foreign crate types because in that case you
// can't access the ctor either way.
format!(
"crate{}", // The method already has leading `::`.
self.r.tcx.def_path(def_id).to_string_no_crate_verbose(),
),
Applicability::MachineApplicable,
);
}
update_message(self, err, &source);
}
if !is_expected(ctor_def) || is_accessible {
return true;
}

let field_spans = match source {
// e.g. `if let Enum::TupleVariant(field1, field2) = _`
PathSource::TupleStruct(_, pattern_spans) => {
err.primary_message(
"cannot match against a tuple struct which contains private fields",
);

// Use spans of the tuple struct pattern.
Some(Vec::from(pattern_spans))
}
// e.g. `let _ = Enum::TupleVariant(field1, field2);`
PathSource::Expr(Some(Expr {
kind: ExprKind::Call(path, ref args),
span: call_span,
..
})) => {
err.primary_message(
"cannot initialize a tuple struct which contains private fields",
);
self.suggest_alternative_construction_methods(
def_id,
err,
path.span,
*call_span,
&args[..],
);
// Use spans of the tuple struct definition.
self.r
.field_idents(def_id)
.map(|fields| fields.iter().map(|f| f.span).collect::<Vec<_>>())
}
_ => None,
};
let field_spans = update_message(self, err, &source);

if let Some(spans) =
field_spans.filter(|spans| spans.len() > 0 && fields.len() == spans.len())
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,11 @@ pub struct Resolver<'ra, 'tcx> {
/// Crate-local macro expanded `macro_export` referred to by a module-relative path.
macro_expanded_macro_export_errors: BTreeSet<(Span, Span)>,

/// When a type is re-exported that has an inaccessible constructor because it has fields that
/// are inaccessible from the import's scope, we mark that as the type won't be able to be built
/// through the re-export. We use this information to extend the existing diagnostic.
inaccessible_ctor_reexport: FxHashMap<Span, Span>,

arenas: &'ra ResolverArenas<'ra>,
dummy_binding: NameBinding<'ra>,
builtin_types_bindings: FxHashMap<Symbol, NameBinding<'ra>>,
Expand Down Expand Up @@ -1477,6 +1482,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
use_injections: Vec::new(),
macro_expanded_macro_export_errors: BTreeSet::new(),

inaccessible_ctor_reexport: Default::default(),

arenas,
dummy_binding: (Res::Err, pub_vis, DUMMY_SP, LocalExpnId::ROOT).to_name_binding(arenas),
builtin_types_bindings: PrimTy::ALL
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![allow(dead_code, unused_variables)]
//@ run-rustfix
pub use my_mod::Foo;
//~^ NOTE the type is accessed through this re-export, but the type's constructor is not visible in this import's scope due to private fields
//~| NOTE the type is accessed through this re-export, but the type's constructor is not visible in this import's scope due to private fields

mod my_mod {
pub struct Foo(u32);

mod my_sub_mod {
fn my_func() {
let crate::my_mod::Foo(x) = crate::my_mod::Foo(42);
//~^ ERROR cannot initialize a tuple struct which contains private fields
//~| HELP the type can be constructed directly, because its fields are available from the current scope
//~| ERROR cannot match against a tuple struct which contains private fields
//~| HELP the type can be constructed directly, because its fields are available from the current scope
}
}
}
fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![allow(dead_code, unused_variables)]
//@ run-rustfix
pub use my_mod::Foo;
//~^ NOTE the type is accessed through this re-export, but the type's constructor is not visible in this import's scope due to private fields
//~| NOTE the type is accessed through this re-export, but the type's constructor is not visible in this import's scope due to private fields

mod my_mod {
pub struct Foo(u32);

mod my_sub_mod {
fn my_func() {
let crate::Foo(x) = crate::Foo(42);
//~^ ERROR cannot initialize a tuple struct which contains private fields
//~| HELP the type can be constructed directly, because its fields are available from the current scope
//~| ERROR cannot match against a tuple struct which contains private fields
//~| HELP the type can be constructed directly, because its fields are available from the current scope
}
}
}
fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
error[E0423]: cannot initialize a tuple struct which contains private fields
--> $DIR/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs:12:33
|
LL | let crate::Foo(x) = crate::Foo(42);
| ^^^^^^^^^^
|
note: the type is accessed through this re-export, but the type's constructor is not visible in this import's scope due to private fields
--> $DIR/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs:3:9
|
LL | pub use my_mod::Foo;
| ^^^^^^^^^^^
help: the type can be constructed directly, because its fields are available from the current scope
|
LL | let crate::Foo(x) = crate::my_mod::Foo(42);
| ~~~~~~~~~~~~~~~~~~

error[E0532]: cannot match against a tuple struct which contains private fields
--> $DIR/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs:12:17
|
LL | let crate::Foo(x) = crate::Foo(42);
| ^^^^^^^^^^
|
note: the type is accessed through this re-export, but the type's constructor is not visible in this import's scope due to private fields
--> $DIR/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs:3:9
|
LL | pub use my_mod::Foo;
| ^^^^^^^^^^^
help: the type can be constructed directly, because its fields are available from the current scope
|
LL | let crate::my_mod::Foo(x) = crate::Foo(42);
| ~~~~~~~~~~~~~~~~~~

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0423, E0532.
For more information about an error, try `rustc --explain E0423`.
Loading