@@ -24,6 +24,7 @@ use rustc_ast::MacroDef;
2424use rustc_ast::visit::{VisitorResult, try_visit};
2525use rustc_data_structures::fx::FxHashSet;
2626use rustc_data_structures::intern::Interned;
27+ use rustc_errors::MultiSpan;
2728use rustc_hir::def::{DefKind, Res};
2829use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId, LocalModDefId};
2930use rustc_hir::intravisit::{self, Visitor};
@@ -38,7 +39,7 @@ use rustc_middle::ty::{
3839use rustc_middle::{bug, span_bug};
3940use rustc_session::lint;
4041use rustc_span::hygiene::Transparency;
41- use rustc_span::{Ident, Span, kw, sym};
42+ use rustc_span::{Ident, Span, Symbol, kw, sym};
4243use tracing::debug;
4344use {rustc_attr_parsing as attr, rustc_hir as hir};
4445
@@ -921,37 +922,95 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
921922 &mut self,
922923 hir_id: hir::HirId, // ID of the field use
923924 use_ctxt: Span, // syntax context of the field name at the use site
924- span: Span, // span of the field pattern, e.g., `x: 0`
925925 def: ty::AdtDef<'tcx>, // definition of the struct or enum
926926 field: &'tcx ty::FieldDef,
927- in_update_syntax: bool,
928- struct_span: Span,
929- ) {
927+ ) -> bool {
930928 if def.is_enum() {
931- return;
929+ return true ;
932930 }
933931
934932 // definition of the field
935933 let ident = Ident::new(kw::Empty, use_ctxt);
936934 let def_id = self.tcx.adjust_ident_and_get_scope(ident, def.did(), hir_id).1;
937- if !field.vis.is_accessible_from(def_id, self.tcx) {
938- self.tcx.dcx().emit_err(FieldIsPrivate {
939- span,
940- struct_span: if self.tcx.sess.source_map().is_multiline(span.between(struct_span)) {
941- Some(struct_span)
942- } else {
943- None
944- },
945- field_name: field.name,
946- variant_descr: def.variant_descr(),
947- def_path_str: self.tcx.def_path_str(def.did()),
948- label: if in_update_syntax {
949- FieldIsPrivateLabel::IsUpdateSyntax { span, field_name: field.name }
950- } else {
951- FieldIsPrivateLabel::Other { span }
952- },
953- });
935+ !field.vis.is_accessible_from(def_id, self.tcx)
936+ }
937+
938+ // Checks that a field in a struct constructor (expression or pattern) is accessible.
939+ fn emit_unreachable_field_error(
940+ &mut self,
941+ fields: Vec<(Symbol, Span, bool /* field is present */)>,
942+ def: ty::AdtDef<'tcx>, // definition of the struct or enum
943+ update_syntax: Option<Span>,
944+ struct_span: Span,
945+ ) {
946+ if def.is_enum() || fields.is_empty() {
947+ return;
954948 }
949+
950+ // error[E0451]: fields `beta` and `gamma` of struct `Alpha` are private
951+ // --> $DIR/visibility.rs:18:13
952+ // |
953+ // LL | let _x = Alpha {
954+ // | ----- in this type # from `def`
955+ // LL | beta: 0,
956+ // | ^^^^^^^ private field # `fields.2` is `true`
957+ // LL | ..
958+ // | ^^ field `gamma` is private # `fields.2` is `false`
959+
960+ // Get the list of all private fields for the main message.
961+ let field_names: Vec<_> = fields.iter().map(|(name, _, _)| name).collect();
962+ let field_names = match &field_names[..] {
963+ [] => return,
964+ [name] => format!("`{name}`"),
965+ [fields @ .., last] => format!(
966+ "{} and `{last}`",
967+ fields.iter().map(|f| format!("`{f}`")).collect::<Vec<_>>().join(", "),
968+ ),
969+ };
970+ let span: MultiSpan = fields.iter().map(|(_, span, _)| *span).collect::<Vec<Span>>().into();
971+
972+ // Get the list of all private fields when pointing at the `..rest`.
973+ let rest_field_names: Vec<_> =
974+ fields.iter().filter(|(_, _, is_present)| !is_present).map(|(n, _, _)| n).collect();
975+ let rest_len = rest_field_names.len();
976+ let rest_field_names = match &rest_field_names[..] {
977+ [] => String::new(),
978+ [name] => format!("`{name}`"),
979+ [fields @ .., last] => format!(
980+ "{} and `{last}`",
981+ fields.iter().map(|f| format!("`{f}`")).collect::<Vec<_>>().join(", "),
982+ ),
983+ };
984+ // Get all the labels for each field or `..rest` in the primary MultiSpan.
985+ let labels = fields
986+ .iter()
987+ .filter(|(_, _, is_present)| *is_present)
988+ .map(|(_, span, _)| FieldIsPrivateLabel::Other { span: *span })
989+ .chain(update_syntax.iter().map(|span| FieldIsPrivateLabel::IsUpdateSyntax {
990+ span: *span,
991+ rest_field_names: rest_field_names.clone(),
992+ rest_len,
993+ }))
994+ .collect();
995+
996+ self.tcx.dcx().emit_err(FieldIsPrivate {
997+ span,
998+ struct_span: if self
999+ .tcx
1000+ .sess
1001+ .source_map()
1002+ .is_multiline(fields[0].1.between(struct_span))
1003+ {
1004+ Some(struct_span)
1005+ } else {
1006+ None
1007+ },
1008+ field_names: field_names.clone(),
1009+ variant_descr: def.variant_descr(),
1010+ def_path_str: self.tcx.def_path_str(def.did()),
1011+ labels,
1012+ len: fields.len(),
1013+ });
9551014 }
9561015
9571016 fn check_expanded_fields(
@@ -963,15 +1022,23 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
9631022 span: Span,
9641023 struct_span: Span,
9651024 ) {
1025+ let mut failed_fields = vec![];
9661026 for (vf_index, variant_field) in variant.fields.iter_enumerated() {
9671027 let field =
9681028 fields.iter().find(|f| self.typeck_results().field_index(f.hir_id) == vf_index);
9691029 let (hir_id, use_ctxt, span) = match field {
9701030 Some(field) => (field.hir_id, field.ident.span, field.span),
9711031 None => (hir_id, span, span),
9721032 };
973- self.check_field(hir_id, use_ctxt, span, adt, variant_field, true, struct_span);
1033+ if self.check_field(hir_id, use_ctxt, adt, variant_field) {
1034+ let name = match field {
1035+ Some(field) => field.ident.name,
1036+ None => variant_field.name,
1037+ };
1038+ failed_fields.push((name, span, field.is_some()));
1039+ }
9741040 }
1041+ self.emit_unreachable_field_error(failed_fields, adt, Some(span), struct_span);
9751042 }
9761043}
9771044
@@ -1017,19 +1084,15 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
10171084 );
10181085 }
10191086 hir::StructTailExpr::None => {
1087+ let mut failed_fields = vec![];
10201088 for field in fields {
1021- let (hir_id, use_ctxt, span ) = (field.hir_id, field.ident.span, field .span);
1089+ let (hir_id, use_ctxt) = (field.hir_id, field.ident.span);
10221090 let index = self.typeck_results().field_index(field.hir_id);
1023- self.check_field(
1024- hir_id,
1025- use_ctxt,
1026- span,
1027- adt,
1028- &variant.fields[index],
1029- false,
1030- qpath.span(),
1031- );
1091+ if self.check_field(hir_id, use_ctxt, adt, &variant.fields[index]) {
1092+ failed_fields.push((field.ident.name, field.ident.span, true));
1093+ }
10321094 }
1095+ self.emit_unreachable_field_error(failed_fields, adt, None, qpath.span());
10331096 }
10341097 }
10351098 }
@@ -1042,19 +1105,15 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
10421105 let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
10431106 let adt = self.typeck_results().pat_ty(pat).ty_adt_def().unwrap();
10441107 let variant = adt.variant_of_res(res);
1108+ let mut failed_fields = vec![];
10451109 for field in fields {
1046- let (hir_id, use_ctxt, span ) = (field.hir_id, field.ident.span, field .span);
1110+ let (hir_id, use_ctxt) = (field.hir_id, field.ident.span);
10471111 let index = self.typeck_results().field_index(field.hir_id);
1048- self.check_field(
1049- hir_id,
1050- use_ctxt,
1051- span,
1052- adt,
1053- &variant.fields[index],
1054- false,
1055- qpath.span(),
1056- );
1112+ if self.check_field(hir_id, use_ctxt, adt, &variant.fields[index]) {
1113+ failed_fields.push((field.ident.name, field.ident.span, true));
1114+ }
10571115 }
1116+ self.emit_unreachable_field_error(failed_fields, adt, None, qpath.span());
10581117 }
10591118
10601119 intravisit::walk_pat(self, pat);
0 commit comments