Skip to content

Commit a3baa7b

Browse files
committed
Auto merge of rust-lang#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: - rust-lang#123748
2 parents 9a7bf4a + b73eb9a commit a3baa7b

24 files changed

+314
-26
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::FallbackToUnit = 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,
@@ -4195,6 +4196,59 @@ declare_lint! {
41954196
report_in_external_macro
41964197
}
41974198

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

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+

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

+2
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
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+
}
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+

0 commit comments

Comments
 (0)