about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTyler Mandry <tmandry@gmail.com>2020-09-10 12:20:02 -0700
committerGitHub <noreply@github.com>2020-09-10 12:20:02 -0700
commit9f8a7827a111140f112a424f8bf8f36740865fbd (patch)
tree33c93968ad3f60e8474e2993ce0c7a81cf3f004c
parent7565ccc32cbfaa3835ae4c27b7fb71bc61922bfc (diff)
parent409c14197372495b26fa8ee9f4b812492a7fb75a (diff)
downloadrust-9f8a7827a111140f112a424f8bf8f36740865fbd.tar.gz
rust-9f8a7827a111140f112a424f8bf8f36740865fbd.zip
Rollup merge of #76524 - davidtwco:issue-76077-inaccessible-private-fields, r=estebank
typeck: don't suggest inaccessible private fields

Fixes #76077.

This PR adjusts the missing field diagnostic logic in typeck so that when none of the missing fields in a struct expr are accessible then the error is less confusing.

r? @estebank
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs118
-rw-r--r--compiler/rustc_typeck/src/check/pat.rs90
-rw-r--r--src/test/ui/issues/issue-76077-1.fixed18
-rw-r--r--src/test/ui/issues/issue-76077-1.rs18
-rw-r--r--src/test/ui/issues/issue-76077-1.stderr24
-rw-r--r--src/test/ui/issues/issue-76077.rs10
-rw-r--r--src/test/ui/issues/issue-76077.stderr8
7 files changed, 244 insertions, 42 deletions
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index fa8b8dbd9f8..dba46f35dca 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -1241,42 +1241,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 tcx.sess.span_err(span, "union expressions should have exactly one field");
             }
         } else if check_completeness && !error_happened && !remaining_fields.is_empty() {
-            let len = remaining_fields.len();
-
-            let mut displayable_field_names =
-                remaining_fields.keys().map(|ident| ident.as_str()).collect::<Vec<_>>();
-
-            displayable_field_names.sort();
+            let no_accessible_remaining_fields = remaining_fields
+                .iter()
+                .filter(|(_, (_, field))| {
+                    field.vis.is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx)
+                })
+                .next()
+                .is_none();
 
-            let truncated_fields_error = if len <= 3 {
-                String::new()
+            if no_accessible_remaining_fields {
+                self.report_no_accessible_fields(adt_ty, span);
             } else {
-                format!(" and {} other field{}", (len - 3), if len - 3 == 1 { "" } else { "s" })
-            };
-
-            let remaining_fields_names = displayable_field_names
-                .iter()
-                .take(3)
-                .map(|n| format!("`{}`", n))
-                .collect::<Vec<_>>()
-                .join(", ");
-
-            struct_span_err!(
-                tcx.sess,
-                span,
-                E0063,
-                "missing field{} {}{} in initializer of `{}`",
-                pluralize!(remaining_fields.len()),
-                remaining_fields_names,
-                truncated_fields_error,
-                adt_ty
-            )
-            .span_label(
-                span,
-                format!("missing {}{}", remaining_fields_names, truncated_fields_error),
-            )
-            .emit();
+                self.report_missing_field(adt_ty, span, remaining_fields);
+            }
         }
+
         error_happened
     }
 
@@ -1293,6 +1272,79 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
+    /// Report an error for a struct field expression when there are fields which aren't provided.
+    ///
+    /// ```ignore (diagnostic)
+    /// error: missing field `you_can_use_this_field` in initializer of `foo::Foo`
+    ///  --> src/main.rs:8:5
+    ///   |
+    /// 8 |     foo::Foo {};
+    ///   |     ^^^^^^^^ missing `you_can_use_this_field`
+    ///
+    /// error: aborting due to previous error
+    /// ```
+    fn report_missing_field(
+        &self,
+        adt_ty: Ty<'tcx>,
+        span: Span,
+        remaining_fields: FxHashMap<Ident, (usize, &ty::FieldDef)>,
+    ) {
+        let tcx = self.tcx;
+        let len = remaining_fields.len();
+
+        let mut displayable_field_names =
+            remaining_fields.keys().map(|ident| ident.as_str()).collect::<Vec<_>>();
+
+        displayable_field_names.sort();
+
+        let truncated_fields_error = if len <= 3 {
+            String::new()
+        } else {
+            format!(" and {} other field{}", (len - 3), if len - 3 == 1 { "" } else { "s" })
+        };
+
+        let remaining_fields_names = displayable_field_names
+            .iter()
+            .take(3)
+            .map(|n| format!("`{}`", n))
+            .collect::<Vec<_>>()
+            .join(", ");
+
+        struct_span_err!(
+            tcx.sess,
+            span,
+            E0063,
+            "missing field{} {}{} in initializer of `{}`",
+            pluralize!(remaining_fields.len()),
+            remaining_fields_names,
+            truncated_fields_error,
+            adt_ty
+        )
+        .span_label(span, format!("missing {}{}", remaining_fields_names, truncated_fields_error))
+        .emit();
+    }
+
+    /// Report an error for a struct field expression when there are no visible fields.
+    ///
+    /// ```ignore (diagnostic)
+    /// error: cannot construct `Foo` with struct literal syntax due to inaccessible fields
+    ///  --> src/main.rs:8:5
+    ///   |
+    /// 8 |     foo::Foo {};
+    ///   |     ^^^^^^^^
+    ///
+    /// error: aborting due to previous error
+    /// ```
+    fn report_no_accessible_fields(&self, adt_ty: Ty<'tcx>, span: Span) {
+        self.tcx.sess.span_err(
+            span,
+            &format!(
+                "cannot construct `{}` with struct literal syntax due to inaccessible fields",
+                adt_ty,
+            ),
+        );
+    }
+
     fn report_unknown_field(
         &self,
         ty: Ty<'tcx>,
diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_typeck/src/check/pat.rs
index f06929aa98f..1896155e327 100644
--- a/compiler/rustc_typeck/src/check/pat.rs
+++ b/compiler/rustc_typeck/src/check/pat.rs
@@ -1078,8 +1078,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let mut unmentioned_fields = variant
             .fields
             .iter()
-            .map(|field| field.ident.normalize_to_macros_2_0())
-            .filter(|ident| !used_fields.contains_key(&ident))
+            .map(|field| (field, field.ident.normalize_to_macros_2_0()))
+            .filter(|(_, ident)| !used_fields.contains_key(&ident))
             .collect::<Vec<_>>();
 
         let inexistent_fields_err = if !(inexistent_fields.is_empty() || variant.is_recovered()) {
@@ -1110,7 +1110,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 tcx.sess.struct_span_err(pat.span, "`..` cannot be used in union patterns").emit();
             }
         } else if !etc && !unmentioned_fields.is_empty() {
-            unmentioned_err = Some(self.error_unmentioned_fields(pat, &unmentioned_fields));
+            let no_accessible_unmentioned_fields = unmentioned_fields
+                .iter()
+                .filter(|(field, _)| {
+                    field.vis.is_accessible_from(tcx.parent_module(pat.hir_id).to_def_id(), tcx)
+                })
+                .next()
+                .is_none();
+
+            if no_accessible_unmentioned_fields {
+                unmentioned_err = Some(self.error_no_accessible_fields(pat, &fields));
+            } else {
+                unmentioned_err = Some(self.error_unmentioned_fields(pat, &unmentioned_fields));
+            }
         }
         match (inexistent_fields_err, unmentioned_err) {
             (Some(mut i), Some(mut u)) => {
@@ -1173,7 +1185,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         &self,
         kind_name: &str,
         inexistent_fields: &[Ident],
-        unmentioned_fields: &mut Vec<Ident>,
+        unmentioned_fields: &mut Vec<(&ty::FieldDef, Ident)>,
         variant: &ty::VariantDef,
     ) -> DiagnosticBuilder<'tcx> {
         let tcx = self.tcx;
@@ -1215,7 +1227,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 ),
             );
             if plural == "" {
-                let input = unmentioned_fields.iter().map(|field| &field.name);
+                let input = unmentioned_fields.iter().map(|(_, field)| &field.name);
                 let suggested_name = find_best_match_for_name(input, ident.name, None);
                 if let Some(suggested_name) = suggested_name {
                     err.span_suggestion(
@@ -1232,7 +1244,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     // `smart_resolve_context_dependent_help`.
                     if suggested_name.to_ident_string().parse::<usize>().is_err() {
                         // We don't want to throw `E0027` in case we have thrown `E0026` for them.
-                        unmentioned_fields.retain(|&x| x.name != suggested_name);
+                        unmentioned_fields.retain(|&(_, x)| x.name != suggested_name);
                     }
                 }
             }
@@ -1300,17 +1312,77 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         None
     }
 
+    /// Returns a diagnostic reporting a struct pattern which is missing an `..` due to
+    /// inaccessible fields.
+    ///
+    /// ```ignore (diagnostic)
+    /// error: pattern requires `..` due to inaccessible fields
+    ///   --> src/main.rs:10:9
+    ///    |
+    /// LL |     let foo::Foo {} = foo::Foo::default();
+    ///    |         ^^^^^^^^^^^
+    ///    |
+    /// help: add a `..`
+    ///    |
+    /// LL |     let foo::Foo { .. } = foo::Foo::default();
+    ///    |                  ^^^^^^
+    /// ```
+    fn error_no_accessible_fields(
+        &self,
+        pat: &Pat<'_>,
+        fields: &'tcx [hir::FieldPat<'tcx>],
+    ) -> DiagnosticBuilder<'tcx> {
+        let mut err = self
+            .tcx
+            .sess
+            .struct_span_err(pat.span, "pattern requires `..` due to inaccessible fields");
+
+        if let Some(field) = fields.last() {
+            err.span_suggestion_verbose(
+                field.span.shrink_to_hi(),
+                "ignore the inaccessible and unused fields",
+                ", ..".to_string(),
+                Applicability::MachineApplicable,
+            );
+        } else {
+            let qpath_span = if let PatKind::Struct(qpath, ..) = &pat.kind {
+                qpath.span()
+            } else {
+                bug!("`error_no_accessible_fields` called on non-struct pattern");
+            };
+
+            // Shrink the span to exclude the `foo:Foo` in `foo::Foo { }`.
+            let span = pat.span.with_lo(qpath_span.shrink_to_hi().hi());
+            err.span_suggestion_verbose(
+                span,
+                "ignore the inaccessible and unused fields",
+                " { .. }".to_string(),
+                Applicability::MachineApplicable,
+            );
+        }
+        err
+    }
+
+    /// Returns a diagnostic reporting a struct pattern which does not mention some fields.
+    ///
+    /// ```ignore (diagnostic)
+    /// error[E0027]: pattern does not mention field `you_cant_use_this_field`
+    ///   --> src/main.rs:15:9
+    ///    |
+    /// LL |     let foo::Foo {} = foo::Foo::new();
+    ///    |         ^^^^^^^^^^^ missing field `you_cant_use_this_field`
+    /// ```
     fn error_unmentioned_fields(
         &self,
         pat: &Pat<'_>,
-        unmentioned_fields: &[Ident],
+        unmentioned_fields: &[(&ty::FieldDef, Ident)],
     ) -> DiagnosticBuilder<'tcx> {
         let field_names = if unmentioned_fields.len() == 1 {
-            format!("field `{}`", unmentioned_fields[0])
+            format!("field `{}`", unmentioned_fields[0].1)
         } else {
             let fields = unmentioned_fields
                 .iter()
-                .map(|name| format!("`{}`", name))
+                .map(|(_, name)| format!("`{}`", name))
                 .collect::<Vec<String>>()
                 .join(", ");
             format!("fields {}", fields)
diff --git a/src/test/ui/issues/issue-76077-1.fixed b/src/test/ui/issues/issue-76077-1.fixed
new file mode 100644
index 00000000000..8103a7ca47d
--- /dev/null
+++ b/src/test/ui/issues/issue-76077-1.fixed
@@ -0,0 +1,18 @@
+// run-rustfix
+#![allow(dead_code, unused_variables)]
+
+pub mod foo {
+    #[derive(Default)]
+    pub struct Foo { invisible: bool, }
+
+    #[derive(Default)]
+    pub struct Bar { pub visible: bool, invisible: bool, }
+}
+
+fn main() {
+    let foo::Foo { .. } = foo::Foo::default();
+    //~^ ERROR pattern requires `..` due to inaccessible fields
+
+    let foo::Bar { visible, .. } = foo::Bar::default();
+    //~^ ERROR pattern requires `..` due to inaccessible fields
+}
diff --git a/src/test/ui/issues/issue-76077-1.rs b/src/test/ui/issues/issue-76077-1.rs
new file mode 100644
index 00000000000..730332853c1
--- /dev/null
+++ b/src/test/ui/issues/issue-76077-1.rs
@@ -0,0 +1,18 @@
+// run-rustfix
+#![allow(dead_code, unused_variables)]
+
+pub mod foo {
+    #[derive(Default)]
+    pub struct Foo { invisible: bool, }
+
+    #[derive(Default)]
+    pub struct Bar { pub visible: bool, invisible: bool, }
+}
+
+fn main() {
+    let foo::Foo {} = foo::Foo::default();
+    //~^ ERROR pattern requires `..` due to inaccessible fields
+
+    let foo::Bar { visible } = foo::Bar::default();
+    //~^ ERROR pattern requires `..` due to inaccessible fields
+}
diff --git a/src/test/ui/issues/issue-76077-1.stderr b/src/test/ui/issues/issue-76077-1.stderr
new file mode 100644
index 00000000000..4557595529f
--- /dev/null
+++ b/src/test/ui/issues/issue-76077-1.stderr
@@ -0,0 +1,24 @@
+error: pattern requires `..` due to inaccessible fields
+  --> $DIR/issue-76077-1.rs:13:9
+   |
+LL |     let foo::Foo {} = foo::Foo::default();
+   |         ^^^^^^^^^^^
+   |
+help: ignore the inaccessible and unused fields
+   |
+LL |     let foo::Foo { .. } = foo::Foo::default();
+   |                  ^^^^^^
+
+error: pattern requires `..` due to inaccessible fields
+  --> $DIR/issue-76077-1.rs:16:9
+   |
+LL |     let foo::Bar { visible } = foo::Bar::default();
+   |         ^^^^^^^^^^^^^^^^^^^^
+   |
+help: ignore the inaccessible and unused fields
+   |
+LL |     let foo::Bar { visible, .. } = foo::Bar::default();
+   |                           ^^^^
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/issues/issue-76077.rs b/src/test/ui/issues/issue-76077.rs
new file mode 100644
index 00000000000..1ecd37de2e1
--- /dev/null
+++ b/src/test/ui/issues/issue-76077.rs
@@ -0,0 +1,10 @@
+pub mod foo {
+    pub struct Foo {
+        you_cant_use_this_field: bool,
+    }
+}
+
+fn main() {
+    foo::Foo {};
+    //~^ ERROR cannot construct `Foo` with struct literal syntax due to inaccessible fields
+}
diff --git a/src/test/ui/issues/issue-76077.stderr b/src/test/ui/issues/issue-76077.stderr
new file mode 100644
index 00000000000..d834ec5e0ed
--- /dev/null
+++ b/src/test/ui/issues/issue-76077.stderr
@@ -0,0 +1,8 @@
+error: cannot construct `Foo` with struct literal syntax due to inaccessible fields
+  --> $DIR/issue-76077.rs:8:5
+   |
+LL |     foo::Foo {};
+   |     ^^^^^^^^
+
+error: aborting due to previous error
+