-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add new lint [manual_checked_sub
]
#14236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
5d1d182
0b9eba2
392c3b4
f7c2e5a
60b5c16
bdf9ac8
2e85d1a
e0b131a
797075a
2669fc9
06383da
6b034a7
0e66fd9
99be19d
e3b0262
cc58db7
9d21893
46d448f
31aca94
510d847
8d8dece
3f42120
3cb4e86
b1f3340
e61986c
090496e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file should be removed |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,193 @@ | ||||||||||||||||||||||||
use clippy_config::Conf; | ||||||||||||||||||||||||
use clippy_utils::diagnostics::span_lint; | ||||||||||||||||||||||||
use clippy_utils::msrvs::{self, Msrv}; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
use clippy_utils::{is_mutable, path_to_local}; | ||||||||||||||||||||||||
use rustc_ast::{BinOpKind, LitIntType, LitKind}; | ||||||||||||||||||||||||
use rustc_data_structures::packed::Pu128; | ||||||||||||||||||||||||
use rustc_hir::intravisit::{Visitor, walk_expr}; | ||||||||||||||||||||||||
use rustc_hir::{Expr, ExprKind}; | ||||||||||||||||||||||||
use rustc_lint::{LateContext, LateLintPass}; | ||||||||||||||||||||||||
use rustc_middle::ty::{self}; | ||||||||||||||||||||||||
use rustc_session::impl_lint_pass; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
declare_clippy_lint! { | ||||||||||||||||||||||||
/// ### What it does | ||||||||||||||||||||||||
/// Checks for manual re-implementations of checked subtraction. | ||||||||||||||||||||||||
/// | ||||||||||||||||||||||||
/// ### Why is this bad? | ||||||||||||||||||||||||
/// Manually re-implementing checked subtraction can be error-prone and less readable. | ||||||||||||||||||||||||
/// Using the standard library method `.checked_sub()` is clearer and less likely to contain bugs. | ||||||||||||||||||||||||
/// | ||||||||||||||||||||||||
/// ### Example | ||||||||||||||||||||||||
/// ```rust | ||||||||||||||||||||||||
/// // Bad: Manual implementation of checked subtraction | ||||||||||||||||||||||||
/// fn get_remaining_items(total: u32, used: u32) -> Option<u32> { | ||||||||||||||||||||||||
/// if total >= used { | ||||||||||||||||||||||||
/// Some(total - used) | ||||||||||||||||||||||||
/// } else { | ||||||||||||||||||||||||
/// None | ||||||||||||||||||||||||
/// } | ||||||||||||||||||||||||
/// } | ||||||||||||||||||||||||
/// ``` | ||||||||||||||||||||||||
/// | ||||||||||||||||||||||||
/// Use instead: | ||||||||||||||||||||||||
/// ```rust | ||||||||||||||||||||||||
/// // Good: Using the standard library's checked_sub | ||||||||||||||||||||||||
/// fn get_remaining_items(total: u32, used: u32) -> Option<u32> { | ||||||||||||||||||||||||
/// total.checked_sub(used) | ||||||||||||||||||||||||
/// } | ||||||||||||||||||||||||
/// ``` | ||||||||||||||||||||||||
#[clippy::version = "1.86.0"] | ||||||||||||||||||||||||
pub MANUAL_CHECKED_SUB, | ||||||||||||||||||||||||
complexity, | ||||||||||||||||||||||||
"Checks for manual re-implementations of checked subtraction." | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
pub struct ManualCheckedSub { | ||||||||||||||||||||||||
msrv: Msrv, | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
impl ManualCheckedSub { | ||||||||||||||||||||||||
#[must_use] | ||||||||||||||||||||||||
pub fn new(conf: &'static Conf) -> Self { | ||||||||||||||||||||||||
Self { msrv: conf.msrv } | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
impl_lint_pass!(ManualCheckedSub => [MANUAL_CHECKED_SUB]); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub { | ||||||||||||||||||||||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | ||||||||||||||||||||||||
if !self.msrv.meets(cx, msrvs::CHECKED_SUB) { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MSRV checking is more expensive than checking for the presence of an |
||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if let Some(if_expr) = clippy_utils::higher::If::hir(expr) | ||||||||||||||||||||||||
&& let ExprKind::Binary(op, lhs, rhs) = if_expr.cond.kind | ||||||||||||||||||||||||
&& !(matches!(lhs.kind, ExprKind::Lit(_)) && matches!(rhs.kind, ExprKind::Lit(_))) | ||||||||||||||||||||||||
&& is_unsigned_int(cx, lhs) | ||||||||||||||||||||||||
&& is_unsigned_int(cx, rhs) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
// Skip if either non-literal operand is mutable | ||||||||||||||||||||||||
if (!matches!(lhs.kind, ExprKind::Lit(_)) && is_mutable(cx, lhs)) | ||||||||||||||||||||||||
|| (!matches!(rhs.kind, ExprKind::Lit(_)) && is_mutable(cx, rhs)) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Skip if either lhs or rhs is a macro call | ||||||||||||||||||||||||
if lhs.span.from_expansion() || rhs.span.from_expansion() { | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if let BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt = op.node { | ||||||||||||||||||||||||
Comment on lines
+73
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those checks can probably be merged into the earlier |
||||||||||||||||||||||||
SubExprVisitor { | ||||||||||||||||||||||||
cx, | ||||||||||||||||||||||||
if_expr: expr, | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
condition_lhs: lhs, | ||||||||||||||||||||||||
condition_rhs: rhs, | ||||||||||||||||||||||||
condition_op: op.node, | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
.visit_expr(if_expr.then); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
struct SubExprVisitor<'cx, 'tcx> { | ||||||||||||||||||||||||
cx: &'cx LateContext<'tcx>, | ||||||||||||||||||||||||
if_expr: &'tcx Expr<'tcx>, | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
condition_lhs: &'tcx Expr<'tcx>, | ||||||||||||||||||||||||
condition_rhs: &'tcx Expr<'tcx>, | ||||||||||||||||||||||||
condition_op: BinOpKind, | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
impl<'tcx> Visitor<'tcx> for SubExprVisitor<'_, 'tcx> { | ||||||||||||||||||||||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | ||||||||||||||||||||||||
if let ExprKind::Binary(op, sub_lhs, sub_rhs) = expr.kind | ||||||||||||||||||||||||
&& let BinOpKind::Sub = op.node | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
// // Skip if either sub_lhs or sub_rhs is a macro call | ||||||||||||||||||||||||
if sub_lhs.span.from_expansion() || sub_rhs.span.from_expansion() { | ||||||||||||||||||||||||
return; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if let ExprKind::Lit(lit) = self.condition_lhs.kind | ||||||||||||||||||||||||
&& self.condition_op == BinOpKind::Lt | ||||||||||||||||||||||||
&& let LitKind::Int(Pu128(0), _) = lit.node | ||||||||||||||||||||||||
&& (is_referencing_same_variable(sub_lhs, self.condition_rhs)) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
self.emit_lint(); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if let ExprKind::Lit(lit) = self.condition_rhs.kind | ||||||||||||||||||||||||
&& self.condition_op == BinOpKind::Gt | ||||||||||||||||||||||||
&& let LitKind::Int(Pu128(0), _) = lit.node | ||||||||||||||||||||||||
&& (is_referencing_same_variable(sub_lhs, self.condition_lhs)) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
self.emit_lint(); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if self.condition_op == BinOpKind::Ge | ||||||||||||||||||||||||
&& (is_referencing_same_variable(sub_lhs, self.condition_lhs) | ||||||||||||||||||||||||
|| are_literals_equal(sub_lhs, self.condition_lhs)) | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code: if a > 0 {
let b = a - 3;
} will be replaced by a if let Some(result) = a.checked_sub(0) {
let b = result;
} |
||||||||||||||||||||||||
&& (is_referencing_same_variable(sub_rhs, self.condition_rhs) | ||||||||||||||||||||||||
|| are_literals_equal(sub_rhs, self.condition_rhs)) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code, when if a > b {
a *= 2;
let c = a - b;
} will be replaced by if let Some(result) = a.checked_sub(b) {
a *= 2;
let c = result;
} |
||||||||||||||||||||||||
self.emit_lint(); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if self.condition_op == BinOpKind::Le | ||||||||||||||||||||||||
&& (is_referencing_same_variable(sub_lhs, self.condition_rhs) | ||||||||||||||||||||||||
|| are_literals_equal(sub_lhs, self.condition_rhs)) | ||||||||||||||||||||||||
&& (is_referencing_same_variable(sub_rhs, self.condition_lhs) | ||||||||||||||||||||||||
|| are_literals_equal(sub_rhs, self.condition_lhs)) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
self.emit_lint(); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
walk_expr(self, expr); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
impl SubExprVisitor<'_, '_> { | ||||||||||||||||||||||||
fn emit_lint(&mut self) { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||
span_lint( | ||||||||||||||||||||||||
self.cx, | ||||||||||||||||||||||||
MANUAL_CHECKED_SUB, | ||||||||||||||||||||||||
self.if_expr.span, | ||||||||||||||||||||||||
"manual re-implementation of checked subtraction - consider using `.checked_sub()`", | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
fn is_unsigned_int<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool { | ||||||||||||||||||||||||
let expr_type = cx.typeck_results().expr_ty(expr).peel_refs(); | ||||||||||||||||||||||||
if matches!(expr_type.kind(), ty::Uint(_)) { | ||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
false | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Comment on lines
+168
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use the shorter
Suggested change
Also, please add tests with references since you seem to need to peel them. |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
fn are_literals_equal<'tcx>(expr1: &Expr<'tcx>, expr2: &Expr<'tcx>) -> bool { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||
if let (ExprKind::Lit(lit1), ExprKind::Lit(lit2)) = (&expr1.kind, &expr2.kind) | ||||||||||||||||||||||||
&& let (LitKind::Int(val1, suffix1), LitKind::Int(val2, suffix2)) = (&lit1.node, &lit2.node) | ||||||||||||||||||||||||
{ | ||||||||||||||||||||||||
return val1 == val2 && suffix1 == suffix2 && *suffix1 != LitIntType::Unsuffixed; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
false | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
benacq marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
fn is_referencing_same_variable<'tcx>(expr1: &'tcx Expr<'_>, expr2: &'tcx Expr<'_>) -> bool { | ||||||||||||||||||||||||
benacq marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
if let (Some(id1), Some(id2)) = (path_to_local(expr1), path_to_local(expr2)) { | ||||||||||||||||||||||||
return id1 == id2; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
false | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
Comment on lines
+187
to
+193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use the more compact
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,9 @@ | ||
#![allow(unused_assignments, unused_mut, clippy::assign_op_pattern)] | ||
#![allow( | ||
unused_assignments, | ||
unused_mut, | ||
clippy::assign_op_pattern, | ||
clippy::manual_checked_sub | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure modifying this test file is needed, as |
||
)] | ||
#![warn(clippy::implicit_saturating_sub)] | ||
|
||
use std::cmp::PartialEq; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be removed