about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc/middle/ty/relate.rs10
-rw-r--r--src/librustc_typeck/check/_match.rs100
-rw-r--r--src/librustc_typeck/check/cast.rs4
-rw-r--r--src/librustc_typeck/check/coercion.rs267
-rw-r--r--src/librustc_typeck/check/demand.rs10
-rw-r--r--src/librustc_typeck/check/mod.rs110
-rw-r--r--src/test/compile-fail/fn-item-type.rs7
-rw-r--r--src/test/compile-fail/issue-13482-2.rs2
-rw-r--r--src/test/compile-fail/issue-17728.rs4
-rw-r--r--src/test/compile-fail/issue-2149.rs2
-rw-r--r--src/test/run-pass/coerce-unify.rs77
11 files changed, 422 insertions, 171 deletions
diff --git a/src/librustc/middle/ty/relate.rs b/src/librustc/middle/ty/relate.rs
index c85d0a1a90d..6da65c85f91 100644
--- a/src/librustc/middle/ty/relate.rs
+++ b/src/librustc/middle/ty/relate.rs
@@ -139,11 +139,11 @@ fn relate_item_substs<'a,'tcx:'a,R>(relation: &mut R,
     relate_substs(relation, opt_variances, a_subst, b_subst)
 }
 
-fn relate_substs<'a,'tcx:'a,R>(relation: &mut R,
-                               variances: Option<&ty::ItemVariances>,
-                               a_subst: &Substs<'tcx>,
-                               b_subst: &Substs<'tcx>)
-                               -> RelateResult<'tcx, Substs<'tcx>>
+pub fn relate_substs<'a,'tcx:'a,R>(relation: &mut R,
+                                   variances: Option<&ty::ItemVariances>,
+                                   a_subst: &Substs<'tcx>,
+                                   b_subst: &Substs<'tcx>)
+                                   -> RelateResult<'tcx, Substs<'tcx>>
     where R: TypeRelation<'a,'tcx>
 {
     let mut substs = Substs::empty();
diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs
index 548f5ee5e94..305970db9e7 100644
--- a/src/librustc_typeck/check/_match.rs
+++ b/src/librustc_typeck/check/_match.rs
@@ -15,9 +15,10 @@ use middle::pat_util::pat_is_resolved_const;
 use middle::subst::Substs;
 use middle::ty::{self, Ty, TypeFoldable, LvaluePreference};
 use check::{check_expr, check_expr_has_type, check_expr_with_expectation};
-use check::{check_expr_coercable_to_type, demand, FnCtxt, Expectation};
+use check::{demand, FnCtxt, Expectation};
 use check::{check_expr_with_lvalue_pref};
 use check::{instantiate_path, resolve_ty_and_def_ufcs, structurally_resolved_type};
+use check::coercion;
 use lint;
 use require_same_types;
 use util::nodemap::FnvHashMap;
@@ -492,54 +493,67 @@ pub fn check_match<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
     // of execution reach it, we will panic, so bottom is an appropriate
     // type in that case)
     let expected = expected.adjust_for_branches(fcx);
-    let result_ty = arms.iter().fold(fcx.infcx().next_diverging_ty_var(), |result_ty, arm| {
-        let bty = match expected {
-            // We don't coerce to `()` so that if the match expression is a
-            // statement it's branches can have any consistent type. That allows
-            // us to give better error messages (pointing to a usually better
-            // arm for inconsistent arms or to the whole match when a `()` type
-            // is required).
-            Expectation::ExpectHasType(ety) if ety != fcx.tcx().mk_nil() => {
-                check_expr_coercable_to_type(fcx, &arm.body, ety);
-                ety
-            }
-            _ => {
-                check_expr_with_expectation(fcx, &arm.body, expected);
-                fcx.node_ty(arm.body.id)
+    let mut result_ty = fcx.infcx().next_diverging_ty_var();
+    let coerce_first = match expected {
+        // We don't coerce to `()` so that if the match expression is a
+        // statement it's branches can have any consistent type. That allows
+        // us to give better error messages (pointing to a usually better
+        // arm for inconsistent arms or to the whole match when a `()` type
+        // is required).
+        Expectation::ExpectHasType(ety) if ety != fcx.tcx().mk_nil() => {
+            ety
+        }
+        _ => result_ty
+    };
+    for (i, arm) in arms.iter().enumerate() {
+        if let Some(ref e) = arm.guard {
+            check_expr_has_type(fcx, e, tcx.types.bool);
+        }
+        check_expr_with_expectation(fcx, &arm.body, expected);
+        let arm_ty = fcx.expr_ty(&arm.body);
+
+        if result_ty.references_error() || arm_ty.references_error() {
+            result_ty = tcx.types.err;
+            continue;
+        }
+
+        // Handle the fallback arm of a desugared if-let like a missing else.
+        let is_if_let_fallback = match match_src {
+            hir::MatchSource::IfLetDesugar { contains_else_clause: false } => {
+                i == arms.len() - 1 && arm_ty.is_nil()
             }
+            _ => false
         };
 
-        if let Some(ref e) = arm.guard {
-            check_expr_has_type(fcx, &e, tcx.types.bool);
-        }
+        let origin = if is_if_let_fallback {
+            TypeOrigin::IfExpressionWithNoElse(expr.span)
+        } else {
+            TypeOrigin::MatchExpressionArm(expr.span, arm.body.span, match_src)
+        };
 
-        if result_ty.references_error() || bty.references_error() {
-            tcx.types.err
+        let result = if is_if_let_fallback {
+            fcx.infcx().eq_types(true, origin, arm_ty, result_ty).map(|_| arm_ty)
+        } else if i == 0 {
+            // Special-case the first arm, as it has no "previous expressions".
+            coercion::try(fcx, &arm.body, coerce_first)
         } else {
-            let (origin, expected, found) = match match_src {
-                /* if-let construct without an else block */
-                hir::MatchSource::IfLetDesugar { contains_else_clause }
-                if !contains_else_clause => (
-                    TypeOrigin::IfExpressionWithNoElse(expr.span),
-                    bty,
-                    result_ty,
-                ),
-                _ => (
-                    TypeOrigin::MatchExpressionArm(expr.span, arm.body.span, match_src),
-                    result_ty,
-                    bty,
-                ),
-            };
+            let prev_arms = || arms[..i].iter().map(|arm| &*arm.body);
+            coercion::try_find_lub(fcx, origin, prev_arms, result_ty, &arm.body)
+        };
 
-            infer::common_supertype(
-                fcx.infcx(),
-                origin,
-                true,
-                expected,
-                found,
-            )
-        }
-    });
+        result_ty = match result {
+            Ok(ty) => ty,
+            Err(e) => {
+                let (expected, found) = if is_if_let_fallback {
+                    (arm_ty, result_ty)
+                } else {
+                    (result_ty, arm_ty)
+                };
+                fcx.infcx().report_mismatched_types(origin, expected, found, e);
+                fcx.tcx().types.err
+            }
+        };
+    }
 
     fcx.write_ty(expr.id, result_ty);
 }
diff --git a/src/librustc_typeck/check/cast.rs b/src/librustc_typeck/check/cast.rs
index 6468713d07a..b5cd5d7f8e5 100644
--- a/src/librustc_typeck/check/cast.rs
+++ b/src/librustc_typeck/check/cast.rs
@@ -240,7 +240,7 @@ impl<'tcx> CastCheck<'tcx> {
                 if let ty::TyFnDef(_, _, f) = self.expr_ty.sty {
                     // Attempt a coercion to a fn pointer type.
                     let res = coercion::try(fcx, self.expr,
-                        self.expr_ty, fcx.tcx().mk_ty(ty::TyFnPtr(f)));
+                                            fcx.tcx().mk_ty(ty::TyFnPtr(f)));
                     if !res.is_ok() {
                         return Err(CastError::NonScalar);
                     }
@@ -390,7 +390,7 @@ impl<'tcx> CastCheck<'tcx> {
     }
 
     fn try_coercion_cast<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) -> bool {
-        coercion::try(fcx, self.expr, self.expr_ty, self.cast_ty).is_ok()
+        coercion::try(fcx, self.expr, self.cast_ty).is_ok()
     }
 
 }
diff --git a/src/librustc_typeck/check/coercion.rs b/src/librustc_typeck/check/coercion.rs
index 42aaea9db5f..aa359c95e2d 100644
--- a/src/librustc_typeck/check/coercion.rs
+++ b/src/librustc_typeck/check/coercion.rs
@@ -62,7 +62,7 @@
 
 use check::{autoderef, FnCtxt, UnresolvedTypeAction};
 
-use middle::infer::{self, Coercion, TypeOrigin};
+use middle::infer::{Coercion, TypeOrigin, TypeTrace};
 use middle::traits::{self, ObligationCause};
 use middle::traits::{predicate_for_trait_def, report_selection_error};
 use middle::ty::adjustment::{AutoAdjustment, AutoDerefRef, AdjustDerefRef};
@@ -71,7 +71,7 @@ use middle::ty::adjustment::{AdjustUnsafeFnPointer, AdjustMutToConstPointer};
 use middle::ty::{self, LvaluePreference, TypeAndMut, Ty, TyCtxt};
 use middle::ty::fold::TypeFoldable;
 use middle::ty::error::TypeError;
-use middle::ty::relate::RelateResult;
+use middle::ty::relate::{relate_substs, RelateResult, TypeRelation};
 use util::common::indent;
 
 use std::cell::RefCell;
@@ -80,17 +80,30 @@ use rustc_front::hir;
 
 struct Coerce<'a, 'tcx: 'a> {
     fcx: &'a FnCtxt<'a, 'tcx>,
-    origin: infer::TypeOrigin,
+    origin: TypeOrigin,
+    use_lub: bool,
     unsizing_obligations: RefCell<Vec<traits::PredicateObligation<'tcx>>>,
 }
 
-type CoerceResult<'tcx> = RelateResult<'tcx, Option<AutoAdjustment<'tcx>>>;
+type CoerceResult<'tcx> = RelateResult<'tcx, (Ty<'tcx>, AutoAdjustment<'tcx>)>;
+
+fn coerce_mutbls<'tcx>(from_mutbl: hir::Mutability,
+                       to_mutbl: hir::Mutability)
+                       -> RelateResult<'tcx, ()> {
+    match (from_mutbl, to_mutbl) {
+        (hir::MutMutable, hir::MutMutable) |
+        (hir::MutImmutable, hir::MutImmutable) |
+        (hir::MutMutable, hir::MutImmutable) => Ok(()),
+        (hir::MutImmutable, hir::MutMutable) => Err(TypeError::Mutability)
+    }
+}
 
 impl<'f, 'tcx> Coerce<'f, 'tcx> {
-    fn new(fcx: &'a FnCtxt<'a, 'tcx>, origin: TypeOrigin) -> Self {
+    fn new(fcx: &'f FnCtxt<'f, 'tcx>, origin: TypeOrigin) -> Self {
         Coerce {
             fcx: fcx,
             origin: origin,
+            use_lub: false,
             unsizing_obligations: RefCell::new(vec![])
         }
     }
@@ -99,9 +112,26 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
         self.fcx.tcx()
     }
 
-    fn subtype(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> {
-        try!(self.fcx.infcx().sub_types(false, self.origin.clone(), a, b));
-        Ok(None) // No coercion required.
+    /// Unify two types (using sub or lub) and produce a noop coercion.
+    fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> {
+        let infcx = self.fcx.infcx();
+        infcx.commit_if_ok(|_| {
+            let trace = TypeTrace::types(self.origin, false, a, b);
+            if self.use_lub {
+                infcx.lub(false, trace).relate(&a, &b)
+            } else {
+                infcx.sub(false, trace).relate(&a, &b)
+            }
+        }).and_then(|ty| self.identity(ty))
+    }
+
+    /// Synthesize an identity adjustment.
+    fn identity(&self, ty: Ty<'tcx>) -> CoerceResult<'tcx> {
+        Ok((ty, AdjustDerefRef(AutoDerefRef {
+            autoderefs: 0,
+            autoref: None,
+            unsize: None
+        })))
     }
 
     fn coerce<'a, E, I>(&self,
@@ -118,7 +148,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
 
         // Just ignore error types.
         if a.references_error() || b.references_error() {
-            return Ok(None);
+            return self.identity(b);
         }
 
         // Consider coercing the subtype to a DST
@@ -137,7 +167,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
             }
 
             ty::TyRef(_, mt_b) => {
-                return self.coerce_borrowed_pointer(expr_a, a, b, mt_b.mutbl);
+                return self.coerce_borrowed_pointer(exprs, a, b, mt_b.mutbl);
             }
 
             _ => {}
@@ -156,8 +186,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
                 self.coerce_from_fn_pointer(a, a_f, b)
             }
             _ => {
-                // Otherwise, just use subtyping rules.
-                self.subtype(a, b)
+                // Otherwise, just use unification rules.
+                self.unify(a, b)
             }
         }
     }
@@ -166,7 +196,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
     /// To match `A` with `B`, autoderef will be performed,
     /// calling `deref`/`deref_mut` where necessary.
     fn coerce_borrowed_pointer<'a, E, I>(&self,
-                                         span: Span,
                                          exprs: &E,
                                          a: Ty<'tcx>,
                                          b: Ty<'tcx>,
@@ -188,7 +217,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
             ty::TyRef(_, mt_a) => {
                 try!(coerce_mutbls(mt_a.mutbl, mutbl_b));
             }
-            _ => return self.subtype(a, b)
+            _ => return self.unify(a, b)
         }
 
         let span = self.origin.span();
@@ -210,19 +239,20 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
             }
             let ty = self.tcx().mk_ref(r_borrow,
                                         TypeAndMut {ty: inner_ty, mutbl: mutbl_b});
-            if let Err(err) = self.subtype(ty, b) {
-                if first_error.is_none() {
-                    first_error = Some(err);
+            match self.unify(ty, b) {
+                Err(err) => {
+                    if first_error.is_none() {
+                        first_error = Some(err);
+                    }
+                    None
                 }
-                None
-            } else {
-                Some(())
+                Ok((ty, _)) => Some(ty)
             }
         });
 
         match success {
-            Some(_) => {
-                Ok(Some(AdjustDerefRef(AutoDerefRef {
+            Some(ty) => {
+                Ok((ty, AdjustDerefRef(AutoDerefRef {
                     autoderefs: autoderefs,
                     autoref: autoref,
                     unsize: None
@@ -341,7 +371,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
             unsize: Some(target)
         };
         debug!("Success, coerced with {:?}", adjustment);
-        Ok(Some(AdjustDerefRef(adjustment)))
+        Ok((target, AdjustDerefRef(adjustment)))
     }
 
     fn coerce_from_fn_pointer(&self,
@@ -362,13 +392,14 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
             match (fn_ty_a.unsafety, fn_ty_b.unsafety) {
                 (hir::Unsafety::Normal, hir::Unsafety::Unsafe) => {
                     let unsafe_a = self.tcx().safe_to_unsafe_fn_ty(fn_ty_a);
-                    try!(self.subtype(unsafe_a, b));
-                    return Ok(Some(AdjustUnsafeFnPointer));
+                    return self.unify(unsafe_a, b).map(|(ty, _)| {
+                        (ty, AdjustUnsafeFnPointer)
+                    });
                 }
                 _ => {}
             }
         }
-        self.subtype(a, b)
+        self.unify(a, b)
     }
 
     fn coerce_from_fn_item(&self,
@@ -387,10 +418,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
         match b.sty {
             ty::TyFnPtr(_) => {
                 let a_fn_pointer = self.tcx().mk_ty(ty::TyFnPtr(fn_ty_a));
-                try!(self.subtype(a_fn_pointer, b));
-                Ok(Some(AdjustReifyFnPointer))
+                self.unify(a_fn_pointer, b).map(|(ty, _)| {
+                    (ty, AdjustReifyFnPointer)
+                })
             }
-            _ => self.subtype(a, b)
+            _ => self.unify(a, b)
         }
     }
 
@@ -407,29 +439,29 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
             ty::TyRef(_, mt) => (true, mt),
             ty::TyRawPtr(mt) => (false, mt),
             _ => {
-                return self.subtype(a, b);
+                return self.unify(a, b);
             }
         };
 
         // Check that the types which they point at are compatible.
         let a_unsafe = self.tcx().mk_ptr(ty::TypeAndMut{ mutbl: mutbl_b, ty: mt_a.ty });
-        try!(self.subtype(a_unsafe, b));
+        let (ty, noop) = try!(self.unify(a_unsafe, b));
         try!(coerce_mutbls(mt_a.mutbl, mutbl_b));
 
         // Although references and unsafe ptrs have the same
         // representation, we still register an AutoDerefRef so that
         // regionck knows that the region for `a` must be valid here.
-        if is_ref {
-            Ok(Some(AdjustDerefRef(AutoDerefRef {
+        Ok((ty, if is_ref {
+            AdjustDerefRef(AutoDerefRef {
                 autoderefs: 1,
                 autoref: Some(AutoUnsafe(mutbl_b)),
                 unsize: None
-            })))
+            })
         } else if mt_a.mutbl != mutbl_b {
-            Ok(Some(AdjustMutToConstPointer))
+            AdjustMutToConstPointer
         } else {
-            Ok(None)
-        }
+            noop
+        }))
     }
 }
 
@@ -437,7 +469,7 @@ fn apply<'a, 'b, 'tcx, E, I>(coerce: &mut Coerce<'a, 'tcx>,
                              exprs: &E,
                              a: Ty<'tcx>,
                              b: Ty<'tcx>)
-                             -> RelateResult<'tcx, Ty<'tcx>>
+                             -> CoerceResult<'tcx>
     where E: Fn() -> I,
           I: IntoIterator<Item=&'b hir::Expr> {
 
@@ -446,44 +478,153 @@ fn apply<'a, 'b, 'tcx, E, I>(coerce: &mut Coerce<'a, 'tcx>,
     let fcx = coerce.fcx;
     if let AdjustDerefRef(auto) = adjustment {
         if auto.unsize.is_some() {
-            for obligation in coerce.unsizing_obligations.borrow_mut().drain() {
+            let mut obligations = coerce.unsizing_obligations.borrow_mut();
+            for obligation in obligations.drain(..) {
                 fcx.register_predicate(obligation);
             }
         }
     }
 
-    if !adjustment.is_identity() {
-        debug!("Success, coerced with {:?}", adjustment);
-        for expr in exprs() {
-            assert!(!fcx.inh.tables.borrow().adjustments.contains(&expr.id));
-            fcx.write_adjustment(expr.id, adjustment);
-        }
-    }
-    Ok(ty)
+    Ok((ty, adjustment))
 }
 
-/// Attempt to coerce an expression from a type (a) to another type (b).
-/// Adjustments are only recorded if the coercion was successful.
+/// Attempt to coerce an expression to a type, and return the
+/// adjusted type of the expression, if successful.
+/// Adjustments are only recorded if the coercion succeeded.
 /// The expressions *must not* have any pre-existing adjustments.
 pub fn try<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
                      expr: &hir::Expr,
-                     a: Ty<'tcx>,
-                     b: Ty<'tcx>)
-                     -> RelateResult<'tcx, ()> {
-    debug!("coercion::try({:?} -> {:?})", a, b);
+                     target: Ty<'tcx>)
+                     -> RelateResult<'tcx, Ty<'tcx>> {
+    let source = fcx.resolve_type_vars_if_possible(fcx.expr_ty(expr));
+    debug!("coercion::try({:?}: {:?} -> {:?})", expr, source, target);
+
     let mut coerce = Coerce::new(fcx, TypeOrigin::ExprAssignable(expr.span));
     fcx.infcx().commit_if_ok(|_| {
-        apply(&mut coerce, &|| Some(expr), a, b)
-    }).map(|_| ())
+        let (ty, adjustment) =
+            try!(apply(&mut coerce, &|| Some(expr), source, target));
+        if !adjustment.is_identity() {
+            debug!("Success, coerced with {:?}", adjustment);
+            assert!(!fcx.inh.tables.borrow().adjustments.contains_key(&expr.id));
+            fcx.write_adjustment(expr.id, adjustment);
+        }
+        Ok(ty)
+    })
 }
 
-fn coerce_mutbls<'tcx>(from_mutbl: hir::Mutability,
-                       to_mutbl: hir::Mutability)
-                       -> CoerceResult<'tcx> {
-    match (from_mutbl, to_mutbl) {
-        (hir::MutMutable, hir::MutMutable) |
-        (hir::MutImmutable, hir::MutImmutable) |
-        (hir::MutMutable, hir::MutImmutable) => Ok(None),
-        (hir::MutImmutable, hir::MutMutable) => Err(TypeError::Mutability)
+/// Given some expressions, their known unified type and another expression,
+/// tries to unify the types, potentially inserting coercions on any of the
+/// provided expressions and returns their LUB (aka "common supertype").
+pub fn try_find_lub<'a, 'b, 'tcx, E, I>(fcx: &FnCtxt<'a, 'tcx>,
+                                        origin: TypeOrigin,
+                                        exprs: E,
+                                        prev_ty: Ty<'tcx>,
+                                        new: &'b hir::Expr)
+                                        -> RelateResult<'tcx, Ty<'tcx>>
+    // FIXME(eddyb) use copyable iterators when that becomes ergonomic.
+    where E: Fn() -> I,
+          I: IntoIterator<Item=&'b hir::Expr> {
+
+    let prev_ty = fcx.resolve_type_vars_if_possible(prev_ty);
+    let new_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(new));
+    debug!("coercion::try_find_lub({:?}, {:?})", prev_ty, new_ty);
+
+    let trace = TypeTrace::types(origin, true, prev_ty, new_ty);
+    let mut lub = fcx.infcx().lub(true, trace);
+
+    // Special-case that coercion alone cannot handle:
+    // Two function item types of differing IDs or Substs.
+    match (&prev_ty.sty, &new_ty.sty) {
+        (&ty::TyFnDef(a_def_id, a_substs, a_fty),
+         &ty::TyFnDef(b_def_id, b_substs, b_fty)) => {
+            // The signature must always match.
+            let fty = try!(lub.relate(a_fty, b_fty));
+
+            if a_def_id == b_def_id {
+                // Same function, maybe the parameters match.
+                let substs = fcx.infcx().commit_if_ok(|_| {
+                    relate_substs(&mut lub, None, a_substs, b_substs)
+                }).map(|s| fcx.tcx().mk_substs(s));
+
+                if let Ok(substs) = substs {
+                    // We have a LUB of prev_ty and new_ty, just return it.
+                    return Ok(fcx.tcx().mk_fn_def(a_def_id, substs, fty));
+                }
+            }
+
+            // Reify both sides and return the reified fn pointer type.
+            for expr in exprs().into_iter().chain(Some(new)) {
+                // No adjustments can produce a fn item, so this should never trip.
+                assert!(!fcx.inh.tables.borrow().adjustments.contains_key(&expr.id));
+                fcx.write_adjustment(expr.id, AdjustReifyFnPointer);
+            }
+            return Ok(fcx.tcx().mk_fn_ptr(fty));
+        }
+        _ => {}
+    }
+
+    let mut coerce = Coerce::new(fcx, origin);
+    coerce.use_lub = true;
+
+    // First try to coerce the new expression to the type of the previous ones,
+    // but only if the new expression has no coercion already applied to it.
+    let mut first_error = None;
+    if !fcx.inh.tables.borrow().adjustments.contains_key(&new.id) {
+        let result = fcx.infcx().commit_if_ok(|_| {
+            apply(&mut coerce, &|| Some(new), new_ty, prev_ty)
+        });
+        match result {
+            Ok((ty, adjustment)) => {
+                if !adjustment.is_identity() {
+                    fcx.write_adjustment(new.id, adjustment);
+                }
+                return Ok(ty);
+            }
+            Err(e) => first_error = Some(e)
+        }
+    }
+
+    // Then try to coerce the previous expressions to the type of the new one.
+    // This requires ensuring there are no coercions applied to *any* of the
+    // previous expressions, other than noop reborrows (ignoring lifetimes).
+    for expr in exprs() {
+        let noop = match fcx.inh.tables.borrow().adjustments.get(&expr.id) {
+            Some(&AdjustDerefRef(AutoDerefRef {
+                autoderefs: 1,
+                autoref: Some(AutoPtr(_, mutbl_adj)),
+                unsize: None
+            })) => match fcx.expr_ty(expr).sty {
+                ty::TyRef(_, mt_orig) => {
+                    // Reborrow that we can safely ignore.
+                    mutbl_adj == mt_orig.mutbl
+                }
+                _ => false
+            },
+            Some(_) => false,
+            None => true
+        };
+
+        if !noop {
+            return fcx.infcx().commit_if_ok(|_| lub.relate(&prev_ty, &new_ty));
+        }
+    }
+
+    match fcx.infcx().commit_if_ok(|_| apply(&mut coerce, &exprs, prev_ty, new_ty)) {
+        Err(_) => {
+            // Avoid giving strange errors on failed attempts.
+            if let Some(e) = first_error {
+                Err(e)
+            } else {
+                fcx.infcx().commit_if_ok(|_| lub.relate(&prev_ty, &new_ty))
+            }
+        }
+        Ok((ty, adjustment)) => {
+            if !adjustment.is_identity() {
+                for expr in exprs() {
+                    fcx.write_adjustment(expr.id, adjustment);
+                }
+            }
+            Ok(ty)
+        }
     }
 }
diff --git a/src/librustc_typeck/check/demand.rs b/src/librustc_typeck/check/demand.rs
index 4dd5a6f5909..1f61198bef9 100644
--- a/src/librustc_typeck/check/demand.rs
+++ b/src/librustc_typeck/check/demand.rs
@@ -39,14 +39,10 @@ pub fn coerce<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
                         sp: Span,
                         expected: Ty<'tcx>,
                         expr: &hir::Expr) {
-    let expr_ty = fcx.expr_ty(expr);
-    debug!("demand::coerce(expected = {:?}, expr_ty = {:?})",
-           expected,
-           expr_ty);
-    let expr_ty = fcx.resolve_type_vars_if_possible(expr_ty);
     let expected = fcx.resolve_type_vars_if_possible(expected);
-    let origin = TypeOrigin::Misc(sp);
-    if let Err(e) = coercion::try(fcx, expr, expr_ty, expected) {
+    if let Err(e) = coercion::try(fcx, expr, expected) {
+        let origin = TypeOrigin::Misc(sp);
+        let expr_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(expr));
         fcx.infcx().report_mismatched_types(origin, expected, expr_ty, e);
     }
 }
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index 0f16b184618..0743c0b9e18 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -89,7 +89,7 @@ use middle::cstore::LOCAL_CRATE;
 use middle::def::{self, Def};
 use middle::def_id::DefId;
 use middle::infer;
-use middle::infer::{TypeOrigin, type_variable};
+use middle::infer::{TypeOrigin, TypeTrace, type_variable};
 use middle::pat_util::{self, pat_id_map};
 use middle::subst::{self, Subst, Substs, VecPerParamSpace, ParamSpace};
 use middle::traits::{self, report_fulfillment_errors};
@@ -101,6 +101,7 @@ use middle::ty::{MethodCall, MethodCallee};
 use middle::ty::adjustment;
 use middle::ty::error::TypeError;
 use middle::ty::fold::{TypeFolder, TypeFoldable};
+use middle::ty::relate::TypeRelation;
 use middle::ty::util::Representability;
 use require_c_abi_if_variadic;
 use rscope::{ElisionFailureInfo, RegionScope};
@@ -2080,7 +2081,7 @@ pub fn autoderef<'a, 'b, 'tcx, E, I, T, F>(fcx: &FnCtxt<'a, 'tcx>,
                 // (i.e. it is an inference variable) because `Ty::builtin_deref`
                 // and `try_overloaded_deref` both simply return `None`
                 // in such a case without producing spurious errors.
-                fcx.resolve_type_vars_if_possible(t)
+                fcx.infcx().resolve_type_vars_if_possible(&t)
             }
         };
         if resolved_t.references_error() {
@@ -2164,7 +2165,7 @@ fn try_overloaded_deref<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
 /// For the overloaded lvalue expressions (`*x`, `x[3]`), the trait returns a type of `&T`, but the
 /// actual type we assign to the *expression* is `T`. So this function just peels off the return
 /// type by one layer to yield `T`.
-fn make_overloaded_lvalue_return_type<'tcx>(tcx: &ty::ctxt<'tcx>,
+fn make_overloaded_lvalue_return_type<'tcx>(tcx: &TyCtxt<'tcx>,
                                             method: MethodCallee<'tcx>)
                                             -> ty::TypeAndMut<'tcx>
 {
@@ -2834,30 +2835,52 @@ fn check_expr_with_expectation_and_lvalue_pref<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
         check_block_with_expected(fcx, then_blk, expected);
         let then_ty = fcx.node_ty(then_blk.id);
 
-        let branches_ty = match opt_else_expr {
-            Some(ref else_expr) => {
-                check_expr_with_expectation(fcx, &else_expr, expected);
-                let else_ty = fcx.expr_ty(&else_expr);
-                infer::common_supertype(fcx.infcx(),
-                                        TypeOrigin::IfExpression(sp),
-                                        true,
-                                        then_ty,
-                                        else_ty)
-            }
-            None => {
-                infer::common_supertype(fcx.infcx(),
-                                        TypeOrigin::IfExpressionWithNoElse(sp),
-                                        false,
-                                        then_ty,
-                                        fcx.tcx().mk_nil())
-            }
-        };
+        let unit = fcx.tcx().mk_nil();
+        let (origin, expected, found, result) =
+        if let Some(else_expr) = opt_else_expr {
+            check_expr_with_expectation(fcx, else_expr, expected);
+            let else_ty = fcx.expr_ty(else_expr);
+            let origin = TypeOrigin::IfExpression(sp);
+
+            // Only try to coerce-unify if we have a then expression
+            // to assign coercions to, otherwise it's () or diverging.
+            let result = if let Some(ref then) = then_blk.expr {
+                let res = coercion::try_find_lub(fcx, origin, || Some(&**then),
+                                                 then_ty, else_expr);
+
+                // In case we did perform an adjustment, we have to update
+                // the type of the block, because old trans still uses it.
+                let adj = fcx.inh.tables.borrow().adjustments.get(&then.id).cloned();
+                if res.is_ok() && adj.is_some() {
+                    fcx.write_ty(then_blk.id, fcx.adjust_expr_ty(then, adj.as_ref()));
+                }
 
-        let cond_ty = fcx.expr_ty(cond_expr);
-        let if_ty = if cond_ty.references_error() {
-            fcx.tcx().types.err
+                res
+            } else {
+                fcx.infcx().commit_if_ok(|_| {
+                    let trace = TypeTrace::types(origin, true, then_ty, else_ty);
+                    fcx.infcx().lub(true, trace).relate(&then_ty, &else_ty)
+                })
+            };
+            (origin, then_ty, else_ty, result)
         } else {
-            branches_ty
+            let origin = TypeOrigin::IfExpressionWithNoElse(sp);
+            (origin, unit, then_ty,
+             fcx.infcx().eq_types(true, origin, unit, then_ty).map(|_| unit))
+        };
+
+        let if_ty = match result {
+            Ok(ty) => {
+                if fcx.expr_ty(cond_expr).references_error() {
+                    fcx.tcx().types.err
+                } else {
+                    ty
+                }
+            }
+            Err(e) => {
+                fcx.infcx().report_mismatched_types(origin, expected, found, e);
+                fcx.tcx().types.err
+            }
         };
 
         fcx.write_ty(id, if_ty);
@@ -3497,23 +3520,30 @@ fn check_expr_with_expectation_and_lvalue_pref<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
             }
         });
 
-        let typ = match uty {
-            Some(uty) => {
-                for e in args {
-                    check_expr_coercable_to_type(fcx, &e, uty);
-                }
-                uty
-            }
-            None => {
-                let t: Ty = fcx.infcx().next_ty_var();
-                for e in args {
-                    check_expr_has_type(fcx, &e, t);
+        let mut unified = fcx.infcx().next_ty_var();
+        let coerce_to = uty.unwrap_or(unified);
+
+        for (i, e) in args.iter().enumerate() {
+            check_expr_with_hint(fcx, e, coerce_to);
+            let e_ty = fcx.expr_ty(e);
+            let origin = TypeOrigin::Misc(e.span);
+
+            // Special-case the first element, as it has no "previous expressions".
+            let result = if i == 0 {
+                coercion::try(fcx, e, coerce_to)
+            } else {
+                let prev_elems = || args[..i].iter().map(|e| &**e);
+                coercion::try_find_lub(fcx, origin, prev_elems, unified, e)
+            };
+
+            match result {
+                Ok(ty) => unified = ty,
+                Err(e) => {
+                    fcx.infcx().report_mismatched_types(origin, unified, e_ty, e);
                 }
-                t
             }
-        };
-        let typ = tcx.mk_array(typ, args.len());
-        fcx.write_ty(id, typ);
+        }
+        fcx.write_ty(id, tcx.mk_array(unified, args.len()));
       }
       hir::ExprRepeat(ref element, ref count_expr) => {
         check_expr_has_type(fcx, &count_expr, tcx.types.usize);
diff --git a/src/test/compile-fail/fn-item-type.rs b/src/test/compile-fail/fn-item-type.rs
index 775e22f0428..2fbd1ddb1e6 100644
--- a/src/test/compile-fail/fn-item-type.rs
+++ b/src/test/compile-fail/fn-item-type.rs
@@ -20,13 +20,6 @@ trait Foo { fn foo() { /* this is a default fn */ } }
 impl<T> Foo for T { /* `foo` is still default here */ }
 
 fn main() {
-    let f = if true { foo::<u8> } else { bar::<u8> };
-    //~^ ERROR if and else have incompatible types
-    //~| expected `fn(isize) -> isize {foo::<u8>}`
-    //~| found `fn(isize) -> isize {bar::<u8>}`
-    //~| expected fn item,
-    //~| found a different fn item
-
     eq(foo::<u8>, bar::<u8>);
     //~^ ERROR mismatched types
     //~|  expected `fn(isize) -> isize {foo::<u8>}`
diff --git a/src/test/compile-fail/issue-13482-2.rs b/src/test/compile-fail/issue-13482-2.rs
index f907be161fa..e1fe2d06993 100644
--- a/src/test/compile-fail/issue-13482-2.rs
+++ b/src/test/compile-fail/issue-13482-2.rs
@@ -17,7 +17,7 @@ fn main() {
     let y = match x {
         [] => None,
 //~^ ERROR mismatched types
-//~| expected `[_#0i; 2]`
+//~| expected `[_#1i; 2]`
 //~| found `[_#7t; 0]`
 //~| expected an array with a fixed size of 2 elements
 //~| found one with 0 elements
diff --git a/src/test/compile-fail/issue-17728.rs b/src/test/compile-fail/issue-17728.rs
index 83e52216be2..787eb7a3b88 100644
--- a/src/test/compile-fail/issue-17728.rs
+++ b/src/test/compile-fail/issue-17728.rs
@@ -107,7 +107,7 @@ impl Debug for Player {
 }
 
 fn str_to_direction(to_parse: &str) -> RoomDirection {
-    match to_parse {
+    match to_parse { //~ ERROR match arms have incompatible types
         "w" | "west" => RoomDirection::West,
         "e" | "east" => RoomDirection::East,
         "n" | "north" => RoomDirection::North,
@@ -116,7 +116,7 @@ fn str_to_direction(to_parse: &str) -> RoomDirection {
         "out" => RoomDirection::Out,
         "up" => RoomDirection::Up,
         "down" => RoomDirection::Down,
-        _ => None //~ ERROR mismatched types
+        _ => None //~ NOTE match arm with an incompatible type
     }
 }
 
diff --git a/src/test/compile-fail/issue-2149.rs b/src/test/compile-fail/issue-2149.rs
index 256c5d8e6f7..9143a226a24 100644
--- a/src/test/compile-fail/issue-2149.rs
+++ b/src/test/compile-fail/issue-2149.rs
@@ -21,5 +21,5 @@ impl<A> vec_monad<A> for Vec<A> {
 }
 fn main() {
     ["hi"].bind(|x| [x] );
-    //~^ ERROR no method named `bind` found for type `[&str; 1]` in the current scope
+    //~^ ERROR no method named `bind` found for type `[&'static str; 1]` in the current scope
 }
diff --git a/src/test/run-pass/coerce-unify.rs b/src/test/run-pass/coerce-unify.rs
new file mode 100644
index 00000000000..3d690146931
--- /dev/null
+++ b/src/test/run-pass/coerce-unify.rs
@@ -0,0 +1,77 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Check that coercions can unify if-else, match arms and array elements.
+
+// Try to construct if-else chains, matches and arrays out of given expressions.
+macro_rules! check {
+    ($last:expr $(, $rest:expr)+) => {
+        // Last expression comes first because of whacky ifs and matches.
+        let _ = $(if false { $rest })else+ else { $last };
+
+        let _ = match 0 { $(_ if false => $rest,)+ _ => $last };
+
+        let _ = [$($rest,)+ $last];
+    }
+}
+
+// Check all non-uniform cases of 2 and 3 expressions of 2 types.
+macro_rules! check2 {
+    ($a:expr, $b:expr) => {
+        check!($a, $b);
+        check!($b, $a);
+
+        check!($a, $a, $b);
+        check!($a, $b, $a);
+        check!($a, $b, $b);
+
+        check!($b, $a, $a);
+        check!($b, $a, $b);
+        check!($b, $b, $a);
+    }
+}
+
+// Check all non-uniform cases of 2 and 3 expressions of 3 types.
+macro_rules! check3 {
+    ($a:expr, $b:expr, $c:expr) => {
+        // Delegate to check2 for cases where a type repeats.
+        check2!($a, $b);
+        check2!($b, $c);
+        check2!($a, $c);
+
+        // Check the remaining cases, i.e. permutations of ($a, $b, $c).
+        check!($a, $b, $c);
+        check!($a, $c, $b);
+        check!($b, $a, $c);
+        check!($b, $c, $a);
+        check!($c, $a, $b);
+        check!($c, $b, $a);
+    }
+}
+
+use std::mem::size_of;
+
+fn foo() {}
+fn bar() {}
+
+pub fn main() {
+    check3!(foo, bar, foo as fn());
+    check3!(size_of::<u8>, size_of::<u16>, size_of::<usize> as fn() -> usize);
+
+    let s = String::from("bar");
+    check2!("foo", &s);
+
+    let a = [1, 2, 3];
+    let v = vec![1, 2, 3];
+    check2!(&a[..], &v);
+
+    // Make sure in-array coercion still works.
+    let _ = [("a", Default::default()), (Default::default(), "b"), (&s, &s)];
+}