diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index f6ea6743a0e04..b42de10b4f4be 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -205,7 +205,7 @@ pub struct LocalTableInContext<'a, V> { /// stored/returned. fn validate_hir_id_for_typeck_results(hir_owner: LocalDefId, hir_id: hir::HirId) { if hir_id.owner != hir_owner { - ty::tls::with(|tcx| { + let _: () = ty::tls::with(|tcx| { bug!( "node {} with HirId::owner {:?} cannot be placed in TypeckResults with hir_owner {:?}", tcx.hir().node_to_string(hir_id), diff --git a/compiler/rustc_middle/src/util/bug.rs b/compiler/rustc_middle/src/util/bug.rs index 0903ef5089861..169bac78291ed 100644 --- a/compiler/rustc_middle/src/util/bug.rs +++ b/compiler/rustc_middle/src/util/bug.rs @@ -26,7 +26,7 @@ fn opt_span_bug_fmt>( args: fmt::Arguments<'_>, location: &Location<'_>, ) -> ! { - tls::with_opt(move |tcx| { + let _: () = tls::with_opt(move |tcx| { let msg = format!("{}: {}", location, args); match (tcx, span) { (Some(tcx), Some(span)) => tcx.sess.diagnostic().span_bug(span, &msg), diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 5c404161004a4..b6f77124bdea7 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -227,7 +227,7 @@ pub fn parse_in<'a, T>( let mut parser = Parser::new(sess, tts, false, Some(name)); let result = f(&mut parser)?; if parser.token != token::Eof { - parser.unexpected()?; + let _: () = parser.unexpected()?; } Ok(result) } diff --git a/compiler/rustc_session/src/lint/builtin.rs b/compiler/rustc_session/src/lint/builtin.rs index b8826a548b8be..60ef14a88f908 100644 --- a/compiler/rustc_session/src/lint/builtin.rs +++ b/compiler/rustc_session/src/lint/builtin.rs @@ -2555,6 +2555,13 @@ declare_lint! { @feature_gate = sym::unsafe_block_in_unsafe_fn; } +declare_lint! { + //FIXME: Add explanation. + pub FALL_BACK_TO_NEVER_TYPE, + Deny, + "Unresolved variable might fall back to never_type `!`" +} + declare_lint! { /// The `cenum_impl_drop_cast` lint detects an `as` cast of a field-less /// `enum` that implements [`Drop`]. @@ -2770,6 +2777,7 @@ declare_lint_pass! { MISSING_DOC_CODE_EXAMPLES, INVALID_HTML_TAGS, PRIVATE_DOC_TESTS, + FALL_BACK_TO_NEVER_TYPE, WHERE_CLAUSES_OBJECT_SAFETY, PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, MACRO_USE_EXTERN_CRATE, diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 324aa1a66a6d5..a0b82b8553b77 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -215,6 +215,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.diverges.set(self.diverges.get() | old_diverges); self.has_errors.set(self.has_errors.get() | old_has_errors); + if self.diverges.get().is_always() { + self.dead_nodes.borrow_mut().insert(expr.hir_id); + debug!("expr with HIR id {:?} is dead on exit", expr.hir_id); + } + debug!("type of {} is...", self.tcx.hir().node_to_string(expr.hir_id)); debug!("... {:?}, expected is {:?}", ty, expected); diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs index a820661d8432a..db3b2f82e19cb 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs @@ -701,6 +701,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.diverges.set(prev_diverges); } + if self.diverges.get().is_always() { + self.dead_nodes.borrow_mut().insert(blk.hir_id); + debug!("expr with HIR id {:?} is dead on exit", blk.hir_id); + } + let mut ty = ctxt.coerce.unwrap().complete(self); if self.has_errors.get() || ty.references_error() { diff --git a/compiler/rustc_typeck/src/check/inherited.rs b/compiler/rustc_typeck/src/check/inherited.rs index 7e580485c3de4..a81e102022104 100644 --- a/compiler/rustc_typeck/src/check/inherited.rs +++ b/compiler/rustc_typeck/src/check/inherited.rs @@ -14,6 +14,7 @@ use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::opaque_types::OpaqueTypeDecl; use rustc_trait_selection::traits::{self, TraitEngine, TraitEngineExt}; +use rustc_data_structures::fx::FxHashSet; use std::cell::RefCell; use std::ops::Deref; @@ -68,6 +69,10 @@ pub struct Inherited<'a, 'tcx> { pub(super) opaque_types_vars: RefCell, Ty<'tcx>>>, pub(super) body_id: Option, + + /// This keeps track of the dead nodes. We use this to determine + /// if there are live nodes with the diverging fallback for linting. + pub(super) dead_nodes: RefCell>, } impl<'a, 'tcx> Deref for Inherited<'a, 'tcx> { @@ -126,6 +131,7 @@ impl Inherited<'a, 'tcx> { opaque_types: RefCell::new(Default::default()), opaque_types_vars: RefCell::new(Default::default()), body_id, + dead_nodes: RefCell::new(FxHashSet::default()), } } diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs index 169ad0df3a5c9..99f1425d1c48f 100644 --- a/compiler/rustc_typeck/src/check/mod.rs +++ b/compiler/rustc_typeck/src/check/mod.rs @@ -566,10 +566,12 @@ fn typeck_with_fallback<'tcx>( fcx.select_obligations_where_possible(false, |_| {}); let mut fallback_has_occurred = false; + let unsolved_variables = fcx.unsolved_variables(); + // We do fallback in two passes, to try to generate // better error messages. // The first time, we do *not* replace opaque types. - for ty in &fcx.unsolved_variables() { + for ty in &unsolved_variables { fallback_has_occurred |= fcx.fallback_if_possible(ty, FallbackMode::NoOpaque); } // We now see if we can make progress. This might @@ -597,6 +599,16 @@ fn typeck_with_fallback<'tcx>( // refer to opaque types. fcx.select_obligations_where_possible(fallback_has_occurred, |_| {}); + // We run through the list of `unsolved_variables` gathered earlier and + // check if there are any that are marked `Diverging` at this point. if + // so, this would imply, that they were assigned Divergent type because + // of fallback. Any type in `unsolved_variables` that is now `!`, is `!` + // as a result of fallback. + let mut from_diverging_fallback = unsolved_variables; + let diverging_default = fcx.tcx.mk_diverging_default(); + from_diverging_fallback + .retain(|ty| fcx.infcx.resolve_vars_if_possible(ty) == diverging_default); + // We now run fallback again, but this time we allow it to replace // unconstrained opaque type variables, in addition to performing // other kinds of fallback. @@ -630,7 +642,7 @@ fn typeck_with_fallback<'tcx>( fcx.regionck_expr(body); } - fcx.resolve_type_vars_in_body(body) + fcx.resolve_type_vars_in_body(body, &from_diverging_fallback) }); // Consistency check our TypeckResults instance can hold all ItemLocalIds diff --git a/compiler/rustc_typeck/src/check/writeback.rs b/compiler/rustc_typeck/src/check/writeback.rs index 5363702a5be6d..8ac9d4e70a99e 100644 --- a/compiler/rustc_typeck/src/check/writeback.rs +++ b/compiler/rustc_typeck/src/check/writeback.rs @@ -34,6 +34,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn resolve_type_vars_in_body( &self, body: &'tcx hir::Body<'tcx>, + from_diverging_fallback: &Vec>, ) -> &'tcx ty::TypeckResults<'tcx> { let item_id = self.tcx.hir().body_owner(body.id()); let item_def_id = self.tcx.hir().local_def_id(item_id); @@ -43,7 +44,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let rustc_dump_user_substs = self.tcx.has_attr(item_def_id.to_def_id(), sym::rustc_dump_user_substs); - let mut wbcx = WritebackCx::new(self, body, rustc_dump_user_substs); + let mut wbcx = + WritebackCx::new(self, body, rustc_dump_user_substs, from_diverging_fallback); for param in body.params { wbcx.visit_node_id(param.pat.span, param.hir_id); } @@ -100,6 +102,11 @@ struct WritebackCx<'cx, 'tcx> { body: &'tcx hir::Body<'tcx>, rustc_dump_user_substs: bool, + + /// List of type variables which became the never type `!` + /// as a result of fallback. + /// This is used to issue lints and warnings for the user. + from_diverging_fallback: &'cx Vec>, } impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { @@ -107,6 +114,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { fcx: &'cx FnCtxt<'cx, 'tcx>, body: &'tcx hir::Body<'tcx>, rustc_dump_user_substs: bool, + from_diverging_fallback: &'cx Vec>, ) -> WritebackCx<'cx, 'tcx> { let owner = body.id().hir_id.owner; @@ -115,6 +123,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { typeck_results: ty::TypeckResults::new(owner), body, rustc_dump_user_substs, + from_diverging_fallback, } } @@ -521,8 +530,35 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.visit_adjustments(span, hir_id); // Resolve the type of the node with id `node_id` - let n_ty = self.fcx.node_ty(hir_id); - let n_ty = self.resolve(&n_ty, &span); + let n_ty_original = self.fcx.node_ty(hir_id); + let n_ty = self.resolve(&n_ty_original, &span); + + debug!("visit_node_id: {:?}", self.from_diverging_fallback); + // check whether the node type contains any of the variables that + // became `!` as a result of type fallback but they are not part of the + // dead nodes. if so, warn. Note that, we concern ourselves with only + // the `n_ty_original` and don't `walk()` the subparts of a type. So, for a + // variable like `Foo>>>` even if `N` is diverging type, + // we will not generate a warning. This might be okay as sometimes we may + // have things like `Result where even though `T` is diverging, + // it might never be used and warning would be confusing for the user. + if !self.from_diverging_fallback.is_empty() { + debug!("hir_id:{}", &hir_id); + debug!("n_ty_original:{}", &n_ty_original); + if !self.fcx.dead_nodes.borrow().contains(&hir_id) + && self.from_diverging_fallback.contains(&n_ty_original) + { + self.tcx().struct_span_lint_hir( + rustc_session::lint::builtin::FALL_BACK_TO_NEVER_TYPE, + hir_id, + span, + |lint| { + lint.build(&format!("resulted from diverging fallback: {:?}", n_ty)).emit() + }, + ); + } + } + self.write_ty_to_typeck_results(hir_id, n_ty); debug!("node {:?} has type {:?}", hir_id, n_ty); diff --git a/src/test/ui/issues/issue-75777.nll.stderr b/src/test/ui/issues/issue-75777.nll.stderr new file mode 100644 index 0000000000000..98aacb1711906 --- /dev/null +++ b/src/test/ui/issues/issue-75777.nll.stderr @@ -0,0 +1,13 @@ +error: lifetime may not live long enough + --> $DIR/issue-75777.rs:13:5 + | +LL | fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box BoxFuture<'a, A>> { + | -- lifetime `'a` defined here +LL | let fut: BoxFuture<'a, A> = Box::pin(future::ready(v)); +LL | Box::new(move |_| fut) + | ^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'a` must outlive `'static` + | + = help: consider replacing `'a` with `'static` + +error: aborting due to previous error + diff --git a/src/test/ui/issues/issue-75777.rs b/src/test/ui/issues/issue-75777.rs new file mode 100644 index 0000000000000..291a3db093672 --- /dev/null +++ b/src/test/ui/issues/issue-75777.rs @@ -0,0 +1,17 @@ +// Regression test for #75777. +// Checks that a boxed future can be properly constructed. + +#![feature(future_readiness_fns)] + +use std::future::{self, Future}; +use std::pin::Pin; + +type BoxFuture<'a, T> = Pin + 'a + Send>>; + +fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box BoxFuture<'a, A>> { + let fut: BoxFuture<'a, A> = Box::pin(future::ready(v)); + Box::new(move |_| fut) + //~^ ERROR: cannot infer an appropriate lifetime +} + +fn main() {} diff --git a/src/test/ui/issues/issue-75777.stderr b/src/test/ui/issues/issue-75777.stderr new file mode 100644 index 0000000000000..16249a33c2fd0 --- /dev/null +++ b/src/test/ui/issues/issue-75777.stderr @@ -0,0 +1,30 @@ +error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements + --> $DIR/issue-75777.rs:13:14 + | +LL | Box::new(move |_| fut) + | ^^^^^^^^^^^^ + | +note: first, the lifetime cannot outlive the lifetime `'a` as defined on the function body at 11:11... + --> $DIR/issue-75777.rs:11:11 + | +LL | fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box BoxFuture<'a, A>> { + | ^^ +note: ...so that the types are compatible + --> $DIR/issue-75777.rs:13:14 + | +LL | Box::new(move |_| fut) + | ^^^^^^^^^^^^ + = note: expected `(Pin + Send>>,)` + found `(Pin + Send + 'a)>>,)` + = note: but, the lifetime must be valid for the static lifetime... +note: ...so that the expression is assignable + --> $DIR/issue-75777.rs:13:5 + | +LL | Box::new(move |_| fut) + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: expected `Box<(dyn FnOnce(&'a Env) -> Pin + Send + 'a)>> + 'static)>` + found `Box Pin + Send + 'a)>>>` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0495`. diff --git a/src/test/ui/never_type/never_type_false_warning_unreachable.rs b/src/test/ui/never_type/never_type_false_warning_unreachable.rs new file mode 100644 index 0000000000000..f9b51e42db592 --- /dev/null +++ b/src/test/ui/never_type/never_type_false_warning_unreachable.rs @@ -0,0 +1,11 @@ +#![deny(fall_back_to_never_type)] + +macro_rules! unreachable1 { + () => {{ panic!("internal error: entered unreachable code") }}; +} + +fn get_unchecked() { + unreachable1!(); +} + +fn main() {} diff --git a/src/test/ui/never_type/never_type_lint.rs b/src/test/ui/never_type/never_type_lint.rs new file mode 100644 index 0000000000000..a9e3bab51a455 --- /dev/null +++ b/src/test/ui/never_type/never_type_lint.rs @@ -0,0 +1,15 @@ +#![feature(never_type_fallback)] + +fn make_unit() {} + +fn unconstrained_return() -> T { + unsafe { + let make_unit_fn: fn() = make_unit; + let ffi: fn() -> T = std::mem::transmute(make_unit_fn); + ffi() + } +} + +fn main() { + let _ = if true { unconstrained_return() } else { panic!() }; +}