@@ -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+
86120impl < ' 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