From 1e7ab0bbd772d4dab2a66a72af20f8b207c252ab Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 20 Jun 2022 16:29:05 +0900 Subject: [PATCH] point at private fields in struct literal --- Cargo.lock | 1 + compiler/rustc_typeck/Cargo.toml | 1 + compiler/rustc_typeck/src/check/expr.rs | 80 ++++++++++++++++--- src/test/ui/issues/issue-76077.rs | 2 +- src/test/ui/issues/issue-76077.stderr | 8 +- src/test/ui/privacy/issue-79593.rs | 2 +- src/test/ui/privacy/issue-79593.stderr | 8 +- ...7872-missing-inaccessible-field-literal.rs | 2 +- ...-missing-inaccessible-field-literal.stderr | 8 +- ...issing-private-fields-in-struct-literal.rs | 18 +++++ ...ng-private-fields-in-struct-literal.stderr | 23 ++++++ 11 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 src/test/ui/typeck/missing-private-fields-in-struct-literal.rs create mode 100644 src/test/ui/typeck/missing-private-fields-in-struct-literal.stderr diff --git a/Cargo.lock b/Cargo.lock index df6f46f26cf..cc0f4cc53e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4590,6 +4590,7 @@ dependencies = [ name = "rustc_typeck" version = "0.0.0" dependencies = [ + "itertools", "rustc_arena", "rustc_ast", "rustc_attr", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index c08023ee6a7..b3dd6955080 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] rustc_arena = { path = "../rustc_arena" } tracing = "0.1" +itertools = "0.10.1" rustc_macros = { path = "../rustc_macros" } rustc_middle = { path = "../rustc_middle" } rustc_attr = { path = "../rustc_attr" } diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index dc9d76160c4..0a017de80f2 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -23,13 +23,14 @@ use crate::type_error_struct; use super::suggest_call_constructor; use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive}; +use itertools::{Either, Itertools}; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stack::ensure_sufficient_stack; -use rustc_errors::Diagnostic; -use rustc_errors::EmissionGuarantee; -use rustc_errors::ErrorGuaranteed; -use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId}; +use rustc_errors::{ + pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, DiagnosticId, + EmissionGuarantee, ErrorGuaranteed, MultiSpan, +}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; @@ -1672,12 +1673,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys); } else if adt_kind != AdtKind::Union && !remaining_fields.is_empty() { - let inaccessible_remaining_fields = remaining_fields.iter().any(|(_, (_, field))| { - !field.vis.is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx) - }); + debug!(?remaining_fields); + let private_fields: Vec<&ty::FieldDef> = variant + .fields + .iter() + .filter(|field| { + !field.vis.is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx) + }) + .collect(); - if inaccessible_remaining_fields { - self.report_inaccessible_fields(adt_ty, span); + if !private_fields.is_empty() + && tcx + .visibility(variant.def_id) + .is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx) + { + self.report_private_fields(adt_ty, span, private_fields, ast_fields); } else { self.report_missing_fields( adt_ty, @@ -1801,7 +1811,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Report an error for a struct field expression when there are invisible fields. /// /// ```text - /// error: cannot construct `Foo` with struct literal syntax due to inaccessible fields + /// error: cannot construct `Foo` with struct literal syntax due to private fields /// --> src/main.rs:8:5 /// | /// 8 | foo::Foo {}; @@ -1809,13 +1819,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// error: aborting due to previous error /// ``` - fn report_inaccessible_fields(&self, adt_ty: Ty<'tcx>, span: Span) { - self.tcx.sess.span_err( + fn report_private_fields( + &self, + adt_ty: Ty<'tcx>, + span: Span, + private_fields: Vec<&ty::FieldDef>, + used_fields: &'tcx [hir::ExprField<'tcx>], + ) { + let field_names = |fields: Vec, len: usize| match &fields + .iter() + .map(|field| field.to_string()) + .collect::>()[..] + { + _ if len > 6 => String::new(), + [name] => format!("`{name}` "), + [names @ .., last] => { + let names = names.iter().map(|name| format!("`{name}`")).collect::>(); + format!("{} and `{last}` ", names.join(", ")) + } + [] => unreachable!(), + }; + + let mut err = self.tcx.sess.struct_span_err( span, &format!( - "cannot construct `{adt_ty}` with struct literal syntax due to inaccessible fields", + "cannot construct `{adt_ty}` with struct literal syntax due to private fields", ), ); + let (used_private_fields, remaining_private_fields): ( + Vec<(Symbol, Span)>, + Vec<(Symbol, Span)>, + ) = private_fields.iter().partition_map(|field| { + match used_fields.iter().find(|used_field| field.name == used_field.ident.name) { + Some(used_field) => Either::Left((field.name, used_field.span)), + None => Either::Right((field.name, self.tcx.def_span(field.did))), + } + }); + let remaining_private_fields_len = remaining_private_fields.len(); + err.span_labels(used_private_fields.iter().map(|(_, span)| *span), "private field"); + err.span_note( + MultiSpan::from_spans(remaining_private_fields.iter().map(|(_, span)| *span).collect()), + format!( + "missing field{s} {names}{are} private", + s = pluralize!(remaining_private_fields_len), + are = pluralize!("is", remaining_private_fields_len), + names = field_names( + remaining_private_fields.iter().map(|(name, _)| *name).collect(), + remaining_private_fields_len + ) + ), + ); + err.emit(); } fn report_unknown_field( diff --git a/src/test/ui/issues/issue-76077.rs b/src/test/ui/issues/issue-76077.rs index 1ecd37de2e1..2d29093b01b 100644 --- a/src/test/ui/issues/issue-76077.rs +++ b/src/test/ui/issues/issue-76077.rs @@ -6,5 +6,5 @@ pub mod foo { fn main() { foo::Foo {}; - //~^ ERROR cannot construct `Foo` with struct literal syntax due to inaccessible fields + //~^ ERROR cannot construct `Foo` with struct literal syntax due to private fields } diff --git a/src/test/ui/issues/issue-76077.stderr b/src/test/ui/issues/issue-76077.stderr index d834ec5e0ed..c70a928f647 100644 --- a/src/test/ui/issues/issue-76077.stderr +++ b/src/test/ui/issues/issue-76077.stderr @@ -1,8 +1,14 @@ -error: cannot construct `Foo` with struct literal syntax due to inaccessible fields +error: cannot construct `Foo` with struct literal syntax due to private fields --> $DIR/issue-76077.rs:8:5 | LL | foo::Foo {}; | ^^^^^^^^ + | +note: missing field `you_cant_use_this_field` is private + --> $DIR/issue-76077.rs:3:9 + | +LL | you_cant_use_this_field: bool, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to previous error diff --git a/src/test/ui/privacy/issue-79593.rs b/src/test/ui/privacy/issue-79593.rs index b94278bfdd2..39c222f7c34 100644 --- a/src/test/ui/privacy/issue-79593.rs +++ b/src/test/ui/privacy/issue-79593.rs @@ -16,7 +16,7 @@ mod foo { fn correct() { foo::Pub {}; - //~^ ERROR cannot construct `Pub` with struct literal syntax due to inaccessible fields + //~^ ERROR cannot construct `Pub` with struct literal syntax due to private fields } fn wrong() { diff --git a/src/test/ui/privacy/issue-79593.stderr b/src/test/ui/privacy/issue-79593.stderr index b8c7d4f23a2..435d4cbf735 100644 --- a/src/test/ui/privacy/issue-79593.stderr +++ b/src/test/ui/privacy/issue-79593.stderr @@ -10,11 +10,17 @@ error[E0063]: missing field `y` in initializer of `Enum` LL | Enum::Variant { x: () }; | ^^^^^^^^^^^^^ missing `y` -error: cannot construct `Pub` with struct literal syntax due to inaccessible fields +error: cannot construct `Pub` with struct literal syntax due to private fields --> $DIR/issue-79593.rs:18:5 | LL | foo::Pub {}; | ^^^^^^^^ + | +note: missing field `private` is private + --> $DIR/issue-79593.rs:2:22 + | +LL | pub struct Pub { private: () } + | ^^^^^^^^^^^ error[E0063]: missing field `y` in initializer of `Enum` --> $DIR/issue-79593.rs:23:5 diff --git a/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.rs b/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.rs index 31761441337..326e958aaa9 100644 --- a/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.rs +++ b/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.rs @@ -7,5 +7,5 @@ pub mod foo { fn main() { foo::Foo {}; - //~^ ERROR cannot construct `Foo` with struct literal syntax due to inaccessible fields + //~^ ERROR cannot construct `Foo` with struct literal syntax due to private fields } diff --git a/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.stderr b/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.stderr index 81b73c00e86..2ade7aea57b 100644 --- a/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.stderr +++ b/src/test/ui/typeck/issue-87872-missing-inaccessible-field-literal.stderr @@ -1,8 +1,14 @@ -error: cannot construct `Foo` with struct literal syntax due to inaccessible fields +error: cannot construct `Foo` with struct literal syntax due to private fields --> $DIR/issue-87872-missing-inaccessible-field-literal.rs:9:5 | LL | foo::Foo {}; | ^^^^^^^^ + | +note: missing field `you_cant_use_this_field` is private + --> $DIR/issue-87872-missing-inaccessible-field-literal.rs:4:9 + | +LL | you_cant_use_this_field: bool, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to previous error diff --git a/src/test/ui/typeck/missing-private-fields-in-struct-literal.rs b/src/test/ui/typeck/missing-private-fields-in-struct-literal.rs new file mode 100644 index 00000000000..9f1560bfb8d --- /dev/null +++ b/src/test/ui/typeck/missing-private-fields-in-struct-literal.rs @@ -0,0 +1,18 @@ +pub mod m { + pub struct S { + pub visible: bool, + a: (), + b: (), + c: (), + d: (), + e: (), + } +} + +fn main() { + let _ = m::S { //~ ERROR cannot construct `S` with struct literal syntax due to private fields + visible: true, + a: (), + b: (), + }; +} diff --git a/src/test/ui/typeck/missing-private-fields-in-struct-literal.stderr b/src/test/ui/typeck/missing-private-fields-in-struct-literal.stderr new file mode 100644 index 00000000000..eb5f460f868 --- /dev/null +++ b/src/test/ui/typeck/missing-private-fields-in-struct-literal.stderr @@ -0,0 +1,23 @@ +error: cannot construct `S` with struct literal syntax due to private fields + --> $DIR/missing-private-fields-in-struct-literal.rs:13:13 + | +LL | let _ = m::S { + | ^^^^ +LL | visible: true, +LL | a: (), + | ----- private field +LL | b: (), + | ----- private field + | +note: missing fields `c`, `d` and `e` are private + --> $DIR/missing-private-fields-in-struct-literal.rs:6:9 + | +LL | c: (), + | ^^^^^ +LL | d: (), + | ^^^^^ +LL | e: (), + | ^^^^^ + +error: aborting due to previous error +