diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 48bbd4d76ea5c..bf5bd744f6298 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -41,8 +41,7 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::InferOk; use rustc_middle::middle::stability; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; -use rustc_middle::ty::error::ExpectedFound; -use rustc_middle::ty::error::TypeError::{FieldMisMatch, Sorts}; +use rustc_middle::ty::error::TypeError::FieldMisMatch; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TypeFoldable}; use rustc_session::parse::feature_err; @@ -65,7 +64,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, expr: &'tcx hir::Expr<'tcx>, expected: Ty<'tcx>, - extend_err: impl Fn(&mut Diagnostic), + extend_err: impl FnMut(&mut Diagnostic), ) -> Ty<'tcx> { self.check_expr_meets_expectation_or_error(expr, ExpectHasType(expected), extend_err) } @@ -74,7 +73,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, - extend_err: impl Fn(&mut Diagnostic), + mut extend_err: impl FnMut(&mut Diagnostic), ) -> Ty<'tcx> { let expected_ty = expected.to_option(&self).unwrap_or(self.tcx.types.bool); let mut ty = self.check_expr_with_expectation(expr, expected); @@ -1480,10 +1479,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // re-link the regions that EIfEO can erase. self.demand_eqtype(span, adt_ty_hint, adt_ty); - let (substs, adt_kind, kind_name) = match adt_ty.kind() { - ty::Adt(adt, substs) => (substs, adt.adt_kind(), adt.variant_descr()), - _ => span_bug!(span, "non-ADT passed to check_expr_struct_fields"), + let ty::Adt(adt, substs) = adt_ty.kind() else { + span_bug!(span, "non-ADT passed to check_expr_struct_fields"); }; + let adt_kind = adt.adt_kind(); let mut remaining_fields = variant .fields @@ -1521,7 +1520,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }); } else { self.report_unknown_field( - adt_ty, variant, field, ast_fields, kind_name, expr_span, + adt_ty, + variant, + field, + ast_fields, + adt.variant_descr(), + expr_span, ); } @@ -1534,7 +1538,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Make sure the programmer specified correct number of fields. - if kind_name == "union" { + if adt_kind == AdtKind::Union { if ast_fields.len() != 1 { struct_span_err!( tcx.sess, @@ -1557,67 +1561,81 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // FIXME: We are currently creating two branches here in order to maintain // consistency. But they should be merged as much as possible. let fru_tys = if self.tcx.features().type_changing_struct_update { - let base_ty = self.check_expr(base_expr); - match adt_ty.kind() { - ty::Adt(adt, substs) if adt.is_struct() => { - match base_ty.kind() { - ty::Adt(base_adt, base_subs) if adt == base_adt => { - variant - .fields - .iter() - .map(|f| { - let fru_ty = self.normalize_associated_types_in( - expr_span, - self.field_ty(base_expr.span, f, base_subs), - ); - let ident = self - .tcx - .adjust_ident(f.ident(self.tcx), variant.def_id); - if let Some(_) = remaining_fields.remove(&ident) { - let target_ty = - self.field_ty(base_expr.span, f, substs); - let cause = self.misc(base_expr.span); - match self - .at(&cause, self.param_env) - .sup(target_ty, fru_ty) - { - Ok(InferOk { obligations, value: () }) => { - self.register_predicates(obligations) - } - // FIXME: Need better diagnostics for `FieldMisMatch` error - Err(_) => { - self.report_mismatched_types( - &cause, - target_ty, - fru_ty, - FieldMisMatch(variant.name, ident.name), - ) - .emit(); - } - } + if let ty::Adt(adt, substs) = adt_ty.kind() && adt.is_struct() { + // Make an ADT with fresh inference substitutions. This + // will allow us to guide inference along so that, e.g. + // ``` + // let x = MyStruct<'a, B, const C: usize> { + // f: 1, + // ..Default::default() + // }; + // ``` + // will have the default base expression constrained to + // `MyStruct<'_, _, _>`, as opposed to just `_`... This + // will allow us to then do a subtyping relation on all + // of the `remaining_fields` below, per the RFC. + let fresh_substs = self.fresh_substs_for_item(base_expr.span, adt.did()); + let fresh_base_ty = self.tcx.mk_adt(*adt, fresh_substs); + let base_ty = self.check_expr_has_type_or_error( + base_expr, + fresh_base_ty, + |_| { + error_happened = true; + }, + ); + let base_ty = self.shallow_resolve(base_ty); + if let ty::Adt(base_adt, base_substs) = base_ty.kind() && adt == base_adt { + variant + .fields + .iter() + .map(|f| { + let fru_ty = self.normalize_associated_types_in( + expr_span, + self.field_ty(base_expr.span, f, base_substs), + ); + let ident = self + .tcx + .adjust_ident(f.ident(self.tcx), variant.def_id); + if let Some(_) = remaining_fields.remove(&ident) { + let target_ty = + self.field_ty(base_expr.span, f, substs); + let cause = self.misc(base_expr.span); + match self + .at(&cause, self.param_env) + .sup(target_ty, fru_ty) + { + Ok(InferOk { obligations, value: () }) => { + self.register_predicates(obligations) } - fru_ty - }) - .collect() - } - _ => { - self.report_mismatched_types( - &self.misc(base_expr.span), - adt_ty, - base_ty, - Sorts(ExpectedFound::new(true, adt_ty, base_ty)), - ) - .emit(); - return; - } + // FIXME: Need better diagnostics for `FieldMisMatch` error + Err(_) => { + self.report_mismatched_types( + &cause, + target_ty, + fru_ty, + FieldMisMatch(variant.name, ident.name), + ) + .emit(); + } + } + } + self.resolve_vars_if_possible(fru_ty) + }) + .collect() + } else { + if !error_happened && !base_ty.references_error() { + span_bug!(base_expr.span, "expected an error to have been reported in `check_expr_has_type_or_error`"); } - } - _ => { - self.tcx - .sess - .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span }); return; } + } else { + // Check the base_expr, regardless of a bad expected adt_ty, so we can get + // type errors on that expression, too. + self.check_expr(base_expr); + self.tcx + .sess + .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span }); + return; } } else { self.check_expr_has_type_or_error(base_expr, adt_ty, |_| { @@ -1653,7 +1671,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys); - } else if kind_name != "union" && !remaining_fields.is_empty() { + } 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) }); diff --git a/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/issue-96878.rs b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/issue-96878.rs new file mode 100644 index 0000000000000..3dfbef0ee90fd --- /dev/null +++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/issue-96878.rs @@ -0,0 +1,31 @@ +// check-pass + +#![feature(type_changing_struct_update)] +#![allow(incomplete_features)] + +use std::borrow::Cow; +use std::marker::PhantomData; + +#[derive(Default)] +struct NonGeneric { + field1: usize, +} + +#[derive(Default)] +struct Generic { + field1: T, + field2: U, +} + +#[derive(Default)] +struct MoreGeneric<'a, const N: usize> { + // If only `for [u32; N]: Default`... + field1: PhantomData<[u32; N]>, + field2: Cow<'a, str>, +} + +fn main() { + let default1 = NonGeneric { ..Default::default() }; + let default2: Generic = Generic { ..Default::default() }; + let default3: MoreGeneric<'static, 12> = MoreGeneric { ..Default::default() }; +}