diff --git a/src/librustc/cfg/construct.rs b/src/librustc/cfg/construct.rs index f1e2794691556..0efecd658ea24 100644 --- a/src/librustc/cfg/construct.rs +++ b/src/librustc/cfg/construct.rs @@ -415,8 +415,7 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> { args: I) -> CFGIndex { let func_or_rcvr_exit = self.expr(func_or_rcvr, pred); let ret = self.straightline(call_expr, func_or_rcvr_exit, args); - // FIXME(canndrew): This is_never should probably be an is_uninhabited. - if self.tables.expr_ty(call_expr).is_never() { + if self.tables.expr_ty(call_expr).conservative_is_uninhabited() { self.add_unreachable_node() } else { ret diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index b828b1bd30a94..f897664f7353e 100644 --- a/src/librustc/middle/liveness.rs +++ b/src/librustc/middle/liveness.rs @@ -1115,8 +1115,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { } hir::ExprKind::Call(ref f, ref args) => { - // FIXME(canndrew): This is_never should really be an is_uninhabited - let succ = if self.tables.expr_ty(expr).is_never() { + let succ = if self.tables.expr_ty(expr).conservative_is_uninhabited() { self.s.exit_ln } else { succ @@ -1126,8 +1125,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { } hir::ExprKind::MethodCall(.., ref args) => { - // FIXME(canndrew): This is_never should really be an is_uninhabited - let succ = if self.tables.expr_ty(expr).is_never() { + let succ = if self.tables.expr_ty(expr).conservative_is_uninhabited() { self.s.exit_ln } else { succ diff --git a/src/librustc/traits/error_reporting.rs b/src/librustc/traits/error_reporting.rs index df26ac670601c..056ec6c694c5e 100644 --- a/src/librustc/traits/error_reporting.rs +++ b/src/librustc/traits/error_reporting.rs @@ -645,8 +645,8 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> { // If this error is due to `!: Trait` not implemented but `(): Trait` is // implemented, and fallback has occured, then it could be due to a - // variable that used to fallback to `()` now falling back to `!`. Issue a - // note informing about the change in behaviour. + // variable that used to fallback to `()` now falling back to `!`. + // Issue a note informing about the change in behaviour. if trait_predicate.skip_binder().self_ty().is_never() && fallback_has_occurred { diff --git a/src/librustc/ty/sty.rs b/src/librustc/ty/sty.rs index dd38188824338..ee2b11bf6bdee 100644 --- a/src/librustc/ty/sty.rs +++ b/src/librustc/ty/sty.rs @@ -1481,6 +1481,15 @@ impl<'a, 'gcx, 'tcx> TyS<'tcx> { } } + pub fn conservative_is_uninhabited(&self) -> bool { + // Uncontentious uninhabitableness check + match self.sty { + ty::TyNever => true, + ty::TyAdt(def, _) => def.variants.is_empty(), + _ => false + } + } + pub fn is_primitive(&self) -> bool { match self.sty { TyBool | TyChar | TyInt(_) | TyUint(_) | TyFloat(_) => true, diff --git a/src/librustc_codegen_llvm/debuginfo/mod.rs b/src/librustc_codegen_llvm/debuginfo/mod.rs index 068dd9821ac97..39c6b85916189 100644 --- a/src/librustc_codegen_llvm/debuginfo/mod.rs +++ b/src/librustc_codegen_llvm/debuginfo/mod.rs @@ -270,7 +270,7 @@ pub fn create_function_debug_context<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>, } None => {} }; - if cx.layout_of(sig.output()).abi == ty::layout::Abi::Uninhabited { + if sig.output().conservative_is_uninhabited() { flags = flags | DIFlags::FlagNoReturn; } diff --git a/src/librustc_codegen_llvm/declare.rs b/src/librustc_codegen_llvm/declare.rs index fdc84f914f502..6f774e82e6df9 100644 --- a/src/librustc_codegen_llvm/declare.rs +++ b/src/librustc_codegen_llvm/declare.rs @@ -23,7 +23,6 @@ use llvm::{self, ValueRef}; use llvm::AttributePlace::Function; use rustc::ty::{self, Ty}; -use rustc::ty::layout::{self, LayoutOf}; use rustc::session::config::Sanitizer; use rustc_target::spec::PanicStrategy; use abi::{Abi, FnType, FnTypeExt}; @@ -134,7 +133,7 @@ pub fn declare_fn<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>, name: &str, let fty = FnType::new(cx, sig, &[]); let llfn = declare_raw_fn(cx, name, fty.llvm_cconv(), fty.llvm_type(cx)); - if cx.layout_of(sig.output()).abi == layout::Abi::Uninhabited { + if sig.output().conservative_is_uninhabited() { llvm::Attribute::NoReturn.apply_llfn(Function, llfn); } diff --git a/src/librustc_mir/borrow_check/nll/type_check/mod.rs b/src/librustc_mir/borrow_check/nll/type_check/mod.rs index e23f9b20a10f8..9a41fa570c205 100644 --- a/src/librustc_mir/borrow_check/nll/type_check/mod.rs +++ b/src/librustc_mir/borrow_check/nll/type_check/mod.rs @@ -1083,8 +1083,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } } None => { - // FIXME(canndrew): This is_never should probably be an is_uninhabited - if !sig.output().is_never() { + if !sig.output().conservative_is_uninhabited() { span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig); } } diff --git a/src/librustc_mir/build/expr/into.rs b/src/librustc_mir/build/expr/into.rs index 59a7f49af8074..481e805eabc20 100644 --- a/src/librustc_mir/build/expr/into.rs +++ b/src/librustc_mir/build/expr/into.rs @@ -213,8 +213,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { exit_block.unit() } ExprKind::Call { ty, fun, args } => { - // FIXME(canndrew): This is_never should probably be an is_uninhabited - let diverges = expr.ty.is_never(); + let diverges = expr.ty.conservative_is_uninhabited(); let intrinsic = match ty.sty { ty::TyFnDef(def_id, _) => { let f = ty.fn_sig(this.hir.tcx()); diff --git a/src/librustc_mir/hair/pattern/check_match.rs b/src/librustc_mir/hair/pattern/check_match.rs index 35f9dcee99f82..f3172ecafa8ba 100644 --- a/src/librustc_mir/hair/pattern/check_match.rs +++ b/src/librustc_mir/hair/pattern/check_match.rs @@ -227,7 +227,11 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> { let scrutinee_is_uninhabited = if self.tcx.features().exhaustive_patterns { self.tcx.is_ty_uninhabited_from(module, pat_ty) } else { - self.conservative_is_uninhabited(pat_ty) + match pat_ty.sty { + ty::TyNever => true, + ty::TyAdt(def, _) => def.variants.is_empty(), + _ => false + } }; if !scrutinee_is_uninhabited { // We know the type is inhabited, so this must be wrong @@ -255,15 +259,6 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> { }) } - fn conservative_is_uninhabited(&self, scrutinee_ty: Ty<'tcx>) -> bool { - // "rustc-1.0-style" uncontentious uninhabitableness check - match scrutinee_ty.sty { - ty::TyNever => true, - ty::TyAdt(def, _) => def.variants.is_empty(), - _ => false - } - } - fn check_irrefutable(&self, pat: &'tcx Pat, origin: &str) { let module = self.tcx.hir.get_module_parent(pat.id); MatchCheckCtxt::create_and_enter(self.tcx, module, |ref mut cx| { diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs index c9b5fd525dd82..ffce81fc3b232 100644 --- a/src/librustc_typeck/check/_match.rs +++ b/src/librustc_typeck/check/_match.rs @@ -606,7 +606,12 @@ https://doc.rust-lang.org/reference/types.html#trait-objects"); // If there are no arms, that is a diverging match; a special case. if arms.is_empty() { - self.diverges.set(self.diverges.get() | Diverges::Always); + let diverges = match self.diverges.get() { + Diverges::Maybe | Diverges::Always => Diverges::Always, + Diverges::WarnedAlways => Diverges::WarnedAlways, + Diverges::UnwarnedAlways => Diverges::UnwarnedAlways, + }; + self.diverges.set(diverges); return tcx.types.never; } @@ -633,18 +638,20 @@ https://doc.rust-lang.org/reference/types.html#trait-objects"); match all_pats_diverge { Diverges::Maybe => Diverges::Maybe, Diverges::Always | Diverges::WarnedAlways => Diverges::WarnedAlways, + Diverges::UnwarnedAlways => Diverges::UnwarnedAlways, } }).collect(); // Now typecheck the blocks. // // The result of the match is the common supertype of all the - // arms. Start out the value as bottom, since it's the, well, - // bottom the type lattice, and we'll be moving up the lattice as + // arms. We start the value as the logical bottom (skipping + // `UnwarnedAlways`, which is a special case), since it's the, well, + // bottom of the type lattice, and we'll be moving up the lattice as // we process each arm. (Note that any match with 0 arms is matching // on any empty type and is therefore unreachable; should the flow // of execution reach it, we will panic, so bottom is an appropriate - // type in that case) + // type in that case.) let mut all_arms_diverge = Diverges::WarnedAlways; let expected = expected.adjust_for_branches(self); diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 4af89d2148dfc..5fe562ef6d6f1 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -434,6 +434,16 @@ pub enum PlaceOp { /// wake). Tracked semi-automatically (through type variables marked /// as diverging), with some manual adjustments for control-flow /// primitives (approximating a CFG). +/// +/// We know a node diverges in the following (conservative) situations: +/// - A function with a parameter whose type is uninhabited necessarily diverges. +/// - A match expression with no arms necessarily diverges. +/// - A match expression whose arms patterns all diverge necessarily diverges. +/// - A match expression whose arms all diverge necessarily diverges. +/// - An expression whose type is uninhabited necessarily diverges. +/// +/// In the above, the node will be marked as diverging `Always` or `WarnedAlways`. +/// In any other situation, it will be marked as `Maybe`. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Diverges { /// Potentially unknown, some cases converge, @@ -445,11 +455,18 @@ pub enum Diverges { Always, /// Same as `Always` but with a reachability - /// warning already emitted - WarnedAlways + /// warning already emitted. + WarnedAlways, + + /// Same as `Always` but without a reachability + /// warning emitted. Unlike `Always`, cannot be + /// converted to `WarnedAlways`. Used when + /// unreachable code is expected (e.g. in + /// function parameters as part of trait impls). + UnwarnedAlways, } -// Convenience impls for combinig `Diverges`. +// Convenience impls for combining `Diverges`. impl ops::BitAnd for Diverges { type Output = Self; @@ -1047,6 +1064,24 @@ fn check_fn<'a, 'gcx, 'tcx>(inherited: &'a Inherited<'a, 'gcx, 'tcx>, fcx.check_pat_walk(&arg.pat, arg_ty, ty::BindingMode::BindByValue(hir::Mutability::MutImmutable), true); + // If any of a function's parameters have a type that is uninhabited, then it + // it is not possible to call that function (because its arguments cannot be constructed). + // Therefore, it must always diverge. + if fcx.tcx.features().exhaustive_patterns { + if arg_ty.conservative_is_uninhabited() { + let mut diverges = Diverges::Always; + if let hir::ExprKind::Block(ref block, _) = body.value.node { + // If the function is completely empty, or has a single trailing + // expression, then we do not issue a warning (as it was likely + // mandated by a trait, rather than being an oversight). + if block.stmts.is_empty() { + diverges = Diverges::UnwarnedAlways; + } + } + fcx.diverges.set(fcx.diverges.get() | diverges); + } + } + // Check that argument is Sized. // The check for a non-trivial pattern is a hack to avoid duplicate warnings // for simple cases like `fn foo(x: Trait)`, @@ -3626,9 +3661,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { /// that there are actually multiple representations for `TyError`, so avoid /// that when err needs to be handled differently. fn check_expr_with_expectation_and_needs(&self, - expr: &'gcx hir::Expr, - expected: Expectation<'tcx>, - needs: Needs) -> Ty<'tcx> { + expr: &'gcx hir::Expr, + expected: Expectation<'tcx>, + needs: Needs) -> Ty<'tcx> { debug!(">> typechecking: expr={:?} expected={:?}", expr, expected); @@ -3652,9 +3687,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); + // Any expression that produces a value of an uninhabited type must have diverged. + if ty.conservative_is_uninhabited() { + if ty.is_never() || self.tcx.features().exhaustive_patterns { + self.diverges.set(self.diverges.get() | Diverges::Always); + } } // Record the type, which applies it effects. diff --git a/src/libstd/error.rs b/src/libstd/error.rs index 1958915602f83..99de84ea0bcff 100644 --- a/src/libstd/error.rs +++ b/src/libstd/error.rs @@ -310,7 +310,7 @@ impl Error for string::FromUtf16Error { #[stable(feature = "str_parse_error2", since = "1.8.0")] impl Error for string::ParseError { fn description(&self) -> &str { - match *self {} + match self {} } } diff --git a/src/test/compile-fail/unreachable-try-pattern.rs b/src/test/compile-fail/unreachable-try-pattern.rs index df340095bb433..7b97a9b1a722f 100644 --- a/src/test/compile-fail/unreachable-try-pattern.rs +++ b/src/test/compile-fail/unreachable-try-pattern.rs @@ -18,6 +18,7 @@ enum Void {} impl From for i32 { fn from(v: Void) -> i32 { match v {} + //~^ WARN unreachable expression } } @@ -39,6 +40,7 @@ fn qux(x: Result) -> Result { fn vom(x: Result) -> Result { let y = (match x { Ok(n) => Ok(n), Err(e) => Err(e) })?; //~^ WARN unreachable pattern + //~| WARN unreachable expression Ok(y) } diff --git a/src/test/debuginfo/nil-enum.rs b/src/test/debuginfo/nil-enum.rs index 94377421c0b0c..c6f1f570ddd1a 100644 --- a/src/test/debuginfo/nil-enum.rs +++ b/src/test/debuginfo/nil-enum.rs @@ -15,31 +15,20 @@ // compile-flags:-g // gdb-command:run -// gdb-command:print first +// gdb-command:print *first // gdbg-check:$1 = {} // gdbr-check:$1 = -// gdb-command:print second -// gdbg-check:$2 = {} -// gdbr-check:$2 = - #![allow(unused_variables)] #![feature(omit_gdb_pretty_printer_section)] #![omit_gdb_pretty_printer_section] -enum ANilEnum {} -enum AnotherNilEnum {} +enum Void {} -// This test relies on gdbg printing the string "{}" for empty -// structs (which may change some time) -// The error from gdbr is expected since nil enums are not supposed to exist. fn main() { - unsafe { - let first: ANilEnum = ::std::mem::zeroed(); - let second: AnotherNilEnum = ::std::mem::zeroed(); + let first: *const Void = 1 as *const _; - zzz(); // #break - } + zzz(); // #break } -fn zzz() {()} +fn zzz() {} diff --git a/src/test/ui/better_divergence_checking.rs b/src/test/ui/better_divergence_checking.rs new file mode 100644 index 0000000000000..3a2cac1eccbf0 --- /dev/null +++ b/src/test/ui/better_divergence_checking.rs @@ -0,0 +1,35 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(exhaustive_patterns)] +#![feature(never_type)] +#![deny(unreachable_code)] + +pub enum Void {} + +pub fn uninhabited_parameter_i(_v: Void) { + // A function with an uninhabited parameter + // is permitted if its body is empty. +} + +pub fn uninhabited_parameter_ii(v: !) -> i32 { + // A function with an uninhabited parameter + // is permitted if it simply returns a value + // as a trailing expression to satisfy the + // return type. + v +} + +pub fn uninhabited_parameter_iii(_v: Void, x: i32) -> i32 { + println!("Call me if you can!"); //~^ ERROR unreachable expression + x +} + +fn main() {} diff --git a/src/test/ui/better_divergence_checking.stderr b/src/test/ui/better_divergence_checking.stderr new file mode 100644 index 0000000000000..5f21027e5479b --- /dev/null +++ b/src/test/ui/better_divergence_checking.stderr @@ -0,0 +1,18 @@ +error: unreachable expression + --> $DIR/better_divergence_checking.rs:30:59 + | +LL | pub fn uninhabited_parameter_iii(_v: Void, x: i32) -> i32 { + | ___________________________________________________________^ +LL | | println!("Call me if you can!"); //~^ ERROR unreachable expression +LL | | x +LL | | } + | |_^ + | +note: lint level defined here + --> $DIR/better_divergence_checking.rs:13:9 + | +LL | #![deny(unreachable_code)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/test/ui/feature-gate-better_divergence_checking.err b/src/test/ui/feature-gate-better_divergence_checking.err new file mode 100644 index 0000000000000..72f2f08b5b8e7 --- /dev/null +++ b/src/test/ui/feature-gate-better_divergence_checking.err @@ -0,0 +1,11 @@ +error[E0658]: box expression syntax is experimental; you can call `Box::new` instead. (see issue #27779) + --> $DIR/feature-gate-box-expr.rs:22:13 + | +LL | let x = box 'c'; //~ ERROR box expression syntax is experimental + | ^^^^^^^ + | + = help: add #![feature(box_syntax)] to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/feature-gate-better_divergence_checking.rs b/src/test/ui/feature-gate-better_divergence_checking.rs new file mode 100644 index 0000000000000..509dfdd3a36c9 --- /dev/null +++ b/src/test/ui/feature-gate-better_divergence_checking.rs @@ -0,0 +1,19 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub enum Void {} + +fn main() { + let x: (Void,) = unsafe { std::mem::uninitialized() }; + let _ = match x {}; //~ ERROR non-exhaustive + + let x: [Void; 1] = unsafe { std::mem::uninitialized() }; + let _ = match x {}; //~ ERROR non-exhaustive +} diff --git a/src/test/ui/feature-gate-better_divergence_checking.stderr b/src/test/ui/feature-gate-better_divergence_checking.stderr new file mode 100644 index 0000000000000..6466d41200cec --- /dev/null +++ b/src/test/ui/feature-gate-better_divergence_checking.stderr @@ -0,0 +1,27 @@ +error[E0004]: non-exhaustive patterns: type (Void,) is non-empty + --> $DIR/feature-gate-better_divergence_checking.rs:15:19 + | +LL | let _ = match x {}; //~ ERROR non-exhaustive + | ^ + | +help: Please ensure that all possible cases are being handled; possibly adding wildcards or more match arms. + --> $DIR/feature-gate-better_divergence_checking.rs:15:19 + | +LL | let _ = match x {}; //~ ERROR non-exhaustive + | ^ + +error[E0004]: non-exhaustive patterns: type [Void; 1] is non-empty + --> $DIR/feature-gate-better_divergence_checking.rs:18:19 + | +LL | let _ = match x {}; //~ ERROR non-exhaustive + | ^ + | +help: Please ensure that all possible cases are being handled; possibly adding wildcards or more match arms. + --> $DIR/feature-gate-better_divergence_checking.rs:18:19 + | +LL | let _ = match x {}; //~ ERROR non-exhaustive + | ^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0004`.