New lint: manual_midpoint
This commit is contained in:
parent
06175f43b3
commit
baadee8fd3
15 changed files with 344 additions and 28 deletions
|
|
@ -608,6 +608,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::operators::IMPOSSIBLE_COMPARISONS_INFO,
|
||||
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
|
||||
crate::operators::INTEGER_DIVISION_INFO,
|
||||
crate::operators::MANUAL_MIDPOINT_INFO,
|
||||
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
|
||||
crate::operators::MODULO_ARITHMETIC_INFO,
|
||||
crate::operators::MODULO_ONE_INFO,
|
||||
|
|
|
|||
64
clippy_lints/src/operators/manual_midpoint.rs
Normal file
64
clippy_lints/src/operators/manual_midpoint.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{is_float_literal, is_integer_literal};
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
use super::MANUAL_MIDPOINT;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
op: BinOpKind,
|
||||
left: &'tcx Expr<'_>,
|
||||
right: &'tcx Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if !left.span.from_expansion()
|
||||
&& !right.span.from_expansion()
|
||||
&& op == BinOpKind::Div
|
||||
&& (is_integer_literal(right, 2) || is_float_literal(right, 2.0))
|
||||
&& let Some((ll_expr, lr_expr)) = add_operands(left)
|
||||
&& add_operands(ll_expr).is_none() && add_operands(lr_expr).is_none()
|
||||
&& let left_ty = cx.typeck_results().expr_ty_adjusted(ll_expr)
|
||||
&& let right_ty = cx.typeck_results().expr_ty_adjusted(lr_expr)
|
||||
&& left_ty == right_ty
|
||||
// Do not lint on `(_+1)/2` and `(1+_)/2`, it is likely a `div_ceil()` operation
|
||||
&& !is_integer_literal(ll_expr, 1) && !is_integer_literal(lr_expr, 1)
|
||||
&& is_midpoint_implemented(left_ty, msrv)
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let left_sugg = Sugg::hir_with_context(cx, ll_expr, expr.span.ctxt(), "..", &mut app);
|
||||
let right_sugg = Sugg::hir_with_context(cx, lr_expr, expr.span.ctxt(), "..", &mut app);
|
||||
let sugg = format!("{left_ty}::midpoint({left_sugg}, {right_sugg})");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_MIDPOINT,
|
||||
expr.span,
|
||||
"manual implementation of `midpoint` which can overflow",
|
||||
format!("use `{left_ty}::midpoint` instead"),
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the left and right operands if `expr` represents an addition
|
||||
fn add_operands<'e, 'tcx>(expr: &'e Expr<'tcx>) -> Option<(&'e Expr<'tcx>, &'e Expr<'tcx>)> {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(op, left, right) if op.node == BinOpKind::Add => Some((left, right)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_midpoint_implemented(ty: Ty<'_>, msrv: &Msrv) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Uint(_) | ty::Float(_) => msrv.meets(msrvs::UINT_FLOAT_MIDPOINT),
|
||||
ty::Int(_) => msrv.meets(msrvs::INT_MIDPOINT),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ mod float_cmp;
|
|||
mod float_equality_without_abs;
|
||||
mod identity_op;
|
||||
mod integer_division;
|
||||
mod manual_midpoint;
|
||||
mod misrefactored_assign_op;
|
||||
mod modulo_arithmetic;
|
||||
mod modulo_one;
|
||||
|
|
@ -24,6 +25,7 @@ mod verbose_bit_mask;
|
|||
pub(crate) mod arithmetic_side_effects;
|
||||
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use rustc_hir::{Body, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -834,10 +836,35 @@ declare_clippy_lint! {
|
|||
"explicit self-assignment"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for manual implementation of `midpoint`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `(x + y) / 2` might cause an overflow on the intermediate
|
||||
/// addition result.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let a: u32 = 0;
|
||||
/// let c = (a + 10) / 2;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # let a: u32 = 0;
|
||||
/// let c = u32::midpoint(a, 10);
|
||||
/// ```
|
||||
#[clippy::version = "1.87.0"]
|
||||
pub MANUAL_MIDPOINT,
|
||||
pedantic,
|
||||
"manual implementation of `midpoint` which can overflow"
|
||||
}
|
||||
|
||||
pub struct Operators {
|
||||
arithmetic_context: numeric_arithmetic::Context,
|
||||
verbose_bit_mask_threshold: u64,
|
||||
modulo_arithmetic_allow_comparison_to_zero: bool,
|
||||
msrv: Msrv,
|
||||
}
|
||||
impl Operators {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
|
|
@ -845,6 +872,7 @@ impl Operators {
|
|||
arithmetic_context: numeric_arithmetic::Context::default(),
|
||||
verbose_bit_mask_threshold: conf.verbose_bit_mask_threshold,
|
||||
modulo_arithmetic_allow_comparison_to_zero: conf.allow_comparison_to_zero,
|
||||
msrv: conf.msrv.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -876,6 +904,7 @@ impl_lint_pass!(Operators => [
|
|||
NEEDLESS_BITWISE_BOOL,
|
||||
PTR_EQ,
|
||||
SELF_ASSIGNMENT,
|
||||
MANUAL_MIDPOINT,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Operators {
|
||||
|
|
@ -893,6 +922,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
identity_op::check(cx, e, op.node, lhs, rhs);
|
||||
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
|
||||
ptr_eq::check(cx, e, op.node, lhs, rhs);
|
||||
manual_midpoint::check(cx, e, op.node, lhs, rhs, &self.msrv);
|
||||
}
|
||||
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
|
||||
bit_mask::check(cx, e, op.node, lhs, rhs);
|
||||
|
|
@ -943,6 +973,8 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &Body<'_>) {
|
||||
self.arithmetic_context.body_post(cx, b);
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn macro_with_not_op(e: &Expr<'_>) -> bool {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue