about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-09-29 11:56:41 +1000
committerGitHub <noreply@github.com>2025-09-29 11:56:41 +1000
commite7760fa4993c5cfe5246d27a7773c08ff093987e (patch)
treeb345b90a4efff548e0922ffa4080576d708a971c
parentf957826bff7a68b267ce75b1ea56352aed0cca0a (diff)
parent8f7d61b9efe1f44701f87fe4647eb1f39d20f434 (diff)
downloadrust-e7760fa4993c5cfe5246d27a7773c08ff093987e.tar.gz
rust-e7760fa4993c5cfe5246d27a7773c08ff093987e.zip
Rollup merge of #133477 - estebank:issue-133343, r=davidtwco
Detect tuple structs that are unconstructable due to re-export

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.
-rw-r--r--compiler/rustc_resolve/src/ident.rs33
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs99
-rw-r--r--compiler/rustc_resolve/src/lib.rs6
-rw-r--r--tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.fixed20
-rw-r--r--tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs20
-rw-r--r--tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.stderr36
6 files changed, 181 insertions, 33 deletions
diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs
index 51489019950..4415300777f 100644
--- a/compiler/rustc_resolve/src/ident.rs
+++ b/compiler/rustc_resolve/src/ident.rs
@@ -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);
     }
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index 9e3c0938836..8c2ddda7f98 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -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())
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 8959068b2a6..b44b1c966a4 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -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),
diff --git a/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.fixed b/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.fixed
new file mode 100644
index 00000000000..63cc3333b6b
--- /dev/null
+++ b/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.fixed
@@ -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() {}
diff --git a/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs b/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs
new file mode 100644
index 00000000000..0b695f90654
--- /dev/null
+++ b/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.rs
@@ -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() {}
diff --git a/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.stderr b/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.stderr
new file mode 100644
index 00000000000..6ab324cb32f
--- /dev/null
+++ b/tests/ui/privacy/ctor-not-accessible-due-to-inaccessible-field-in-reexport.stderr
@@ -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`.