Auto merge of #148214 - WaffleLapkin:never-worn-never-type, r=fee1-dead

Consider `Result<T, Uninhabited>` and `ControlFlow<Uninhabited, T>` to be equivalent to `T` for must use lint





This is an extension to rust-lang/rust#147382.

With this PR `Result<T, Uninhabited>` and `ControlFlow<Uninhabited, T>` considered as must use iif `T` must be used.

For such cases the lint will mention that `T` is wrapped in a `Result`/`ControlFlow` with an uninhabited error/break.

The reasoning here is that `Result<T, Uninhabited>` is equivalent to `T` in which values can be represented and thus the must-used-ness should also be equivalent.

Fixes https://github.com/rust-lang/rust/issues/65861
This commit is contained in:
bors
2026-05-09 22:49:40 +00:00
3 changed files with 39 additions and 45 deletions
+11 -38
View File
@@ -133,18 +133,11 @@ pub enum MustUsePath {
/// Returns `Some(path)` if `ty` should be considered as "`must_use`" in the context of `expr`
/// (`expr` is used to get the parent module, which can affect which types are considered uninhabited).
///
/// If `simplify_uninhabited` is true, this function considers `Result<T, Uninhabited>` and
/// `ControlFlow<Uninhabited, T>` the same as `T` (we don't set this *yet* in rustc, but expose this
/// so clippy can use this).
//
// FIXME: remove `simplify_uninhabited` once clippy had a release with the new semantics.
#[instrument(skip(cx, expr), level = "debug", ret)]
pub fn is_ty_must_use<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
expr: &hir::Expr<'_>,
simplify_uninhabited: bool,
) -> IsTyMustUse {
if ty.is_unit() {
return IsTyMustUse::Trivial;
@@ -157,50 +150,29 @@ pub fn is_ty_must_use<'tcx>(
match *ty.kind() {
_ if is_uninhabited(ty) => IsTyMustUse::Trivial,
ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => {
is_ty_must_use(cx, boxed, expr, simplify_uninhabited)
.map(|inner| MustUsePath::Boxed(Box::new(inner)))
is_ty_must_use(cx, boxed, expr).map(|inner| MustUsePath::Boxed(Box::new(inner)))
}
ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => {
let pinned_ty = args.type_at(0);
is_ty_must_use(cx, pinned_ty, expr, simplify_uninhabited)
.map(|inner| MustUsePath::Pinned(Box::new(inner)))
is_ty_must_use(cx, pinned_ty, expr).map(|inner| MustUsePath::Pinned(Box::new(inner)))
}
// Consider `Result<T, Uninhabited>` (e.g. `Result<(), !>`) equivalent to `T`.
ty::Adt(def, args)
if simplify_uninhabited
&& cx.tcx.is_diagnostic_item(sym::Result, def.did())
if cx.tcx.is_diagnostic_item(sym::Result, def.did())
&& is_uninhabited(args.type_at(1)) =>
{
let ok_ty = args.type_at(0);
is_ty_must_use(cx, ok_ty, expr, simplify_uninhabited)
.map(|path| MustUsePath::Result(Box::new(path)))
is_ty_must_use(cx, ok_ty, expr).map(|path| MustUsePath::Result(Box::new(path)))
}
// Consider `ControlFlow<Uninhabited, T>` (e.g. `ControlFlow<!, ()>`) equivalent to `T`.
ty::Adt(def, args)
if simplify_uninhabited
&& cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did())
if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did())
&& is_uninhabited(args.type_at(0)) =>
{
let continue_ty = args.type_at(1);
is_ty_must_use(cx, continue_ty, expr, simplify_uninhabited)
is_ty_must_use(cx, continue_ty, expr)
.map(|path| MustUsePath::ControlFlow(Box::new(path)))
}
// Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`).
ty::Adt(def, args)
if cx.tcx.is_diagnostic_item(sym::Result, def.did())
&& args.type_at(0).is_unit()
&& is_uninhabited(args.type_at(1)) =>
{
IsTyMustUse::Trivial
}
// Suppress warnings on `ControlFlow<Uninhabited, ()>` (e.g. `ControlFlow<!, ()>`).
ty::Adt(def, args)
if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did())
&& args.type_at(1).is_unit()
&& is_uninhabited(args.type_at(0)) =>
{
IsTyMustUse::Trivial
}
ty::Adt(def, _) => {
is_def_must_use(cx, def.did(), expr.span).map_or(IsTyMustUse::No, IsTyMustUse::Yes)
}
@@ -258,7 +230,7 @@ pub fn is_ty_must_use<'tcx>(
let mut nested_must_use = Vec::new();
tys.iter().zip(elem_exprs).enumerate().for_each(|(i, (ty, expr))| {
let must_use = is_ty_must_use(cx, ty, expr, simplify_uninhabited);
let must_use = is_ty_must_use(cx, ty, expr);
all_trivial &= matches!(must_use, IsTyMustUse::Trivial);
if let IsTyMustUse::Yes(path) = must_use {
@@ -280,8 +252,9 @@ pub fn is_ty_must_use<'tcx>(
// If the array is empty we don't lint, to avoid false positives
Some(0) | None => IsTyMustUse::No,
// If the array is definitely non-empty, we can do `#[must_use]` checking.
Some(len) => is_ty_must_use(cx, ty, expr, simplify_uninhabited)
.map(|inner| MustUsePath::Array(Box::new(inner), len)),
Some(len) => {
is_ty_must_use(cx, ty, expr).map(|inner| MustUsePath::Array(Box::new(inner), len))
}
},
ty::Closure(..) | ty::CoroutineClosure(..) => {
IsTyMustUse::Yes(MustUsePath::Closure(expr.span))
@@ -345,7 +318,7 @@ fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
let must_use_result = is_ty_must_use(cx, ty, expr, false);
let must_use_result = is_ty_must_use(cx, ty, expr);
let type_lint_emitted_or_trivial = match must_use_result {
IsTyMustUse::Yes(path) => {
emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block);
@@ -7,6 +7,11 @@
use core::ops::{ControlFlow, ControlFlow::Continue};
use dep::{MyUninhabited, MyUninhabitedNonexhaustive};
#[must_use]
struct MustUse;
struct Struct;
fn result_unit_unit() -> Result<(), ()> {
Ok(())
}
@@ -19,6 +24,14 @@ fn result_unit_never() -> Result<(), !> {
Ok(())
}
fn result_struct_never() -> Result<Struct, !> {
Ok(Struct)
}
fn result_must_use_never() -> Result<MustUse, !> {
Ok(MustUse)
}
fn result_unit_myuninhabited() -> Result<(), MyUninhabited> {
Ok(())
}
@@ -80,6 +93,8 @@ fn main() {
result_unit_unit(); //~ ERROR: unused `Result` that must be used
result_unit_infallible();
result_unit_never();
result_must_use_never(); //~ ERROR: unused `MustUse` in a `Result` with an uninhabited error that must be used
result_struct_never();
result_unit_myuninhabited();
result_unit_myuninhabited_nonexhaustive(); //~ ERROR: unused `Result` that must be used
result_unit_assoctype(S1);
@@ -1,5 +1,5 @@
error: unused `Result` that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:80:5
--> $DIR/must_use-result-unit-uninhabited.rs:93:5
|
LL | result_unit_unit();
| ^^^^^^^^^^^^^^^^^^
@@ -15,8 +15,14 @@ help: use `let _ = ...` to ignore the resulting value
LL | let _ = result_unit_unit();
| +++++++
error: unused `MustUse` in a `Result` with an uninhabited error that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:96:5
|
LL | result_must_use_never();
| ^^^^^^^^^^^^^^^^^^^^^^^
error: unused `Result` that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:84:5
--> $DIR/must_use-result-unit-uninhabited.rs:99:5
|
LL | result_unit_myuninhabited_nonexhaustive();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -28,7 +34,7 @@ LL | let _ = result_unit_myuninhabited_nonexhaustive();
| +++++++
error: unused `Result` that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:86:5
--> $DIR/must_use-result-unit-uninhabited.rs:101:5
|
LL | result_unit_assoctype(S2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -40,7 +46,7 @@ LL | let _ = result_unit_assoctype(S2);
| +++++++
error: unused `Result` that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:88:5
--> $DIR/must_use-result-unit-uninhabited.rs:103:5
|
LL | S2.method_use_assoc_type();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -52,7 +58,7 @@ LL | let _ = S2.method_use_assoc_type();
| +++++++
error: unused `ControlFlow` that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:90:5
--> $DIR/must_use-result-unit-uninhabited.rs:105:5
|
LL | controlflow_unit();
| ^^^^^^^^^^^^^^^^^^
@@ -63,7 +69,7 @@ LL | let _ = controlflow_unit();
| +++++++
error: unused `Result` that must be used
--> $DIR/must_use-result-unit-uninhabited.rs:99:9
--> $DIR/must_use-result-unit-uninhabited.rs:114:9
|
LL | self.generate();
| ^^^^^^^^^^^^^^^
@@ -74,5 +80,5 @@ help: use `let _ = ...` to ignore the resulting value
LL | let _ = self.generate();
| +++++++
error: aborting due to 6 previous errors
error: aborting due to 7 previous errors