Skip to content

Commit 206e4ad

Browse files
committed
Auto merge of #13046 - Jarcho:check_conv, r=dswij
Refactor `checked_conversions` Removes redundant checks across functions and check the HIR tree before anything else. changelog: none
2 parents e7d17e6 + 65b9fae commit 206e4ad

File tree

1 file changed

+59
-74
lines changed

1 file changed

+59
-74
lines changed

clippy_lints/src/checked_conversions.rs

+59-74
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
55
use clippy_utils::source::snippet_with_applicability;
66
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq};
77
use rustc_errors::Applicability;
8-
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
8+
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind};
99
use rustc_lint::{LateContext, LateLintPass, LintContext};
1010
use rustc_middle::lint::in_external_macro;
1111
use rustc_session::impl_lint_pass;
@@ -50,61 +50,54 @@ impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]);
5050

5151
impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
5252
fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
53-
if !self.msrv.meets(msrvs::TRY_FROM) {
54-
return;
55-
}
56-
57-
let result = if !in_constant(cx, item.hir_id)
58-
&& !in_external_macro(cx.sess(), item.span)
59-
&& let ExprKind::Binary(op, left, right) = &item.kind
60-
{
61-
match op.node {
62-
BinOpKind::Ge | BinOpKind::Le => single_check(item),
63-
BinOpKind::And => double_check(cx, left, right),
64-
_ => None,
53+
if let ExprKind::Binary(op, lhs, rhs) = item.kind
54+
&& let (lt1, gt1, op2) = match op.node {
55+
BinOpKind::Le => (lhs, rhs, None),
56+
BinOpKind::Ge => (rhs, lhs, None),
57+
BinOpKind::And
58+
if let ExprKind::Binary(op1, lhs1, rhs1) = lhs.kind
59+
&& let ExprKind::Binary(op2, lhs2, rhs2) = rhs.kind
60+
&& let Some((lt1, gt1)) = read_le_ge(op1.node, lhs1, rhs1)
61+
&& let Some((lt2, gt2)) = read_le_ge(op2.node, lhs2, rhs2) =>
62+
{
63+
(lt1, gt1, Some((lt2, gt2)))
64+
},
65+
_ => return,
6566
}
66-
} else {
67-
None
68-
};
69-
70-
if let Some(cv) = result {
71-
if let Some(to_type) = cv.to_type {
72-
let mut applicability = Applicability::MachineApplicable;
73-
let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
74-
span_lint_and_sugg(
75-
cx,
76-
CHECKED_CONVERSIONS,
77-
item.span,
78-
"checked cast can be simplified",
79-
"try",
80-
format!("{to_type}::try_from({snippet}).is_ok()"),
81-
applicability,
82-
);
67+
&& !in_external_macro(cx.sess(), item.span)
68+
&& !in_constant(cx, item.hir_id)
69+
&& self.msrv.meets(msrvs::TRY_FROM)
70+
&& let Some(cv) = match op2 {
71+
// todo: check for case signed -> larger unsigned == only x >= 0
72+
None => check_upper_bound(lt1, gt1).filter(|cv| cv.cvt == ConversionType::FromUnsigned),
73+
Some((lt2, gt2)) => {
74+
let upper_lower = |lt1, gt1, lt2, gt2| {
75+
check_upper_bound(lt1, gt1)
76+
.zip(check_lower_bound(lt2, gt2))
77+
.and_then(|(l, r)| l.combine(r, cx))
78+
};
79+
upper_lower(lt1, gt1, lt2, gt2).or_else(|| upper_lower(lt2, gt2, lt1, gt1))
80+
},
8381
}
82+
&& let Some(to_type) = cv.to_type
83+
{
84+
let mut applicability = Applicability::MachineApplicable;
85+
let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
86+
span_lint_and_sugg(
87+
cx,
88+
CHECKED_CONVERSIONS,
89+
item.span,
90+
"checked cast can be simplified",
91+
"try",
92+
format!("{to_type}::try_from({snippet}).is_ok()"),
93+
applicability,
94+
);
8495
}
8596
}
8697

8798
extract_msrv_attr!(LateContext);
8899
}
89100

90-
/// Searches for a single check from unsigned to _ is done
91-
/// todo: check for case signed -> larger unsigned == only x >= 0
92-
fn single_check<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
93-
check_upper_bound(expr).filter(|cv| cv.cvt == ConversionType::FromUnsigned)
94-
}
95-
96-
/// Searches for a combination of upper & lower bound checks
97-
fn double_check<'a>(cx: &LateContext<'_>, left: &'a Expr<'_>, right: &'a Expr<'_>) -> Option<Conversion<'a>> {
98-
let upper_lower = |l, r| {
99-
let upper = check_upper_bound(l);
100-
let lower = check_lower_bound(r);
101-
102-
upper.zip(lower).and_then(|(l, r)| l.combine(r, cx))
103-
};
104-
105-
upper_lower(left, right).or_else(|| upper_lower(right, left))
106-
}
107-
108101
/// Contains the result of a tried conversion check
109102
#[derive(Clone, Debug)]
110103
struct Conversion<'a> {
@@ -121,6 +114,19 @@ enum ConversionType {
121114
FromUnsigned,
122115
}
123116

117+
/// Attempts to read either `<=` or `>=` with a normalized operand order.
118+
fn read_le_ge<'tcx>(
119+
op: BinOpKind,
120+
lhs: &'tcx Expr<'tcx>,
121+
rhs: &'tcx Expr<'tcx>,
122+
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
123+
match op {
124+
BinOpKind::Le => Some((lhs, rhs)),
125+
BinOpKind::Ge => Some((rhs, lhs)),
126+
_ => None,
127+
}
128+
}
129+
124130
impl<'a> Conversion<'a> {
125131
/// Combine multiple conversions if the are compatible
126132
pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>> {
@@ -188,29 +194,17 @@ impl ConversionType {
188194
}
189195

190196
/// Check for `expr <= (to_type::MAX as from_type)`
191-
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
192-
if let ExprKind::Binary(ref op, left, right) = &expr.kind
193-
&& let Some((candidate, check)) = normalize_le_ge(op, left, right)
194-
&& let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX")
195-
{
196-
Conversion::try_new(candidate, from, to)
197+
fn check_upper_bound<'tcx>(lt: &'tcx Expr<'tcx>, gt: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
198+
if let Some((from, to)) = get_types_from_cast(gt, INTS, "max_value", "MAX") {
199+
Conversion::try_new(lt, from, to)
197200
} else {
198201
None
199202
}
200203
}
201204

202205
/// Check for `expr >= 0|(to_type::MIN as from_type)`
203-
fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
204-
fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option<Conversion<'a>> {
205-
(check_lower_bound_zero(candidate, check)).or_else(|| (check_lower_bound_min(candidate, check)))
206-
}
207-
208-
// First of we need a binary containing the expression & the cast
209-
if let ExprKind::Binary(ref op, left, right) = &expr.kind {
210-
normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
211-
} else {
212-
None
213-
}
206+
fn check_lower_bound<'tcx>(lt: &'tcx Expr<'tcx>, gt: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
207+
check_lower_bound_zero(gt, lt).or_else(|| check_lower_bound_min(gt, lt))
214208
}
215209

216210
/// Check for `expr >= 0`
@@ -309,15 +303,6 @@ fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
309303
}
310304
}
311305

312-
/// Will return the expressions as if they were expr1 <= expr2
313-
fn normalize_le_ge<'a>(op: &BinOp, left: &'a Expr<'a>, right: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
314-
match op.node {
315-
BinOpKind::Le => Some((left, right)),
316-
BinOpKind::Ge => Some((right, left)),
317-
_ => None,
318-
}
319-
}
320-
321306
// Constants
322307
const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"];
323308
const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"];

0 commit comments

Comments
 (0)