diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index a51f0f7f24c36..ff69f2e06ad62 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -2286,7 +2286,8 @@ impl<'tcx> TyCtxt<'tcx> { #[inline] pub fn mk_diverging_default(self) -> Ty<'tcx> { - if self.features().never_type_fallback { self.types.never } else { self.types.unit } + self.types.never + //if self.features().never_type_fallback { self.types.never } else { self.types.unit } } #[inline] diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 4affdc4a9d64e..a7df03d8b7905 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -80,6 +80,7 @@ mod expr; mod generator_interior; pub mod intrinsic; pub mod method; +mod never_compat; mod op; mod pat; mod regionck; @@ -135,6 +136,7 @@ use syntax::util::parser::ExprPrecedence; use rustc_error_codes::*; +use std::borrow::Cow; use std::cell::{Cell, Ref, RefCell, RefMut}; use std::cmp; use std::collections::hash_map::Entry; @@ -254,6 +256,14 @@ pub struct Inherited<'a, 'tcx> { /// not clear. implicit_region_bound: Option>, + /// Maps each expression with a generic path + /// (e.g. `foo::()` to `InferredPath` containing + /// additional information used by `NeverCompatHandler`. + /// + /// Each entry in this map is gradually filled in during typecheck, + /// as the information we need is not available all at once. + inferred_paths: RefCell>>, + body_id: Option, } @@ -619,6 +629,48 @@ pub struct FnCtxt<'a, 'tcx> { inh: &'a Inherited<'a, 'tcx>, } +/// Stores additional data about a generic path +/// containing inference variables (e.g. `my_method::<_, u8>(bar)`). +/// This is used by `NeverCompatHandler` to inspect +/// all method calls that contain inference variables. +/// +/// This struct is a little strange, in that its data +/// is filled in from two different places in typecheck. +/// Thw two `Option` fields are written by `check_argument_types` +/// and `instantiate_value_path`, since neither method +/// has all of the information it needs. +#[derive(Clone, Debug)] +struct InferredPath<'tcx> { + /// The span of the corresponding expression. + span: Span, + /// The type of this path. For method calls, + /// this is a `ty::FnDef` + ty: Option>, + /// The types of the arguments (*not* generic substs) + /// provided to this path, if it represents a method + /// call. For example, `foo(true, 25)` would have + /// types `[bool, i32]`. If this path does not + /// correspond to a method, then this will be `None` + /// + /// This is a `Cow` rather than a `Vec` or slice + /// to accommodate `check_argument_types`, which may + /// be called with either an interned slice or a Vec. + /// A `Cow` lets us avoid unecessary interning + /// and Vec construction, since we just need to iterate + /// over this + args: Option]>>, + /// The unresolved inference variables for each + /// generic substs. Each entry in the outer vec + /// corresponds to a generic substs in the function. + /// + /// For example, suppose we have the function + /// `fn foo (){ ... }`. + /// + /// The method call `foo::, true>>()` + /// will have an `unresolved_vars` of `[[_#0t, _#1t], []]` + unresolved_vars: Vec>>, +} + impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> { type Target = Inherited<'a, 'tcx>; fn deref(&self) -> &Self::Target { @@ -685,6 +737,7 @@ impl Inherited<'a, 'tcx> { opaque_types: RefCell::new(Default::default()), opaque_types_vars: RefCell::new(Default::default()), implicit_region_bound, + inferred_paths: RefCell::new(Default::default()), body_id, } } @@ -1053,6 +1106,7 @@ fn typeck_tables_of_with_fallback<'tcx>( // All type checking constraints were added, try to fallback unsolved variables. fcx.select_obligations_where_possible(false, |_| {}); let mut fallback_has_occurred = false; + let never_compat = never_compat::NeverCompatHandler::pre_fallback(&fcx); // We do fallback in two passes, to try to generate // better error messages. @@ -1095,6 +1149,8 @@ fn typeck_tables_of_with_fallback<'tcx>( // See if we can make any more progress. fcx.select_obligations_where_possible(fallback_has_occurred, |_| {}); + never_compat.post_fallback(&fcx); + // Even though coercion casts provide type hints, we check casts after fallback for // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. fcx.check_casts(); @@ -3618,7 +3674,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_argument_types( sp, expr, - &err_inputs[..], + err_inputs, &[], args_no_rcvr, false, @@ -3726,13 +3782,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, sp: Span, expr: &'tcx hir::Expr<'tcx>, - fn_inputs: &[Ty<'tcx>], + fn_inputs: impl Into]>>, expected_arg_tys: &[Ty<'tcx>], args: &'tcx [hir::Expr<'tcx>], c_variadic: bool, tuple_arguments: TupleArgumentsFlag, def_span: Option, ) { + let fn_inputs = fn_inputs.into(); + debug!("check_argument_types: storing arguments for expr {:?}", expr); + // We now have the arguments types available for this msthod call, + // so store them in the `inferred_paths` entry for this method call. + // We set `ty` as `None` if we are the first to access the entry + // for this method, and leave it untouched otherwise. + match self.inferred_paths.borrow_mut().entry(expr.hir_id) { + Entry::Vacant(e) => { + debug!("check_argument_types: making new entry for types {:?}", fn_inputs); + e.insert(InferredPath { + span: sp, + ty: None, + args: Some(fn_inputs.clone()), + unresolved_vars: vec![], + }); + } + Entry::Occupied(mut e) => { + debug!( + "check_argument_types: modifying existing {:?} with types {:?}", + e.get(), + fn_inputs + ); + e.get_mut().args = Some(fn_inputs.clone()); + } + } + let tcx = self.tcx; // Grab the argument types, supplying fresh type variables // if the wrong number of arguments were supplied @@ -5419,6 +5501,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // the referenced item. let ty_substituted = self.instantiate_type_scheme(span, &substs, &ty); + if ty_substituted.has_infer_types() { + debug!( + "instantiate_value_path: saving path with infer: ({:?}, {:?})", + span, ty_substituted + ); + let parent_id = tcx.hir().get_parent_node(hir_id); + let parent = tcx.hir().get(parent_id); + match parent { + Node::Expr(hir::Expr { span: p_span, kind: ExprKind::Call(..), .. }) + | Node::Expr(hir::Expr { span: p_span, kind: ExprKind::MethodCall(..), .. }) => { + // Fill in the type for our parent expression. This might not be + // a method call - if it is, the argumetns will be filled in by + // `check_argument_types` + match self.inferred_paths.borrow_mut().entry(parent_id) { + Entry::Vacant(e) => { + debug!("instantiate_value_path: inserting new path"); + e.insert(InferredPath { + span: *p_span, + ty: Some(ty_substituted), + args: None, + unresolved_vars: vec![], + }); + } + Entry::Occupied(mut e) => { + debug!("instantiate_value_path: updating existing path {:?}", e.get()); + e.get_mut().ty = Some(ty_substituted); + } + } + } + _ => {} + } + } + if let Some(UserSelfTy { impl_def_id, self_ty }) = user_self_ty { // In the case of `Foo::method` and `>::method`, if `method` // is inherent, there is no `Self` parameter; instead, the impl needs diff --git a/src/librustc_typeck/check/never_compat.rs b/src/librustc_typeck/check/never_compat.rs new file mode 100644 index 0000000000000..672eb7b2f6fab --- /dev/null +++ b/src/librustc_typeck/check/never_compat.rs @@ -0,0 +1,455 @@ +use crate::check::{FnCtxt, InferredPath}; +use crate::rustc::ty::TypeFoldable; +use rustc::infer::type_variable::TypeVariableOriginKind; +use rustc::infer::InferCtxt; +use rustc::ty; +use rustc::ty::fold::TypeFolder; +use rustc::ty::{Ty, TyCtxt, TyVid}; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::HirId; +use std::borrow::Cow; + +/// Code to detect cases where using `!` (never-type) fallback instead of `()` fallback +/// may result in the introduction of undefined behavior; +/// +/// TL;DR: We look for method calls whose arguments are all inhabited, but +/// that have a generic parameter (e.g. `T` in `fn foo() { ... } `) +/// that's uninhabited due to fallback. We emit an error, forcing +/// users to add explicit type annotations indicating whether they +/// actually want the `!` type, or something else. +/// +/// Background: When we first tried to enable never-type fallback, it was found to +/// cause code in the 'obj' crate to segfault. The root cause was a piece +/// of code that looked like this: +/// +/// ```rust +///fn unconstrained_return() -> Result { +/// let ffi: fn() -> T = transmute(some_pointer); +/// Ok(ffi()) +/// } +///fn foo() { +/// match unconstrained_return::<_>() { +/// Ok(x) => x, // `x` has type `_`, which is unconstrained +/// Err(s) => panic!(s), // … except for unifying with the type of `panic!()` +/// // so that both `match` arms have the same type. +/// // Therefore `_` resolves to `!` and we "return" an `Ok(!)` value. +/// }; +///} +/// ``` +/// +/// Previously, the return type of `panic!()` would end up as `()` due to fallback +/// (even though `panic()` is declared as returning `!`). This meant that +/// the function pointer would get transmuted to `fn() -> ()`. +/// +/// With never-type fallback enabled, the function pointer was transmuted +/// to `fn() -> !`, despite the fact that it actually returned. This lead +/// to undefined behavior, since we actually "produced" an instance of `!` +/// at runtime. +/// +/// We want to prevent this code from compiling, unless the user provides +/// explicit type annotations indicating that they want the `!` type to be used. +/// +/// However, we want code like this to continue working: +/// +/// ```rust +/// fn foo(e: !) -> Box { +/// Box::<_>::new(e) +/// }``` +/// +/// From the perspective of fallback, these two snippets of code are very +/// similar. We have some expression that starts out with type `!`, which +/// we replace with a divergent inference variable. When fallback runs, +/// we unify the inference variable with `!`, which leads to other +/// expressions getting a type of `!` as well (`Box` in this case), +/// becoming uninhabited as well. +/// +/// Our task is to distinguish things that are "legitimately" uninhabited, +/// (such as `Box` in the second example), from things that are +/// "suspiciously" uninhabited and may lead to undefined behavior +/// (the `Result` in the first example). +/// +/// The key difference between these two snippets of code turns out +/// to be the *arguments* used in a function call. In the first example, +/// we call `unconstrained_return` with no arguments, and an inferred +/// type parameter of `!` (a.k.a `unconstrained_return::()`). +/// +/// In the second example, we call `Box::new(!)`, again inferring +/// the type parameter to `!`. Since at least one argument to `Box::new`, +/// is uninhabited, we know that this call is *statically unreachable* - +/// if it were ever executed, we would have instantaited an uninhabited +/// type for a parameter, which is impossible. This means that while +/// the fallback to `!` has affected the type-checking of this call, +/// it won't actually affect the runtime behavior of the call, since +/// the call can never be executed. +/// +/// In the first example, the call to `unconstrained_return::()` is +/// still (potentially) live, since it trivially has no uninhabited arguments +/// (it has no arguments at all). This means that our fallback to `!` may +/// have changed the runtime behavior of `unconstrained_return` (in this case, +/// it did) - we may actually execute the call at runtime, but with an uninhabited +/// type parameter that the user did not intend to provide. +/// +/// This distinction forms the basis for our lint. We look for +/// any function/method calls that meet the following requirements: +/// +/// * Unresolved inference variables are present in the generic substs prior to fallback +/// * All arguments are inhabited +/// * After fallback, at least one generic subsst is uninhabited. +/// +/// Let's break down each of these requirements: +/// +/// 1. We only look at method calls, not other expressions. +/// This is because the issue boils down to a generic subst being +/// uninhabited due to fallback. Generic parameters can only be used +/// in two places - as arguments to types, and arguments to methods. +/// We don't care about types - the only relevant expression would be +/// a constructor (e.g. `MyStruct { a: true, b: 25 }`). In and of itself, +/// constructing a type this way (after all field values are evaluated) +/// can never cause undefined behavior. +/// +/// Methods calls, on the other hand, might cause undefined behavior +/// due to how they use their generic paramters. Note that this +/// still true if the undefineed behavior occurs in the same function +/// where fallback occurs - there still must be a call `transmute` +/// or some other intrinsic that allows 'instantating' an `!` instance +/// somehow. +/// +/// 2. Only function calls with inference variables in their generic +/// substs can possibly be affected by fallback - if all types are resolved, +/// then fallback cannot possibly have any effect. +/// +/// 3. If any arguments are uninhabited, then the function call cannot ever +/// cause undefined behavior, since it can never be executed. This check +/// ensures that we don't lint on `Box::<_>::new(!)` - even though +/// we're inferring a generic subst (`_`) to `!`, it has no runtime +/// effect. +/// +/// 4. If a generic substs is uninhabited after fallback, then we have +/// the potential for undefined behavior. Note that this check can +/// have false positives - if a generic argument was already uninhabited +/// prior to fallback, we might end up triggering the lint even if fallback +/// didn't cause any generic substs to become uninhabited. However, this isn't +/// a big deal for a few reasons: +/// +/// * This lint should be incredibly rare (as of the time of writing, only +/// `obj` has run into the relevant issue). +/// * Even if fallback didn't cause anything to become uninhabited +/// (e.g. a `*const _` being inferred to `*const !`), it's still influencing +/// the generic substs of a potentially live (all arguments inhabited) function +/// call. This is arguably something worth linting against, since the user +/// might not be aware that a live call is being affected by a seemingly +/// unrelated expression. +/// +/// * It should always be possible to resolve this lint while keeping the +/// existing behavior, simply by adding explicit type annotations. This +/// ensures that fallback never runs (since the type variable will +/// actually be constrained to `!` or something else). At worst, +/// we will force users to write some arguably unecessary type annotation, +/// but we will not permanently break any code. + +/// The main interface to this module. +/// It exposes two hooks: `pre_fallback` and `post_fallback`, +/// which are invoked by `typeck_tables_of_with_fallback` respectively before +/// and after fallback is run. +pub struct NeverCompatHandler<'tcx> { + /// A map from method call `HirId`'s to `InferredPaths`. + /// This is computed from `FnCtxt.inferred_paths`, which + /// is in turn populated during typecheck. + unresolved_paths: FxHashMap>, + /// All divering type variables that were unconstrained + /// prior to fallback. We use this to help generate + /// better diagnostics by determining which + /// inference variables were equated with a diverging + /// fallback variable. + unconstrained_diverging: Vec>, +} + +/// A simpler helper to collect all `InferTy::TyVar`s found +/// in a `TypeFoldable`. +struct TyVarFinder<'a, 'tcx> { + infcx: &'a InferCtxt<'a, 'tcx>, + vars: Vec>, +} +impl<'a, 'tcx> TypeFolder<'tcx> for TyVarFinder<'a, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + + fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { + if let ty::Infer(ty::InferTy::TyVar(_)) = t.kind { + self.vars.push(t); + } + t.super_fold_with(self) + } +} + +/// The core logic of this check. Given +/// an `InferredPath`, we determine +/// if this method call is "questionable" using the criteria +/// described at the top of the module. +/// +/// If the method call is "questionable", and should +/// be linted, we return Ok(tys), where `tys` +/// is a list of all inference variables involved +/// with the generic paramter affected by fallback. +/// +/// Otherwise, we return None +fn find_questionable_call<'a, 'tcx>( + path: &'a InferredPath<'tcx>, + fcx: &FnCtxt<'a, 'tcx>, +) -> Option<&'a [Ty<'tcx>]> { + let tcx = fcx.tcx; + let ty = fcx.infcx.resolve_vars_if_possible(&path.ty); + debug!("find_questionable_call: Fully resolved ty: {:?}", ty); + + let ty = ty.unwrap_or_else(|| bug!("Missing ty in path: {:?}", path)); + + // Check that this InferredPath actually corresponds to a method + // invocation. Currently, type-checking resolves generic paths + // (e.g. `Box::<_>::new` separately from determining that a method call + // is occurring). We use this check to skip over `InferredPaths` for + // non-method-calls (e.g. struct constructors). + if let ty::FnDef(_, substs) = ty.kind { + debug!("find_questionable_call: Got substs: {:?}", substs); + + // See if we can find any arguments that are definitely uninhabited. + // If we can, we're done - the method call is dead, so fallback + // cannot affect its runtime behavior. + for arg in &**path.args.as_ref().unwrap() { + let resolved_arg = fcx.infcx.resolve_vars_if_possible(arg); + + // We use `conservative_is_privately_uninhabited` so that we only + // bail out if we're certain that a type is uninhabited. + if resolved_arg.conservative_is_privately_uninhabited(tcx) { + debug!( + "find_questionable_call: bailing out due to uninhabited arg: {:?}", + resolved_arg + ); + return None; + } else { + debug!("find_questionable_call: Arg is inhabited: {:?}", resolved_arg); + } + } + + // Now, check the generic substs for the method (e.g. `T` and `V` in `foo::()`. + // If we find an uninhabited subst, + for (subst_ty, vars) in substs.types().zip(path.unresolved_vars.iter()) { + let resolved_subst = fcx.infcx.resolve_vars_if_possible(&subst_ty); + // We use `is_ty_uninhabited_from_any_module` instead of `conservative_is_privately_uninhabited`. + // Using a broader check for uninhabitedness ensures that we lint + // whenever the subst is uninhabted, regardless of whether or not + // the user code could know this. + if tcx.is_ty_uninhabited_from_any_module(resolved_subst) { + debug!("find_questionable_call: Subst is uninhabited: {:?}", resolved_subst); + if !vars.is_empty() { + debug!("find_questionable_call: Found fallback vars: {:?}", vars); + debug!( + "find_questionable_call: All arguments are inhabited, at least one subst is not inhabited!" + ); + // These variables were recorded prior to fallback runntime. + // When generating a diagnostic message, we will use these + // to determine which spans will we show to the user. + return Some(vars); + } else { + debug!("find_questionable_call: No fallback vars") + } + } else { + debug!("find_questionable_call: Subst is inhabited: {:?}", resolved_subst); + } + } + } + return None; +} + +/// Holds the "best" type variables that we want +/// to use to generate a message for the user. +/// +/// Note that these two variables are unified with each other, +/// and therefore represent the same type. However, they have different +/// origin information, which we can use to generate a nice error message. +struct VarData { + /// A type variable that was actually used in an uninhabited + /// generic subst (e.g. `foo::<_#0t>()` where `_#0t = !`) + /// This is used to point at the span where the U.B potentially + /// occurs + best_var: TyVid, + /// The diverging type variable that is ultimately responsible + /// for the U.B. occuring. In the `obj` example, this would + /// be the type variable corresponding the `panic!()` expression. + best_diverging_var: TyVid, +} + +impl<'tcx> NeverCompatHandler<'tcx> { + /// The pre-fallback hook invoked by typecheck. We collect + /// all unconstrained inference variables used in method call substs, + /// so that we can check if any changes occur due to fallback. + pub fn pre_fallback(fcx: &FnCtxt<'a, 'tcx>) -> NeverCompatHandler<'tcx> { + let unresolved_paths: FxHashMap> = fcx + .inferred_paths + .borrow() + .iter() + .map(|(id, path)| (*id, path.clone())) + .filter_map(|(hir_id, mut path)| { + debug!("pre_fallback: inspecting path ({:?}, {:?})", hir_id, path); + + let ty_resolved = fcx.infcx.resolve_vars_if_possible(&path.ty); + + // We only care about method calls, not other uses of generic paths. + let fn_substs = match ty_resolved { + Some(ty::TyS { kind: ty::FnDef(_, substs), .. }) => substs, + _ => { + debug!("pre_fallback: non-fn ty {:?}, skipping", ty_resolved); + return None; + } + }; + + let args_infer = match path.args.as_ref().unwrap() { + Cow::Borrowed(b) => b.iter().any(|ty| { + fcx.infcx + .unresolved_type_vars(&fcx.infcx.resolve_vars_if_possible(ty)) + .is_some() + }), + Cow::Owned(o) => fcx + .infcx + .unresolved_type_vars(&fcx.infcx.resolve_vars_if_possible(o)) + .is_some(), + }; + + if args_infer { + debug!( + "pre_fallback: skipping due to inference vars in fn {:?} args {:?}", + ty_resolved, + path.args.unwrap() + ); + return None; + } + + // Any method call with inference variables in its substs + // could potentially be affected by fallback. + if fcx.infcx.unresolved_type_vars(fn_substs).is_some() { + for subst in fn_substs.types() { + let mut finder = TyVarFinder { infcx: &fcx.infcx, vars: vec![] }; + subst.fold_with(&mut finder); + path.unresolved_vars.push(finder.vars); + } + + debug!( + "pre_fallback: unresolved vars in ty {:?} : {:?}", + ty_resolved, path.unresolved_vars + ); + + Some((hir_id, path)) + } else { + debug!("pre_fallback: all vars resolved in ty: {:?}", ty_resolved); + None + } + }) + .collect(); + + // Collect all divering inference variables, so that we + // can later compare them against other inference variables. + let unconstrained_diverging: Vec<_> = fcx + .unsolved_variables() + .iter() + .cloned() + .filter(|ty| fcx.infcx.type_var_diverges(ty)) + .collect(); + + NeverCompatHandler { unresolved_paths, unconstrained_diverging } + } + + /// Finds the 'best' type variables to use for display to the user, + /// given a list of the type variables originally used in the + /// generic substs for a method. + /// + /// We look for a type variable that ended up unified with a diverging + /// inference variable. This represents a place where fallback to a never + /// type ended up affecting a live method call. We then return the + /// inference variable, along with the corresponding divering inference + /// variable that was unified with it + /// + /// Note that their be multiple such pairs of inference variables - howver, + /// we only display one to the user, to avoid overwhelming them with information. + fn find_best_vars(&self, fcx: &FnCtxt<'a, 'tcx>, vars: &[Ty<'tcx>]) -> VarData { + for var in vars { + for diverging_var in &self.unconstrained_diverging { + match (&var.kind, &diverging_var.kind) { + (ty::Infer(ty::InferTy::TyVar(vid1)), ty::Infer(ty::InferTy::TyVar(vid2))) => { + if fcx.infcx.type_variables.borrow_mut().sub_unified(*vid1, *vid2) { + debug!( + "Type variable {:?} is equal to diverging var {:?}", + var, diverging_var + ); + + debug!( + "Var origin: {:?}", + fcx.infcx.type_variables.borrow().var_origin(*vid1) + ); + return VarData { best_var: *vid1, best_diverging_var: *vid2 }; + } + } + _ => bug!("Unexpected types: var={:?} diverging_var={:?}", var, diverging_var), + } + } + } + bug!("No vars were equated to diverging vars: {:?}", vars) + } + + /// The hook used by typecheck after fallback has been run. + /// We look for any "questionable" calls, generating diagnostic + /// message for them. + pub fn post_fallback(self, fcx: &FnCtxt<'a, 'tcx>) { + let tcx = fcx.tcx; + for (call_id, path) in &self.unresolved_paths { + debug!( + "post_fallback: resolved ty: {:?} at span {:?} : expr={:?} parent={:?} path={:?}", + path.span, + path.ty, + tcx.hir().get(*call_id), + tcx.hir().get(tcx.hir().get_parent_node(*call_id)), + path + ); + + let span = path.span; + if let Some(vars) = find_questionable_call(path, fcx) { + let VarData { best_var, best_diverging_var } = self.find_best_vars(fcx, &vars); + + let var_origin = *fcx.infcx.type_variables.borrow().var_origin(best_var); + let diverging_var_span = + fcx.infcx.type_variables.borrow().var_origin(best_diverging_var).span; + + let mut err = tcx + .sess + .struct_span_err(span, "Fallback to `!` may introduce undefined behavior"); + + // There are two possible cases here: + match var_origin.kind { + // 1. This inference variable was automatically generated due to the user + // not using the turbofish syntax. For example, `foo()` where foo is deinfed + // as `fn foo() { ... }`. + // In this case, we point at the definition of the type variable (e.g. the `T` + // in `foo`), to better explain to the user what's going on. + TypeVariableOriginKind::TypeParameterDefinition(name, did) => { + err.span_note( + var_origin.span, + &format!("the type parameter {} here was inferred to `!`", name), + ); + if let Some(did) = did { + err.span_note(fcx.tcx.def_span(did), "(type parameter defined here)"); + } + } + // The user wrote an explicit inference variable: e.g. `foo::<_>()`. We point + // directly at its span, since this is sufficient to explain to the user + // what's going on. + _ => { + err.span_note(var_origin.span, "the type here was inferred to `!`"); + } + } + + err.span_note(diverging_var_span, "... due to this expression evaluating to `!`") + .note("If you want the `!` type to be used here, add explicit type annotations") + .emit(); + } + } + } +} diff --git a/src/test/ui/never-fallback/box_it.rs b/src/test/ui/never-fallback/box_it.rs new file mode 100644 index 0000000000000..e98024a518467 --- /dev/null +++ b/src/test/ui/never-fallback/box_it.rs @@ -0,0 +1,11 @@ +// check-pass +// Verifies that our fallback-UB check doesn't trigger +// on correct code +#![feature(never_type)] +#![feature(never_type_fallback)] + +fn foo(e: !) -> Box { + Box::<_>::new(e) +} + +fn main() {} diff --git a/src/test/ui/never-fallback/from.rs b/src/test/ui/never-fallback/from.rs new file mode 100644 index 0000000000000..ede419dbb8ea1 --- /dev/null +++ b/src/test/ui/never-fallback/from.rs @@ -0,0 +1,14 @@ +// check-pass +// Tests that we correctly infer types to ! +#![feature(never_type)] +#![feature(never_type_fallback)] + +struct E; +impl From for E { + fn from(x: !) -> E { x } +} +fn foo(never: !) { + >::from(never); +} + +fn main() {} diff --git a/src/test/ui/never-fallback/from_try.rs b/src/test/ui/never-fallback/from_try.rs new file mode 100644 index 0000000000000..5b445f0f716c5 --- /dev/null +++ b/src/test/ui/never-fallback/from_try.rs @@ -0,0 +1,17 @@ +// check-pass +use std::convert::{TryFrom, Infallible}; + +struct E; + +impl From for E { + fn from(_: Infallible) -> E { + E + } +} + +fn foo() -> Result<(), E> { + u32::try_from(1u32)?; + Ok(()) +} + +fn main() {} diff --git a/src/test/ui/never-fallback/obj.rs b/src/test/ui/never-fallback/obj.rs new file mode 100644 index 0000000000000..3c79ba87233c3 --- /dev/null +++ b/src/test/ui/never-fallback/obj.rs @@ -0,0 +1,28 @@ +#![feature(never_type)] +#![feature(never_type_fallback)] + +fn get_type(_: T) -> &'static str { + std::any::type_name::() +} + +fn unconstrained_return() -> Result { + Err("Hi".to_string()) +} + +fn foo() { + let a = || { + match unconstrained_return::<_>() { //~ ERROR Fallback to `!` may introduce undefined + Ok(x) => x, // `x` has type `_`, which is unconstrained + Err(s) => panic!(s), // … except for unifying with the type of `panic!()` + // so that both `match` arms have the same type. + // Therefore `_` resolves to `!` and we "return" an `Ok(!)` value. + } + }; + + let cast: &dyn FnOnce() -> _ = &a; + println!("Return type: {:?}", get_type(cast)); +} + +fn main() { + foo() +} diff --git a/src/test/ui/never-fallback/obj.stderr b/src/test/ui/never-fallback/obj.stderr new file mode 100644 index 0000000000000..e4dfcd278bfaa --- /dev/null +++ b/src/test/ui/never-fallback/obj.stderr @@ -0,0 +1,21 @@ +error: Fallback to `!` may introduce undefined behavior + --> $DIR/obj.rs:14:15 + | +LL | match unconstrained_return::<_>() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the type here was inferred to `!` + --> $DIR/obj.rs:14:38 + | +LL | match unconstrained_return::<_>() { + | ^ +note: ... due to this expression evaluating to `!` + --> $DIR/obj.rs:16:23 + | +LL | Err(s) => panic!(s), // … except for unifying with the type of `panic!()` + | ^^^^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/test/ui/never-fallback/obj_implicit.rs b/src/test/ui/never-fallback/obj_implicit.rs new file mode 100644 index 0000000000000..025eee1213e20 --- /dev/null +++ b/src/test/ui/never-fallback/obj_implicit.rs @@ -0,0 +1,28 @@ +#![feature(never_type)] +#![feature(never_type_fallback)] + +fn get_type(_: T) -> &'static str { + std::any::type_name::() +} + +fn unconstrained_return() -> Result { + Err("Hi".to_string()) +} + +fn foo() { + let a = || { + match unconstrained_return() { //~ ERROR Fallback to `!` may introduce undefined behavior + Ok(x) => x, // `x` has type `_`, which is unconstrained + Err(s) => panic!(s), // … except for unifying with the type of `panic!()` + // so that both `match` arms have the same type. + // Therefore `_` resolves to `!` and we "return" an `Ok(!)` value. + } + }; + + let cast: &dyn FnOnce() -> _ = &a; + println!("Return type: {:?}", get_type(cast)); +} + +fn main() { + foo() +} diff --git a/src/test/ui/never-fallback/obj_implicit.stderr b/src/test/ui/never-fallback/obj_implicit.stderr new file mode 100644 index 0000000000000..554d1a110c823 --- /dev/null +++ b/src/test/ui/never-fallback/obj_implicit.stderr @@ -0,0 +1,26 @@ +error: Fallback to `!` may introduce undefined behavior + --> $DIR/obj_implicit.rs:14:15 + | +LL | match unconstrained_return() { + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter T here was inferred to `!` + --> $DIR/obj_implicit.rs:14:15 + | +LL | match unconstrained_return() { + | ^^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/obj_implicit.rs:8:25 + | +LL | fn unconstrained_return() -> Result { + | ^ +note: ... due to this expression evaluating to `!` + --> $DIR/obj_implicit.rs:16:23 + | +LL | Err(s) => panic!(s), // … except for unifying with the type of `panic!()` + | ^^^^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/test/ui/never_type/diverging-fallback-control-flow.rs b/src/test/ui/never_type/diverging-fallback-control-flow.rs index ea4881049d792..6cb28f66389d5 100644 --- a/src/test/ui/never_type/diverging-fallback-control-flow.rs +++ b/src/test/ui/never_type/diverging-fallback-control-flow.rs @@ -1,5 +1,3 @@ -// run-pass - #![allow(dead_code)] #![allow(unused_assignments)] #![allow(unused_variables)] @@ -33,7 +31,7 @@ fn assignment() { let x; if true { - x = BadDefault::default(); + x = BadDefault::default(); //~ ERROR Fallback to `!` } else { x = return; } @@ -45,13 +43,13 @@ fn assignment_rev() { if true { x = return; } else { - x = BadDefault::default(); + x = BadDefault::default(); //~ ERROR Fallback to `!` } } fn if_then_else() { let _x = if true { - BadDefault::default() + BadDefault::default() //~ ERROR Fallback to `!` } else { return; }; @@ -61,19 +59,19 @@ fn if_then_else_rev() { let _x = if true { return; } else { - BadDefault::default() + BadDefault::default() //~ ERROR Fallback to `!` }; } fn match_arm() { - let _x = match Ok(BadDefault::default()) { + let _x = match Ok(BadDefault::default()) { //~ ERROR Fallback to `!` Ok(v) => v, Err(()) => return, }; } fn match_arm_rev() { - let _x = match Ok(BadDefault::default()) { + let _x = match Ok(BadDefault::default()) { //~ ERROR Fallback to `!` Err(()) => return, Ok(v) => v, }; @@ -84,7 +82,7 @@ fn loop_break() { if false { break return; } else { - break BadDefault::default(); + break BadDefault::default(); //~ ERROR Fallback to `!` } }; } @@ -94,7 +92,7 @@ fn loop_break_rev() { if false { break return; } else { - break BadDefault::default(); + break BadDefault::default(); //~ ERROR Fallback to `!` } }; } diff --git a/src/test/ui/never_type/diverging-fallback-control-flow.stderr b/src/test/ui/never_type/diverging-fallback-control-flow.stderr new file mode 100644 index 0000000000000..a3154b92bcb20 --- /dev/null +++ b/src/test/ui/never_type/diverging-fallback-control-flow.stderr @@ -0,0 +1,209 @@ +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:34:13 + | +LL | x = BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:34:13 + | +LL | x = BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:36:13 + | +LL | x = return; + | ^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:46:13 + | +LL | x = BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:46:13 + | +LL | x = BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:44:13 + | +LL | x = return; + | ^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:52:9 + | +LL | BadDefault::default() + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:52:9 + | +LL | BadDefault::default() + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:54:9 + | +LL | return; + | ^^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:62:9 + | +LL | BadDefault::default() + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:62:9 + | +LL | BadDefault::default() + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:59:22 + | +LL | let _x = if true { + | ______________________^ +LL | | return; +LL | | } else { + | |_____^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:67:23 + | +LL | let _x = match Ok(BadDefault::default()) { + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:67:23 + | +LL | let _x = match Ok(BadDefault::default()) { + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:67:14 + | +LL | let _x = match Ok(BadDefault::default()) { + | ______________^ +LL | | Ok(v) => v, +LL | | Err(()) => return, +LL | | }; + | |_____^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:74:23 + | +LL | let _x = match Ok(BadDefault::default()) { + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:74:23 + | +LL | let _x = match Ok(BadDefault::default()) { + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:75:20 + | +LL | Err(()) => return, + | ^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:85:19 + | +LL | break BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:85:19 + | +LL | break BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:83:19 + | +LL | break return; + | ^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: Fallback to `!` may introduce undefined behavior + --> $DIR/diverging-fallback-control-flow.rs:95:19 + | +LL | break BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: the type parameter Self here was inferred to `!` + --> $DIR/diverging-fallback-control-flow.rs:95:19 + | +LL | break BadDefault::default(); + | ^^^^^^^^^^^^^^^^^^^ +note: (type parameter defined here) + --> $DIR/diverging-fallback-control-flow.rs:14:1 + | +LL | / trait BadDefault { +LL | | fn default() -> Self; +LL | | } + | |_^ +note: ... due to this expression evaluating to `!` + --> $DIR/diverging-fallback-control-flow.rs:93:19 + | +LL | break return; + | ^^^^^^ + = note: If you want the `!` type to be used here, add explicit type annotations + +error: aborting due to 8 previous errors +