Skip to content

Commit eb12c32

Browse files
committed
Fix manual_is_variant_and condition generation
When comparing `x.map(func) == Some(bool_lit)`, the value of `bool_lit` was ignored, despite the fact that its value should determine the value of the proposed expression. `func` can be either a closure or a path. For the latter, η-expansion will be used if needed to invert the result of the function call.
1 parent b631cef commit eb12c32

File tree

4 files changed

+453
-33
lines changed

4 files changed

+453
-33
lines changed

clippy_lints/src/methods/manual_is_variant_and.rs

Lines changed: 134 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::get_parent_expr;
32
use clippy_utils::msrvs::{self, Msrv};
4-
use clippy_utils::source::{snippet, snippet_opt};
3+
use clippy_utils::source::{snippet, snippet_with_applicability};
4+
use clippy_utils::sugg::Sugg;
55
use clippy_utils::ty::is_type_diagnostic_item;
6+
use clippy_utils::{get_parent_expr, sym};
7+
use rustc_ast::LitKind;
68
use rustc_errors::Applicability;
79
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
8-
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
10+
use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath};
911
use rustc_lint::LateContext;
1012
use rustc_middle::ty;
11-
use rustc_span::{BytePos, Span, sym};
13+
use rustc_span::{Span, Symbol};
1214

1315
use super::MANUAL_IS_VARIANT_AND;
1416

@@ -62,54 +64,156 @@ pub(super) fn check(
6264
);
6365
}
6466

65-
fn emit_lint(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, method_span: Span, is_option: bool) {
66-
if let Some(before_map_snippet) = snippet_opt(cx, parent.span.with_hi(method_span.lo()))
67-
&& let Some(after_map_snippet) = snippet_opt(cx, method_span.with_lo(method_span.lo() + BytePos(3)))
67+
#[derive(Clone, Copy, PartialEq)]
68+
enum Flavor {
69+
Option,
70+
Result,
71+
}
72+
73+
impl Flavor {
74+
const fn symbol(self) -> Symbol {
75+
match self {
76+
Self::Option => sym::Option,
77+
Self::Result => sym::Result,
78+
}
79+
}
80+
81+
const fn positive(self) -> Symbol {
82+
match self {
83+
Self::Option => sym::Some,
84+
Self::Result => sym::Ok,
85+
}
86+
}
87+
}
88+
89+
#[derive(Clone, Copy, PartialEq)]
90+
enum Op {
91+
Eq,
92+
Ne,
93+
}
94+
95+
impl std::fmt::Display for Op {
96+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97+
match self {
98+
Self::Eq => write!(f, "=="),
99+
Self::Ne => write!(f, "!="),
100+
}
101+
}
102+
}
103+
104+
impl TryFrom<BinOpKind> for Op {
105+
type Error = ();
106+
fn try_from(op: BinOpKind) -> Result<Self, Self::Error> {
107+
match op {
108+
BinOpKind::Eq => Ok(Self::Eq),
109+
BinOpKind::Ne => Ok(Self::Ne),
110+
_ => Err(()),
111+
}
112+
}
113+
}
114+
115+
/// Represents the argument of the `.map()` function, as a closure or as a path
116+
/// in case η-reduction is used.
117+
enum MapFunc<'hir> {
118+
Closure(&'hir Closure<'hir>),
119+
Path(&'hir Expr<'hir>),
120+
}
121+
122+
impl<'hir> TryFrom<&'hir Expr<'hir>> for MapFunc<'hir> {
123+
type Error = ();
124+
125+
fn try_from(expr: &'hir Expr<'hir>) -> Result<Self, Self::Error> {
126+
match expr.kind {
127+
ExprKind::Closure(closure) => Ok(Self::Closure(closure)),
128+
ExprKind::Path(_) => Ok(Self::Path(expr)),
129+
_ => Err(()),
130+
}
131+
}
132+
}
133+
134+
impl<'hir> MapFunc<'hir> {
135+
/// Build a suggestion suitable for use in a `.map()`-like function. η-expansion will be applied
136+
/// as needed.
137+
fn sugg(self, cx: &LateContext<'hir>, invert: bool, app: &mut Applicability) -> String {
138+
match self {
139+
Self::Closure(closure) => {
140+
let body = Sugg::hir_with_applicability(cx, cx.tcx.hir_body(closure.body).value, "..", app);
141+
format!(
142+
"{} {}",
143+
snippet_with_applicability(cx, closure.fn_decl_span, "|..|", app),
144+
if invert { !body } else { body }
145+
)
146+
},
147+
Self::Path(expr) => {
148+
let path = snippet_with_applicability(cx, expr.span, "_", app);
149+
if invert {
150+
format!("|x| !{path}(x)")
151+
} else {
152+
path.to_string()
153+
}
154+
},
155+
}
156+
}
157+
}
158+
159+
fn emit_lint<'tcx>(
160+
cx: &LateContext<'tcx>,
161+
span: Span,
162+
op: Op,
163+
flavor: Flavor,
164+
in_some_or_ok: bool,
165+
map_func: MapFunc<'tcx>,
166+
recv: &Expr<'_>,
167+
) {
168+
let mut app = Applicability::MachineApplicable;
169+
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
170+
171+
let (invert_expr, method, invert_body) = match (flavor, op, in_some_or_ok) {
172+
(Flavor::Option, Op::Eq, bool_cst) => (false, "is_some_and", !bool_cst),
173+
(Flavor::Option, Op::Ne, bool_cst) => (false, "is_none_or", bool_cst),
174+
(Flavor::Result, Op::Eq, bool_cst) => (false, "is_ok_and", !bool_cst),
175+
(Flavor::Result, Op::Ne, bool_cst) => (true, "is_ok_and", !bool_cst),
176+
};
68177
{
69178
span_lint_and_sugg(
70179
cx,
71180
MANUAL_IS_VARIANT_AND,
72-
parent.span,
181+
span,
182+
format!("called `.map() {op} {pos}()`", pos = flavor.positive(),),
183+
"use",
73184
format!(
74-
"called `.map() {}= {}()`",
75-
if op == BinOpKind::Eq { '=' } else { '!' },
76-
if is_option { "Some" } else { "Ok" },
185+
"{inversion}{recv}.{method}({body})",
186+
inversion = if invert_expr { "!" } else { "" },
187+
body = map_func.sugg(cx, invert_body, &mut app),
77188
),
78-
"use",
79-
if is_option && op == BinOpKind::Ne {
80-
format!("{before_map_snippet}is_none_or{after_map_snippet}",)
81-
} else {
82-
format!(
83-
"{}{before_map_snippet}{}{after_map_snippet}",
84-
if op == BinOpKind::Eq { "" } else { "!" },
85-
if is_option { "is_some_and" } else { "is_ok_and" },
86-
)
87-
},
88-
Applicability::MachineApplicable,
189+
app,
89190
);
90-
}
191+
};
91192
}
92193

93194
pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
94195
if let Some(parent_expr) = get_parent_expr(cx, expr)
95196
&& let ExprKind::Binary(op, left, right) = parent_expr.kind
96-
&& matches!(op.node, BinOpKind::Eq | BinOpKind::Ne)
97197
&& op.span.eq_ctxt(expr.span)
198+
&& let Ok(op) = Op::try_from(op.node)
98199
{
99200
// Check `left` and `right` expression in any order, and for `Option` and `Result`
100201
for (expr1, expr2) in [(left, right), (right, left)] {
101-
for item in [sym::Option, sym::Result] {
102-
if let ExprKind::Call(call, ..) = expr1.kind
202+
for flavor in [Flavor::Option, Flavor::Result] {
203+
if let ExprKind::Call(call, [arg]) = expr1.kind
204+
&& let ExprKind::Lit(lit) = arg.kind
205+
&& let LitKind::Bool(bool_cst) = lit.node
103206
&& let ExprKind::Path(QPath::Resolved(_, path)) = call.kind
104207
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res
105208
&& let ty = cx.typeck_results().expr_ty(expr1)
106209
&& let ty::Adt(adt, args) = ty.kind()
107-
&& cx.tcx.is_diagnostic_item(item, adt.did())
210+
&& cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did())
108211
&& args.type_at(0).is_bool()
109-
&& let ExprKind::MethodCall(_, recv, _, span) = expr2.kind
110-
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), item)
212+
&& let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind
213+
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), flavor.symbol())
214+
&& let Ok(map_func) = MapFunc::try_from(map_expr)
111215
{
112-
return emit_lint(cx, op.node, parent_expr, span, item == sym::Option);
216+
return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv);
113217
}
114218
}
115219
}

tests/ui/manual_is_variant_and.fixed

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn option_methods() {
6161

6262
let _ = Some(2).is_some_and(|x| x % 2 == 0);
6363
//~^ manual_is_variant_and
64-
let _ = Some(2).is_none_or(|x| x % 2 == 0);
64+
let _ = Some(2).is_none_or(|x| x % 2 != 0);
6565
//~^ manual_is_variant_and
6666
let _ = Some(2).is_some_and(|x| x % 2 == 0);
6767
//~^ manual_is_variant_and
@@ -116,3 +116,113 @@ fn main() {
116116
option_methods();
117117
result_methods();
118118
}
119+
120+
fn issue15202() {
121+
let xs = [None, Some(b'_'), Some(b'1')];
122+
for x in xs {
123+
let a1 = x.is_none_or(|b| !b.is_ascii_digit());
124+
//~^ manual_is_variant_and
125+
let a2 = x.is_none_or(|b| !b.is_ascii_digit());
126+
assert_eq!(a1, a2);
127+
}
128+
129+
for x in xs {
130+
let a1 = x.is_none_or(|b| b.is_ascii_digit());
131+
//~^ manual_is_variant_and
132+
let a2 = x.is_none_or(|b| b.is_ascii_digit());
133+
assert_eq!(a1, a2);
134+
}
135+
136+
for x in xs {
137+
let a1 = x.is_some_and(|b| b.is_ascii_digit());
138+
//~^ manual_is_variant_and
139+
let a2 = x.is_some_and(|b| b.is_ascii_digit());
140+
assert_eq!(a1, a2);
141+
}
142+
143+
for x in xs {
144+
let a1 = x.is_some_and(|b| !b.is_ascii_digit());
145+
//~^ manual_is_variant_and
146+
let a2 = x.is_some_and(|b| !b.is_ascii_digit());
147+
assert_eq!(a1, a2);
148+
}
149+
150+
let xs = [Err("foo"), Ok(b'_'), Ok(b'1')];
151+
for x in xs {
152+
let a1 = !x.is_ok_and(|b| b.is_ascii_digit());
153+
//~^ manual_is_variant_and
154+
let a2 = !x.is_ok_and(|b| b.is_ascii_digit());
155+
assert_eq!(a1, a2);
156+
}
157+
158+
for x in xs {
159+
let a1 = !x.is_ok_and(|b| !b.is_ascii_digit());
160+
//~^ manual_is_variant_and
161+
let a2 = !x.is_ok_and(|b| !b.is_ascii_digit());
162+
assert_eq!(a1, a2);
163+
}
164+
165+
for x in xs {
166+
let a1 = x.is_ok_and(|b| b.is_ascii_digit());
167+
//~^ manual_is_variant_and
168+
let a2 = x.is_ok_and(|b| b.is_ascii_digit());
169+
assert_eq!(a1, a2);
170+
}
171+
172+
for x in xs {
173+
let a1 = x.is_ok_and(|b| !b.is_ascii_digit());
174+
//~^ manual_is_variant_and
175+
let a2 = x.is_ok_and(|b| !b.is_ascii_digit());
176+
assert_eq!(a1, a2);
177+
}
178+
}
179+
180+
mod with_func {
181+
fn iad(b: u8) -> bool {
182+
b.is_ascii_digit()
183+
}
184+
185+
fn check_option(b: Option<u8>) {
186+
let a1 = b.is_some_and(iad);
187+
//~^ manual_is_variant_and
188+
let a2 = b.is_some_and(iad);
189+
assert_eq!(a1, a2);
190+
191+
let a1 = b.is_some_and(|x| !iad(x));
192+
//~^ manual_is_variant_and
193+
let a2 = b.is_some_and(|x| !iad(x));
194+
assert_eq!(a1, a2);
195+
196+
let a1 = b.is_none_or(|x| !iad(x));
197+
//~^ manual_is_variant_and
198+
let a2 = b.is_none_or(|x| !iad(x));
199+
assert_eq!(a1, a2);
200+
201+
let a1 = b.is_none_or(iad);
202+
//~^ manual_is_variant_and
203+
let a2 = b.is_none_or(iad);
204+
assert_eq!(a1, a2);
205+
}
206+
207+
fn check_result(b: Result<u8, ()>) {
208+
let a1 = b.is_ok_and(iad);
209+
//~^ manual_is_variant_and
210+
let a2 = b.is_ok_and(iad);
211+
assert_eq!(a1, a2);
212+
213+
let a1 = b.is_ok_and(|x| !iad(x));
214+
//~^ manual_is_variant_and
215+
let a2 = b.is_ok_and(|x| !iad(x));
216+
assert_eq!(a1, a2);
217+
218+
let a1 = !b.is_ok_and(iad);
219+
//~^ manual_is_variant_and
220+
let a2 = !b.is_ok_and(iad);
221+
assert_eq!(a1, a2);
222+
223+
let a1 = !b.is_ok_and(|x| !iad(x));
224+
//~^ manual_is_variant_and
225+
let a2 = !b.is_ok_and(|x| !iad(x));
226+
assert_eq!(a1, a2);
227+
}
228+
}

0 commit comments

Comments
 (0)