Skip to content

Commit 93c3e3a

Browse files
Extend manual_is_variant_and lint to check for boolean map comparisons
1 parent 30e9cd5 commit 93c3e3a

File tree

2 files changed

+117
-5
lines changed

2 files changed

+117
-5
lines changed

clippy_lints/src/methods/manual_is_variant_and.rs

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::get_parent_expr;
23
use clippy_utils::msrvs::{self, Msrv};
3-
use clippy_utils::source::snippet;
4+
use clippy_utils::source::{snippet, snippet_opt};
45
use clippy_utils::ty::is_type_diagnostic_item;
56
use rustc_errors::Applicability;
7+
use rustc_hir::{BinOpKind, Expr, ExprKind};
68
use rustc_lint::LateContext;
7-
use rustc_span::{Span, sym};
9+
use rustc_middle::ty::{self, GenericArg};
10+
use rustc_span::{BytePos, Span, Symbol, sym};
811

912
use super::MANUAL_IS_VARIANT_AND;
1013

1114
pub(super) fn check<'tcx>(
1215
cx: &LateContext<'_>,
13-
expr: &'tcx rustc_hir::Expr<'_>,
14-
map_recv: &'tcx rustc_hir::Expr<'_>,
15-
map_arg: &'tcx rustc_hir::Expr<'_>,
16+
expr: &'tcx Expr<'_>,
17+
map_recv: &'tcx Expr<'_>,
18+
map_arg: &'tcx Expr<'_>,
1619
map_span: Span,
1720
msrv: Msrv,
1821
) {
@@ -57,3 +60,111 @@ pub(super) fn check<'tcx>(
5760
Applicability::MachineApplicable,
5861
);
5962
}
63+
64+
fn is_option_result_bool<F: Fn(&[GenericArg<'_>]) -> bool>(
65+
cx: &LateContext<'_>,
66+
expr: &Expr<'_>,
67+
symbol: Symbol,
68+
callback: F,
69+
) -> bool {
70+
if let ExprKind::Call(..) = expr.kind
71+
&& let ty = cx.typeck_results().expr_ty(expr)
72+
&& let ty::Adt(adt, generics) = ty.kind()
73+
&& cx.tcx.is_diagnostic_item(symbol, adt.did())
74+
{
75+
callback(generics.as_slice())
76+
} else {
77+
false
78+
}
79+
}
80+
81+
fn is_option_bool(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
82+
is_option_result_bool(cx, expr, sym::Option, |generics| {
83+
if let [generic] = generics
84+
&& let Some(generic) = generic.as_type()
85+
{
86+
generic.is_bool()
87+
} else {
88+
false
89+
}
90+
})
91+
}
92+
93+
fn is_option_result_map(cx: &LateContext<'_>, expr: &Expr<'_>, symbol: Symbol) -> Option<Span> {
94+
if let ExprKind::MethodCall(_, recv, _, span) = expr.kind
95+
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), symbol)
96+
{
97+
Some(span)
98+
} else {
99+
None
100+
}
101+
}
102+
103+
fn is_result_bool(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
104+
is_option_result_bool(cx, expr, sym::Result, |generics| {
105+
if let [generic, _] = generics
106+
&& let Some(generic) = generic.as_type()
107+
{
108+
generic.is_bool()
109+
} else {
110+
false
111+
}
112+
})
113+
}
114+
115+
fn emit_lint(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, method_span: Span, is_option: bool) {
116+
if let Some(before_map_snippet) = snippet_opt(cx, parent.span.with_hi(method_span.lo()))
117+
&& let Some(after_map_snippet) = snippet_opt(cx, method_span.with_lo(method_span.lo() + BytePos(3)))
118+
{
119+
span_lint_and_sugg(
120+
cx,
121+
MANUAL_IS_VARIANT_AND,
122+
parent.span,
123+
format!(
124+
"called `.map() {}= {}()`",
125+
if op == BinOpKind::Eq { '=' } else { '!' },
126+
if is_option { "Some" } else { "Ok" },
127+
),
128+
"use",
129+
if is_option && op == BinOpKind::Ne {
130+
format!("{before_map_snippet}is_none_or{after_map_snippet}",)
131+
} else {
132+
format!(
133+
"{}{before_map_snippet}{}{after_map_snippet}",
134+
if op == BinOpKind::Eq { "" } else { "!" },
135+
if is_option { "is_some_and" } else { "is_ok_and" },
136+
)
137+
},
138+
Applicability::MachineApplicable,
139+
);
140+
}
141+
}
142+
143+
fn check_left_and_right(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, left: &Expr<'_>, right: &Expr<'_>) {
144+
if is_option_bool(cx, right) {
145+
if let Some(sp) = is_option_result_map(cx, left, sym::Option) {
146+
emit_lint(cx, op, parent, sp, true);
147+
}
148+
} else if is_option_bool(cx, left)
149+
&& let Some(sp) = is_option_result_map(cx, right, sym::Option)
150+
{
151+
emit_lint(cx, op, parent, sp, true);
152+
} else if is_result_bool(cx, left) {
153+
if let Some(sp) = is_option_result_map(cx, right, sym::Result) {
154+
emit_lint(cx, op, parent, sp, false);
155+
}
156+
} else if is_result_bool(cx, right)
157+
&& let Some(sp) = is_option_result_map(cx, left, sym::Result)
158+
{
159+
emit_lint(cx, op, parent, sp, false);
160+
}
161+
}
162+
163+
pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
164+
if let Some(parent_expr) = get_parent_expr(cx, expr)
165+
&& let ExprKind::Binary(op, left, right) = parent_expr.kind
166+
&& matches!(op.node, BinOpKind::Eq | BinOpKind::Ne)
167+
{
168+
check_left_and_right(cx, op.node, parent_expr, left, right);
169+
}
170+
}

clippy_lints/src/methods/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5168,6 +5168,7 @@ impl Methods {
51685168
unused_enumerate_index::check(cx, expr, recv, m_arg);
51695169
map_clone::check(cx, expr, recv, m_arg, self.msrv);
51705170
map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span);
5171+
manual_is_variant_and::check_map(cx, expr);
51715172
match method_call(recv) {
51725173
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
51735174
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv);

0 commit comments

Comments
 (0)