Detect unconstructable re-exported tuple structs

When a tuple-struct is re-exported that has inaccessible fields at the `use` scope, the type's constructor cannot be accessed through that re-export. We now account for this case and extend the resulting resolution error. We also check if the constructor would be accessible directly, not through the re-export, and if so, we suggest using the full path instead.

```
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);
   |                                 ~~~~~~~~~~~~~~~~~~
```

Fix #133343.
This commit is contained in:
Esteban Küber 2024-11-25 21:52:08 +00:00
parent 8d72d3e1e9
commit 8f7d61b9ef
6 changed files with 181 additions and 33 deletions

View file

@ -901,6 +901,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
binding,
if resolution.non_glob_binding.is_some() { resolution.glob_binding } else { None },
parent_scope,
module,
finalize,
shadowing,
);
@ -1025,6 +1026,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
binding: Option<NameBinding<'ra>>,
shadowed_glob: Option<NameBinding<'ra>>,
parent_scope: &ParentScope<'ra>,
module: Module<'ra>,
finalize: Finalize,
shadowing: Shadowing,
) -> Result<NameBinding<'ra>, (Determinacy, Weak)> {
@ -1076,6 +1078,37 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
self.macro_expanded_macro_export_errors.insert((path_span, binding.span));
}
// 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);
}
}
self.record_use(ident, binding, used);
return Ok(binding);
}

View file

@ -1942,44 +1942,77 @@ impl<'ast, 'ra, '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, 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, 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())

View file

@ -1167,6 +1167,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)> = BTreeSet::new(),
/// 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>>,
@ -1595,6 +1600,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
glob_map: Default::default(),
used_imports: FxHashSet::default(),
maybe_unused_trait_imports: Default::default(),
inaccessible_ctor_reexport: Default::default(),
arenas,
dummy_binding: arenas.new_pub_res_binding(Res::Err, DUMMY_SP, LocalExpnId::ROOT),

View file

@ -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() {}

View file

@ -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() {}

View file

@ -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`.