change the strategy for diverging types

The new strategy is as follows. First, the `!` type is assigned
in two cases:

- a block with a diverging statement and no tail expression (e.g.,
  `{return;}`);
- any expression with the type `!` is considered diverging.

Second, we track when we are in a diverging state, and we permit a value
of any type to be coerced **into** `!` if the expression that produced
it is diverging. This means that `fn foo() -> ! { panic!(); 22 }`
type-checks, even though the block has a type of `usize`.

Finally, coercions **from** the `!` type to any other are always
permitted.

Fixes #39808.
This commit is contained in:
Niko Matsakis 2017-03-02 21:15:26 -05:00
parent 066d44bc0d
commit 4c6c26eba1
5 changed files with 83 additions and 12 deletions

View file

@ -67,8 +67,30 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
}
// Checks that the type of `expr` can be coerced to `expected`.
pub fn demand_coerce(&self, expr: &hir::Expr, checked_ty: Ty<'tcx>, expected: Ty<'tcx>) {
//
// NB: This code relies on `self.diverges` to be accurate. In
// particular, assignments to `!` will be permitted if the
// diverges flag is currently "always".
pub fn demand_coerce(&self,
expr: &hir::Expr,
checked_ty: Ty<'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) {
let cause = self.misc(expr.span);
let expr_ty = self.resolve_type_vars_with_obligations(checked_ty);

View file

@ -1533,18 +1533,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
#[inline]
pub fn write_ty(&self, node_id: ast::NodeId, ty: Ty<'tcx>) {
debug!("write_ty({}, {:?}) in fcx {}",
node_id, ty, self.tag());
node_id, self.resolve_type_vars_if_possible(&ty), self.tag());
self.tables.borrow_mut().node_types.insert(node_id, ty);
if ty.references_error() {
self.has_errors.set(true);
self.set_tainted_by_errors();
}
// FIXME(canndrew): This is_never should probably be an is_uninhabited
if ty.is_never() || self.type_var_diverges(ty) {
self.diverges.set(self.diverges.get() | Diverges::Always);
}
}
pub fn write_substs(&self, node_id: ast::NodeId, substs: ty::ItemSubsts<'tcx>) {
@ -3282,6 +3277,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
_ => self.warn_if_unreachable(expr.id, expr.span, "expression")
}
// Any expression that produces a value of type `!` must have diverged
if ty.is_never() {
self.diverges.set(self.diverges.get() | Diverges::Always);
}
// Record the type, which applies it effects.
// We need to do this after the warning above, so that
// we don't warn for the diverging expression itself.
@ -3967,7 +3967,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
self.diverges.set(Diverges::Maybe);
self.has_errors.set(false);
let (node_id, span) = match stmt.node {
let (node_id, _span) = match stmt.node {
hir::StmtDecl(ref decl, id) => {
let span = match decl.node {
hir::DeclLocal(ref l) => {
@ -3993,9 +3993,6 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
if self.has_errors.get() {
self.write_error(node_id);
} else if self.diverges.get().always() {
self.write_ty(node_id, self.next_diverging_ty_var(
TypeVariableOrigin::DivergingStmt(span)));
} else {
self.write_nil(node_id);
}
@ -4046,7 +4043,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
cause = self.misc(e.span);
},
None => {
e_ty = self.tcx.mk_nil();
e_ty = if self.diverges.get().always() {
self.next_diverging_ty_var(TypeVariableOrigin::DivergingBlockExpr(blk.span))
} else {
self.tcx.mk_nil()
};
cause = self.misc(blk.span);
}
};
@ -4054,6 +4055,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
(e_ty, cause)
});
if let ExpectHasType(ety) = expected {
if let Some(ref e) = blk.expr {
let result = if !ctxt.may_break {
self.try_coerce(e, e_ty, ctxt.coerce_to)

View file

@ -0,0 +1,25 @@
// Copyright 2014 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.
// After #39485, this test used to pass, but that change was reverted
// due to numerous inference failures like #39808, so it now fails
// again. #39485 made it so that diverging types never propagate
// upward; but we now do propagate such types upward in many more
// cases.
fn g() {
&panic!() //~ ERROR mismatched types
}
fn f() -> isize {
(return 1, return 2) //~ ERROR mismatched types
}
fn main() {}

View file

@ -11,6 +11,7 @@
// Test that we can't use another type in place of !
#![feature(never_type)]
#![deny(warnings)]
fn main() {
let x: ! = "hello"; //~ ERROR mismatched types

View file

@ -0,0 +1,21 @@
// 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.
// Regression test: even though `Ok` is dead-code, its type needs to
// be influenced by the result of `Err` or else we get a "type
// variable unconstrained" error.
fn main() {
let _ = if false {
Ok(return)
} else {
Err("")
};
}