about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2021-11-09 19:00:41 +0100
committerGitHub <noreply@github.com>2021-11-09 19:00:41 +0100
commit610b4e503ccc5cb6d4ef98bb5016ba42eaf94522 (patch)
treec8db226e573691cc2ffcd379127dc226d48bd564
parentfd74c93403c455187c343f3828274824addc9881 (diff)
parent926892ddc0b75b50f5d0a3483d829e501aa8e895 (diff)
downloadrust-610b4e503ccc5cb6d4ef98bb5016ba42eaf94522.tar.gz
rust-610b4e503ccc5cb6d4ef98bb5016ba42eaf94522.zip
Rollup merge of #90035 - SparrowLii:rfc2528, r=jackh726
implement rfc-2528 type_changing-struct-update

This PR implement rfc2528-type_changing-struct-update.
The main change process is as follows:
1. Move the processing part of `base_expr` into `check_expr_struct_fields` to avoid returning `remaining_fields` (a relatively complex hash table)
2. Before performing the type consistency check(`check_expr_has_type_or_error`), if the `type_changing_struct_update` feature is set, enter a different processing flow, otherwise keep the original flow
3. In the case of the same structure definition, check each field in `remaining_fields`. If the field in `base_expr` is not the suptype of the field in `adt_ty`, an error(`FeildMisMatch`) will be reported.

The MIR part does not need to be changed, because only the items contained in `remaining_fields` will be extracted from `base_expr` when MIR is generated. This means that fields with different types in `base_expr` will not be used
Updates #86618
cc `@nikomatsakis`
-rw-r--r--compiler/rustc_middle/src/ty/error.rs3
-rw-r--r--compiler/rustc_middle/src/ty/structural_impls.rs1
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs159
-rw-r--r--src/doc/unstable-book/src/language-features/type-changing-struct-update.md33
-rw-r--r--src/test/ui/feature-gates/feature-gate-type_changing_struct_update.stderr12
-rw-r--r--src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.rs (renamed from src/test/ui/feature-gates/feature-gate-type_changing_struct_update.rs)7
-rw-r--r--src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.stderr22
-rw-r--r--src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.rs43
-rw-r--r--src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.stderr15
-rw-r--r--src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.rs57
-rw-r--r--src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.stderr30
11 files changed, 328 insertions, 54 deletions
diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs
index 2bd9415171d..b14a6989265 100644
--- a/compiler/rustc_middle/src/ty/error.rs
+++ b/compiler/rustc_middle/src/ty/error.rs
@@ -42,6 +42,7 @@ pub enum TypeError<'tcx> {
     TupleSize(ExpectedFound<usize>),
     FixedArraySize(ExpectedFound<u64>),
     ArgCount,
+    FieldMisMatch(Symbol, Symbol),
 
     RegionsDoesNotOutlive(Region<'tcx>, Region<'tcx>),
     RegionsInsufficientlyPolymorphic(BoundRegionKind, Region<'tcx>),
@@ -134,6 +135,7 @@ impl<'tcx> fmt::Display for TypeError<'tcx> {
                 pluralize!(values.found)
             ),
             ArgCount => write!(f, "incorrect number of function parameters"),
+            FieldMisMatch(adt, field) => write!(f, "field type mismatch: {}.{}", adt, field),
             RegionsDoesNotOutlive(..) => write!(f, "lifetime mismatch"),
             RegionsInsufficientlyPolymorphic(br, _) => write!(
                 f,
@@ -224,6 +226,7 @@ impl<'tcx> TypeError<'tcx> {
             | ArgumentMutability(_)
             | TupleSize(_)
             | ArgCount
+            | FieldMisMatch(..)
             | RegionsDoesNotOutlive(..)
             | RegionsInsufficientlyPolymorphic(..)
             | RegionsOverlyPolymorphic(..)
diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs
index d6069395474..0f8e80806e3 100644
--- a/compiler/rustc_middle/src/ty/structural_impls.rs
+++ b/compiler/rustc_middle/src/ty/structural_impls.rs
@@ -602,6 +602,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::error::TypeError<'a> {
             TupleSize(x) => TupleSize(x),
             FixedArraySize(x) => FixedArraySize(x),
             ArgCount => ArgCount,
+            FieldMisMatch(x, y) => FieldMisMatch(x, y),
             RegionsDoesNotOutlive(a, b) => {
                 return tcx.lift((a, b)).map(|(a, b)| RegionsDoesNotOutlive(a, b));
             }
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 93e292394bd..a9c0b65a098 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -34,12 +34,16 @@ use rustc_hir::intravisit::Visitor;
 use rustc_hir::{ExprKind, QPath};
 use rustc_infer::infer;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
+use rustc_infer::infer::InferOk;
 use rustc_middle::ty;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
+use rustc_middle::ty::error::TypeError::{FieldMisMatch, Sorts};
+use rustc_middle::ty::relate::expected_found_bool;
 use rustc_middle::ty::subst::SubstsRef;
 use rustc_middle::ty::Ty;
 use rustc_middle::ty::TypeFoldable;
 use rustc_middle::ty::{AdtKind, Visibility};
+use rustc_session::parse::feature_err;
 use rustc_span::edition::LATEST_STABLE_EDITION;
 use rustc_span::hygiene::DesugaringKind;
 use rustc_span::lev_distance::find_best_match_for_name;
@@ -1283,49 +1287,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 .emit_err(StructExprNonExhaustive { span: expr.span, what: adt.variant_descr() });
         }
 
-        let error_happened = self.check_expr_struct_fields(
+        self.check_expr_struct_fields(
             adt_ty,
             expected,
             expr.hir_id,
             qpath.span(),
             variant,
             fields,
-            base_expr.is_none(),
+            base_expr,
             expr.span,
         );
-        if let Some(base_expr) = base_expr {
-            // If check_expr_struct_fields hit an error, do not attempt to populate
-            // the fields with the base_expr. This could cause us to hit errors later
-            // when certain fields are assumed to exist that in fact do not.
-            if !error_happened {
-                self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {});
-                match adt_ty.kind() {
-                    ty::Adt(adt, substs) if adt.is_struct() => {
-                        let fru_field_types = adt
-                            .non_enum_variant()
-                            .fields
-                            .iter()
-                            .map(|f| {
-                                self.normalize_associated_types_in(
-                                    expr.span,
-                                    f.ty(self.tcx, substs),
-                                )
-                            })
-                            .collect();
-
-                        self.typeck_results
-                            .borrow_mut()
-                            .fru_field_types_mut()
-                            .insert(expr.hir_id, fru_field_types);
-                    }
-                    _ => {
-                        self.tcx
-                            .sess
-                            .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
-                    }
-                }
-            }
-        }
+
         self.require_type_is_sized(adt_ty, expr.span, traits::StructInitializerSized);
         adt_ty
     }
@@ -1338,9 +1310,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         span: Span,
         variant: &'tcx ty::VariantDef,
         ast_fields: &'tcx [hir::ExprField<'tcx>],
-        check_completeness: bool,
+        base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
         expr_span: Span,
-    ) -> bool {
+    ) {
         let tcx = self.tcx;
 
         let adt_ty_hint = self
@@ -1415,7 +1387,116 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 )
                 .emit();
             }
-        } else if check_completeness && !error_happened && !remaining_fields.is_empty() {
+        }
+
+        // If check_expr_struct_fields hit an error, do not attempt to populate
+        // the fields with the base_expr. This could cause us to hit errors later
+        // when certain fields are assumed to exist that in fact do not.
+        if error_happened {
+            return;
+        }
+
+        if let Some(base_expr) = base_expr {
+            // 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, 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.ident.name,
+                                                            ident.name,
+                                                        ),
+                                                    )
+                                                    .emit(),
+                                            }
+                                        }
+                                        fru_ty
+                                    })
+                                    .collect()
+                            }
+                            _ => {
+                                return self
+                                    .report_mismatched_types(
+                                        &self.misc(base_expr.span),
+                                        adt_ty,
+                                        base_ty,
+                                        Sorts(expected_found_bool(true, adt_ty, base_ty)),
+                                    )
+                                    .emit();
+                            }
+                        }
+                    }
+                    _ => {
+                        return self
+                            .tcx
+                            .sess
+                            .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
+                    }
+                }
+            } else {
+                self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {
+                    let base_ty = self.check_expr(base_expr);
+                    let same_adt = match (adt_ty.kind(), base_ty.kind()) {
+                        (ty::Adt(adt, _), ty::Adt(base_adt, _)) if adt == base_adt => true,
+                        _ => false,
+                    };
+                    if self.tcx.sess.is_nightly_build() && same_adt {
+                        feature_err(
+                            &self.tcx.sess.parse_sess,
+                            sym::type_changing_struct_update,
+                            base_expr.span,
+                            "type changing struct updating is experimental",
+                        )
+                        .emit();
+                    }
+                });
+                match adt_ty.kind() {
+                    ty::Adt(adt, substs) if adt.is_struct() => variant
+                        .fields
+                        .iter()
+                        .map(|f| {
+                            self.normalize_associated_types_in(expr_span, f.ty(self.tcx, substs))
+                        })
+                        .collect(),
+                    _ => {
+                        return self
+                            .tcx
+                            .sess
+                            .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
+                    }
+                }
+            };
+            self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys);
+        } else if kind_name != "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)
             });
@@ -1426,8 +1507,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 self.report_missing_fields(adt_ty, span, remaining_fields);
             }
         }
-
-        error_happened
     }
 
     fn check_struct_fields_on_error(
diff --git a/src/doc/unstable-book/src/language-features/type-changing-struct-update.md b/src/doc/unstable-book/src/language-features/type-changing-struct-update.md
new file mode 100644
index 00000000000..9909cf35b5b
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/type-changing-struct-update.md
@@ -0,0 +1,33 @@
+# `type_changing_struct_update`
+
+The tracking issue for this feature is: [#86555]
+
+[#86555]: https://github.com/rust-lang/rust/issues/86555
+
+------------------------
+
+This implements [RFC2528]. When turned on, you can create instances of the same struct
+that have different generic type or lifetime parameters.
+
+[RFC2528]: https://github.com/rust-lang/rfcs/blob/master/text/2528-type-changing-struct-update-syntax.md
+
+```rust
+#![allow(unused_variables, dead_code)]
+#![feature(type_changing_struct_update)]
+
+fn main () {
+    struct Foo<T, U> {
+        field1: T,
+        field2: U,
+    }
+
+    let base: Foo<String, i32> = Foo {
+        field1: String::from("hello"),
+        field2: 1234,
+    };
+    let updated: Foo<f64, i32> = Foo {
+        field1: 3.14,
+        ..base
+    };
+}
+```
diff --git a/src/test/ui/feature-gates/feature-gate-type_changing_struct_update.stderr b/src/test/ui/feature-gates/feature-gate-type_changing_struct_update.stderr
deleted file mode 100644
index 9934fe68164..00000000000
--- a/src/test/ui/feature-gates/feature-gate-type_changing_struct_update.stderr
+++ /dev/null
@@ -1,12 +0,0 @@
-error[E0308]: mismatched types
-  --> $DIR/feature-gate-type_changing_struct_update.rs:20:11
-   |
-LL |         ..m1
-   |           ^^ expected struct `State2`, found struct `State1`
-   |
-   = note: expected struct `Machine<State2>`
-              found struct `Machine<State1>`
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/feature-gates/feature-gate-type_changing_struct_update.rs b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.rs
index 520c1478f32..1e8b99ba564 100644
--- a/src/test/ui/feature-gates/feature-gate-type_changing_struct_update.rs
+++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.rs
@@ -1,3 +1,5 @@
+// gate-test-type_changing_struct_update
+
 #[derive(Debug)]
 struct Machine<S> {
     state: S,
@@ -17,9 +19,10 @@ fn update_to_state2() {
     };
     let m2: Machine<State2> = Machine {
         state: State2,
-        ..m1 //~ ERROR mismatched types
+        ..m1
+        //~^ ERROR type changing struct updating is experimental [E0658]
+        //~| ERROR mismatched types [E0308]
     };
-    // FIXME: this should trigger feature gate
     assert_eq!(State2, m2.state);
 }
 
diff --git a/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.stderr b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.stderr
new file mode 100644
index 00000000000..2217b8c0498
--- /dev/null
+++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.stderr
@@ -0,0 +1,22 @@
+error[E0658]: type changing struct updating is experimental
+  --> $DIR/feature-gate.rs:22:11
+   |
+LL |         ..m1
+   |           ^^
+   |
+   = note: see issue #86555 <https://github.com/rust-lang/rust/issues/86555> for more information
+   = help: add `#![feature(type_changing_struct_update)]` to the crate attributes to enable
+
+error[E0308]: mismatched types
+  --> $DIR/feature-gate.rs:22:11
+   |
+LL |         ..m1
+   |           ^^ expected struct `State2`, found struct `State1`
+   |
+   = note: expected struct `Machine<State2>`
+              found struct `Machine<State1>`
+
+error: aborting due to 2 previous errors
+
+Some errors have detailed explanations: E0308, E0658.
+For more information about an error, try `rustc --explain E0308`.
diff --git a/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.rs b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.rs
new file mode 100644
index 00000000000..df2fef55dd2
--- /dev/null
+++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.rs
@@ -0,0 +1,43 @@
+#![feature(type_changing_struct_update)]
+#![allow(incomplete_features)]
+
+#[derive(Clone)]
+struct Machine<'a, S> {
+    state: S,
+    lt_str: &'a str,
+    common_field: i32,
+}
+
+#[derive(Clone)]
+struct State1;
+#[derive(Clone)]
+struct State2;
+
+fn update_to_state2() {
+    let s = String::from("hello");
+    let m1: Machine<State1> = Machine {
+        state: State1,
+        lt_str: &s,
+                //~^ ERROR `s` does not live long enough [E0597]
+                // FIXME: The error here actually comes from line 34. The
+                // span of the error message should be corrected to line 34
+        common_field: 2,
+    };
+    // update lifetime
+    let m3: Machine<'static, State1> = Machine {
+        lt_str: "hello, too",
+        ..m1.clone()
+    };
+    // update lifetime and type
+    let m4: Machine<'static, State2> = Machine {
+        state: State2,
+        lt_str: "hello, again",
+        ..m1.clone()
+    };
+    // updating to `static should fail.
+    let m2: Machine<'static, State1> = Machine {
+        ..m1
+    };
+}
+
+fn main() {}
diff --git a/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.stderr b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.stderr
new file mode 100644
index 00000000000..5f93ad6e027
--- /dev/null
+++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/lifetime-update.stderr
@@ -0,0 +1,15 @@
+error[E0597]: `s` does not live long enough
+  --> $DIR/lifetime-update.rs:20:17
+   |
+LL |         lt_str: &s,
+   |                 ^^ borrowed value does not live long enough
+...
+LL |     let m2: Machine<'static, State1> = Machine {
+   |             ------------------------ type annotation requires that `s` is borrowed for `'static`
+...
+LL | }
+   | - `s` dropped here while still borrowed
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0597`.
diff --git a/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.rs b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.rs
new file mode 100644
index 00000000000..d8b1396a692
--- /dev/null
+++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.rs
@@ -0,0 +1,57 @@
+#![feature(type_changing_struct_update)]
+#![allow(incomplete_features)]
+
+struct Machine<'a, S, M> {
+    state: S,
+    message: M,
+    lt_str: &'a str,
+    common_field: i32,
+}
+
+struct State1;
+struct State2;
+
+struct Message1;
+struct Message2;
+
+fn update() {
+    let m1: Machine<State1, Message1> = Machine {
+        state: State1,
+        message: Message1,
+        lt_str: "hello",
+        common_field: 2,
+    };
+    // single type update
+    let m2: Machine<State2, Message1> = Machine {
+        state: State2,
+        ..m1
+    };
+    // multiple type update
+    let m3: Machine<State2, Message2> = Machine {
+        state: State2,
+        message: Message2,
+        ..m1
+    };
+}
+
+fn fail_update() {
+    let m1: Machine<f64, f64> = Machine {
+        state: 3.2,
+        message: 6.4,
+        lt_str: "hello",
+        common_field: 2,
+    };
+    // single type update fail
+    let m2: Machine<i32, f64> = Machine {
+        ..m1
+        //~^ ERROR mismatched types [E0308]
+    };
+    // multiple type update fail
+    let m3 = Machine::<i32, i32> {
+        ..m1
+        //~^ ERROR mismatched types [E0308]
+        //~| ERROR mismatched types [E0308]
+    };
+}
+
+fn main() {}
diff --git a/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.stderr b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.stderr
new file mode 100644
index 00000000000..fa8d6ee23d5
--- /dev/null
+++ b/src/test/ui/rfcs/rfc-2528-type-changing-struct-update/type-generic-update.stderr
@@ -0,0 +1,30 @@
+error[E0308]: mismatched types
+  --> $DIR/type-generic-update.rs:46:11
+   |
+LL |         ..m1
+   |           ^^ field type mismatch: Machine.state
+   |
+   = note: expected type `i32`
+              found type `f64`
+
+error[E0308]: mismatched types
+  --> $DIR/type-generic-update.rs:51:11
+   |
+LL |         ..m1
+   |           ^^ field type mismatch: Machine.state
+   |
+   = note: expected type `i32`
+              found type `f64`
+
+error[E0308]: mismatched types
+  --> $DIR/type-generic-update.rs:51:11
+   |
+LL |         ..m1
+   |           ^^ field type mismatch: Machine.message
+   |
+   = note: expected type `i32`
+              found type `f64`
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0308`.