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:
parent
066d44bc0d
commit
4c6c26eba1
5 changed files with 83 additions and 12 deletions
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
25
src/test/compile-fail/diverging-tuple-parts-39485.rs
Normal file
25
src/test/compile-fail/diverging-tuple-parts-39485.rs
Normal 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() {}
|
|
@ -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
|
||||
|
|
21
src/test/run-pass/issue-39808.rs
Normal file
21
src/test/run-pass/issue-39808.rs
Normal 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("")
|
||||
};
|
||||
}
|
Loading…
Add table
Reference in a new issue