Fix if_then_some_else_none FP when require type coercion (#15267)

Closes rust-lang/rust-clippy#15257

changelog: [`if_then_some_else_none`] fix FP when require type coercion
This commit is contained in:
Alejandra González 2025-07-24 23:50:14 +00:00 committed by GitHub
commit 98205e60cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 251 additions and 3 deletions

View file

@ -5,7 +5,8 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks,
contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor,
path_res, peel_blocks,
};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
@ -92,6 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
expr.span,
format!("this could be simplified with `bool::{method_name}`"),
|diag| {
if expr_adjustment_requires_coercion(cx, then_arg) {
return;
}
let mut app = Applicability::MachineApplicable;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
.maybe_paren()

View file

@ -113,7 +113,7 @@ use rustc_middle::hir::nested_filter;
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::lint::LevelAndSource;
use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, PointerCoercion};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
@ -3567,3 +3567,14 @@ pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>)
// enclosing body.
false
}
/// Checks if the expression has adjustments that require coercion, for example: dereferencing with
/// overloaded deref, coercing pointers and `dyn` objects.
pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
cx.typeck_results().expr_adjustments(expr).iter().any(|adj| {
matches!(
adj.kind,
Adjust::Deref(Some(_)) | Adjust::Pointer(PointerCoercion::Unsize) | Adjust::NeverToAny
)
})
}

View file

@ -122,3 +122,46 @@ const fn issue12103(x: u32) -> Option<u32> {
// Should not issue an error in `const` context
if x > 42 { Some(150) } else { None }
}
mod issue15257 {
struct Range {
start: u8,
end: u8,
}
fn can_be_safely_rewrite(rs: &[&Range]) -> Option<Vec<u8>> {
(rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start])
}
fn reborrow_as_ptr(i: *mut i32) -> Option<*const i32> {
let modulo = unsafe { *i % 2 };
(modulo == 0).then_some(i)
}
fn reborrow_as_fn_ptr(i: i32) {
fn do_something(fn_: Option<fn(i32)>) {
todo!()
}
fn item_fn(i: i32) {
todo!()
}
do_something((i % 2 == 0).then_some(item_fn));
}
fn reborrow_as_fn_unsafe(i: i32) {
fn do_something(fn_: Option<unsafe fn(i32)>) {
todo!()
}
fn item_fn(i: i32) {
todo!()
}
do_something((i % 2 == 0).then_some(item_fn));
let closure_fn = |i: i32| {};
do_something((i % 2 == 0).then_some(closure_fn));
}
}

View file

@ -143,3 +143,71 @@ const fn issue12103(x: u32) -> Option<u32> {
// Should not issue an error in `const` context
if x > 42 { Some(150) } else { None }
}
mod issue15257 {
struct Range {
start: u8,
end: u8,
}
fn can_be_safely_rewrite(rs: &[&Range]) -> Option<Vec<u8>> {
if rs.len() == 1 && rs[0].start == rs[0].end {
//~^ if_then_some_else_none
Some(vec![rs[0].start])
} else {
None
}
}
fn reborrow_as_ptr(i: *mut i32) -> Option<*const i32> {
let modulo = unsafe { *i % 2 };
if modulo == 0 {
//~^ if_then_some_else_none
Some(i)
} else {
None
}
}
fn reborrow_as_fn_ptr(i: i32) {
fn do_something(fn_: Option<fn(i32)>) {
todo!()
}
fn item_fn(i: i32) {
todo!()
}
do_something(if i % 2 == 0 {
//~^ if_then_some_else_none
Some(item_fn)
} else {
None
});
}
fn reborrow_as_fn_unsafe(i: i32) {
fn do_something(fn_: Option<unsafe fn(i32)>) {
todo!()
}
fn item_fn(i: i32) {
todo!()
}
do_something(if i % 2 == 0 {
//~^ if_then_some_else_none
Some(item_fn)
} else {
None
});
let closure_fn = |i: i32| {};
do_something(if i % 2 == 0 {
//~^ if_then_some_else_none
Some(closure_fn)
} else {
None
});
}
}

View file

@ -58,5 +58,63 @@ error: this could be simplified with `bool::then`
LL | if s == "1" { Some(true) } else { None }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(s == "1").then(|| true)`
error: aborting due to 6 previous errors
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:154:9
|
LL | / if rs.len() == 1 && rs[0].start == rs[0].end {
LL | |
LL | | Some(vec![rs[0].start])
LL | | } else {
LL | | None
LL | | }
| |_________^ help: try: `(rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start])`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:164:9
|
LL | / if modulo == 0 {
LL | |
LL | | Some(i)
LL | | } else {
LL | | None
LL | | }
| |_________^ help: try: `(modulo == 0).then_some(i)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:181:22
|
LL | do_something(if i % 2 == 0 {
| ______________________^
LL | |
LL | | Some(item_fn)
LL | | } else {
LL | | None
LL | | });
| |_________^ help: try: `(i % 2 == 0).then_some(item_fn)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:198:22
|
LL | do_something(if i % 2 == 0 {
| ______________________^
LL | |
LL | | Some(item_fn)
LL | | } else {
LL | | None
LL | | });
| |_________^ help: try: `(i % 2 == 0).then_some(item_fn)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:206:22
|
LL | do_something(if i % 2 == 0 {
| ______________________^
LL | |
LL | | Some(closure_fn)
LL | | } else {
LL | | None
LL | | });
| |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)`
error: aborting due to 11 previous errors

View file

@ -0,0 +1,35 @@
#![warn(clippy::if_then_some_else_none)]
#![allow(clippy::manual_is_multiple_of)]
mod issue15257 {
use std::pin::Pin;
#[derive(Default)]
pub struct Foo {}
pub trait Bar {}
impl Bar for Foo {}
fn pointer_unsized_coercion(i: u32) -> Option<Box<dyn Bar>> {
if i % 2 == 0 {
//~^ if_then_some_else_none
Some(Box::new(Foo::default()))
} else {
None
}
}
fn reborrow_as_pin(i: Pin<&mut i32>) {
use std::ops::Rem;
fn do_something(i: Option<&i32>) {
todo!()
}
do_something(if i.rem(2) == 0 {
//~^ if_then_some_else_none
Some(&i)
} else {
None
});
}
}

View file

@ -0,0 +1,28 @@
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none_unfixable.rs:13:9
|
LL | / if i % 2 == 0 {
LL | |
LL | | Some(Box::new(Foo::default()))
LL | | } else {
LL | | None
LL | | }
| |_________^
|
= note: `-D clippy::if-then-some-else-none` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::if_then_some_else_none)]`
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none_unfixable.rs:28:22
|
LL | do_something(if i.rem(2) == 0 {
| ______________________^
LL | |
LL | | Some(&i)
LL | | } else {
LL | | None
LL | | });
| |_________^
error: aborting due to 2 previous errors