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`
This commit is contained in:
Matthias Krüger 2021-11-09 19:00:41 +01:00 committed by GitHub
commit 610b4e503c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 327 additions and 53 deletions

View File

@ -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(..)

View File

@ -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));
}

View File

@ -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(

View File

@ -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
};
}
```

View File

@ -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`.

View File

@ -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);
}

View File

@ -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`.

View File

@ -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() {}

View File

@ -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`.

View File

@ -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() {}

View File

@ -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`.