Skip to content

Commit 5137bb3

Browse files
committed
Handle Result<T, !> and ControlFlow<!, T> as T wrt #[must_use]
There is a proposal to change the behaviour of rustc's `must_use` lint to consider `Result<T, U>` and `ControlFlow<U, T>` as `T` when `U` is uninhabited. See <rust-lang/rust#148214>. This might make the user adding extra `#[must_use]` attributes to functions returning `Result<T, !>` or `ControlFlow<!, T>`, which would trigger the `double_must_use` lint in Clippy without the current change.
1 parent 98ced04 commit 5137bb3

11 files changed

Lines changed: 163 additions & 14 deletions

clippy_lints/src/functions/must_use.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use clippy_utils::res::MaybeDef as _;
12
use hir::FnSig;
23
use rustc_errors::Applicability;
34
use rustc_hir::def::Res;
@@ -222,6 +223,13 @@ fn check_must_use_candidate<'tcx>(
222223
format!("#[must_use] \n{indent}"),
223224
Applicability::MachineApplicable,
224225
);
226+
if let Some(msg) = match return_ty(cx, item_id).opt_diag_name(cx) {
227+
Some(sym::ControlFlow) => Some("`ControlFlow<B, C>` as `C` when `B` is uninhabited"),
228+
Some(sym::Result) => Some("`Result<T, E>` as `T` when `E` is uninhabited"),
229+
_ => None,
230+
} {
231+
diag.note(format!("a future version of Rust will treat {msg} wrt `#[must_use]`"));
232+
}
225233
});
226234
}
227235

clippy_utils/src/ty/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,20 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
305305
}
306306
}
307307

308-
// Returns whether the type has #[must_use] attribute
308+
// Returns whether the `ty` has `#[must_use]` attribute. If `ty` is a `Result`/`ControlFlow`
309+
// whose `Err`/`Break` payload is an uninhabited type, the `Ok`/`Continue` payload type
310+
// will be used instead. See <https://github.com/rust-lang/rust/pull/148214>.
309311
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
310312
match ty.kind() {
311-
ty::Adt(adt, _) => find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::MustUse { .. }),
313+
ty::Adt(adt, args) => match cx.tcx.get_diagnostic_name(adt.did()) {
314+
Some(sym::Result) if args.type_at(1).is_privately_uninhabited(cx.tcx, cx.typing_env()) => {
315+
is_must_use_ty(cx, args.type_at(0))
316+
},
317+
Some(sym::ControlFlow) if args.type_at(0).is_privately_uninhabited(cx.tcx, cx.typing_env()) => {
318+
is_must_use_ty(cx, args.type_at(1))
319+
},
320+
_ => find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::MustUse { .. }),
321+
},
312322
ty::Foreign(did) => find_attr!(cx.tcx.get_all_attrs(*did), AttributeKind::MustUse { .. }),
313323
ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => {
314324
// for the Array case we don't need to care for the len == 0 case

tests/ui/double_must_use.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#![warn(clippy::double_must_use)]
22
#![allow(clippy::result_unit_err)]
3+
#![feature(never_type)]
4+
5+
use std::ops::ControlFlow;
36

47
#[must_use]
58
pub fn must_use_result() -> Result<(), ()> {
@@ -40,6 +43,31 @@ async fn async_must_use_result() -> Result<(), ()> {
4043
Ok(())
4144
}
4245

46+
#[must_use]
47+
pub fn must_use_result_with_uninhabited() -> Result<(), !> {
48+
unimplemented!();
49+
}
50+
51+
#[must_use]
52+
pub struct T;
53+
54+
#[must_use]
55+
pub fn must_use_result_with_uninhabited_2() -> Result<T, !> {
56+
//~^ double_must_use
57+
unimplemented!();
58+
}
59+
60+
#[must_use]
61+
pub fn must_use_controlflow_with_uninhabited() -> ControlFlow<std::convert::Infallible> {
62+
unimplemented!();
63+
}
64+
65+
#[must_use]
66+
pub fn must_use_controlflow_with_uninhabited_2() -> ControlFlow<std::convert::Infallible, T> {
67+
//~^ double_must_use
68+
unimplemented!();
69+
}
70+
4371
fn main() {
4472
must_use_result();
4573
must_use_tuple();

tests/ui/double_must_use.stderr

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
2-
--> tests/ui/double_must_use.rs:5:1
2+
--> tests/ui/double_must_use.rs:8:1
33
|
44
LL | pub fn must_use_result() -> Result<(), ()> {
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -9,28 +9,44 @@ LL | pub fn must_use_result() -> Result<(), ()> {
99
= help: to override `-D warnings` add `#[allow(clippy::double_must_use)]`
1010

1111
error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
12-
--> tests/ui/double_must_use.rs:12:1
12+
--> tests/ui/double_must_use.rs:15:1
1313
|
1414
LL | pub fn must_use_tuple() -> (Result<(), ()>, u8) {
1515
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1616
|
1717
= help: either add some descriptive message or remove the attribute
1818

1919
error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
20-
--> tests/ui/double_must_use.rs:19:1
20+
--> tests/ui/double_must_use.rs:22:1
2121
|
2222
LL | pub fn must_use_array() -> [Result<(), ()>; 1] {
2323
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2424
|
2525
= help: either add some descriptive message or remove the attribute
2626

2727
error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
28-
--> tests/ui/double_must_use.rs:37:1
28+
--> tests/ui/double_must_use.rs:40:1
2929
|
3030
LL | async fn async_must_use_result() -> Result<(), ()> {
3131
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3232
|
3333
= help: either add some descriptive message or remove the attribute
3434

35-
error: aborting due to 4 previous errors
35+
error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
36+
--> tests/ui/double_must_use.rs:55:1
37+
|
38+
LL | pub fn must_use_result_with_uninhabited_2() -> Result<T, !> {
39+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40+
|
41+
= help: either add some descriptive message or remove the attribute
42+
43+
error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
44+
--> tests/ui/double_must_use.rs:66:1
45+
|
46+
LL | pub fn must_use_controlflow_with_uninhabited_2() -> ControlFlow<std::convert::Infallible, T> {
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48+
|
49+
= help: either add some descriptive message or remove the attribute
50+
51+
error: aborting due to 6 previous errors
3652

tests/ui/drop_non_drop.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ fn make_result<T>(t: T) -> Result<T, ()> {
66
Ok(t)
77
}
88

9+
// The return type should behave as `T` as the `Err` variant is uninhabited
10+
fn make_result_uninhabited_err<T>(t: T) -> Result<T, std::convert::Infallible> {
11+
Ok(t)
12+
}
13+
914
#[must_use]
1015
fn must_use<T>(t: T) -> T {
1116
t
@@ -41,4 +46,8 @@ fn main() {
4146

4247
// Don't lint
4348
drop(Baz(Bar));
49+
50+
// Lint
51+
drop(make_result_uninhabited_err(Foo));
52+
//~^ drop_non_drop
4453
}

tests/ui/drop_non_drop.stderr

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
error: call to `std::mem::drop` with a value that does not implement `Drop`. Dropping such a type only extends its contained lifetimes
2-
--> tests/ui/drop_non_drop.rs:22:5
2+
--> tests/ui/drop_non_drop.rs:27:5
33
|
44
LL | drop(Foo);
55
| ^^^^^^^^^
66
|
77
note: argument has type `main::Foo`
8-
--> tests/ui/drop_non_drop.rs:22:10
8+
--> tests/ui/drop_non_drop.rs:27:10
99
|
1010
LL | drop(Foo);
1111
| ^^^
1212
= note: `-D clippy::drop-non-drop` implied by `-D warnings`
1313
= help: to override `-D warnings` add `#[allow(clippy::drop_non_drop)]`
1414

1515
error: call to `std::mem::drop` with a value that does not implement `Drop`. Dropping such a type only extends its contained lifetimes
16-
--> tests/ui/drop_non_drop.rs:39:5
16+
--> tests/ui/drop_non_drop.rs:44:5
1717
|
1818
LL | drop(Baz(Foo));
1919
| ^^^^^^^^^^^^^^
2020
|
2121
note: argument has type `main::Baz<main::Foo>`
22-
--> tests/ui/drop_non_drop.rs:39:10
22+
--> tests/ui/drop_non_drop.rs:44:10
2323
|
2424
LL | drop(Baz(Foo));
2525
| ^^^^^^^^
2626

27-
error: aborting due to 2 previous errors
27+
error: call to `std::mem::drop` with a value that does not implement `Drop`. Dropping such a type only extends its contained lifetimes
28+
--> tests/ui/drop_non_drop.rs:51:5
29+
|
30+
LL | drop(make_result_uninhabited_err(Foo));
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32+
|
33+
note: argument has type `std::result::Result<main::Foo, std::convert::Infallible>`
34+
--> tests/ui/drop_non_drop.rs:51:10
35+
|
36+
LL | drop(make_result_uninhabited_err(Foo));
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
error: aborting due to 3 previous errors
2840

tests/ui/let_underscore_must_use.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,14 @@ fn main() {
109109

110110
#[allow(clippy::let_underscore_must_use)]
111111
let _ = a;
112+
113+
// No lint because this type should behave as `()`
114+
let _ = Result::<_, std::convert::Infallible>::Ok(());
115+
116+
#[must_use]
117+
struct T;
118+
119+
// Lint because this type should behave as `T`
120+
let _ = Result::<_, std::convert::Infallible>::Ok(T);
121+
//~^ let_underscore_must_use
112122
}

tests/ui/let_underscore_must_use.stderr

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,13 @@ LL | let _ = a;
9696
|
9797
= help: consider explicitly using expression value
9898

99-
error: aborting due to 12 previous errors
99+
error: non-binding `let` on an expression with `#[must_use]` type
100+
--> tests/ui/let_underscore_must_use.rs:120:5
101+
|
102+
LL | let _ = Result::<_, std::convert::Infallible>::Ok(T);
103+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
104+
|
105+
= help: consider explicitly using expression value
106+
107+
error: aborting due to 13 previous errors
100108

tests/ui/must_use_candidates.fixed

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,15 @@ pub extern "C" fn unmangled(i: bool) -> bool {
107107
fn main() {
108108
assert_eq!(1, pure(1));
109109
}
110+
111+
//~v must_use_candidate
112+
#[must_use]
113+
pub fn result_uninhabited() -> Result<i32, std::convert::Infallible> {
114+
todo!()
115+
}
116+
117+
//~v must_use_candidate
118+
#[must_use]
119+
pub fn controlflow_uninhabited() -> std::ops::ControlFlow<std::convert::Infallible, i32> {
120+
todo!()
121+
}

tests/ui/must_use_candidates.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,13 @@ pub extern "C" fn unmangled(i: bool) -> bool {
102102
fn main() {
103103
assert_eq!(1, pure(1));
104104
}
105+
106+
//~v must_use_candidate
107+
pub fn result_uninhabited() -> Result<i32, std::convert::Infallible> {
108+
todo!()
109+
}
110+
111+
//~v must_use_candidate
112+
pub fn controlflow_uninhabited() -> std::ops::ControlFlow<std::convert::Infallible, i32> {
113+
todo!()
114+
}

0 commit comments

Comments
 (0)