Skip to content

Commit 810f309

Browse files
committed
MIR sanity check: validate types on assignment
1 parent 1a4e2b6 commit 810f309

File tree

1 file changed

+51
-3
lines changed

1 file changed

+51
-3
lines changed

src/librustc_mir/transform/validate.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_middle::{
77
BasicBlock, Body, Location, Operand, Rvalue, Statement, StatementKind, Terminator,
88
TerminatorKind,
99
},
10-
ty::{self, ParamEnv, TyCtxt},
10+
ty::{self, fold::BottomUpFolder, ParamEnv, Ty, TyCtxt, TypeFoldable},
1111
};
1212

1313
#[derive(Copy, Clone, Debug)]
@@ -83,6 +83,40 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
8383
}
8484
}
8585

86+
/// Check if src can be assigned into dest.
87+
/// This is not precise, it will accept some incorrect assignments.
88+
fn mir_assign_valid_types<'tcx>(tcx: TyCtxt<'tcx>, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool {
89+
if src == dest {
90+
// Equal types, all is good.
91+
return true;
92+
}
93+
94+
// Type-changing assignments can happen for (at least) two reasons:
95+
// 1. `&mut T` -> `&T` gets optimized from a reborrow to a mere assignment.
96+
// 2. Subtyping is used. While all normal lifetimes are erased, higher-ranked types
97+
// with their late-bound lifetimes are still around and can lead to type differences.
98+
// Normalize both of them away.
99+
// FIXME: Share this code with `interpret/eval_context.rs`.
100+
let normalize = |ty: Ty<'tcx>| {
101+
ty.fold_with(&mut BottomUpFolder {
102+
tcx,
103+
// Normalize all references to immutable.
104+
ty_op: |ty| match ty.kind {
105+
ty::Ref(_, pointee, _) => tcx.mk_imm_ref(tcx.lifetimes.re_erased, pointee),
106+
_ => ty,
107+
},
108+
// We just erase all late-bound lifetimes, but this is not fully correct (FIXME):
109+
// lifetimes in invariant positions could matter (e.g. through associated types).
110+
// But that just means we miss some potential incompatible types, it will not
111+
// lead to wrong errors.
112+
lt_op: |_| tcx.lifetimes.re_erased,
113+
// Leave consts unchanged.
114+
ct_op: |ct| ct,
115+
})
116+
};
117+
normalize(src) == normalize(dest)
118+
}
119+
86120
impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
87121
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
88122
// `Operand::Copy` is only supposed to be used with `Copy` types.
@@ -99,9 +133,23 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
99133
}
100134

101135
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
102-
// The sides of an assignment must not alias. Currently this just checks whether the places
103-
// are identical.
104136
if let StatementKind::Assign(box (dest, rvalue)) = &statement.kind {
137+
// LHS and RHS of the assignment must have the same type.
138+
let left_ty = dest.ty(&self.body.local_decls, self.tcx).ty;
139+
let right_ty = rvalue.ty(&self.body.local_decls, self.tcx);
140+
if !mir_assign_valid_types(self.tcx, right_ty, left_ty) {
141+
self.fail(
142+
location,
143+
format!(
144+
"encountered `Assign` statement with incompatible types:\n\
145+
left-hand side has type: {}\n\
146+
right-hand side has type: {}",
147+
left_ty, right_ty,
148+
),
149+
);
150+
}
151+
// The sides of an assignment must not alias. Currently this just checks whether the places
152+
// are identical.
105153
match rvalue {
106154
Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) => {
107155
if dest == src {

0 commit comments

Comments
 (0)