Skip to content

Commit 6670acd

Browse files
committed
Check for all-positive or all-negative sums
1 parent 1cbb58b commit 6670acd

File tree

1 file changed

+83
-7
lines changed

1 file changed

+83
-7
lines changed

clippy_lints/src/casts/cast_sign_loss.rs

+83-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::convert::Infallible;
12
use std::ops::ControlFlow;
23

34
use clippy_utils::consts::{constant, Constant};
@@ -63,11 +64,14 @@ fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx
6364
return false;
6465
}
6566

66-
// We don't check for sums of all-positive or all-negative values, but we could.
6767
if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) {
6868
return false;
6969
}
7070

71+
if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) {
72+
return false;
73+
}
74+
7175
true
7276
},
7377

@@ -182,13 +186,13 @@ fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'
182186
}
183187
}
184188

185-
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
186-
/// which the result could always be positive under certain conditions, ignoring overflow.
189+
/// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`],
190+
/// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details.
187191
///
188192
/// Returns the sign of the list of peeled expressions.
189193
fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
190-
let mut uncertain_count = 0;
191194
let mut negative_count = 0;
195+
let mut uncertain_count = 0;
192196

193197
// Peel off possible binary expressions, for example:
194198
// x * x / y => [x, x, y]
@@ -215,18 +219,58 @@ fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
215219
}
216220
}
217221

222+
/// Peels binary operators such as [`BinOpKind::Add`], where the result could always be positive.
223+
/// See [`exprs_with_add_binop_peeled()`] for details.
224+
///
225+
/// Returns the sign of the list of peeled expressions.
226+
fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
227+
let mut negative_count = 0;
228+
let mut uncertain_count = 0;
229+
let mut positive_count = 0;
230+
231+
// Peel off possible binary expressions, for example:
232+
// a + b + c => [a, b, c]
233+
let exprs = exprs_with_add_binop_peeled(expr);
234+
for expr in exprs {
235+
match expr_sign(cx, expr, None) {
236+
Sign::Negative => negative_count += 1,
237+
Sign::Uncertain => uncertain_count += 1,
238+
Sign::ZeroOrPositive => positive_count += 1,
239+
};
240+
}
241+
242+
// A sum is:
243+
// - uncertain if there are any uncertain values (because they could be negative or positive),
244+
// - positive or zero if there are only positive (or zero) values,
245+
// - negative if there are only negative (or zero) values.
246+
// We could split Zero out into its own variant, but we don't yet.
247+
if uncertain_count > 0 {
248+
Sign::Uncertain
249+
} else if negative_count == 0 {
250+
Sign::ZeroOrPositive
251+
} else if positive_count == 0 {
252+
Sign::Negative
253+
} else {
254+
Sign::Uncertain
255+
}
256+
}
257+
218258
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
219-
/// which the result could always be positive under certain conditions, ignoring overflow.
259+
/// where the result depends on:
260+
/// - the number of negative values in the entire expression, or
261+
/// - the number of negative values on the left hand side of the expression.
262+
/// Ignores overflow.
263+
///
220264
///
221265
/// Expressions using other operators are preserved, so we can try to evaluate them later.
222266
fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
223267
let mut res = vec![];
224268

225-
for_each_expr(expr, |sub_expr| {
269+
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
226270
// We don't check for mul/div/rem methods here, but we could.
227271
if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind {
228272
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) {
229-
// For binary operators which both contribute to the sign of the result,
273+
// For binary operators where both sides contribute to the sign of the result,
230274
// collect all their operands, recursively. This ignores overflow.
231275
ControlFlow::Continue(Descend::Yes)
232276
} else if matches!(op.node, BinOpKind::Rem | BinOpKind::Shr) {
@@ -259,3 +303,35 @@ fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
259303

260304
res
261305
}
306+
307+
/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on:
308+
/// - all the expressions being positive, or
309+
/// - all the expressions being negative.
310+
/// Ignores overflow.
311+
///
312+
/// Expressions using other operators are preserved, so we can try to evaluate them later.
313+
fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
314+
let mut res = vec![];
315+
316+
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
317+
// We don't check for add methods here, but we could.
318+
if let ExprKind::Binary(op, _lhs, _rhs) = sub_expr.kind {
319+
if matches!(op.node, BinOpKind::Add) {
320+
// For binary operators where both sides contribute to the sign of the result,
321+
// collect all their operands, recursively. This ignores overflow.
322+
ControlFlow::Continue(Descend::Yes)
323+
} else {
324+
// The sign of the result of other binary operators depends on the values of the operands,
325+
// so try to evaluate the expression.
326+
res.push(sub_expr);
327+
ControlFlow::Continue(Descend::No)
328+
}
329+
} else {
330+
// For other expressions, including unary operators and constants, try to evaluate the expression.
331+
res.push(sub_expr);
332+
ControlFlow::Continue(Descend::No)
333+
}
334+
});
335+
336+
res
337+
}

0 commit comments

Comments
 (0)