From 5d66c563a2ad4fbbc6c5655b4a0b664acb2db25f Mon Sep 17 00:00:00 2001 From: Frank King Date: Sat, 24 Feb 2024 21:57:34 +0800 Subject: [PATCH] Check struct patterns with unnamed fields --- compiler/rustc_hir_analysis/src/collect.rs | 25 +- compiler/rustc_hir_typeck/messages.ftl | 5 + compiler/rustc_hir_typeck/src/errors.rs | 18 ++ compiler/rustc_hir_typeck/src/expr.rs | 44 ++-- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 33 +++ compiler/rustc_hir_typeck/src/lib.rs | 1 + compiler/rustc_hir_typeck/src/pat.rs | 221 +++++++++++++++--- compiler/rustc_middle/src/query/mod.rs | 4 - compiler/rustc_middle/src/ty/mod.rs | 11 + .../rustc_middle/src/ty/typeck_results.rs | 115 ++++++++- ...d_access.bar.SimplifyCfg-initial.after.mir | 2 +- ...d_access.foo.SimplifyCfg-initial.after.mir | 2 +- tests/mir-opt/unnamed-fields/field_access.rs | 14 +- tests/ui/union/union-fields-2.stderr | 22 ++ .../union/unnamed-fields/pat_field_check.rs | 78 +++++++ .../unnamed-fields/pat_field_check.stderr | 71 ++++++ 16 files changed, 570 insertions(+), 96 deletions(-) create mode 100644 tests/ui/union/unnamed-fields/pat_field_check.rs create mode 100644 tests/ui/union/unnamed-fields/pat_field_check.stderr diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index e9c9ec6ba53f8..c69eb240075a9 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -31,7 +31,6 @@ use rustc_middle::ty::util::{Discr, IntTypeExt}; use rustc_middle::ty::{self, AdtKind, Const, IsSuggestable, ToPredicate, Ty, TyCtxt}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::Span; -use rustc_target::abi::FieldIdx; use rustc_target::spec::abi; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName; @@ -84,7 +83,6 @@ pub fn provide(providers: &mut Providers) { coroutine_for_closure, collect_mod_item_types, is_type_alias_impl_trait, - find_field, ..*providers }; } @@ -790,18 +788,6 @@ fn convert_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) { } } -fn find_field(tcx: TyCtxt<'_>, (def_id, ident): (DefId, Ident)) -> Option { - tcx.adt_def(def_id).non_enum_variant().fields.iter_enumerated().find_map(|(idx, field)| { - if field.is_unnamed() { - let field_ty = tcx.type_of(field.did).instantiate_identity(); - let adt_def = field_ty.ty_adt_def().expect("expect Adt for unnamed field"); - tcx.find_field((adt_def.did(), ident)).map(|_| idx) - } else { - (field.ident(tcx).normalize_to_macros_2_0() == ident).then_some(idx) - } - }) -} - #[derive(Clone, Copy)] struct NestedSpan { span: Span, @@ -899,15 +885,8 @@ impl<'tcx> FieldUniquenessCheckContext<'tcx> { for field in adt_def.all_fields() { if field.is_unnamed() { // Here we don't care about the generic parameters, so `instantiate_identity` is enough. - match self.tcx.type_of(field.did).instantiate_identity().kind() { - ty::Adt(adt_def, _) => { - self.check_field_in_nested_adt(*adt_def, unnamed_field_span); - } - ty_kind => span_bug!( - self.tcx.def_span(field.did), - "Unexpected TyKind in FieldUniquenessCheckContext::check_field_in_nested_adt(): {ty_kind:?}" - ), - } + let adt_def = field.nested_adt_def(self.tcx); + self.check_field_in_nested_adt(adt_def, unnamed_field_span); } else { self.check_field_decl( field.ident(self.tcx), diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl index 220da19a29dc8..f415bdaf7f028 100644 --- a/compiler/rustc_hir_typeck/messages.ftl +++ b/compiler/rustc_hir_typeck/messages.ftl @@ -138,6 +138,11 @@ hir_typeck_trivial_cast = trivial {$numeric -> }: `{$expr_ty}` as `{$cast_ty}` .help = cast can be replaced by coercion; this might require a temporary variable +hir_typeck_union_pat_absent = there are no fields from this union + .label = union defined here + +hir_typeck_union_pat_conflict = there are multiple fields from this union + hir_typeck_union_pat_dotdot = `..` cannot be used in union patterns hir_typeck_union_pat_multiple_fields = union patterns should have exactly one field diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index f609d0f7e8f5d..4eb99cf423c53 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -412,11 +412,29 @@ pub struct ConstSelectMustBeFn<'a> { pub ty: Ty<'a>, } +#[derive(Subdiagnostic)] +#[note(hir_typeck_union_pat_absent)] +pub struct UnionPatAbsent { + #[primary_span] + pub span: MultiSpan, +} + +#[derive(Subdiagnostic)] +#[note(hir_typeck_union_pat_conflict)] +pub struct UnionPatConflict { + #[primary_span] + pub span: MultiSpan, +} + #[derive(Diagnostic)] #[diag(hir_typeck_union_pat_multiple_fields)] pub struct UnionPatMultipleFields { #[primary_span] pub span: Span, + #[subdiagnostic] + pub conflict_set: Vec, + #[subdiagnostic] + pub absent_set: Vec, } #[derive(Diagnostic)] diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 81440b0562e24..845011aadcd29 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -2363,39 +2363,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let body_hir_id = self.tcx.local_def_id_to_hir_id(self.body_id); let (ident, def_scope) = self.tcx.adjust_ident_and_get_scope(field, base_def.did(), body_hir_id); - let mut adt_def = *base_def; - let mut last_ty = None; - let mut nested_fields = Vec::new(); - let mut index = None; - while let Some(idx) = self.tcx.find_field((adt_def.did(), ident)) { - let &mut first_idx = index.get_or_insert(idx); - let field = &adt_def.non_enum_variant().fields[idx]; - let field_ty = self.field_ty(expr.span, field, args); - if let Some(ty) = last_ty { - nested_fields.push((ty, idx)); - } - if field.ident(self.tcx).normalize_to_macros_2_0() == ident { - // Save the index of all fields regardless of their visibility in case - // of error recovery. - self.write_field_index(expr.hir_id, first_idx, nested_fields); - let adjustments = self.adjust_steps(&autoderef); - if field.vis.is_accessible_from(def_scope, self.tcx) { - self.apply_adjustments(base, adjustments); - self.register_predicates(autoderef.into_obligations()); + if let Some((first_idx, nested_fields, field, field_ty)) = + self.lookup_ident(base_def.non_enum_variant(), ident, args, expr.span) + { + // Save the index of all fields regardless of their visibility in case + // of error recovery. + self.write_field_index(expr.hir_id, first_idx, nested_fields); + let adjustments = self.adjust_steps(&autoderef); + if field.vis.is_accessible_from(def_scope, self.tcx) { + self.apply_adjustments(base, adjustments); + self.register_predicates(autoderef.into_obligations()); - self.tcx.check_stability( - field.did, - Some(expr.hir_id), - expr.span, - None, - ); - return field_ty; - } - private_candidate = Some((adjustments, base_def.did())); - break; + self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None); + return field_ty; } - last_ty = Some(field_ty); - adt_def = field_ty.ty_adt_def().expect("expect Adt for unnamed field"); + private_candidate = Some((adjustments, base_def.did())); } } ty::Tuple(tys) => { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 9303e437a968e..7408a520af4d0 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -157,6 +157,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + pub fn lookup_ident( + &self, + variant: &'tcx ty::VariantDef, + ident: Ident, + args: GenericArgsRef<'tcx>, + span: Span, + ) -> Option<( + // The outer-most field index + FieldIdx, + // The nested fields, see `TypeckResults::nested_fields` + Vec<(Ty<'tcx>, FieldIdx)>, + // The inner-most field def + &'tcx ty::FieldDef, + // The inner-most field type + Ty<'tcx>, + )> { + let path = self + .typeck_results + .borrow_mut() + .lookup_field(self.tcx, variant, ident) + .map(|(idx, field)| (idx, field, self.field_ty(span, field, args))) + .collect::>(); + match &path[..] { + [] => None, + &[(idx, field, ty)] => Some((idx, Vec::new(), field, ty)), + &[(idx, ..), .., (_, field, ty)] => { + let nested_fields = + path.array_windows().map(|&[(.., ty), (idx, ..)]| (ty, idx)).collect(); + Some((idx, nested_fields, field, ty)) + } + } + } + #[instrument(level = "debug", skip(self))] pub(in super::super) fn write_resolution( &self, diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 70ddd6b2f4cf5..051df7ac56512 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -5,6 +5,7 @@ #![feature(try_blocks)] #![feature(never_type)] #![feature(box_patterns)] +#![feature(array_windows)] #![cfg_attr(bootstrap, feature(min_specialization))] #![feature(control_flow_enum)] diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index b15c9ef901877..ee8a212b0b527 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -1,5 +1,5 @@ use crate::gather_locals::DeclOrigin; -use crate::{errors, FnCtxt, LoweredTy}; +use crate::{errors, FnCtxt, LoweredTy, TyCtxt}; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{ @@ -1359,13 +1359,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span_bug!(pat.span, "struct pattern is not an ADT"); }; - // Index the struct fields' types. - let field_map = variant - .fields - .iter_enumerated() - .map(|(i, field)| (field.ident(self.tcx).normalize_to_macros_2_0(), (i, field))) - .collect::>(); - // Keep track of which fields have already appeared in the pattern. let mut used_fields = FxHashMap::default(); let mut no_field_errors = true; @@ -1383,13 +1376,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } Vacant(vacant) => { vacant.insert(span); - field_map - .get(&ident) - .map(|(i, f)| { - // FIXME: handle nested fields - self.write_field_index(field.hir_id, *i, Vec::new()); + self.lookup_ident(variant, ident, args, span) + .map(|(idx, nested_fields, f, field_ty)| { + self.write_field_index(field.hir_id, idx, nested_fields); self.tcx.check_stability(f.did, Some(pat.hir_id), span, None); - self.field_ty(span, f, args) + field_ty }) .unwrap_or_else(|| { inexistent_fields.push(field); @@ -1402,12 +1393,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_pat(field.pat, field_ty, pat_info); } - let mut unmentioned_fields = variant - .fields - .iter() - .map(|field| (field, field.ident(self.tcx).normalize_to_macros_2_0())) - .filter(|(_, ident)| !used_fields.contains_key(ident)) - .collect::>(); + let (has_union, mut unmentioned_fields) = + PatFieldCheckCtxt::new(self.tcx, &used_fields, pat.span).do_check(*adt, variant); let inexistent_fields_err = if !(inexistent_fields.is_empty() || variant.is_recovered()) && !inexistent_fields.iter().any(|field| field.ident.name == kw::Underscore) @@ -1431,14 +1418,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let mut unmentioned_err = None; - // Report an error if an incorrect number of fields was specified. - if adt.is_union() { - if fields.len() != 1 { - tcx.dcx().emit_err(errors::UnionPatMultipleFields { span: pat.span }); - } - if has_rest_pat { - tcx.dcx().emit_err(errors::UnionPatDotDot { span: pat.span }); - } + // Report an error if `..` is not allowed. + if has_union && has_rest_pat { + tcx.dcx().emit_err(errors::UnionPatDotDot { span: pat.span }); } else if !unmentioned_fields.is_empty() { let accessible_unmentioned_fields: Vec<_> = unmentioned_fields .iter() @@ -2334,3 +2316,186 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } + +struct PatFieldCheckCtxt<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + pat_span: Span, + pat_fields: &'a FxHashMap, + unmentioned_fields: Vec<(&'tcx ty::FieldDef, Ident)>, + conflict_set: Vec, + absent_set: Vec, + has_union: bool, +} + +type FieldIter<'tcx> = std::iter::Take>>; + +impl<'tcx, 'a> PatFieldCheckCtxt<'tcx, 'a> { + pub fn new(tcx: TyCtxt<'tcx>, pat_fields: &'a FxHashMap, pat_span: Span) -> Self { + Self { + tcx, + pat_span, + pat_fields, + unmentioned_fields: Vec::new(), + conflict_set: Vec::new(), + absent_set: Vec::new(), + has_union: false, + } + } + + #[inline] + fn field_mentioned(&self, field: &'tcx ty::FieldDef) -> Result { + let ident = field.ident(self.tcx).normalize_to_macros_2_0(); + self.pat_fields.get_key_value(&ident).map(|(&ident, _)| ident).ok_or(ident) + } + + pub fn do_check( + mut self, + adt_def: ty::AdtDef<'tcx>, + variant: &'tcx ty::VariantDef, + ) -> (/* has_union */ bool, Vec<(&'tcx ty::FieldDef, Ident)>) { + match adt_def.adt_kind() { + ty::AdtKind::Enum | ty::AdtKind::Struct => self.check_struct_variant(variant, true), + ty::AdtKind::Union => self.check_union_variant(adt_def), + }; + if !self.conflict_set.is_empty() || !self.absent_set.is_empty() { + self.tcx.dcx().emit_err(errors::UnionPatMultipleFields { + span: self.pat_span, + conflict_set: self.conflict_set, + absent_set: self.absent_set, + }); + } + (self.has_union, self.unmentioned_fields) + } + + fn check_struct_variant( + &mut self, + variant: &'tcx ty::VariantDef, + collect_unmentioned: bool, + ) -> bool { + let mut has_mentioned = false; + for (idx, field) in variant.fields.iter_enumerated() { + if field.is_unnamed() { + let nested_adt_def = field.nested_adt_def(self.tcx); + has_mentioned |= if nested_adt_def.is_union() { + self.check_union_variant(nested_adt_def) + } else { + self.check_struct_variant( + nested_adt_def.non_enum_variant(), + collect_unmentioned, + ) + }; + } else if self.field_mentioned(field).is_ok() { + has_mentioned = true; + } else { + return self.collect_unmentioned( + variant.fields.iter().skip(idx.index()).take(usize::MAX), + collect_unmentioned, + ) || has_mentioned; + } + } + has_mentioned + } + + fn check_union_variant(&mut self, adt_def: ty::AdtDef<'tcx>) -> bool { + self.has_union = true; + let variant = adt_def.non_enum_variant(); + let mut mentioned = None; + for (idx, field) in variant.fields.iter_enumerated() { + let current_mentioned = if field.is_unnamed() { + let nested_adt_def = field.nested_adt_def(self.tcx); + if nested_adt_def.is_union() { + self.check_union_variant(nested_adt_def) + .then_some((idx, /* need_recheck */ false)) + } else { + // dummy check, does not collect the unmentioned fields. + // In case of the multiple fields from the union are used, + // we do not collect those fields to report unmentioned errors. + self.check_struct_variant(nested_adt_def.non_enum_variant(), false) + .then_some((idx, /* need_recheck */ true)) + } + } else { + self.field_mentioned(field).is_ok().then_some((idx, /* need_recheck */ false)) + }; + match (mentioned, current_mentioned) { + (None, Some(_)) => mentioned = current_mentioned, + (Some((idx, _)), Some(_)) => { + let mut span = MultiSpan::from_spans(self.collect_mentioned( + variant.fields.iter().skip(idx.index()).take(usize::MAX), + )); + span.push_span_label(self.tcx.def_span(adt_def.did()), "union defined here"); + + self.conflict_set.push(errors::UnionPatConflict { span }); + return true; + } + _ => {} + } + } + match mentioned { + Some((idx, _need_recheck @ true)) => { + // Check the only mentioned field of the union again, + // really collecting the unamed fields. + let field = &variant.fields[idx]; + if field.is_unnamed() { + self.check_struct_variant( + field.nested_adt_def(self.tcx).non_enum_variant(), + true, + ); + } + } + None if !variant.fields.is_empty() => { + let mut span = MultiSpan::new(); + span.push_span_label(self.tcx.def_span(adt_def.did()), "union defined here"); + self.absent_set.push(errors::UnionPatAbsent { span }); + } + _ => {} + } + mentioned.is_some() + } + + fn traverse_fields( + &mut self, + fields: FieldIter<'tcx>, + union_traverse_single: bool, + f: &mut impl FnMut(&mut Self, &'tcx ty::FieldDef), + ) { + for field in fields { + if field.is_unnamed() { + let nested_adt_def = field.nested_adt_def(self.tcx); + let nested_fields = &nested_adt_def.non_enum_variant().fields; + // For unions, we only traverse the first field + // to report the unmentioned fields. + let n = + if union_traverse_single && nested_adt_def.is_union() { 1 } else { usize::MAX }; + self.traverse_fields( + nested_fields.iter().skip(0).take(n), + union_traverse_single, + &mut *f, + ); + } else { + f(self, field); + } + } + } + + fn collect_mentioned(&mut self, fields: FieldIter<'tcx>) -> Vec { + let mut mentioned = Vec::new(); + self.traverse_fields(fields, false, &mut |this, field| { + if let Ok(pat_ident) = this.field_mentioned(field) { + mentioned.push(pat_ident.span); + } + }); + mentioned + } + + fn collect_unmentioned(&mut self, fields: FieldIter<'tcx>, do_collect: bool) -> bool { + let mut has_mentioned = false; + // For unions, we only collect all the nested fields in the first field + // to report the unmentioned fields. + self.traverse_fields(fields, true, &mut |this, field| match this.field_mentioned(field) { + Ok(_) => has_mentioned = true, + Err(def_ident) if do_collect => this.unmentioned_fields.push((field, def_ident)), + Err(_) => {} + }); + has_mentioned + } +} diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index e87bc581e6eab..9f7341bcc22d8 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -2243,10 +2243,6 @@ rustc_queries! { desc { "whether the item should be made inlinable across crates" } separate_provide_extern } - - query find_field((def_id, ident): (DefId, rustc_span::symbol::Ident)) -> Option { - desc { |tcx| "find the index of maybe nested field `{ident}` in `{}`", tcx.def_path_str(def_id) } - } } rustc_query_append! { define_callbacks! } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 30409e990e13c..9918a3245c297 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1409,6 +1409,17 @@ impl<'tcx> FieldDef { pub fn is_unnamed(&self) -> bool { self.name == rustc_span::symbol::kw::Underscore } + + /// Returns the definition of an anonymous ADT of this unnamed field. + pub fn nested_adt_def(&self, tcx: TyCtxt<'tcx>) -> ty::AdtDef<'tcx> { + assert!(self.is_unnamed(), "Expect an unnamed field to evaluate the nested ADT"); + match tcx.type_of(self.did).instantiate_identity().kind() { + ty::Adt(adt_def, ..) => *adt_def, + ty_kind => { + span_bug!(tcx.def_span(self.did), "Expect anonymous ADT but found: {ty_kind:?}") + } + } + } } #[derive(Debug, PartialEq, Eq)] diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index 4287b382604ee..72406ac3c6f05 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -4,7 +4,7 @@ use crate::{ traits::ObligationCause, ty::{ self, tls, BindingMode, BoundVar, CanonicalPolyFnSig, ClosureSizeProfileData, - GenericArgKind, GenericArgs, GenericArgsRef, Ty, UserArgs, + GenericArgKind, GenericArgs, GenericArgsRef, Ty, TyCtxt, UserArgs, }, }; use rustc_data_structures::{ @@ -23,7 +23,7 @@ use rustc_index::{Idx, IndexVec}; use rustc_macros::HashStable; use rustc_middle::mir::FakeReadCause; use rustc_session::Session; -use rustc_span::Span; +use rustc_span::{def_id::DefIdMap, symbol::Ident, Span}; use rustc_target::abi::{FieldIdx, VariantIdx}; use std::{collections::hash_map::Entry, hash::Hash, iter}; @@ -50,6 +50,11 @@ pub struct TypeckResults<'tcx> { /// `_(2).field`. nested_fields: ItemLocalMap, FieldIdx)>>, + /// Field map of variants. For ADTs with unnamed fields, the map contains all fields + /// including nested fields recursively, where the `FieldIdx` indicates the field of + /// the outer most (unnamed) field. + field_maps: DefIdMap>, + /// Stores the types for various nodes in the AST. Note that this table /// is not guaranteed to be populated outside inference. See /// typeck::check::fn_ctxt for details. @@ -221,6 +226,7 @@ impl<'tcx> TypeckResults<'tcx> { type_dependent_defs: Default::default(), field_indices: Default::default(), nested_fields: Default::default(), + field_maps: Default::default(), user_provided_types: Default::default(), user_provided_sigs: Default::default(), node_types: Default::default(), @@ -304,6 +310,40 @@ impl<'tcx> TypeckResults<'tcx> { self.nested_fields().get(id).map_or(&[], Vec::as_slice) } + pub fn get_or_eval_field_map( + &mut self, + tcx: TyCtxt<'tcx>, + variant: &'tcx ty::VariantDef, + ) -> &FxIndexMap { + if self.field_maps.contains_key(&variant.def_id) { + return self.field_maps.get(&variant.def_id).unwrap(); + } + let mut field_map = FxIndexMap::default(); + for (idx, f) in variant.fields.iter_enumerated() { + if f.is_unnamed() { + let field_ty = tcx.type_of(f.did).instantiate_identity(); + let ty::Adt(field_adt, _) = &field_ty.kind() else { + bug!("expect Adt but found {field_ty:?}") + }; + let nested_field_map = + self.get_or_eval_field_map(tcx, field_adt.non_enum_variant()); + field_map.extend(nested_field_map.keys().map(|&ident| (ident, idx))); + } else { + field_map.insert(f.ident(tcx).normalize_to_macros_2_0(), idx); + } + } + self.field_maps.entry(variant.def_id).or_insert(field_map) + } + + pub fn lookup_field( + &mut self, + tcx: TyCtxt<'tcx>, + variant: &'tcx ty::VariantDef, + ident: Ident, + ) -> LookupField<'tcx, '_> { + LookupField::new(self, tcx, variant, ident) + } + pub fn user_provided_types(&self) -> LocalTableInContext<'_, CanonicalUserType<'tcx>> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.user_provided_types } } @@ -690,3 +730,74 @@ impl<'tcx> std::fmt::Display for UserType<'tcx> { } } } + +/// An iterator for looking up a nested field in an ADT. +/// +/// Take the following code for example, +/// ``` +/// # #![feature(unnamed_fields)] +/// +/// #[repr(C)] +/// struct Foo { +/// a: i32, +/// _ /* _0 */: union /* {anon_adt#0} */ { +/// _ /* _1 */: struct /* {anon_adt#1} */ { +/// b: i64, +/// c: u8, +/// d: [u16; 2], +/// } +/// }, +/// } +/// ``` +/// If we would like to lookup the field `d` in `Foo`, this iterator will yield: +/// - `(1, {anon_adt#0})`, where `_` represents `_0`; +/// - `(0, {anon_adt#1})`, where `_` represents `_1`; +/// - `(2, [u16; 2])`. +/// +/// Note it also works for enums since the field maps is indxed by variant's `def_id`. +pub struct LookupField<'tcx, 'a> { + typeck_results: Option<&'a mut TypeckResults<'tcx>>, + tcx: TyCtxt<'tcx>, + variant: &'tcx ty::VariantDef, + ident: Ident, +} + +impl<'tcx, 'a> LookupField<'tcx, 'a> { + fn new( + typeck_results: &'a mut TypeckResults<'tcx>, + tcx: TyCtxt<'tcx>, + variant: &'tcx ty::VariantDef, + ident: Ident, + ) -> Self { + let typeck_results = Some(typeck_results); + Self { typeck_results, tcx, variant, ident } + } +} + +impl<'tcx> Iterator for LookupField<'tcx, '_> { + type Item = (FieldIdx, &'tcx ty::FieldDef); + + fn next(&mut self) -> Option { + let typeck_results = self.typeck_results.as_deref_mut()?; + let field_map = typeck_results.get_or_eval_field_map(self.tcx, self.variant); + let &idx = field_map.get(&self.ident)?; + let field = &self.variant.fields[idx]; + if field.ident(self.tcx).normalize_to_macros_2_0() == self.ident { + // Find the expected ident, stop the iteration. + self.typeck_results.take(); + return Some((idx, field)); + } + // Prepare for the next iteration. + // + // Note it is unreachable here for enums, since unnamed field can + // only have type of either struct or union. + self.variant = field.nested_adt_def(self.tcx).non_enum_variant(); + Some((idx, field)) + } + + fn size_hint(&self) -> (usize, Option) { + if self.typeck_results.is_some() { (0, None) } else { (0, Some(0)) } + } +} + +impl std::iter::FusedIterator for LookupField<'_, '_> {} diff --git a/tests/mir-opt/unnamed-fields/field_access.bar.SimplifyCfg-initial.after.mir b/tests/mir-opt/unnamed-fields/field_access.bar.SimplifyCfg-initial.after.mir index 8edc7b5df88f9..53b6e4cf1b243 100644 --- a/tests/mir-opt/unnamed-fields/field_access.bar.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/unnamed-fields/field_access.bar.SimplifyCfg-initial.after.mir @@ -44,7 +44,7 @@ fn bar(_1: Bar) -> () { StorageDead(_6); StorageLive(_8); StorageLive(_9); - _9 = (((_1.2: Bar::{anon_adt#1}).0: Bar::{anon_adt#1}::{anon_adt#0}).0: [u8; 1]); + _9 = (((_1.2: Bar::{anon_adt#1}).0: Nested).0: [u8; 1]); _8 = access::<[u8; 1]>(move _9) -> [return: bb4, unwind: bb5]; } diff --git a/tests/mir-opt/unnamed-fields/field_access.foo.SimplifyCfg-initial.after.mir b/tests/mir-opt/unnamed-fields/field_access.foo.SimplifyCfg-initial.after.mir index d48a969f06ebd..9d040b0c560c9 100644 --- a/tests/mir-opt/unnamed-fields/field_access.foo.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/unnamed-fields/field_access.foo.SimplifyCfg-initial.after.mir @@ -42,7 +42,7 @@ fn foo(_1: Foo) -> () { StorageDead(_6); StorageLive(_8); StorageLive(_9); - _9 = (((_1.2: Foo::{anon_adt#1}).0: Foo::{anon_adt#1}::{anon_adt#0}).0: [u8; 1]); + _9 = (((_1.2: Foo::{anon_adt#1}).0: Nested).0: [u8; 1]); _8 = access::<[u8; 1]>(move _9) -> [return: bb4, unwind: bb5]; } diff --git a/tests/mir-opt/unnamed-fields/field_access.rs b/tests/mir-opt/unnamed-fields/field_access.rs index 3d33ca26875ba..7f9ad4948c3cc 100644 --- a/tests/mir-opt/unnamed-fields/field_access.rs +++ b/tests/mir-opt/unnamed-fields/field_access.rs @@ -5,6 +5,12 @@ #![allow(incomplete_features)] #![feature(unnamed_fields)] +#[repr(C)] +#[derive(Clone, Copy)] +struct Nested { + d: [u8; 1], +} + #[repr(C)] struct Foo { a: u8, @@ -13,9 +19,7 @@ struct Foo { c: bool, }, _: struct { - _: struct { - d: [u8; 1], - } + _: Nested, } } @@ -27,9 +31,7 @@ union Bar { c: bool, }, _: union { - _: union { - d: [u8; 1], - } + _: Nested, } } diff --git a/tests/ui/union/union-fields-2.stderr b/tests/ui/union/union-fields-2.stderr index 142186885caa8..6ce28d864a139 100644 --- a/tests/ui/union/union-fields-2.stderr +++ b/tests/ui/union/union-fields-2.stderr @@ -41,18 +41,38 @@ error: union patterns should have exactly one field | LL | let U {} = u; | ^^^^ + | +note: there are no fields from this union error: union patterns should have exactly one field --> $DIR/union-fields-2.rs:17:9 | LL | let U { a, b } = u; | ^^^^^^^^^^ + | +note: there are multiple fields from this union + --> $DIR/union-fields-2.rs:17:13 + | +LL | union U { + | ------- union defined here +... +LL | let U { a, b } = u; + | ^ ^ error: union patterns should have exactly one field --> $DIR/union-fields-2.rs:18:9 | LL | let U { a, b, c } = u; | ^^^^^^^^^^^^^ + | +note: there are multiple fields from this union + --> $DIR/union-fields-2.rs:18:13 + | +LL | union U { + | ------- union defined here +... +LL | let U { a, b, c } = u; + | ^ ^ error[E0026]: union `U` does not have a field named `c` --> $DIR/union-fields-2.rs:18:19 @@ -65,6 +85,8 @@ error: union patterns should have exactly one field | LL | let U { .. } = u; | ^^^^^^^^ + | +note: there are no fields from this union error: `..` cannot be used in union patterns --> $DIR/union-fields-2.rs:20:9 diff --git a/tests/ui/union/unnamed-fields/pat_field_check.rs b/tests/ui/union/unnamed-fields/pat_field_check.rs new file mode 100644 index 0000000000000..7f6ca61bd4601 --- /dev/null +++ b/tests/ui/union/unnamed-fields/pat_field_check.rs @@ -0,0 +1,78 @@ +#![allow(incomplete_features)] +#![feature(unnamed_fields)] + +#[repr(C)] +#[derive(Clone, Copy)] +struct Foo { + x: (), + y: (), +} + +#[repr(C)] +#[derive(Clone, Copy)] +union Bar { + w: (), + z: (), +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct U { + a: (), + _: struct { + b: (), + c: (), + _: union { + d: (), + e: (), + }, + _: union {}, + }, + _: union { + f: (), + g: (), + _: struct { + i: (), + j: (), + }, + _: struct {}, + }, + _: Foo, + _: Bar, +} + +#[repr(C)] +#[derive(Clone, Copy)] +union V { + a: (), + _: struct { + b: (), + c: (), + _: union { + d: (), + e: (), + }, + _: union {}, + }, + _: union { + f: (), + g: (), + _: struct { + i: (), + j: (), + }, + _: struct {}, + }, + _: Foo, + _: Bar, +} + +fn case_u(u: U) { + let U { a, b, c, d, e, f, g, i, j, x, y, w, z } = u; //~ ERROR union patterns should have exactly one field +} + +fn case_v(v: V) { + let V { a, b, c, d, e, f, g, i, j, x, y, w, z } = v; //~ ERROR union patterns should have exactly one field +} + +fn main() {} diff --git a/tests/ui/union/unnamed-fields/pat_field_check.stderr b/tests/ui/union/unnamed-fields/pat_field_check.stderr new file mode 100644 index 0000000000000..0331ea06080bc --- /dev/null +++ b/tests/ui/union/unnamed-fields/pat_field_check.stderr @@ -0,0 +1,71 @@ +error: union patterns should have exactly one field + --> $DIR/pat_field_check.rs:71:9 + | +LL | let U { a, b, c, d, e, f, g, i, j, x, y, w, z } = u; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: there are multiple fields from this union + --> $DIR/pat_field_check.rs:71:22 + | +LL | _: union { + | ____________- +LL | | d: (), +LL | | e: (), +LL | | }, + | |_________- union defined here +... +LL | let U { a, b, c, d, e, f, g, i, j, x, y, w, z } = u; + | ^ ^ +note: there are multiple fields from this union + --> $DIR/pat_field_check.rs:71:28 + | +LL | _: union { + | ________- +LL | | f: (), +LL | | g: (), +LL | | _: struct { +... | +LL | | _: struct {}, +LL | | }, + | |_____- union defined here +... +LL | let U { a, b, c, d, e, f, g, i, j, x, y, w, z } = u; + | ^ ^ ^ ^ +note: there are multiple fields from this union + --> $DIR/pat_field_check.rs:71:46 + | +LL | union Bar { + | --------- union defined here +... +LL | let U { a, b, c, d, e, f, g, i, j, x, y, w, z } = u; + | ^ ^ + +error: union patterns should have exactly one field + --> $DIR/pat_field_check.rs:75:9 + | +LL | let V { a, b, c, d, e, f, g, i, j, x, y, w, z } = v; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: there are multiple fields from this union + --> $DIR/pat_field_check.rs:75:22 + | +LL | _: union { + | ____________- +LL | | d: (), +LL | | e: (), +LL | | }, + | |_________- union defined here +... +LL | let V { a, b, c, d, e, f, g, i, j, x, y, w, z } = v; + | ^ ^ +note: there are multiple fields from this union + --> $DIR/pat_field_check.rs:75:13 + | +LL | union V { + | ------- union defined here +... +LL | let V { a, b, c, d, e, f, g, i, j, x, y, w, z } = v; + | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + +error: aborting due to 2 previous errors +