do not eagerly convert ! to a diverging variable

Instead, wait until coercion time.  This has some small effects on a few
tests (one less temporary, generally better errors when trying to call
methods or otherwise "force" the type).
This commit is contained in:
Niko Matsakis 2017-03-17 11:48:18 -04:00
parent 4967f1ae57
commit 1ae620bbeb
8 changed files with 71 additions and 47 deletions

View File

@ -169,7 +169,23 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
}
if a.is_never() {
return success(Adjust::NeverToAny, b, vec![]);
// Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound
// type variable, we want `?T` to fallback to `!` if not
// otherwise constrained. An example where this arises:
//
// let _: Option<?T> = Some({ return; });
//
// here, we would coerce from `!` to `?T`.
let b = self.shallow_resolve(b);
return if self.shallow_resolve(b).is_ty_var() {
// micro-optimization: no need for this if `b` is
// already resolved in some way.
let diverging_ty = self.next_diverging_ty_var(
TypeVariableOrigin::AdjustmentType(self.cause.span));
self.unify_and(&b, &diverging_ty, Adjust::NeverToAny)
} else {
success(Adjust::NeverToAny, b, vec![])
};
}
// Consider coercing the subtype to a DST
@ -687,11 +703,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
let adjustment = self.register_infer_ok_obligations(ok);
if !adjustment.is_identity() {
debug!("Success, coerced with {:?}", adjustment);
match self.tables.borrow().adjustments.get(&expr.id) {
None |
Some(&Adjustment { kind: Adjust::NeverToAny, .. }) => (),
_ => bug!("expr already has an adjustment on it!"),
};
if self.tables.borrow().adjustments.get(&expr.id).is_some() {
bug!("expr already has an adjustment on it!");
}
self.write_adjustment(expr.id, adjustment);
}
Ok(adjustment.target)

View File

@ -2664,7 +2664,22 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn check_expr_has_type(&self,
expr: &'gcx hir::Expr,
expected: Ty<'tcx>) -> Ty<'tcx> {
let ty = self.check_expr_with_hint(expr, expected);
let mut ty = self.check_expr_with_hint(expr, expected);
// While we don't allow *arbitrary* coercions here, we *do* allow
// coercions from ! to `expected`.
if ty.is_never() {
assert!(!self.tables.borrow().adjustments.contains_key(&expr.id),
"expression with never type wound up being adjusted");
let adj_ty = self.next_diverging_ty_var(
TypeVariableOrigin::AdjustmentType(expr.span));
self.write_adjustment(expr.id, adjustment::Adjustment {
kind: adjustment::Adjust::NeverToAny,
target: adj_ty
});
ty = adj_ty;
}
self.demand_suptype(expr.span, expected, ty);
ty
}
@ -3370,18 +3385,6 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
debug!("type of {} is...", self.tcx.hir.node_to_string(expr.id));
debug!("... {:?}, expected is {:?}", ty, expected);
// Add adjustments to !-expressions
if ty.is_never() {
if let Some(hir::map::NodeExpr(node_expr)) = self.tcx.hir.find(expr.id) {
let adj_ty = self.next_diverging_ty_var(
TypeVariableOrigin::AdjustmentType(node_expr.span));
self.write_adjustment(expr.id, adjustment::Adjustment {
kind: adjustment::Adjust::NeverToAny,
target: adj_ty
});
return adj_ty;
}
}
ty
}
@ -4072,7 +4075,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn check_block_no_value(&self, blk: &'gcx hir::Block) {
let unit = self.tcx.mk_nil();
let ty = self.check_block_with_expected(blk, ExpectHasType(unit));
self.demand_suptype(blk.span, unit, ty);
// if the block produces a `!` value, that can always be
// (effectively) coerced to unit.
if !ty.is_never() {
self.demand_suptype(blk.span, unit, ty);
}
}
fn check_block_with_expected(&self,
@ -4111,7 +4119,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
},
None => {
e_ty = if self.diverges.get().always() {
self.next_diverging_ty_var(TypeVariableOrigin::DivergingBlockExpr(blk.span))
self.tcx.types.never
} else {
self.tcx.mk_nil()
};
@ -4135,6 +4143,10 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
Err(err) =>
self.report_mismatched_types(&cause, ctxt.unified, e_ty, err).emit(),
}
} else if self.diverges.get().always() {
// No tail expression and the body diverges; ignore
// the expected type, and keep `!` as the type of the
// block.
} else {
self.check_block_no_expr(blk, self.tcx.mk_nil(), e_ty);
};
@ -4147,33 +4159,32 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
let mut ty = match blk.expr {
Some(ref e) => self.check_expr_with_expectation(e, expected),
None => self.tcx.mk_nil()
None => if self.diverges.get().always() {
self.tcx.types.never
} else {
self.tcx.mk_nil()
},
};
if self.diverges.get().always() {
if let ExpectHasType(ety) = expected {
// Avoid forcing a type (only `!` for now) in unreachable code.
// FIXME(aburka) do we need this special case? and should it be is_uninhabited?
if !ety.is_never() {
if let Some(ref e) = blk.expr {
// Coerce the tail expression to the right type.
self.demand_coerce(e, ty, ety);
}
}
}
ty = self.next_diverging_ty_var(TypeVariableOrigin::DivergingBlockExpr(blk.span));
} else if let ExpectHasType(ety) = expected {
if let ExpectHasType(ety) = expected {
if let Some(ref e) = blk.expr {
// Coerce the tail expression to the right type.
self.demand_coerce(e, ty, ety);
// We already applied the type (and potentially errored),
// use the expected type to avoid further errors out.
ty = ety;
} else if self.diverges.get().always() {
// No tail expression and the body diverges; ignore
// the expected type, and keep `!` as the type of the
// block.
} else {
self.check_block_no_expr(blk, ty, ety);
}
// We already applied the type (and potentially errored),
// use the expected type to avoid further errors out.
ty = ety;
// We already applied the type (and potentially errored),
// use the expected type to avoid further errors out.
ty = ety;
}
}
ty
};

View File

@ -9,5 +9,5 @@
// except according to those terms.
fn main() {
(return)[0]; //~ ERROR the type of this value must be known in this context
(return)[0]; //~ ERROR cannot index a value of type `!`
}

View File

@ -9,5 +9,5 @@
// except according to those terms.
fn main() {
return.is_failure //~ ERROR the type of this value must be known in this context
return.is_failure //~ ERROR no field `is_failure` on type `!`
}

View File

@ -10,7 +10,7 @@
fn main() {
loop {
break.push(1) //~ ERROR the type of this value must be known in this context
break.push(1) //~ ERROR no method named `push` found for type `!`
;
}
}

View File

@ -11,7 +11,7 @@
fn main() {
return
{ return () }
//~^ ERROR the type of this value must be known in this context
//~^ ERROR expected function, found `!`
()
;
}

View File

@ -9,6 +9,6 @@
// except according to those terms.
fn main() {
*return //~ ERROR the type of this value must be known in this context
*return //~ ERROR type `!` cannot be dereferenced
;
}

View File

@ -13,6 +13,5 @@
// into it.
fn main() {
(return)((),());
//~^ ERROR the type of this value must be known
(return)((),()); //~ ERROR expected function, found `!`
}