Skip to content

Commit b6e5e3f

Browse files
committed
Auto merge of #125289 - WaffleLapkin:never-obligations, r=compiler-errors
Implement lint for obligations broken by never type fallback change This is the second (and probably last major?) lint required for the never type fallback change. The idea is to check if the code errors with `fallback = ()` and if it errors with `fallback = !` and if it went from "ok" to "error", lint. I'm not happy with the diagnostic, ideally we'd highlight what bound is the problem. But I'm really unsure how to do that (cc `@jackh726,` iirc you had some ideas?) r? `@compiler-errors` Thanks `@BoxyUwU` with helping with trait solver stuff when I was implementing the initial version of this lint. Tracking: - #123748
2 parents 9fdbfe1 + ea98e42 commit b6e5e3f

27 files changed

+344
-29
lines changed

compiler/rustc_hir_typeck/messages.ftl

+3
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

+5
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

+50
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
1414
use rustc_session::lint;
1515
use rustc_span::DUMMY_SP;
1616
use rustc_span::{def_id::LocalDefId, Span};
17+
use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt};
1718

1819
#[derive(Copy, Clone)]
1920
pub enum DivergingFallbackBehavior {
@@ -344,6 +345,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
344345
// `!`.
345346
let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len());
346347
let unsafe_infer_vars = OnceCell::new();
348+
349+
self.lint_obligations_broken_by_never_type_fallback_change(behavior, &diverging_vids);
350+
347351
for &diverging_vid in &diverging_vids {
348352
let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
349353
let root_vid = self.root_var(diverging_vid);
@@ -468,6 +472,52 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
468472
}
469473
}
470474

475+
fn lint_obligations_broken_by_never_type_fallback_change(
476+
&self,
477+
behavior: DivergingFallbackBehavior,
478+
diverging_vids: &[ty::TyVid],
479+
) {
480+
let DivergingFallbackBehavior::ToUnit = behavior else { return };
481+
482+
// Fallback happens if and only if there are diverging variables
483+
if diverging_vids.is_empty() {
484+
return;
485+
}
486+
487+
// Returns errors which happen if fallback is set to `fallback`
488+
let remaining_errors_if_fallback_to = |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+
.expect("expected diverging var to be unconstrained");
499+
}
500+
501+
ocx.select_where_possible()
502+
})
503+
};
504+
505+
// If we have no errors with `fallback = ()`, but *do* have errors with `fallback = !`,
506+
// then this code will be broken by the never type fallback change.qba
507+
let unit_errors = remaining_errors_if_fallback_to(self.tcx.types.unit);
508+
if unit_errors.is_empty()
509+
&& let never_errors = remaining_errors_if_fallback_to(self.tcx.types.never)
510+
&& !never_errors.is_empty()
511+
{
512+
self.tcx.emit_node_span_lint(
513+
lint::builtin::DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK,
514+
self.tcx.local_def_id_to_hir_id(self.body_id),
515+
self.tcx.def_span(self.body_id),
516+
errors::DependencyOnUnitNeverTypeFallback {},
517+
)
518+
}
519+
}
520+
471521
/// Returns a graph whose nodes are (unresolved) inference variables and where
472522
/// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
473523
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid, true> {

compiler/rustc_lint_defs/src/builtin.rs

+54
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ declare_lint_pass! {
3434
CONST_EVALUATABLE_UNCHECKED,
3535
CONST_ITEM_MUTATION,
3636
DEAD_CODE,
37+
DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK,
3738
DEPRECATED,
3839
DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
3940
DEPRECATED_IN_FUTURE,
@@ -4199,6 +4200,59 @@ declare_lint! {
41994200
report_in_external_macro
42004201
}
42014202

4203+
declare_lint! {
4204+
/// The `dependency_on_unit_never_type_fallback` lint detects cases where code compiles with
4205+
/// [never type fallback] being [`()`], but will stop compiling with fallback being [`!`].
4206+
///
4207+
/// [never type fallback]: https://doc.rust-lang.org/nightly/core/primitive.never.html#never-type-fallback
4208+
/// [`!`]: https://doc.rust-lang.org/core/primitive.never.html
4209+
/// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html
4210+
///
4211+
/// ### Example
4212+
///
4213+
/// ```rust,compile_fail
4214+
/// #![deny(dependency_on_unit_never_type_fallback)]
4215+
/// fn main() {
4216+
/// if true {
4217+
/// // return has type `!` which, is some cases, causes never type fallback
4218+
/// return
4219+
/// } else {
4220+
/// // the type produced by this call is not specified explicitly,
4221+
/// // so it will be inferred from the previous branch
4222+
/// Default::default()
4223+
/// };
4224+
/// // depending on the fallback, this may compile (because `()` implements `Default`),
4225+
/// // or it may not (because `!` does not implement `Default`)
4226+
/// }
4227+
/// ```
4228+
///
4229+
/// {{produces}}
4230+
///
4231+
/// ### Explanation
4232+
///
4233+
/// Due to historic reasons never type fallback was `()`, meaning that `!` got spontaneously
4234+
/// coerced to `()`. There are plans to change that, but they may make the code such as above
4235+
/// not compile. Instead of depending on the fallback, you should specify the type explicitly:
4236+
/// ```
4237+
/// if true {
4238+
/// return
4239+
/// } else {
4240+
/// // type is explicitly specified, fallback can't hurt us no more
4241+
/// <() as Default>::default()
4242+
/// };
4243+
/// ```
4244+
///
4245+
/// See [Tracking Issue for making `!` fall back to `!`](https://github.com./rust-lang/rust/issues/123748).
4246+
pub DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK,
4247+
Warn,
4248+
"never type fallback affecting unsafe function calls",
4249+
@future_incompatible = FutureIncompatibleInfo {
4250+
reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps,
4251+
reference: "issue #123748 <https://github.com./rust-lang/rust/issues/123748>",
4252+
};
4253+
report_in_external_macro
4254+
}
4255+
42024256
declare_lint! {
42034257
/// The `byte_slice_in_packed_struct_with_derive` lint detects cases where a byte slice field
42044258
/// (`[u8]`) or string slice field (`str`) is used in a `packed` struct that derives one or

src/tools/clippy/tests/ui/new_ret_no_self.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,7 @@ mod issue7344 {
390390

391391
impl<T> RetImplTraitSelf2<T> {
392392
// should not trigger lint
393-
fn new(t: T) -> impl Trait2<(), Self> {
394-
unimplemented!()
395-
}
393+
fn new(t: T) -> impl Trait2<(), Self> {}
396394
}
397395

398396
struct RetImplTraitNoSelf2<T>(T);
@@ -401,7 +399,6 @@ mod issue7344 {
401399
// should trigger lint
402400
fn new(t: T) -> impl Trait2<(), i32> {
403401
//~^ ERROR: methods called `new` usually return `Self`
404-
unimplemented!()
405402
}
406403
}
407404

src/tools/clippy/tests/ui/new_ret_no_self.stderr

+1-2
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,10 @@ LL | | }
9696
| |_________^
9797

9898
error: methods called `new` usually return `Self`
99-
--> tests/ui/new_ret_no_self.rs:402:9
99+
--> tests/ui/new_ret_no_self.rs:400:9
100100
|
101101
LL | / fn new(t: T) -> impl Trait2<(), i32> {
102102
LL | |
103-
LL | | unimplemented!()
104103
LL | | }
105104
| |_________^
106105

tests/ui/delegation/not-supported.rs

+4
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,16 @@ mod opaque {
7070

7171
pub fn opaque_arg(_: impl Trait) -> i32 { 0 }
7272
pub fn opaque_ret() -> impl Trait { unimplemented!() }
73+
//~^ warn: this function depends on never type fallback being `()`
74+
//~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
7375
}
7476
reuse to_reuse::opaque_arg;
7577
//~^ ERROR delegation with early bound generics is not supported yet
7678

7779
trait ToReuse {
7880
fn opaque_ret() -> impl Trait { unimplemented!() }
81+
//~^ warn: this function depends on never type fallback being `()`
82+
//~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
7983
}
8084

8185
// FIXME: Inherited `impl Trait`s create query cycles when used inside trait impls.

tests/ui/delegation/not-supported.stderr

+36-15
Original file line numberDiff line numberDiff line change
@@ -107,62 +107,83 @@ LL | reuse Trait::foo2 { &self.0 }
107107
| ^^^^
108108

109109
error: delegation with early bound generics is not supported yet
110-
--> $DIR/not-supported.rs:74:21
110+
--> $DIR/not-supported.rs:76:21
111111
|
112112
LL | pub fn opaque_arg(_: impl Trait) -> i32 { 0 }
113113
| --------------------------------------- callee defined here
114114
...
115115
LL | reuse to_reuse::opaque_arg;
116116
| ^^^^^^^^^^
117117

118-
error[E0391]: cycle detected when computing type of `opaque::<impl at $DIR/not-supported.rs:82:5: 82:24>::{synthetic#0}`
119-
--> $DIR/not-supported.rs:83:25
118+
warning: this function depends on never type fallback being `()`
119+
--> $DIR/not-supported.rs:80:9
120+
|
121+
LL | fn opaque_ret() -> impl Trait { unimplemented!() }
122+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123+
|
124+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
125+
= note: for more information, see issue #123748 <https://github.com./rust-lang/rust/issues/123748>
126+
= help: specify the types explicitly
127+
= note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
128+
129+
error[E0391]: cycle detected when computing type of `opaque::<impl at $DIR/not-supported.rs:86:5: 86:24>::{synthetic#0}`
130+
--> $DIR/not-supported.rs:87:25
120131
|
121132
LL | reuse to_reuse::opaque_ret;
122133
| ^^^^^^^^^^
123134
|
124135
note: ...which requires comparing an impl and trait method signature, inferring any hidden `impl Trait` types in the process...
125-
--> $DIR/not-supported.rs:83:25
136+
--> $DIR/not-supported.rs:87:25
126137
|
127138
LL | reuse to_reuse::opaque_ret;
128139
| ^^^^^^^^^^
129-
= note: ...which again requires computing type of `opaque::<impl at $DIR/not-supported.rs:82:5: 82:24>::{synthetic#0}`, completing the cycle
130-
note: cycle used when checking that `opaque::<impl at $DIR/not-supported.rs:82:5: 82:24>` is well-formed
131-
--> $DIR/not-supported.rs:82:5
140+
= note: ...which again requires computing type of `opaque::<impl at $DIR/not-supported.rs:86:5: 86:24>::{synthetic#0}`, completing the cycle
141+
note: cycle used when checking that `opaque::<impl at $DIR/not-supported.rs:86:5: 86:24>` is well-formed
142+
--> $DIR/not-supported.rs:86:5
132143
|
133144
LL | impl ToReuse for u8 {
134145
| ^^^^^^^^^^^^^^^^^^^
135146
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
136147

137-
error[E0391]: cycle detected when computing type of `opaque::<impl at $DIR/not-supported.rs:85:5: 85:25>::{synthetic#0}`
138-
--> $DIR/not-supported.rs:86:24
148+
warning: this function depends on never type fallback being `()`
149+
--> $DIR/not-supported.rs:72:9
150+
|
151+
LL | pub fn opaque_ret() -> impl Trait { unimplemented!() }
152+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
153+
|
154+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
155+
= note: for more information, see issue #123748 <https://github.com./rust-lang/rust/issues/123748>
156+
= help: specify the types explicitly
157+
158+
error[E0391]: cycle detected when computing type of `opaque::<impl at $DIR/not-supported.rs:89:5: 89:25>::{synthetic#0}`
159+
--> $DIR/not-supported.rs:90:24
139160
|
140161
LL | reuse ToReuse::opaque_ret;
141162
| ^^^^^^^^^^
142163
|
143164
note: ...which requires comparing an impl and trait method signature, inferring any hidden `impl Trait` types in the process...
144-
--> $DIR/not-supported.rs:86:24
165+
--> $DIR/not-supported.rs:90:24
145166
|
146167
LL | reuse ToReuse::opaque_ret;
147168
| ^^^^^^^^^^
148-
= note: ...which again requires computing type of `opaque::<impl at $DIR/not-supported.rs:85:5: 85:25>::{synthetic#0}`, completing the cycle
149-
note: cycle used when checking that `opaque::<impl at $DIR/not-supported.rs:85:5: 85:25>` is well-formed
150-
--> $DIR/not-supported.rs:85:5
169+
= note: ...which again requires computing type of `opaque::<impl at $DIR/not-supported.rs:89:5: 89:25>::{synthetic#0}`, completing the cycle
170+
note: cycle used when checking that `opaque::<impl at $DIR/not-supported.rs:89:5: 89:25>` is well-formed
171+
--> $DIR/not-supported.rs:89:5
151172
|
152173
LL | impl ToReuse for u16 {
153174
| ^^^^^^^^^^^^^^^^^^^^
154175
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
155176

156177
error: recursive delegation is not supported yet
157-
--> $DIR/not-supported.rs:99:22
178+
--> $DIR/not-supported.rs:103:22
158179
|
159180
LL | pub reuse to_reuse2::foo;
160181
| --- callee defined here
161182
...
162183
LL | reuse to_reuse1::foo;
163184
| ^^^
164185

165-
error: aborting due to 16 previous errors
186+
error: aborting due to 16 previous errors; 2 warnings emitted
166187

167188
Some errors have detailed explanations: E0049, E0195, E0391.
168189
For more information about an error, try `rustc --explain E0049`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
warning: this function depends on never type fallback being `()`
2+
--> $DIR/never-type-fallback-breaking.rs:15:1
3+
|
4+
LL | fn m() {
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/never-type-fallback-breaking.rs:27:1
14+
|
15+
LL | fn q() -> Option<()> {
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/editions/never-type-fallback-breaking.e2024.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0277]: the trait bound `!: Default` is not satisfied
2-
--> $DIR/never-type-fallback-breaking.rs:17:17
2+
--> $DIR/never-type-fallback-breaking.rs:19:17
33
|
44
LL | true => Default::default(),
55
| ^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `!`
@@ -8,15 +8,15 @@ LL | true => Default::default(),
88
= help: did you intend to use the type `()` here instead?
99

1010
error[E0277]: the trait bound `!: Default` is not satisfied
11-
--> $DIR/never-type-fallback-breaking.rs:30:5
11+
--> $DIR/never-type-fallback-breaking.rs:34:5
1212
|
1313
LL | deserialize()?;
1414
| ^^^^^^^^^^^^^ the trait `Default` is not implemented for `!`
1515
|
1616
= 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)
1717
= help: did you intend to use the type `()` here instead?
1818
note: required by a bound in `deserialize`
19-
--> $DIR/never-type-fallback-breaking.rs:26:23
19+
--> $DIR/never-type-fallback-breaking.rs:30:23
2020
|
2121
LL | fn deserialize<T: Default>() -> Option<T> {
2222
| ^^^^^^^ required by this bound in `deserialize`

tests/ui/editions/never-type-fallback-breaking.rs

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ fn main() {
1313
}
1414

1515
fn m() {
16+
//[e2021]~^ this function depends on never type fallback being `()`
17+
//[e2021]~| this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
1618
let x = match true {
1719
true => Default::default(),
1820
//[e2024]~^ error: the trait bound `!: Default` is not satisfied
@@ -23,6 +25,8 @@ fn m() {
2325
}
2426

2527
fn q() -> Option<()> {
28+
//[e2021]~^ this function depends on never type fallback being `()`
29+
//[e2021]~| this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
2630
fn deserialize<T: Default>() -> Option<T> {
2731
Some(T::default())
2832
}

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

+1-1
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 `!`
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+

0 commit comments

Comments
 (0)