Skip to content

Commit 610b4e5

Browse files
authored
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`
2 parents fd74c93 + 926892d commit 610b4e5

File tree

11 files changed

+328
-54
lines changed

11 files changed

+328
-54
lines changed

compiler/rustc_middle/src/ty/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub enum TypeError<'tcx> {
4242
TupleSize(ExpectedFound<usize>),
4343
FixedArraySize(ExpectedFound<u64>),
4444
ArgCount,
45+
FieldMisMatch(Symbol, Symbol),
4546

4647
RegionsDoesNotOutlive(Region<'tcx>, Region<'tcx>),
4748
RegionsInsufficientlyPolymorphic(BoundRegionKind, Region<'tcx>),
@@ -134,6 +135,7 @@ impl<'tcx> fmt::Display for TypeError<'tcx> {
134135
pluralize!(values.found)
135136
),
136137
ArgCount => write!(f, "incorrect number of function parameters"),
138+
FieldMisMatch(adt, field) => write!(f, "field type mismatch: {}.{}", adt, field),
137139
RegionsDoesNotOutlive(..) => write!(f, "lifetime mismatch"),
138140
RegionsInsufficientlyPolymorphic(br, _) => write!(
139141
f,
@@ -224,6 +226,7 @@ impl<'tcx> TypeError<'tcx> {
224226
| ArgumentMutability(_)
225227
| TupleSize(_)
226228
| ArgCount
229+
| FieldMisMatch(..)
227230
| RegionsDoesNotOutlive(..)
228231
| RegionsInsufficientlyPolymorphic(..)
229232
| RegionsOverlyPolymorphic(..)

compiler/rustc_middle/src/ty/structural_impls.rs

+1
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::error::TypeError<'a> {
602602
TupleSize(x) => TupleSize(x),
603603
FixedArraySize(x) => FixedArraySize(x),
604604
ArgCount => ArgCount,
605+
FieldMisMatch(x, y) => FieldMisMatch(x, y),
605606
RegionsDoesNotOutlive(a, b) => {
606607
return tcx.lift((a, b)).map(|(a, b)| RegionsDoesNotOutlive(a, b));
607608
}

compiler/rustc_typeck/src/check/expr.rs

+119-40
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,16 @@ use rustc_hir::intravisit::Visitor;
3434
use rustc_hir::{ExprKind, QPath};
3535
use rustc_infer::infer;
3636
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
37+
use rustc_infer::infer::InferOk;
3738
use rustc_middle::ty;
3839
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
40+
use rustc_middle::ty::error::TypeError::{FieldMisMatch, Sorts};
41+
use rustc_middle::ty::relate::expected_found_bool;
3942
use rustc_middle::ty::subst::SubstsRef;
4043
use rustc_middle::ty::Ty;
4144
use rustc_middle::ty::TypeFoldable;
4245
use rustc_middle::ty::{AdtKind, Visibility};
46+
use rustc_session::parse::feature_err;
4347
use rustc_span::edition::LATEST_STABLE_EDITION;
4448
use rustc_span::hygiene::DesugaringKind;
4549
use rustc_span::lev_distance::find_best_match_for_name;
@@ -1283,49 +1287,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12831287
.emit_err(StructExprNonExhaustive { span: expr.span, what: adt.variant_descr() });
12841288
}
12851289

1286-
let error_happened = self.check_expr_struct_fields(
1290+
self.check_expr_struct_fields(
12871291
adt_ty,
12881292
expected,
12891293
expr.hir_id,
12901294
qpath.span(),
12911295
variant,
12921296
fields,
1293-
base_expr.is_none(),
1297+
base_expr,
12941298
expr.span,
12951299
);
1296-
if let Some(base_expr) = base_expr {
1297-
// If check_expr_struct_fields hit an error, do not attempt to populate
1298-
// the fields with the base_expr. This could cause us to hit errors later
1299-
// when certain fields are assumed to exist that in fact do not.
1300-
if !error_happened {
1301-
self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {});
1302-
match adt_ty.kind() {
1303-
ty::Adt(adt, substs) if adt.is_struct() => {
1304-
let fru_field_types = adt
1305-
.non_enum_variant()
1306-
.fields
1307-
.iter()
1308-
.map(|f| {
1309-
self.normalize_associated_types_in(
1310-
expr.span,
1311-
f.ty(self.tcx, substs),
1312-
)
1313-
})
1314-
.collect();
1315-
1316-
self.typeck_results
1317-
.borrow_mut()
1318-
.fru_field_types_mut()
1319-
.insert(expr.hir_id, fru_field_types);
1320-
}
1321-
_ => {
1322-
self.tcx
1323-
.sess
1324-
.emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
1325-
}
1326-
}
1327-
}
1328-
}
1300+
13291301
self.require_type_is_sized(adt_ty, expr.span, traits::StructInitializerSized);
13301302
adt_ty
13311303
}
@@ -1338,9 +1310,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13381310
span: Span,
13391311
variant: &'tcx ty::VariantDef,
13401312
ast_fields: &'tcx [hir::ExprField<'tcx>],
1341-
check_completeness: bool,
1313+
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
13421314
expr_span: Span,
1343-
) -> bool {
1315+
) {
13441316
let tcx = self.tcx;
13451317

13461318
let adt_ty_hint = self
@@ -1415,7 +1387,116 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14151387
)
14161388
.emit();
14171389
}
1418-
} else if check_completeness && !error_happened && !remaining_fields.is_empty() {
1390+
}
1391+
1392+
// If check_expr_struct_fields hit an error, do not attempt to populate
1393+
// the fields with the base_expr. This could cause us to hit errors later
1394+
// when certain fields are assumed to exist that in fact do not.
1395+
if error_happened {
1396+
return;
1397+
}
1398+
1399+
if let Some(base_expr) = base_expr {
1400+
// FIXME: We are currently creating two branches here in order to maintain
1401+
// consistency. But they should be merged as much as possible.
1402+
let fru_tys = if self.tcx.features().type_changing_struct_update {
1403+
let base_ty = self.check_expr(base_expr);
1404+
match adt_ty.kind() {
1405+
ty::Adt(adt, substs) if adt.is_struct() => {
1406+
match base_ty.kind() {
1407+
ty::Adt(base_adt, base_subs) if adt == base_adt => {
1408+
variant
1409+
.fields
1410+
.iter()
1411+
.map(|f| {
1412+
let fru_ty = self.normalize_associated_types_in(
1413+
expr_span,
1414+
self.field_ty(base_expr.span, f, base_subs),
1415+
);
1416+
let ident = self.tcx.adjust_ident(f.ident, variant.def_id);
1417+
if let Some(_) = remaining_fields.remove(&ident) {
1418+
let target_ty =
1419+
self.field_ty(base_expr.span, f, substs);
1420+
let cause = self.misc(base_expr.span);
1421+
match self
1422+
.at(&cause, self.param_env)
1423+
.sup(target_ty, fru_ty)
1424+
{
1425+
Ok(InferOk { obligations, value: () }) => {
1426+
self.register_predicates(obligations)
1427+
}
1428+
// FIXME: Need better diagnostics for `FieldMisMatch` error
1429+
Err(_) => self
1430+
.report_mismatched_types(
1431+
&cause,
1432+
target_ty,
1433+
fru_ty,
1434+
FieldMisMatch(
1435+
variant.ident.name,
1436+
ident.name,
1437+
),
1438+
)
1439+
.emit(),
1440+
}
1441+
}
1442+
fru_ty
1443+
})
1444+
.collect()
1445+
}
1446+
_ => {
1447+
return self
1448+
.report_mismatched_types(
1449+
&self.misc(base_expr.span),
1450+
adt_ty,
1451+
base_ty,
1452+
Sorts(expected_found_bool(true, adt_ty, base_ty)),
1453+
)
1454+
.emit();
1455+
}
1456+
}
1457+
}
1458+
_ => {
1459+
return self
1460+
.tcx
1461+
.sess
1462+
.emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
1463+
}
1464+
}
1465+
} else {
1466+
self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {
1467+
let base_ty = self.check_expr(base_expr);
1468+
let same_adt = match (adt_ty.kind(), base_ty.kind()) {
1469+
(ty::Adt(adt, _), ty::Adt(base_adt, _)) if adt == base_adt => true,
1470+
_ => false,
1471+
};
1472+
if self.tcx.sess.is_nightly_build() && same_adt {
1473+
feature_err(
1474+
&self.tcx.sess.parse_sess,
1475+
sym::type_changing_struct_update,
1476+
base_expr.span,
1477+
"type changing struct updating is experimental",
1478+
)
1479+
.emit();
1480+
}
1481+
});
1482+
match adt_ty.kind() {
1483+
ty::Adt(adt, substs) if adt.is_struct() => variant
1484+
.fields
1485+
.iter()
1486+
.map(|f| {
1487+
self.normalize_associated_types_in(expr_span, f.ty(self.tcx, substs))
1488+
})
1489+
.collect(),
1490+
_ => {
1491+
return self
1492+
.tcx
1493+
.sess
1494+
.emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
1495+
}
1496+
}
1497+
};
1498+
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys);
1499+
} else if kind_name != "union" && !remaining_fields.is_empty() {
14191500
let inaccessible_remaining_fields = remaining_fields.iter().any(|(_, (_, field))| {
14201501
!field.vis.is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx)
14211502
});
@@ -1426,8 +1507,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14261507
self.report_missing_fields(adt_ty, span, remaining_fields);
14271508
}
14281509
}
1429-
1430-
error_happened
14311510
}
14321511

14331512
fn check_struct_fields_on_error(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# `type_changing_struct_update`
2+
3+
The tracking issue for this feature is: [#86555]
4+
5+
[#86555]: https://github.com/rust-lang/rust/issues/86555
6+
7+
------------------------
8+
9+
This implements [RFC2528]. When turned on, you can create instances of the same struct
10+
that have different generic type or lifetime parameters.
11+
12+
[RFC2528]: https://github.com/rust-lang/rfcs/blob/master/text/2528-type-changing-struct-update-syntax.md
13+
14+
```rust
15+
#![allow(unused_variables, dead_code)]
16+
#![feature(type_changing_struct_update)]
17+
18+
fn main () {
19+
struct Foo<T, U> {
20+
field1: T,
21+
field2: U,
22+
}
23+
24+
let base: Foo<String, i32> = Foo {
25+
field1: String::from("hello"),
26+
field2: 1234,
27+
};
28+
let updated: Foo<f64, i32> = Foo {
29+
field1: 3.14,
30+
..base
31+
};
32+
}
33+
```

src/test/ui/feature-gates/feature-gate-type_changing_struct_update.stderr

-12
This file was deleted.

src/test/ui/feature-gates/feature-gate-type_changing_struct_update.rs renamed to src/test/ui/rfcs/rfc-2528-type-changing-struct-update/feature-gate.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// gate-test-type_changing_struct_update
2+
13
#[derive(Debug)]
24
struct Machine<S> {
35
state: S,
@@ -17,9 +19,10 @@ fn update_to_state2() {
1719
};
1820
let m2: Machine<State2> = Machine {
1921
state: State2,
20-
..m1 //~ ERROR mismatched types
22+
..m1
23+
//~^ ERROR type changing struct updating is experimental [E0658]
24+
//~| ERROR mismatched types [E0308]
2125
};
22-
// FIXME: this should trigger feature gate
2326
assert_eq!(State2, m2.state);
2427
}
2528

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error[E0658]: type changing struct updating is experimental
2+
--> $DIR/feature-gate.rs:22:11
3+
|
4+
LL | ..m1
5+
| ^^
6+
|
7+
= note: see issue #86555 <https://github.com/rust-lang/rust/issues/86555> for more information
8+
= help: add `#![feature(type_changing_struct_update)]` to the crate attributes to enable
9+
10+
error[E0308]: mismatched types
11+
--> $DIR/feature-gate.rs:22:11
12+
|
13+
LL | ..m1
14+
| ^^ expected struct `State2`, found struct `State1`
15+
|
16+
= note: expected struct `Machine<State2>`
17+
found struct `Machine<State1>`
18+
19+
error: aborting due to 2 previous errors
20+
21+
Some errors have detailed explanations: E0308, E0658.
22+
For more information about an error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#![feature(type_changing_struct_update)]
2+
#![allow(incomplete_features)]
3+
4+
#[derive(Clone)]
5+
struct Machine<'a, S> {
6+
state: S,
7+
lt_str: &'a str,
8+
common_field: i32,
9+
}
10+
11+
#[derive(Clone)]
12+
struct State1;
13+
#[derive(Clone)]
14+
struct State2;
15+
16+
fn update_to_state2() {
17+
let s = String::from("hello");
18+
let m1: Machine<State1> = Machine {
19+
state: State1,
20+
lt_str: &s,
21+
//~^ ERROR `s` does not live long enough [E0597]
22+
// FIXME: The error here actually comes from line 34. The
23+
// span of the error message should be corrected to line 34
24+
common_field: 2,
25+
};
26+
// update lifetime
27+
let m3: Machine<'static, State1> = Machine {
28+
lt_str: "hello, too",
29+
..m1.clone()
30+
};
31+
// update lifetime and type
32+
let m4: Machine<'static, State2> = Machine {
33+
state: State2,
34+
lt_str: "hello, again",
35+
..m1.clone()
36+
};
37+
// updating to `static should fail.
38+
let m2: Machine<'static, State1> = Machine {
39+
..m1
40+
};
41+
}
42+
43+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0597]: `s` does not live long enough
2+
--> $DIR/lifetime-update.rs:20:17
3+
|
4+
LL | lt_str: &s,
5+
| ^^ borrowed value does not live long enough
6+
...
7+
LL | let m2: Machine<'static, State1> = Machine {
8+
| ------------------------ type annotation requires that `s` is borrowed for `'static`
9+
...
10+
LL | }
11+
| - `s` dropped here while still borrowed
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0597`.

0 commit comments

Comments
 (0)