Skip to content

Commit

Permalink
change the strategy for diverging types
Browse files Browse the repository at this point in the history
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 rust-lang#39808.
  • Loading branch information
nikomatsakis committed Mar 25, 2017
1 parent 0960d7f commit 21b2cdf
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 12 deletions.
24 changes: 23 additions & 1 deletion src/librustc_typeck/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 13 additions & 11 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1531,18 +1531,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>) {
Expand Down Expand Up @@ -3280,6 +3275,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.
Expand Down Expand Up @@ -3965,7 +3965,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) => {
Expand All @@ -3991,9 +3991,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);
}
Expand Down Expand Up @@ -4044,14 +4041,19 @@ 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);
}
};

(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)
Expand Down
25 changes: 25 additions & 0 deletions src/test/compile-fail/diverging-tuple-parts-39485.rs
Original file line number Diff line number Diff line change
@@ -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() {}
1 change: 1 addition & 0 deletions src/test/compile-fail/never-assign-wrong-type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions src/test/run-pass/issue-39808.rs
Original file line number Diff line number Diff line change
@@ -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("")
};
}

0 comments on commit 21b2cdf

Please sign in to comment.