From 47334d2184eb88139cea6dffdf794a00e215030c Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 5 Apr 2024 17:35:33 +0000 Subject: [PATCH 1/5] Refactor never type behavior code --- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 31 +++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index afba812a8e7bb..2a2c4ac10b258 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, @@ -380,9 +380,30 @@ 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 { + use DivergingFallbackBehavior::*; + + // `feature(never_type_fallback)`: fallback to `!` or `()` trying to not break stuff + if tcx.features().never_type_fallback { + return FallbackToNiko; + } + + // Otherwise: fallback to `()` + FallbackToUnit +} + fn parse_never_type_options_attr( tcx: TyCtxt<'_>, -) -> (DivergingFallbackBehavior, DivergingBlockBehavior) { +) -> (Option, Option) { use DivergingFallbackBehavior::*; // Error handling is dubious here (unwraps), but that's probably fine for an internal attribute. @@ -432,11 +453,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) } From 31a88794a0c2e1e066dbbab66dae60f8d9e19bcd Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 5 Apr 2024 17:57:27 +0000 Subject: [PATCH 2/5] Edition 2024: Make `!` fallback to `!` --- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index 2a2c4ac10b258..10d832f97264f 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -392,6 +392,11 @@ fn never_type_behavior(tcx: TyCtxt<'_>) -> (DivergingFallbackBehavior, Diverging fn default_fallback(tcx: TyCtxt<'_>) -> DivergingFallbackBehavior { use DivergingFallbackBehavior::*; + // Edition 2024: fallback to `!` + if tcx.sess.edition().at_least_rust_2024() { + return FallbackToNever; + } + // `feature(never_type_fallback)`: fallback to `!` or `()` trying to not break stuff if tcx.features().never_type_fallback { return FallbackToNiko; From 3b3e5042353359f8a2398680c25b8b160e0fad0a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 5 Apr 2024 17:58:40 +0000 Subject: [PATCH 3/5] Add a test for never type fallback edition change --- .../never-type-fallback.e2021.run.stdout | 1 + .../never-type-fallback.e2024.run.stdout | 1 + tests/ui/editions/never-type-fallback.rs | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 tests/ui/editions/never-type-fallback.e2021.run.stdout create mode 100644 tests/ui/editions/never-type-fallback.e2024.run.stdout create mode 100644 tests/ui/editions/never-type-fallback.rs 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::()); +} From 0f63cd1056dc44a1a7d167cee7c80f46abdeae71 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 11 Apr 2024 17:29:36 +0000 Subject: [PATCH 4/5] Add some more tests for the never type fallback --- .../never-type-fallback-breaking.e2024.stderr | 26 +++++++++++ .../editions/never-type-fallback-breaking.rs | 34 ++++++++++++++ .../from_infer_breaking_with_unit_fallback.rs | 29 ++++++++++++ ...er_breaking_with_unit_fallback.unit.stderr | 12 +++++ .../ui/never_type/question_mark_from_never.rs | 46 +++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 tests/ui/editions/never-type-fallback-breaking.e2024.stderr create mode 100644 tests/ui/editions/never-type-fallback-breaking.rs create mode 100644 tests/ui/never_type/from_infer_breaking_with_unit_fallback.rs create mode 100644 tests/ui/never_type/from_infer_breaking_with_unit_fallback.unit.stderr create mode 100644 tests/ui/never_type/question_mark_from_never.rs 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/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() {} From e79aafcaa8891b28279739c236ae5f032a2c50b0 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 17 May 2024 14:56:26 +0200 Subject: [PATCH 5/5] Rename `DivergingFallbackBehavior` variants and don't use `::*` --- compiler/rustc_hir_typeck/src/fallback.rs | 15 +++++++-------- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 18 +++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index c79b6be656066..3cfc7437a4375 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, } @@ -403,13 +403,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 @@ -445,14 +444,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 10d832f97264f..d6c6093e31903 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -390,27 +390,23 @@ fn never_type_behavior(tcx: TyCtxt<'_>) -> (DivergingFallbackBehavior, Diverging /// Returns the default fallback which is used when there is no explicit override via `#![never_type_options(...)]`. fn default_fallback(tcx: TyCtxt<'_>) -> DivergingFallbackBehavior { - use DivergingFallbackBehavior::*; - // Edition 2024: fallback to `!` if tcx.sess.edition().at_least_rust_2024() { - return FallbackToNever; + return DivergingFallbackBehavior::ToNever; } // `feature(never_type_fallback)`: fallback to `!` or `()` trying to not break stuff if tcx.features().never_type_fallback { - return FallbackToNiko; + return DivergingFallbackBehavior::ContextDependent; } // Otherwise: fallback to `()` - FallbackToUnit + DivergingFallbackBehavior::ToUnit } fn parse_never_type_options_attr( tcx: TyCtxt<'_>, ) -> (Option, Option) { - use DivergingFallbackBehavior::*; - // Error handling is dubious here (unwraps), but that's probably fine for an internal attribute. // Just don't write incorrect attributes <3 @@ -426,10 +422,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`)")); }