diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 7d6fdc9392ea0..5ba67c52ef297 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -18,12 +18,12 @@ use rustc_span::{def_id::LocalDefId, Span}; #[derive(Copy, Clone)] pub enum DivergingFallbackBehavior { /// Always fallback to `()` (aka "always spontaneous decay") - FallbackToUnit, + ToUnit, /// Sometimes fallback to `!`, but mainly fallback to `()` so that most of the crates are not broken. - FallbackToNiko, + ContextDependent, /// Always fallback to `!` (which should be equivalent to never falling back + not making /// never-to-any coercions unless necessary) - FallbackToNever, + ToNever, /// Don't fallback at all NoFallback, } @@ -373,13 +373,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> { diverging_fallback.insert(diverging_ty, ty); }; - use DivergingFallbackBehavior::*; match behavior { - FallbackToUnit => { + DivergingFallbackBehavior::ToUnit => { debug!("fallback to () - legacy: {:?}", diverging_vid); fallback_to(self.tcx.types.unit); } - FallbackToNiko => { + DivergingFallbackBehavior::ContextDependent => { if found_infer_var_info.self_in_trait && found_infer_var_info.output { // This case falls back to () to ensure that the code pattern in // tests/ui/never_type/fallback-closure-ret.rs continues to @@ -415,14 +414,14 @@ impl<'tcx> FnCtxt<'_, 'tcx> { fallback_to(self.tcx.types.never); } } - FallbackToNever => { + DivergingFallbackBehavior::ToNever => { debug!( "fallback to ! - `rustc_never_type_mode = \"fallback_to_never\")`: {:?}", diverging_vid ); fallback_to(self.tcx.types.never); } - NoFallback => { + DivergingFallbackBehavior::NoFallback => { debug!( "no fallback - `rustc_never_type_mode = \"no_fallback\"`: {:?}", diverging_vid diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index f02b0f953900a..2ef27e6a0ba7b 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -124,7 +124,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { body_id: LocalDefId, ) -> FnCtxt<'a, 'tcx> { let (diverging_fallback_behavior, diverging_block_behavior) = - parse_never_type_options_attr(root_ctxt.tcx); + never_type_behavior(root_ctxt.tcx); FnCtxt { body_id, param_env, @@ -387,11 +387,33 @@ impl<'tcx> LoweredTy<'tcx> { } } +fn never_type_behavior(tcx: TyCtxt<'_>) -> (DivergingFallbackBehavior, DivergingBlockBehavior) { + let (fallback, block) = parse_never_type_options_attr(tcx); + let fallback = fallback.unwrap_or_else(|| default_fallback(tcx)); + let block = block.unwrap_or_default(); + + (fallback, block) +} + +/// Returns the default fallback which is used when there is no explicit override via `#![never_type_options(...)]`. +fn default_fallback(tcx: TyCtxt<'_>) -> DivergingFallbackBehavior { + // Edition 2024: fallback to `!` + if tcx.sess.edition().at_least_rust_2024() { + return DivergingFallbackBehavior::ToNever; + } + + // `feature(never_type_fallback)`: fallback to `!` or `()` trying to not break stuff + if tcx.features().never_type_fallback { + return DivergingFallbackBehavior::ContextDependent; + } + + // Otherwise: fallback to `()` + DivergingFallbackBehavior::ToUnit +} + fn parse_never_type_options_attr( tcx: TyCtxt<'_>, -) -> (DivergingFallbackBehavior, DivergingBlockBehavior) { - use DivergingFallbackBehavior::*; - +) -> (Option, Option) { // Error handling is dubious here (unwraps), but that's probably fine for an internal attribute. // Just don't write incorrect attributes <3 @@ -407,10 +429,10 @@ fn parse_never_type_options_attr( if item.has_name(sym::fallback) && fallback.is_none() { let mode = item.value_str().unwrap(); match mode { - sym::unit => fallback = Some(FallbackToUnit), - sym::niko => fallback = Some(FallbackToNiko), - sym::never => fallback = Some(FallbackToNever), - sym::no => fallback = Some(NoFallback), + sym::unit => fallback = Some(DivergingFallbackBehavior::ToUnit), + sym::niko => fallback = Some(DivergingFallbackBehavior::ContextDependent), + sym::never => fallback = Some(DivergingFallbackBehavior::ToNever), + sym::no => fallback = Some(DivergingFallbackBehavior::NoFallback), _ => { tcx.dcx().span_err(item.span(), format!("unknown never type fallback mode: `{mode}` (supported: `unit`, `niko`, `never` and `no`)")); } @@ -439,11 +461,5 @@ fn parse_never_type_options_attr( ); } - let fallback = fallback.unwrap_or_else(|| { - if tcx.features().never_type_fallback { FallbackToNiko } else { FallbackToUnit } - }); - - let block = block.unwrap_or_default(); - (fallback, block) } diff --git a/tests/ui/editions/never-type-fallback-breaking.e2024.stderr b/tests/ui/editions/never-type-fallback-breaking.e2024.stderr new file mode 100644 index 0000000000000..e9a8882eb6ca1 --- /dev/null +++ b/tests/ui/editions/never-type-fallback-breaking.e2024.stderr @@ -0,0 +1,26 @@ +error[E0277]: the trait bound `!: Default` is not satisfied + --> $DIR/never-type-fallback-breaking.rs:17:17 + | +LL | true => Default::default(), + | ^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `!` + | + = note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #48950 for more information) + = help: did you intend to use the type `()` here instead? + +error[E0277]: the trait bound `!: Default` is not satisfied + --> $DIR/never-type-fallback-breaking.rs:30:5 + | +LL | deserialize()?; + | ^^^^^^^^^^^^^ the trait `Default` is not implemented for `!` + | + = note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #48950 for more information) + = help: did you intend to use the type `()` here instead? +note: required by a bound in `deserialize` + --> $DIR/never-type-fallback-breaking.rs:26:23 + | +LL | fn deserialize() -> Option { + | ^^^^^^^ required by this bound in `deserialize` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/editions/never-type-fallback-breaking.rs b/tests/ui/editions/never-type-fallback-breaking.rs new file mode 100644 index 0000000000000..7dfa4702807b5 --- /dev/null +++ b/tests/ui/editions/never-type-fallback-breaking.rs @@ -0,0 +1,34 @@ +//@ revisions: e2021 e2024 +// +//@[e2021] edition: 2021 +//@[e2024] edition: 2024 +//@[e2024] compile-flags: -Zunstable-options +// +//@[e2021] run-pass +//@[e2024] check-fail + +fn main() { + m(); + q(); +} + +fn m() { + let x = match true { + true => Default::default(), + //[e2024]~^ error: the trait bound `!: Default` is not satisfied + false => panic!("..."), + }; + + dbg!(x); +} + +fn q() -> Option<()> { + fn deserialize() -> Option { + Some(T::default()) + } + + deserialize()?; + //[e2024]~^ error: the trait bound `!: Default` is not satisfied + + None +} diff --git a/tests/ui/editions/never-type-fallback.e2021.run.stdout b/tests/ui/editions/never-type-fallback.e2021.run.stdout new file mode 100644 index 0000000000000..4122f7ac1eed2 --- /dev/null +++ b/tests/ui/editions/never-type-fallback.e2021.run.stdout @@ -0,0 +1 @@ +return type = () diff --git a/tests/ui/editions/never-type-fallback.e2024.run.stdout b/tests/ui/editions/never-type-fallback.e2024.run.stdout new file mode 100644 index 0000000000000..df2eeae60b4c5 --- /dev/null +++ b/tests/ui/editions/never-type-fallback.e2024.run.stdout @@ -0,0 +1 @@ +return type = ! diff --git a/tests/ui/editions/never-type-fallback.rs b/tests/ui/editions/never-type-fallback.rs new file mode 100644 index 0000000000000..a5b75219295c9 --- /dev/null +++ b/tests/ui/editions/never-type-fallback.rs @@ -0,0 +1,16 @@ +//@ revisions: e2021 e2024 +// +//@[e2021] edition: 2021 +//@[e2024] edition: 2024 +//@[e2024] compile-flags: -Zunstable-options +// +//@ run-pass +//@ check-run-results + +fn main() { + print_return_type_of(|| panic!()); +} + +fn print_return_type_of(_: impl FnOnce() -> R) { + println!("return type = {}", std::any::type_name::()); +} diff --git a/tests/ui/never_type/from_infer_breaking_with_unit_fallback.rs b/tests/ui/never_type/from_infer_breaking_with_unit_fallback.rs new file mode 100644 index 0000000000000..19a1f9d0e1318 --- /dev/null +++ b/tests/ui/never_type/from_infer_breaking_with_unit_fallback.rs @@ -0,0 +1,29 @@ +// issue: rust-lang/rust#66757 +// +// This is a *minimization* of the issue. +// Note that the original version with the `?` does not fail anymore even with fallback to unit, +// see `tests/ui/never_type/question_mark_from_never.rs`. +// +//@ revisions: unit never +//@[never] check-pass +#![allow(internal_features)] +#![feature(rustc_attrs, never_type)] +#![cfg_attr(unit, rustc_never_type_options(fallback = "unit"))] +#![cfg_attr(never, rustc_never_type_options(fallback = "never"))] + +struct E; + +impl From for E { + fn from(_: !) -> E { + E + } +} + +#[allow(unreachable_code)] +fn foo(never: !) { + >::from(never); // Ok + >::from(never); // Should the inference fail? + //[unit]~^ error: the trait bound `E: From<()>` is not satisfied +} + +fn main() {} diff --git a/tests/ui/never_type/from_infer_breaking_with_unit_fallback.unit.stderr b/tests/ui/never_type/from_infer_breaking_with_unit_fallback.unit.stderr new file mode 100644 index 0000000000000..3b8913ccf4577 --- /dev/null +++ b/tests/ui/never_type/from_infer_breaking_with_unit_fallback.unit.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `E: From<()>` is not satisfied + --> $DIR/from_infer_breaking_with_unit_fallback.rs:25:6 + | +LL | >::from(never); // Should the inference fail? + | ^ the trait `From<()>` is not implemented for `E` + | + = help: the trait `From` is implemented for `E` + = help: for that trait implementation, expected `!`, found `()` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/never_type/question_mark_from_never.rs b/tests/ui/never_type/question_mark_from_never.rs new file mode 100644 index 0000000000000..06d2a1926ea90 --- /dev/null +++ b/tests/ui/never_type/question_mark_from_never.rs @@ -0,0 +1,46 @@ +// issue: rust-lang/rust#66757 +// +// See also: `tests/ui/never_type/from_infer_breaking_with_unit_fallback.rs`. +// +//@ revisions: unit never +//@ check-pass +#![allow(internal_features)] +#![feature(rustc_attrs, never_type)] +#![cfg_attr(unit, rustc_never_type_options(fallback = "unit"))] +#![cfg_attr(never, rustc_never_type_options(fallback = "never"))] + +type Infallible = !; + +struct E; + +impl From for E { + fn from(_: Infallible) -> E { + E + } +} + +fn u32_try_from(x: u32) -> Result { + Ok(x) +} + +fn _f() -> Result<(), E> { + // In an old attempt to make `Infallible = !` this caused a problem. + // + // Because at the time the code desugared to + // + // match u32::try_from(1u32) { + // Ok(x) => x, Err(e) => return Err(E::from(e)) + // } + // + // With `Infallible = !`, `e: !` but with fallback to `()`, `e` in `E::from(e)` decayed to `()` + // causing an error. + // + // This does not happen with `Infallible = !`. + // And also does not happen with the newer `?` desugaring that does not pass `e` by value. + // (instead we only pass `Result` (where `Error = !` in this case) which does not get + // the implicit coercion and thus does not decay even with fallback to unit) + u32_try_from(1u32)?; + Ok(()) +} + +fn main() {}