Skip to content

Commit 9a7bf4a

Browse files
committed
Auto merge of #123508 - WaffleLapkin:never-type-2024, r=compiler-errors
Edition 2024: Make `!` fall back to `!` This PR changes never type fallback to be `!` (the never type itself) in the next, 2024, edition. This makes the never type's behavior more intuitive (in 2024 edition) and is the first step of the path to stabilize it. r? `@compiler-errors`
2 parents ebcb862 + e79aafc commit 9a7bf4a

10 files changed

+202
-22
lines changed

compiler/rustc_hir_typeck/src/fallback.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ use rustc_span::{def_id::LocalDefId, Span};
1818
#[derive(Copy, Clone)]
1919
pub enum DivergingFallbackBehavior {
2020
/// Always fallback to `()` (aka "always spontaneous decay")
21-
FallbackToUnit,
21+
ToUnit,
2222
/// Sometimes fallback to `!`, but mainly fallback to `()` so that most of the crates are not broken.
23-
FallbackToNiko,
23+
ContextDependent,
2424
/// Always fallback to `!` (which should be equivalent to never falling back + not making
2525
/// never-to-any coercions unless necessary)
26-
FallbackToNever,
26+
ToNever,
2727
/// Don't fallback at all
2828
NoFallback,
2929
}
@@ -373,13 +373,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
373373
diverging_fallback.insert(diverging_ty, ty);
374374
};
375375

376-
use DivergingFallbackBehavior::*;
377376
match behavior {
378-
FallbackToUnit => {
377+
DivergingFallbackBehavior::ToUnit => {
379378
debug!("fallback to () - legacy: {:?}", diverging_vid);
380379
fallback_to(self.tcx.types.unit);
381380
}
382-
FallbackToNiko => {
381+
DivergingFallbackBehavior::ContextDependent => {
383382
if found_infer_var_info.self_in_trait && found_infer_var_info.output {
384383
// This case falls back to () to ensure that the code pattern in
385384
// tests/ui/never_type/fallback-closure-ret.rs continues to
@@ -415,14 +414,14 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
415414
fallback_to(self.tcx.types.never);
416415
}
417416
}
418-
FallbackToNever => {
417+
DivergingFallbackBehavior::ToNever => {
419418
debug!(
420419
"fallback to ! - `rustc_never_type_mode = \"fallback_to_never\")`: {:?}",
421420
diverging_vid
422421
);
423422
fallback_to(self.tcx.types.never);
424423
}
425-
NoFallback => {
424+
DivergingFallbackBehavior::NoFallback => {
426425
debug!(
427426
"no fallback - `rustc_never_type_mode = \"no_fallback\"`: {:?}",
428427
diverging_vid

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

+30-14
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
124124
body_id: LocalDefId,
125125
) -> FnCtxt<'a, 'tcx> {
126126
let (diverging_fallback_behavior, diverging_block_behavior) =
127-
parse_never_type_options_attr(root_ctxt.tcx);
127+
never_type_behavior(root_ctxt.tcx);
128128
FnCtxt {
129129
body_id,
130130
param_env,
@@ -387,11 +387,33 @@ impl<'tcx> LoweredTy<'tcx> {
387387
}
388388
}
389389

390+
fn never_type_behavior(tcx: TyCtxt<'_>) -> (DivergingFallbackBehavior, DivergingBlockBehavior) {
391+
let (fallback, block) = parse_never_type_options_attr(tcx);
392+
let fallback = fallback.unwrap_or_else(|| default_fallback(tcx));
393+
let block = block.unwrap_or_default();
394+
395+
(fallback, block)
396+
}
397+
398+
/// Returns the default fallback which is used when there is no explicit override via `#![never_type_options(...)]`.
399+
fn default_fallback(tcx: TyCtxt<'_>) -> DivergingFallbackBehavior {
400+
// Edition 2024: fallback to `!`
401+
if tcx.sess.edition().at_least_rust_2024() {
402+
return DivergingFallbackBehavior::ToNever;
403+
}
404+
405+
// `feature(never_type_fallback)`: fallback to `!` or `()` trying to not break stuff
406+
if tcx.features().never_type_fallback {
407+
return DivergingFallbackBehavior::ContextDependent;
408+
}
409+
410+
// Otherwise: fallback to `()`
411+
DivergingFallbackBehavior::ToUnit
412+
}
413+
390414
fn parse_never_type_options_attr(
391415
tcx: TyCtxt<'_>,
392-
) -> (DivergingFallbackBehavior, DivergingBlockBehavior) {
393-
use DivergingFallbackBehavior::*;
394-
416+
) -> (Option<DivergingFallbackBehavior>, Option<DivergingBlockBehavior>) {
395417
// Error handling is dubious here (unwraps), but that's probably fine for an internal attribute.
396418
// Just don't write incorrect attributes <3
397419

@@ -407,10 +429,10 @@ fn parse_never_type_options_attr(
407429
if item.has_name(sym::fallback) && fallback.is_none() {
408430
let mode = item.value_str().unwrap();
409431
match mode {
410-
sym::unit => fallback = Some(FallbackToUnit),
411-
sym::niko => fallback = Some(FallbackToNiko),
412-
sym::never => fallback = Some(FallbackToNever),
413-
sym::no => fallback = Some(NoFallback),
432+
sym::unit => fallback = Some(DivergingFallbackBehavior::ToUnit),
433+
sym::niko => fallback = Some(DivergingFallbackBehavior::ContextDependent),
434+
sym::never => fallback = Some(DivergingFallbackBehavior::ToNever),
435+
sym::no => fallback = Some(DivergingFallbackBehavior::NoFallback),
414436
_ => {
415437
tcx.dcx().span_err(item.span(), format!("unknown never type fallback mode: `{mode}` (supported: `unit`, `niko`, `never` and `no`)"));
416438
}
@@ -439,11 +461,5 @@ fn parse_never_type_options_attr(
439461
);
440462
}
441463

442-
let fallback = fallback.unwrap_or_else(|| {
443-
if tcx.features().never_type_fallback { FallbackToNiko } else { FallbackToUnit }
444-
});
445-
446-
let block = block.unwrap_or_default();
447-
448464
(fallback, block)
449465
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0277]: the trait bound `!: Default` is not satisfied
2+
--> $DIR/never-type-fallback-breaking.rs:17:17
3+
|
4+
LL | true => Default::default(),
5+
| ^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `!`
6+
|
7+
= note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #48950 <https://github.com/rust-lang/rust/issues/48950> for more information)
8+
= help: did you intend to use the type `()` here instead?
9+
10+
error[E0277]: the trait bound `!: Default` is not satisfied
11+
--> $DIR/never-type-fallback-breaking.rs:30:5
12+
|
13+
LL | deserialize()?;
14+
| ^^^^^^^^^^^^^ the trait `Default` is not implemented for `!`
15+
|
16+
= note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #48950 <https://github.com/rust-lang/rust/issues/48950> for more information)
17+
= help: did you intend to use the type `()` here instead?
18+
note: required by a bound in `deserialize`
19+
--> $DIR/never-type-fallback-breaking.rs:26:23
20+
|
21+
LL | fn deserialize<T: Default>() -> Option<T> {
22+
| ^^^^^^^ required by this bound in `deserialize`
23+
24+
error: aborting due to 2 previous errors
25+
26+
For more information about this error, try `rustc --explain E0277`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//@ revisions: e2021 e2024
2+
//
3+
//@[e2021] edition: 2021
4+
//@[e2024] edition: 2024
5+
//@[e2024] compile-flags: -Zunstable-options
6+
//
7+
//@[e2021] run-pass
8+
//@[e2024] check-fail
9+
10+
fn main() {
11+
m();
12+
q();
13+
}
14+
15+
fn m() {
16+
let x = match true {
17+
true => Default::default(),
18+
//[e2024]~^ error: the trait bound `!: Default` is not satisfied
19+
false => panic!("..."),
20+
};
21+
22+
dbg!(x);
23+
}
24+
25+
fn q() -> Option<()> {
26+
fn deserialize<T: Default>() -> Option<T> {
27+
Some(T::default())
28+
}
29+
30+
deserialize()?;
31+
//[e2024]~^ error: the trait bound `!: Default` is not satisfied
32+
33+
None
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return type = ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return type = !
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ revisions: e2021 e2024
2+
//
3+
//@[e2021] edition: 2021
4+
//@[e2024] edition: 2024
5+
//@[e2024] compile-flags: -Zunstable-options
6+
//
7+
//@ run-pass
8+
//@ check-run-results
9+
10+
fn main() {
11+
print_return_type_of(|| panic!());
12+
}
13+
14+
fn print_return_type_of<R>(_: impl FnOnce() -> R) {
15+
println!("return type = {}", std::any::type_name::<R>());
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// issue: rust-lang/rust#66757
2+
//
3+
// This is a *minimization* of the issue.
4+
// Note that the original version with the `?` does not fail anymore even with fallback to unit,
5+
// see `tests/ui/never_type/question_mark_from_never.rs`.
6+
//
7+
//@ revisions: unit never
8+
//@[never] check-pass
9+
#![allow(internal_features)]
10+
#![feature(rustc_attrs, never_type)]
11+
#![cfg_attr(unit, rustc_never_type_options(fallback = "unit"))]
12+
#![cfg_attr(never, rustc_never_type_options(fallback = "never"))]
13+
14+
struct E;
15+
16+
impl From<!> for E {
17+
fn from(_: !) -> E {
18+
E
19+
}
20+
}
21+
22+
#[allow(unreachable_code)]
23+
fn foo(never: !) {
24+
<E as From<!>>::from(never); // Ok
25+
<E as From<_>>::from(never); // Should the inference fail?
26+
//[unit]~^ error: the trait bound `E: From<()>` is not satisfied
27+
}
28+
29+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0277]: the trait bound `E: From<()>` is not satisfied
2+
--> $DIR/from_infer_breaking_with_unit_fallback.rs:25:6
3+
|
4+
LL | <E as From<_>>::from(never); // Should the inference fail?
5+
| ^ the trait `From<()>` is not implemented for `E`
6+
|
7+
= help: the trait `From<!>` is implemented for `E`
8+
= help: for that trait implementation, expected `!`, found `()`
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0277`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// issue: rust-lang/rust#66757
2+
//
3+
// See also: `tests/ui/never_type/from_infer_breaking_with_unit_fallback.rs`.
4+
//
5+
//@ revisions: unit never
6+
//@ check-pass
7+
#![allow(internal_features)]
8+
#![feature(rustc_attrs, never_type)]
9+
#![cfg_attr(unit, rustc_never_type_options(fallback = "unit"))]
10+
#![cfg_attr(never, rustc_never_type_options(fallback = "never"))]
11+
12+
type Infallible = !;
13+
14+
struct E;
15+
16+
impl From<Infallible> for E {
17+
fn from(_: Infallible) -> E {
18+
E
19+
}
20+
}
21+
22+
fn u32_try_from(x: u32) -> Result<u32, Infallible> {
23+
Ok(x)
24+
}
25+
26+
fn _f() -> Result<(), E> {
27+
// In an old attempt to make `Infallible = !` this caused a problem.
28+
//
29+
// Because at the time the code desugared to
30+
//
31+
// match u32::try_from(1u32) {
32+
// Ok(x) => x, Err(e) => return Err(E::from(e))
33+
// }
34+
//
35+
// With `Infallible = !`, `e: !` but with fallback to `()`, `e` in `E::from(e)` decayed to `()`
36+
// causing an error.
37+
//
38+
// This does not happen with `Infallible = !`.
39+
// And also does not happen with the newer `?` desugaring that does not pass `e` by value.
40+
// (instead we only pass `Result<!, Error>` (where `Error = !` in this case) which does not get
41+
// the implicit coercion and thus does not decay even with fallback to unit)
42+
u32_try_from(1u32)?;
43+
Ok(())
44+
}
45+
46+
fn main() {}

0 commit comments

Comments
 (0)