Skip to content

Commit f8c1ad3

Browse files
committed
Implement lint for obligations broken by never type fallback change
1 parent 44d679b commit f8c1ad3

20 files changed

+273
-6
lines changed

compiler/rustc_hir_typeck/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ hir_typeck_convert_using_method = try using `{$sugg}` to convert `{$found}` to `
4444
4545
hir_typeck_ctor_is_private = tuple struct constructor `{$def}` is private
4646
47+
hir_typeck_dependency_on_unit_never_type_fallback = this function depends on never type fallback being `()`
48+
.help = specify the types explicitly
49+
4750
hir_typeck_deref_is_empty = this expression `Deref`s to `{$deref_ty}` which implements `is_empty`
4851
4952
hir_typeck_expected_default_return_type = expected `()` because of default return type

compiler/rustc_hir_typeck/src/errors.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ pub enum NeverTypeFallbackFlowingIntoUnsafe {
183183
Deref,
184184
}
185185

186+
#[derive(LintDiagnostic)]
187+
#[help]
188+
#[diag(hir_typeck_dependency_on_unit_never_type_fallback)]
189+
pub struct DependencyOnUnitNeverTypeFallback {}
190+
186191
#[derive(Subdiagnostic)]
187192
#[multipart_suggestion(
188193
hir_typeck_add_missing_parentheses_in_range,

compiler/rustc_hir_typeck/src/fallback.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ use rustc_data_structures::{
88
use rustc_hir as hir;
99
use rustc_hir::intravisit::Visitor;
1010
use rustc_hir::HirId;
11-
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
1211
use rustc_middle::bug;
12+
use rustc_infer::{
13+
infer::{DefineOpaqueTypes, InferOk},
14+
traits::ObligationCause,
15+
};
1316
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
1417
use rustc_session::lint;
1518
use rustc_span::DUMMY_SP;
1619
use rustc_span::{def_id::LocalDefId, Span};
20+
use rustc_trait_selection::traits::ObligationCtxt;
21+
use rustc_type_ir::TyVid;
1722

1823
#[derive(Copy, Clone)]
1924
pub enum DivergingFallbackBehavior {
@@ -344,6 +349,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
344349
// `!`.
345350
let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len());
346351
let unsafe_infer_vars = OnceCell::new();
352+
353+
self.lint_obligations_broken_by_never_type_fallback_change(behavior, &diverging_vids);
354+
347355
for &diverging_vid in &diverging_vids {
348356
let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
349357
let root_vid = self.root_var(diverging_vid);
@@ -469,6 +477,46 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
469477
}
470478
}
471479

480+
fn lint_obligations_broken_by_never_type_fallback_change(
481+
&self,
482+
behavior: DivergingFallbackBehavior,
483+
diverging_vids: &[TyVid],
484+
) {
485+
let DivergingFallbackBehavior::FallbackToUnit = behavior else { return };
486+
487+
// Returns errors which happen if fallback is set to `fallback`
488+
let try_out = |fallback| {
489+
self.probe(|_| {
490+
let obligations = self.fulfillment_cx.borrow().pending_obligations();
491+
let ocx = ObligationCtxt::new(&self.infcx);
492+
ocx.register_obligations(obligations.iter().cloned());
493+
494+
for &diverging_vid in diverging_vids {
495+
let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
496+
497+
_ = ocx.eq(&ObligationCause::dummy(), self.param_env, diverging_ty, fallback);
498+
}
499+
500+
ocx.select_where_possible()
501+
})
502+
};
503+
504+
// If we have no errors with `fallback = ()`, but *do* have errors with `fallback = !`,
505+
// then this code will be broken by the never type fallback change.qba
506+
let unit_errors = try_out(self.tcx.types.unit);
507+
if unit_errors.is_empty()
508+
&& let never_errors = try_out(self.tcx.types.never)
509+
&& !never_errors.is_empty()
510+
{
511+
self.tcx.emit_node_span_lint(
512+
lint::builtin::DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK,
513+
self.tcx.local_def_id_to_hir_id(self.body_id),
514+
self.tcx.def_span(self.body_id),
515+
errors::DependencyOnUnitNeverTypeFallback {},
516+
)
517+
}
518+
}
519+
472520
/// Returns a graph whose nodes are (unresolved) inference variables and where
473521
/// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
474522
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid, true> {

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4328,6 +4328,62 @@ declare_lint! {
43284328
report_in_external_macro
43294329
}
43304330

4331+
declare_lint! {
4332+
/// The `dependency_on_unit_never_type_fallback` lint detects cases where code compiles with
4333+
/// [never type fallback] being [`()`], but will stop compiling with fallback being [`!`].
4334+
///
4335+
/// [never type fallback]: prim@never#never-type-fallback
4336+
/// [`()`]: prim@unit
4337+
/// [`!`]:
4338+
///
4339+
/// ### Example
4340+
///
4341+
/// ```rust,compile_fail
4342+
/// #![deny(never_type_fallback_flowing_into_unsafe)]
4343+
/// fn main() {
4344+
/// if true {
4345+
/// // return has type `!` which, is some cases, causes never type fallback
4346+
/// return
4347+
/// } else {
4348+
/// // the type produced by this call is not specified explicitly,
4349+
/// // so it will be inferred from the previous branch
4350+
/// Default::default()
4351+
/// };
4352+
/// // depending on the fallback, this may compile (because `()` implements `Default`),
4353+
/// // or it may not (because `!` does not implement `Default`)
4354+
/// }
4355+
/// ```
4356+
///
4357+
/// {{produces}}
4358+
///
4359+
/// ### Explanation
4360+
///
4361+
/// Due to historic reasons never type fallback was `()`, meaning that `!` got spontaneously
4362+
/// coerced to `()`. There are plans to change that, but they may make the code such as above
4363+
/// not compile. Instead of depending on the fallback, you should specify the type explicitly:
4364+
/// ```
4365+
/// if true {
4366+
/// return
4367+
/// } else {
4368+
/// // type is explicitly specified, fallback can't hurt us no more
4369+
/// <() as Default>::default()
4370+
/// };
4371+
/// ```
4372+
///
4373+
/// See [Tracking Issue for making `!` fall back to `!`](https://github.com/rust-lang/rust/issues/123748).
4374+
///
4375+
/// [`!`]: https://doc.rust-lang.org/core/primitive.never.html
4376+
/// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html
4377+
pub DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK,
4378+
Warn,
4379+
"never type fallback affecting unsafe function calls",
4380+
@future_incompatible = FutureIncompatibleInfo {
4381+
reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps,
4382+
reference: "issue #123748 <https://github.com/rust-lang/rust/issues/123748>",
4383+
};
4384+
report_in_external_macro
4385+
}
4386+
43314387
declare_lint! {
43324388
/// The `byte_slice_in_packed_struct_with_derive` lint detects cases where a byte slice field
43334389
/// (`[u8]`) or string slice field (`str`) is used in a `packed` struct that derives one or

tests/ui/never_type/defaulted-never-note.fallback.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0277]: the trait bound `!: ImplementedForUnitButNotNever` is not satisfied
2-
--> $DIR/defaulted-never-note.rs:30:9
2+
--> $DIR/defaulted-never-note.rs:32:9
33
|
44
LL | foo(_x);
55
| --- ^^ the trait `ImplementedForUnitButNotNever` is not implemented for `!`
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/defaulted-never-note.rs:28:1
3+
|
4+
LL | fn smeg() {
5+
| ^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: 1 warning emitted
13+

tests/ui/never_type/defaulted-never-note.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ fn foo<T: ImplementedForUnitButNotNever>(_t: T) {}
2626
//[fallback]~^ NOTE required by this bound in `foo`
2727
//[fallback]~| NOTE required by a bound in `foo`
2828
fn smeg() {
29+
//[nofallback]~^ warn: this function depends on never type fallback being `()`
30+
//[nofallback]~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
2931
let _x = return;
3032
foo(_x);
3133
//[fallback]~^ ERROR the trait bound
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ check-pass
2+
3+
fn main() {
4+
def();
5+
_ = question_mark();
6+
}
7+
8+
fn def() {
9+
//~^ warn: this function depends on never type fallback being `()`
10+
//~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
11+
match true {
12+
false => <_>::default(),
13+
true => return,
14+
};
15+
}
16+
17+
// <https://github.com/rust-lang/rust/issues/51125>
18+
// <https://github.com/rust-lang/rust/issues/39216>
19+
fn question_mark() -> Result<(), ()> {
20+
//~^ warn: this function depends on never type fallback being `()`
21+
//~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
22+
deserialize()?;
23+
Ok(())
24+
}
25+
26+
fn deserialize<T: Default>() -> Result<T, ()> {
27+
Ok(T::default())
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/dependency-on-fallback-to-unit.rs:8:1
3+
|
4+
LL | fn def() {
5+
| ^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: this function depends on never type fallback being `()`
13+
--> $DIR/dependency-on-fallback-to-unit.rs:19:1
14+
|
15+
LL | fn question_mark() -> Result<(), ()> {
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17+
|
18+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
19+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
20+
= help: specify the types explicitly
21+
22+
warning: 2 warnings emitted
23+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/diverging-fallback-control-flow.rs:30:1
3+
|
4+
LL | fn assignment() {
5+
| ^^^^^^^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: this function depends on never type fallback being `()`
13+
--> $DIR/diverging-fallback-control-flow.rs:42:1
14+
|
15+
LL | fn assignment_rev() {
16+
| ^^^^^^^^^^^^^^^^^^^
17+
|
18+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
19+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
20+
= help: specify the types explicitly
21+
22+
warning: 2 warnings emitted
23+

tests/ui/never_type/diverging-fallback-control-flow.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ impl UnitDefault for () {
2828
}
2929

3030
fn assignment() {
31+
//[nofallback]~^ warn: this function depends on never type fallback being `()`
32+
//[nofallback]~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
3133
let x;
3234

3335
if true {
@@ -38,6 +40,8 @@ fn assignment() {
3840
}
3941

4042
fn assignment_rev() {
43+
//[nofallback]~^ warn: this function depends on never type fallback being `()`
44+
//[nofallback]~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
4145
let x;
4246

4347
if true {

tests/ui/never_type/diverging-fallback-no-leak.fallback.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0277]: the trait bound `!: Test` is not satisfied
2-
--> $DIR/diverging-fallback-no-leak.rs:17:23
2+
--> $DIR/diverging-fallback-no-leak.rs:20:23
33
|
44
LL | unconstrained_arg(return);
55
| ----------------- ^^^^^^ the trait `Test` is not implemented for `!`
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/diverging-fallback-no-leak.rs:14:1
3+
|
4+
LL | fn main() {
5+
| ^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: 1 warning emitted
13+

tests/ui/never_type/diverging-fallback-no-leak.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ impl Test for () {}
1212
fn unconstrained_arg<T: Test>(_: T) {}
1313

1414
fn main() {
15+
//[nofallback]~^ warn: this function depends on never type fallback being `()`
16+
//[nofallback]~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
17+
1518
// Here the type variable falls back to `!`,
1619
// and hence we get a type error.
1720
unconstrained_arg(return);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/diverging-fallback-unconstrained-return.rs:28:1
3+
|
4+
LL | fn main() {
5+
| ^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: 1 warning emitted
13+

tests/ui/never_type/diverging-fallback-unconstrained-return.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ fn unconstrained_return<T: UnitReturn>() -> T {
2626
}
2727

2828
fn main() {
29+
//[nofallback]~^ warn: this function depends on never type fallback being `()`
30+
//[nofallback]~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
31+
2932
// In Ye Olde Days, the `T` parameter of `unconstrained_return`
3033
// winds up "entangled" with the `!` type that results from
3134
// `panic!`, and hence falls back to `()`. This is kind of unfortunate
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/fallback-closure-ret.rs:21:1
3+
|
4+
LL | fn main() {
5+
| ^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: 1 warning emitted
13+

tests/ui/never_type/fallback-closure-ret.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212

1313
#![cfg_attr(fallback, feature(never_type_fallback))]
1414

15-
trait Bar { }
16-
impl Bar for () { }
17-
impl Bar for u32 { }
15+
trait Bar {}
16+
impl Bar for () {}
17+
impl Bar for u32 {}
1818

1919
fn foo<R: Bar>(_: impl Fn() -> R) {}
2020

2121
fn main() {
22+
//[nofallback]~^ warn: this function depends on never type fallback being `()`
23+
//[nofallback]~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
2224
foo(|| panic!());
2325
}

tests/ui/never_type/impl_trait_fallback.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ trait T {}
66
impl T for () {}
77

88
fn should_ret_unit() -> impl T {
9+
//~^ warn: this function depends on never type fallback being `()`
10+
//~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
911
panic!()
1012
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/impl_trait_fallback.rs:8:1
3+
|
4+
LL | fn should_ret_unit() -> impl T {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
9+
= help: specify the types explicitly
10+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
11+
12+
warning: 1 warning emitted
13+

0 commit comments

Comments
 (0)