From f83706454fd00c2a3b7873b50223b68ca2cbd0d0 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 24 Mar 2017 11:48:44 -0400 Subject: [PATCH] coercion now depends on whether the expression diverges Before I was checking this in `demand_coerce` but that's not really the right place. The right place is to move that into the coercion routine itself. --- src/librustc_typeck/check/_match.rs | 2 +- src/librustc_typeck/check/cast.rs | 10 ++++-- src/librustc_typeck/check/coercion.rs | 46 ++++++++++++++++++++------- src/librustc_typeck/check/demand.rs | 16 +--------- src/librustc_typeck/check/mod.rs | 18 +++++++---- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs index 5ca8f2c01c58..e83b786b9a83 100644 --- a/src/librustc_typeck/check/_match.rs +++ b/src/librustc_typeck/check/_match.rs @@ -504,7 +504,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { arm_span: arm.body.span, source: match_src }); - coercion.coerce(self, &cause, &arm.body, arm_ty); + coercion.coerce(self, &cause, &arm.body, arm_ty, self.diverges.get()); } } diff --git a/src/librustc_typeck/check/cast.rs b/src/librustc_typeck/check/cast.rs index 441d427fe499..ea0aad007dd7 100644 --- a/src/librustc_typeck/check/cast.rs +++ b/src/librustc_typeck/check/cast.rs @@ -38,7 +38,7 @@ //! expression, `e as U2` is not necessarily so (in fact it will only be valid if //! `U1` coerces to `U2`). -use super::FnCtxt; +use super::{Diverges, FnCtxt}; use lint; use hir::def_id::DefId; @@ -376,7 +376,10 @@ impl<'a, 'gcx, 'tcx> CastCheck<'tcx> { (None, Some(t_cast)) => { if let ty::TyFnDef(.., f) = self.expr_ty.sty { // Attempt a coercion to a fn pointer type. - let res = fcx.try_coerce(self.expr, self.expr_ty, fcx.tcx.mk_fn_ptr(f)); + let res = fcx.try_coerce(self.expr, + self.expr_ty, + Diverges::Maybe, // TODO + fcx.tcx.mk_fn_ptr(f)); if !res.is_ok() { return Err(CastError::NonScalar); } @@ -542,7 +545,8 @@ impl<'a, 'gcx, 'tcx> CastCheck<'tcx> { } fn try_coercion_cast(&self, fcx: &FnCtxt<'a, 'gcx, 'tcx>) -> bool { - fcx.try_coerce(self.expr, self.expr_ty, self.cast_ty).is_ok() + // TODO + fcx.try_coerce(self.expr, self.expr_ty, Diverges::Maybe, self.cast_ty).is_ok() } } diff --git a/src/librustc_typeck/check/coercion.rs b/src/librustc_typeck/check/coercion.rs index eba58df781f1..a737b82700e3 100644 --- a/src/librustc_typeck/check/coercion.rs +++ b/src/librustc_typeck/check/coercion.rs @@ -60,7 +60,7 @@ //! sort of a minor point so I've opted to leave it for later---after all //! we may want to adjust precisely when coercions occur. -use check::FnCtxt; +use check::{Diverges, FnCtxt}; use rustc::hir; use rustc::hir::def_id::DefId; @@ -156,7 +156,11 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> { }) } - fn coerce(&self, exprs: &[E], a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> + fn coerce(&self, + exprs: &[E], + a: Ty<'tcx>, + b: Ty<'tcx>) + -> CoerceResult<'tcx> where E: AsCoercionSite { let a = self.shallow_resolve(a); @@ -689,11 +693,19 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { pub fn try_coerce(&self, expr: &hir::Expr, expr_ty: Ty<'tcx>, + expr_diverges: Diverges, target: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { let source = self.resolve_type_vars_with_obligations(expr_ty); debug!("coercion::try({:?}: {:?} -> {:?})", expr, source, target); + // Special-ish case: we can coerce any type `T` into the `!` + // type, but only if the source expression diverges. + if target.is_never() && expr_diverges.always() { + debug!("permit coercion to `!` because expr diverges"); + return Ok(target); + } + let cause = self.cause(expr.span, ObligationCauseCode::ExprAssignable); let coerce = Coerce::new(self, cause); self.commit_if_ok(|_| { @@ -721,15 +733,22 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { exprs: &[E], prev_ty: Ty<'tcx>, new: &hir::Expr, - new_ty: Ty<'tcx>) + new_ty: Ty<'tcx>, + new_diverges: Diverges) -> RelateResult<'tcx, Ty<'tcx>> where E: AsCoercionSite { - let prev_ty = self.resolve_type_vars_with_obligations(prev_ty); let new_ty = self.resolve_type_vars_with_obligations(new_ty); debug!("coercion::try_find_lub({:?}, {:?})", prev_ty, new_ty); + // Special-ish case: we can coerce any type `T` into the `!` + // type, but only if the source expression diverges. + if prev_ty.is_never() && new_diverges.always() { + debug!("permit coercion to `!` because expr diverges"); + return Ok(prev_ty); + } + let trace = TypeTrace::types(cause, true, prev_ty, new_ty); // Special-case that coercion alone cannot handle: @@ -982,9 +1001,10 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E> fcx: &FnCtxt<'a, 'gcx, 'tcx>, cause: &ObligationCause<'tcx>, expression: &'gcx hir::Expr, - expression_ty: Ty<'tcx>) + expression_ty: Ty<'tcx>, + expression_diverges: Diverges) { - self.coerce_inner(fcx, cause, Some(expression), expression_ty) + self.coerce_inner(fcx, cause, Some(expression), expression_ty, expression_diverges) } /// Indicates that one of the inputs is a "forced unit". This @@ -1002,7 +1022,8 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E> self.coerce_inner(fcx, cause, None, - fcx.tcx.mk_nil()) + fcx.tcx.mk_nil(), + Diverges::Maybe) } /// The inner coercion "engine". If `expression` is `None`, this @@ -1012,7 +1033,8 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E> fcx: &FnCtxt<'a, 'gcx, 'tcx>, cause: &ObligationCause<'tcx>, expression: Option<&'gcx hir::Expr>, - mut expression_ty: Ty<'tcx>) + mut expression_ty: Ty<'tcx>, + expression_diverges: Diverges) { // Incorporate whatever type inference information we have // until now; in principle we might also want to process @@ -1035,7 +1057,7 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E> if self.pushed == 0 { // Special-case the first expression we are coercing. // To be honest, I'm not entirely sure why we do this. - fcx.try_coerce(expression, expression_ty, self.expected_ty) + fcx.try_coerce(expression, expression_ty, expression_diverges, self.expected_ty) } else { match self.expressions { Expressions::Dynamic(ref exprs) => @@ -1043,13 +1065,15 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E> exprs, self.merged_ty(), expression, - expression_ty), + expression_ty, + expression_diverges), Expressions::UpFront(ref coercion_sites) => fcx.try_find_coercion_lub(cause, &coercion_sites[0..self.pushed], self.merged_ty(), expression, - expression_ty), + expression_ty, + expression_diverges), } } } else { diff --git a/src/librustc_typeck/check/demand.rs b/src/librustc_typeck/check/demand.rs index 25d689b3c2c4..e922c7447ff8 100644 --- a/src/librustc_typeck/check/demand.rs +++ b/src/librustc_typeck/check/demand.rs @@ -77,21 +77,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { expected: Ty<'tcx>) { let expected = self.resolve_type_vars_with_obligations(expected); - // If we are "assigning" to a `!` location, then we can permit - // any type to be assigned there, so long as we are in - // dead-code. This applies to e.g. `fn foo() -> ! { return; 22 - // }` but also `fn foo() { let x: ! = { return; 22 }; }`. - // - // You might imagine that we could just *always* bail if we - // are in dead-code, but we don't want to do that, because - // that leaves a lot of type variables unconstrained. See - // e.g. #39808 and #39984. - let in_dead_code = self.diverges.get().always(); - if expected.is_never() && in_dead_code { - return; - } - - if let Err(e) = self.try_coerce(expr, checked_ty, expected) { + if let Err(e) = self.try_coerce(expr, checked_ty, self.diverges.get(), expected) { let cause = self.misc(expr.span); let expr_ty = self.resolve_type_vars_with_obligations(checked_ty); let mode = probe::Mode::MethodCall; diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 4b7d9a0fd3aa..539f16ec6709 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -366,7 +366,7 @@ impl UnsafetyState { /// as diverging), with some manual adjustments for control-flow /// primitives (approximating a CFG). #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -enum Diverges { +pub enum Diverges { /// Potentially unknown, some cases converge, /// others require a CFG to determine them. Maybe, @@ -2833,7 +2833,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { .coerce(self, &self.misc(return_expr.span), return_expr, - return_expr_ty); + return_expr_ty, + self.diverges.get()); } @@ -2864,13 +2865,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { let mut coerce: DynamicCoerceMany = CoerceMany::new(coerce_to_ty); let if_cause = self.cause(sp, ObligationCauseCode::IfExpression); - coerce.coerce(self, &if_cause, then_expr, then_ty); + coerce.coerce(self, &if_cause, then_expr, then_ty, then_diverges); if let Some(else_expr) = opt_else_expr { let else_ty = self.check_expr_with_expectation(else_expr, expected); let else_diverges = self.diverges.get(); - coerce.coerce(self, &if_cause, else_expr, else_ty); + coerce.coerce(self, &if_cause, else_expr, else_ty, else_diverges); // We won't diverge unless both branches do (or the condition does). self.diverges.set(cond_diverges | then_diverges & else_diverges); @@ -3553,7 +3554,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { } hir::ExprBreak(destination, ref expr_opt) => { if let Some(target_id) = destination.target_id.opt_id() { - let (e_ty, cause); + let (e_ty, e_diverges, cause); if let Some(ref e) = *expr_opt { // If this is a break with a value, we need to type-check // the expression. Get an expected type from the loop context. @@ -3572,11 +3573,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { // Recurse without `enclosing_breakables` borrowed. e_ty = self.check_expr_with_hint(e, coerce_to); + e_diverges = self.diverges.get(); cause = self.misc(e.span); } else { // Otherwise, this is a break *without* a value. That's // always legal, and is equivalent to `break ()`. e_ty = tcx.mk_nil(); + e_diverges = Diverges::Maybe; cause = self.misc(expr.span); } @@ -3587,7 +3590,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { let ctxt = enclosing_breakables.find_breakable(target_id); if let Some(ref mut coerce) = ctxt.coerce { if let Some(ref e) = *expr_opt { - coerce.coerce(self, &cause, e, e_ty); + coerce.coerce(self, &cause, e, e_ty, e_diverges); } else { assert!(e_ty.is_nil()); coerce.coerce_forced_unit(self, &cause); @@ -3769,10 +3772,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { let coerce_to = uty.unwrap_or_else( || self.next_ty_var(TypeVariableOrigin::TypeInference(expr.span))); let mut coerce = CoerceMany::with_coercion_sites(coerce_to, args); + assert_eq!(self.diverges.get(), Diverges::Maybe); for e in args { let e_ty = self.check_expr_with_hint(e, coerce_to); let cause = self.misc(e.span); - coerce.coerce(self, &cause, e, e_ty); + coerce.coerce(self, &cause, e, e_ty, self.diverges.get()); } coerce.complete(self) } else {