Skip to content

Commit 3ed43d8

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

11 files changed

Lines changed: 121 additions & 22 deletions

clippy_lints/src/drop_forget_ref.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
102102
sym::mem_drop
103103
if !(arg_ty.needs_drop(cx.tcx, cx.typing_env())
104104
|| is_must_use_func_call(cx, arg)
105-
|| is_must_use_ty(cx, arg_ty)
105+
|| is_must_use_ty(cx, arg_ty, true)
106106
|| drop_is_single_call_in_arm) =>
107107
{
108108
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY.into(), Some(arg.span))

clippy_lints/src/functions/must_use.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,12 @@ fn check_needless_must_use(
173173
"remove `must_use`",
174174
);
175175
}
176-
} else if reason.is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
176+
} else if reason.is_none() && is_must_use_ty(cx, return_ty(cx, item_id), true) {
177177
// Ignore async functions unless Future::Output type is a must_use type
178178
if sig.header.is_async() {
179179
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
180180
if let Some(future_ty) = infcx.err_ctxt().get_impl_future_output_ty(return_ty(cx, item_id))
181-
&& !is_must_use_ty(cx, future_ty)
181+
&& !is_must_use_ty(cx, future_ty, true)
182182
{
183183
return;
184184
}
@@ -209,7 +209,9 @@ fn check_must_use_candidate<'tcx>(
209209
|| item_span.in_external_macro(cx.sess().source_map())
210210
|| returns_unit(decl)
211211
|| !cx.effective_visibilities.is_exported(item_id.def_id)
212-
|| is_must_use_ty(cx, return_ty(cx, item_id))
212+
// The `simplify_uninhabited` argument can be later changed to `true` when
213+
// rustc's `must_use` lint handles `Result<T, !>` and `ControlFlow<!, T>` as `T`.
214+
|| is_must_use_ty(cx, return_ty(cx, item_id), false)
213215
|| item_span.from_expansion()
214216
{
215217
return;

clippy_lints/src/let_underscore.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
170170
diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`");
171171
},
172172
);
173-
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
173+
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init), true) {
174174
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
175175
span_lint_and_then(
176176
cx,

clippy_lints/src/return_self_not_must_use.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, spa
8888
// there is one, we shouldn't emit a warning!
8989
&& self_arg.peel_refs() == ret_ty
9090
// If `Self` is already marked as `#[must_use]`, no need for the attribute here.
91-
&& !is_must_use_ty(cx, ret_ty)
91+
&& !is_must_use_ty(cx, ret_ty, false)
9292
{
9393
span_lint_and_help(
9494
cx,

clippy_utils/src/ty/mod.rs

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

308-
// Returns whether the type has #[must_use] attribute
309-
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
308+
// Returns whether the `ty` has `#[must_use]` attribute. If `simplify_uninhabited` is set and
309+
// `ty` is a `Result`/`ControlFlow` whose `Err`/`Break` payload is an uninhabited type,
310+
// the `Ok`/`Continue` payload type will be used instead. See <https://github.com/rust-lang/rust/pull/148214>.
311+
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, simplify_uninhabited: bool) -> 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)
315+
if simplify_uninhabited && args.type_at(1).is_privately_uninhabited(cx.tcx, cx.typing_env()) =>
316+
{
317+
is_must_use_ty(cx, args.type_at(0), simplify_uninhabited)
318+
},
319+
Some(sym::ControlFlow)
320+
if simplify_uninhabited && args.type_at(0).is_privately_uninhabited(cx.tcx, cx.typing_env()) =>
321+
{
322+
is_must_use_ty(cx, args.type_at(1), simplify_uninhabited)
323+
},
324+
_ => find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::MustUse { .. }),
325+
},
312326
ty::Foreign(did) => find_attr!(cx.tcx.get_all_attrs(*did), AttributeKind::MustUse { .. }),
313327
ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => {
314328
// for the Array case we don't need to care for the len == 0 case
315329
// because we don't want to lint functions returning empty arrays
316-
is_must_use_ty(cx, *ty)
330+
is_must_use_ty(cx, *ty, simplify_uninhabited)
317331
},
318-
ty::Tuple(args) => args.iter().any(|ty| is_must_use_ty(cx, ty)),
332+
ty::Tuple(args) => args.iter().any(|ty| is_must_use_ty(cx, ty, simplify_uninhabited)),
319333
ty::Alias(ty::Opaque, AliasTy { def_id, .. }) => {
320334
for (predicate, _) in cx.tcx.explicit_item_self_bounds(def_id).skip_binder() {
321335
if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()

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
}

0 commit comments

Comments
 (0)