about summary refs log tree commit diff
diff options
context:
space:
mode:
author许杰友 Jieyou Xu (Joe) <39484203+jieyouxu@users.noreply.github.com>2025-01-20 12:37:54 +0800
committerGitHub <noreply@github.com>2025-01-20 12:37:54 +0800
commit6db2d1aae5d74f57f5218bcf6c839f2e653f1ac2 (patch)
tree76e83804b684a5097e524445eee02d54fd6a3b6f
parent5740a552e3ed402c6f5a5c20abdd2ae82f8681eb (diff)
parentdeef3ebaec2ff0ff818161f3b9b86a42bed5fe36 (diff)
downloadrust-6db2d1aae5d74f57f5218bcf6c839f2e653f1ac2.tar.gz
rust-6db2d1aae5d74f57f5218bcf6c839f2e653f1ac2.zip
Rollup merge of #135700 - estebank:priv-field-dfv, r=wesleywiser
Emit single privacy error for struct literal with multiple private fields and add test for `default_field_values` privacy

Add test ensuring that struct with default field values is not constructable if the fields are not accessible.

Collect all unreachable fields in a single struct literal struct and emit a single error, instead of one error per private field.

```
error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
  --> $DIR/visibility.rs:18:13
   |
LL |     let _x = Alpha {
   |              ----- in this type
LL |         beta: 0,
   |         ^^^^^^^ private field
LL |         ..
   |         ^^ field `gamma` is private
```
-rw-r--r--compiler/rustc_privacy/messages.ftl18
-rw-r--r--compiler/rustc_privacy/src/errors.rs14
-rw-r--r--compiler/rustc_privacy/src/lib.rs151
-rw-r--r--tests/ui/deprecation/deprecation-lint.stderr5
-rw-r--r--tests/ui/error-codes/E0451.stderr2
-rw-r--r--tests/ui/pattern/usefulness/issue-82772-match-box-as-struct.stderr2
-rw-r--r--tests/ui/privacy/private-struct-field-ctor.stderr2
-rw-r--r--tests/ui/privacy/private-struct-field-pattern.stderr2
-rw-r--r--tests/ui/privacy/restricted/struct-literal-field.stderr2
-rw-r--r--tests/ui/privacy/union-field-privacy-1.stderr2
-rw-r--r--tests/ui/structs/default-field-values/visibility.rs42
-rw-r--r--tests/ui/structs/default-field-values/visibility.stderr61
-rw-r--r--tests/ui/typeck/issue-82772.stderr6
13 files changed, 261 insertions, 48 deletions
diff --git a/compiler/rustc_privacy/messages.ftl b/compiler/rustc_privacy/messages.ftl
index 7785f1a7f81..43c34a109d7 100644
--- a/compiler/rustc_privacy/messages.ftl
+++ b/compiler/rustc_privacy/messages.ftl
@@ -1,5 +1,19 @@
-privacy_field_is_private = field `{$field_name}` of {$variant_descr} `{$def_path_str}` is private
-privacy_field_is_private_is_update_syntax_label = field `{$field_name}` is private
+privacy_field_is_private =
+    {$len ->
+        [1] field
+        *[other] fields
+    } {$field_names} of {$variant_descr} `{$def_path_str}` {$len ->
+        [1] is
+        *[other] are
+    } private
+    .label = in this type
+privacy_field_is_private_is_update_syntax_label = {$rest_len ->
+        [1] field
+        *[other] fields
+    } {$rest_field_names} {$rest_len ->
+        [1] is
+        *[other] are
+    } private
 privacy_field_is_private_label = private field
 
 privacy_from_private_dep_in_public_interface =
diff --git a/compiler/rustc_privacy/src/errors.rs b/compiler/rustc_privacy/src/errors.rs
index f5e641eb642..4d1d58c0852 100644
--- a/compiler/rustc_privacy/src/errors.rs
+++ b/compiler/rustc_privacy/src/errors.rs
@@ -1,5 +1,5 @@
-use rustc_errors::DiagArgFromDisplay;
 use rustc_errors::codes::*;
+use rustc_errors::{DiagArgFromDisplay, MultiSpan};
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_span::{Span, Symbol};
 
@@ -7,12 +7,15 @@ use rustc_span::{Span, Symbol};
 #[diag(privacy_field_is_private, code = E0451)]
 pub(crate) struct FieldIsPrivate {
     #[primary_span]
-    pub span: Span,
-    pub field_name: Symbol,
+    pub span: MultiSpan,
+    #[label]
+    pub struct_span: Option<Span>,
+    pub field_names: String,
     pub variant_descr: &'static str,
     pub def_path_str: String,
     #[subdiagnostic]
-    pub label: FieldIsPrivateLabel,
+    pub labels: Vec<FieldIsPrivateLabel>,
+    pub len: usize,
 }
 
 #[derive(Subdiagnostic)]
@@ -21,7 +24,8 @@ pub(crate) enum FieldIsPrivateLabel {
     IsUpdateSyntax {
         #[primary_span]
         span: Span,
-        field_name: Symbol,
+        rest_field_names: String,
+        rest_len: usize,
     },
     #[label(privacy_field_is_private_label)]
     Other {
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index e484cfed06b..cb7b0815a49 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -24,6 +24,7 @@ use rustc_ast::MacroDef;
 use rustc_ast::visit::{VisitorResult, try_visit};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::intern::Interned;
+use rustc_errors::MultiSpan;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId, LocalModDefId};
 use rustc_hir::intravisit::{self, Visitor};
@@ -38,7 +39,7 @@ use rustc_middle::ty::{
 use rustc_middle::{bug, span_bug};
 use rustc_session::lint;
 use rustc_span::hygiene::Transparency;
-use rustc_span::{Ident, Span, kw, sym};
+use rustc_span::{Ident, Span, Symbol, kw, sym};
 use tracing::debug;
 use {rustc_attr_parsing as attr, rustc_hir as hir};
 
@@ -921,31 +922,95 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
         &mut self,
         hir_id: hir::HirId,    // ID of the field use
         use_ctxt: Span,        // syntax context of the field name at the use site
-        span: Span,            // span of the field pattern, e.g., `x: 0`
         def: ty::AdtDef<'tcx>, // definition of the struct or enum
         field: &'tcx ty::FieldDef,
-        in_update_syntax: bool,
-    ) {
+    ) -> bool {
         if def.is_enum() {
-            return;
+            return true;
         }
 
         // definition of the field
         let ident = Ident::new(kw::Empty, use_ctxt);
         let def_id = self.tcx.adjust_ident_and_get_scope(ident, def.did(), hir_id).1;
-        if !field.vis.is_accessible_from(def_id, self.tcx) {
-            self.tcx.dcx().emit_err(FieldIsPrivate {
-                span,
-                field_name: field.name,
-                variant_descr: def.variant_descr(),
-                def_path_str: self.tcx.def_path_str(def.did()),
-                label: if in_update_syntax {
-                    FieldIsPrivateLabel::IsUpdateSyntax { span, field_name: field.name }
-                } else {
-                    FieldIsPrivateLabel::Other { span }
-                },
-            });
+        !field.vis.is_accessible_from(def_id, self.tcx)
+    }
+
+    // Checks that a field in a struct constructor (expression or pattern) is accessible.
+    fn emit_unreachable_field_error(
+        &mut self,
+        fields: Vec<(Symbol, Span, bool /* field is present */)>,
+        def: ty::AdtDef<'tcx>, // definition of the struct or enum
+        update_syntax: Option<Span>,
+        struct_span: Span,
+    ) {
+        if def.is_enum() || fields.is_empty() {
+            return;
         }
+
+        //   error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
+        //   --> $DIR/visibility.rs:18:13
+        //    |
+        // LL |     let _x = Alpha {
+        //    |              ----- in this type      # from `def`
+        // LL |         beta: 0,
+        //    |         ^^^^^^^ private field        # `fields.2` is `true`
+        // LL |         ..
+        //    |         ^^ field `gamma` is private  # `fields.2` is `false`
+
+        // Get the list of all private fields for the main message.
+        let field_names: Vec<_> = fields.iter().map(|(name, _, _)| name).collect();
+        let field_names = match &field_names[..] {
+            [] => return,
+            [name] => format!("`{name}`"),
+            [fields @ .., last] => format!(
+                "{} and `{last}`",
+                fields.iter().map(|f| format!("`{f}`")).collect::<Vec<_>>().join(", "),
+            ),
+        };
+        let span: MultiSpan = fields.iter().map(|(_, span, _)| *span).collect::<Vec<Span>>().into();
+
+        // Get the list of all private fields when pointing at the `..rest`.
+        let rest_field_names: Vec<_> =
+            fields.iter().filter(|(_, _, is_present)| !is_present).map(|(n, _, _)| n).collect();
+        let rest_len = rest_field_names.len();
+        let rest_field_names = match &rest_field_names[..] {
+            [] => String::new(),
+            [name] => format!("`{name}`"),
+            [fields @ .., last] => format!(
+                "{} and `{last}`",
+                fields.iter().map(|f| format!("`{f}`")).collect::<Vec<_>>().join(", "),
+            ),
+        };
+        // Get all the labels for each field or `..rest` in the primary MultiSpan.
+        let labels = fields
+            .iter()
+            .filter(|(_, _, is_present)| *is_present)
+            .map(|(_, span, _)| FieldIsPrivateLabel::Other { span: *span })
+            .chain(update_syntax.iter().map(|span| FieldIsPrivateLabel::IsUpdateSyntax {
+                span: *span,
+                rest_field_names: rest_field_names.clone(),
+                rest_len,
+            }))
+            .collect();
+
+        self.tcx.dcx().emit_err(FieldIsPrivate {
+            span,
+            struct_span: if self
+                .tcx
+                .sess
+                .source_map()
+                .is_multiline(fields[0].1.between(struct_span))
+            {
+                Some(struct_span)
+            } else {
+                None
+            },
+            field_names: field_names.clone(),
+            variant_descr: def.variant_descr(),
+            def_path_str: self.tcx.def_path_str(def.did()),
+            labels,
+            len: fields.len(),
+        });
     }
 
     fn check_expanded_fields(
@@ -955,7 +1020,9 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
         fields: &[hir::ExprField<'tcx>],
         hir_id: hir::HirId,
         span: Span,
+        struct_span: Span,
     ) {
+        let mut failed_fields = vec![];
         for (vf_index, variant_field) in variant.fields.iter_enumerated() {
             let field =
                 fields.iter().find(|f| self.typeck_results().field_index(f.hir_id) == vf_index);
@@ -963,8 +1030,15 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
                 Some(field) => (field.hir_id, field.ident.span, field.span),
                 None => (hir_id, span, span),
             };
-            self.check_field(hir_id, use_ctxt, span, adt, variant_field, true);
+            if self.check_field(hir_id, use_ctxt, adt, variant_field) {
+                let name = match field {
+                    Some(field) => field.ident.name,
+                    None => variant_field.name,
+                };
+                failed_fields.push((name, span, field.is_some()));
+            }
         }
+        self.emit_unreachable_field_error(failed_fields, adt, Some(span), struct_span);
     }
 }
 
@@ -990,24 +1064,35 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
                     // If the expression uses FRU we need to make sure all the unmentioned fields
                     // are checked for privacy (RFC 736). Rather than computing the set of
                     // unmentioned fields, just check them all.
-                    self.check_expanded_fields(adt, variant, fields, base.hir_id, base.span);
+                    self.check_expanded_fields(
+                        adt,
+                        variant,
+                        fields,
+                        base.hir_id,
+                        base.span,
+                        qpath.span(),
+                    );
                 }
                 hir::StructTailExpr::DefaultFields(span) => {
-                    self.check_expanded_fields(adt, variant, fields, expr.hir_id, span);
+                    self.check_expanded_fields(
+                        adt,
+                        variant,
+                        fields,
+                        expr.hir_id,
+                        span,
+                        qpath.span(),
+                    );
                 }
                 hir::StructTailExpr::None => {
+                    let mut failed_fields = vec![];
                     for field in fields {
-                        let (hir_id, use_ctxt, span) = (field.hir_id, field.ident.span, field.span);
+                        let (hir_id, use_ctxt) = (field.hir_id, field.ident.span);
                         let index = self.typeck_results().field_index(field.hir_id);
-                        self.check_field(
-                            hir_id,
-                            use_ctxt,
-                            span,
-                            adt,
-                            &variant.fields[index],
-                            false,
-                        );
+                        if self.check_field(hir_id, use_ctxt, adt, &variant.fields[index]) {
+                            failed_fields.push((field.ident.name, field.ident.span, true));
+                        }
                     }
+                    self.emit_unreachable_field_error(failed_fields, adt, None, qpath.span());
                 }
             }
         }
@@ -1020,11 +1105,15 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
             let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
             let adt = self.typeck_results().pat_ty(pat).ty_adt_def().unwrap();
             let variant = adt.variant_of_res(res);
+            let mut failed_fields = vec![];
             for field in fields {
-                let (hir_id, use_ctxt, span) = (field.hir_id, field.ident.span, field.span);
+                let (hir_id, use_ctxt) = (field.hir_id, field.ident.span);
                 let index = self.typeck_results().field_index(field.hir_id);
-                self.check_field(hir_id, use_ctxt, span, adt, &variant.fields[index], false);
+                if self.check_field(hir_id, use_ctxt, adt, &variant.fields[index]) {
+                    failed_fields.push((field.ident.name, field.ident.span, true));
+                }
             }
+            self.emit_unreachable_field_error(failed_fields, adt, None, qpath.span());
         }
 
         intravisit::walk_pat(self, pat);
diff --git a/tests/ui/deprecation/deprecation-lint.stderr b/tests/ui/deprecation/deprecation-lint.stderr
index 2098073409d..95ae1b04d86 100644
--- a/tests/ui/deprecation/deprecation-lint.stderr
+++ b/tests/ui/deprecation/deprecation-lint.stderr
@@ -739,8 +739,11 @@ LL |              _)
 error[E0451]: field `i` of struct `this_crate::nested::DeprecatedStruct` is private
   --> $DIR/deprecation-lint.rs:280:13
    |
+LL |         let _ = nested::DeprecatedStruct {
+   |                 ------------------------ in this type
+LL |
 LL |             i: 0
-   |             ^^^^ private field
+   |             ^ private field
 
 error: aborting due to 123 previous errors
 
diff --git a/tests/ui/error-codes/E0451.stderr b/tests/ui/error-codes/E0451.stderr
index 419cf117efe..2cd30095c80 100644
--- a/tests/ui/error-codes/E0451.stderr
+++ b/tests/ui/error-codes/E0451.stderr
@@ -8,7 +8,7 @@ error[E0451]: field `b` of struct `Foo` is private
   --> $DIR/E0451.rs:18:29
    |
 LL |     let f = bar::Foo{ a: 0, b: 0 };
-   |                             ^^^^ private field
+   |                             ^ private field
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/pattern/usefulness/issue-82772-match-box-as-struct.stderr b/tests/ui/pattern/usefulness/issue-82772-match-box-as-struct.stderr
index ba7573839ed..ee21b4c8d46 100644
--- a/tests/ui/pattern/usefulness/issue-82772-match-box-as-struct.stderr
+++ b/tests/ui/pattern/usefulness/issue-82772-match-box-as-struct.stderr
@@ -2,7 +2,7 @@ error[E0451]: field `1` of struct `Box` is private
   --> $DIR/issue-82772-match-box-as-struct.rs:4:15
    |
 LL |     let Box { 1: _, .. }: Box<()>;
-   |               ^^^^ private field
+   |               ^ private field
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/privacy/private-struct-field-ctor.stderr b/tests/ui/privacy/private-struct-field-ctor.stderr
index 2a35537237a..8eb1bf7990b 100644
--- a/tests/ui/privacy/private-struct-field-ctor.stderr
+++ b/tests/ui/privacy/private-struct-field-ctor.stderr
@@ -2,7 +2,7 @@ error[E0451]: field `x` of struct `Foo` is private
   --> $DIR/private-struct-field-ctor.rs:8:22
    |
 LL |     let s = a::Foo { x: 1 };
-   |                      ^^^^ private field
+   |                      ^ private field
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/privacy/private-struct-field-pattern.stderr b/tests/ui/privacy/private-struct-field-pattern.stderr
index de24d1e0962..5609596721d 100644
--- a/tests/ui/privacy/private-struct-field-pattern.stderr
+++ b/tests/ui/privacy/private-struct-field-pattern.stderr
@@ -2,7 +2,7 @@ error[E0451]: field `x` of struct `Foo` is private
   --> $DIR/private-struct-field-pattern.rs:15:15
    |
 LL |         Foo { x: _ } => {}
-   |               ^^^^ private field
+   |               ^ private field
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/privacy/restricted/struct-literal-field.stderr b/tests/ui/privacy/restricted/struct-literal-field.stderr
index dcdadf1da4b..e1cf7c2fadb 100644
--- a/tests/ui/privacy/restricted/struct-literal-field.stderr
+++ b/tests/ui/privacy/restricted/struct-literal-field.stderr
@@ -2,7 +2,7 @@ error[E0451]: field `x` of struct `S` is private
   --> $DIR/struct-literal-field.rs:18:9
    |
 LL |     S { x: 0 };
-   |         ^^^^ private field
+   |         ^ private field
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/privacy/union-field-privacy-1.stderr b/tests/ui/privacy/union-field-privacy-1.stderr
index b1f0b785ea7..6f883b16d02 100644
--- a/tests/ui/privacy/union-field-privacy-1.stderr
+++ b/tests/ui/privacy/union-field-privacy-1.stderr
@@ -2,7 +2,7 @@ error[E0451]: field `c` of union `U` is private
   --> $DIR/union-field-privacy-1.rs:12:20
    |
 LL |     let u = m::U { c: 0 };
-   |                    ^^^^ private field
+   |                    ^ private field
 
 error[E0451]: field `c` of union `U` is private
   --> $DIR/union-field-privacy-1.rs:16:16
diff --git a/tests/ui/structs/default-field-values/visibility.rs b/tests/ui/structs/default-field-values/visibility.rs
new file mode 100644
index 00000000000..ff1245551b0
--- /dev/null
+++ b/tests/ui/structs/default-field-values/visibility.rs
@@ -0,0 +1,42 @@
+#![feature(default_field_values)]
+pub mod foo {
+    #[derive(Default)]
+    pub struct Alpha {
+        beta: u8 = 42,
+        gamma: bool = true,
+    }
+}
+
+mod bar {
+    use crate::foo::Alpha;
+    fn baz() {
+        let _x = Alpha { .. };
+        //~^ ERROR fields `beta` and `gamma` of struct `Alpha` are private
+        let _x = Alpha {
+            beta: 0, //~ ERROR fields `beta` and `gamma` of struct `Alpha` are private
+            gamma: false,
+        };
+        let _x = Alpha {
+            beta: 0, //~ ERROR fields `beta` and `gamma` of struct `Alpha` are private
+            ..
+        };
+        let _x = Alpha { beta: 0, .. };
+        //~^ ERROR fields `beta` and `gamma` of struct `Alpha` are private
+        let _x = Alpha { beta: 0, ..Default::default() };
+        //~^ ERROR fields `beta` and `gamma` of struct `Alpha` are private
+    }
+}
+
+pub mod baz {
+    pub struct S {
+        x: i32 = 1,
+    }
+}
+fn main() {
+    let _a = baz::S {
+        .. //~ ERROR field `x` of struct `S` is private
+    };
+    let _b = baz::S {
+        x: 0, //~ ERROR field `x` of struct `S` is private
+    };
+}
diff --git a/tests/ui/structs/default-field-values/visibility.stderr b/tests/ui/structs/default-field-values/visibility.stderr
new file mode 100644
index 00000000000..38b96033252
--- /dev/null
+++ b/tests/ui/structs/default-field-values/visibility.stderr
@@ -0,0 +1,61 @@
+error[E0451]: field `x` of struct `S` is private
+  --> $DIR/visibility.rs:37:9
+   |
+LL |     let _a = baz::S {
+   |              ------ in this type
+LL |         ..
+   |         ^^ field `x` is private
+
+error[E0451]: field `x` of struct `S` is private
+  --> $DIR/visibility.rs:40:9
+   |
+LL |     let _b = baz::S {
+   |              ------ in this type
+LL |         x: 0,
+   |         ^ private field
+
+error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
+  --> $DIR/visibility.rs:13:26
+   |
+LL |         let _x = Alpha { .. };
+   |                          ^^ fields `beta` and `gamma` are private
+
+error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
+  --> $DIR/visibility.rs:16:13
+   |
+LL |         let _x = Alpha {
+   |                  ----- in this type
+LL |             beta: 0,
+   |             ^^^^ private field
+LL |             gamma: false,
+   |             ^^^^^ private field
+
+error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
+  --> $DIR/visibility.rs:20:13
+   |
+LL |         let _x = Alpha {
+   |                  ----- in this type
+LL |             beta: 0,
+   |             ^^^^^^^ private field
+LL |             ..
+   |             ^^ field `gamma` is private
+
+error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
+  --> $DIR/visibility.rs:23:26
+   |
+LL |         let _x = Alpha { beta: 0, .. };
+   |                          ^^^^^^^  ^^ field `gamma` is private
+   |                          |
+   |                          private field
+
+error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
+  --> $DIR/visibility.rs:25:26
+   |
+LL |         let _x = Alpha { beta: 0, ..Default::default() };
+   |                          ^^^^^^^    ^^^^^^^^^^^^^^^^^^ field `gamma` is private
+   |                          |
+   |                          private field
+
+error: aborting due to 7 previous errors
+
+For more information about this error, try `rustc --explain E0451`.
diff --git a/tests/ui/typeck/issue-82772.stderr b/tests/ui/typeck/issue-82772.stderr
index 321143cb968..a314306137a 100644
--- a/tests/ui/typeck/issue-82772.stderr
+++ b/tests/ui/typeck/issue-82772.stderr
@@ -2,19 +2,19 @@ error[E0451]: field `0` of struct `Box` is private
   --> $DIR/issue-82772.rs:5:15
    |
 LL |     let Box { 0: _, .. }: Box<()>;
-   |               ^^^^ private field
+   |               ^ private field
 
 error[E0451]: field `1` of struct `Box` is private
   --> $DIR/issue-82772.rs:6:15
    |
 LL |     let Box { 1: _, .. }: Box<()>;
-   |               ^^^^ private field
+   |               ^ private field
 
 error[E0451]: field `1` of struct `ModPrivateStruct` is private
   --> $DIR/issue-82772.rs:7:28
    |
 LL |     let ModPrivateStruct { 1: _, .. } = ModPrivateStruct::default();
-   |                            ^^^^ private field
+   |                            ^ private field
 
 error: aborting due to 3 previous errors