]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / implicit_saturating_add.rs
1 use clippy_utils::consts::{constant, Constant};
2 use clippy_utils::diagnostics::span_lint_and_sugg;
3 use clippy_utils::get_parent_expr;
4 use clippy_utils::source::snippet_with_context;
5 use rustc_ast::ast::{LitIntType, LitKind};
6 use rustc_errors::Applicability;
7 use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_middle::ty::{Int, IntTy, Ty, Uint, UintTy};
10 use rustc_session::declare_lint_pass;
11
12 declare_clippy_lint! {
13 /// ### What it does
14 /// Checks for implicit saturating addition.
15 ///
16 /// ### Why is this bad?
17 /// The built-in function is more readable and may be faster.
18 ///
19 /// ### Example
20 /// ```no_run
21 ///let mut u:u32 = 7000;
22 ///
23 /// if u != u32::MAX {
24 /// u += 1;
25 /// }
26 /// ```
27 /// Use instead:
28 /// ```no_run
29 ///let mut u:u32 = 7000;
30 ///
31 /// u = u.saturating_add(1);
32 /// ```
33 #[clippy::version = "1.66.0"]
34 pub IMPLICIT_SATURATING_ADD,
35 style,
36 "Perform saturating addition instead of implicitly checking max bound of data type"
37 }
38 declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
39
40 impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
41 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
42 if let ExprKind::If(cond, then, None) = expr.kind
43 && let ExprKind::DropTemps(expr1) = cond.kind
44 && let Some((c, op_node, l)) = get_const(cx, expr1)
45 && let BinOpKind::Ne | BinOpKind::Lt = op_node
46 && let ExprKind::Block(block, None) = then.kind
47 && let Block {
48 stmts:
49 [
50 Stmt {
51 kind: StmtKind::Expr(ex) | StmtKind::Semi(ex),
52 ..
53 },
54 ],
55 expr: None,
56 ..
57 }
58 | Block {
59 stmts: [],
60 expr: Some(ex),
61 ..
62 } = block
63 && let ExprKind::AssignOp(op1, target, value) = ex.kind
64 && let ty = cx.typeck_results().expr_ty(target)
65 && Some(c) == get_int_max(ty)
66 && let ctxt = expr.span.ctxt()
67 && ex.span.ctxt() == ctxt
68 && expr1.span.ctxt() == ctxt
69 && clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
70 && BinOpKind::Add == op1.node
71 && let ExprKind::Lit(lit) = value.kind
72 && let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node
73 && block.expr.is_none()
74 {
75 let mut app = Applicability::MachineApplicable;
76 let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
77 let sugg = if let Some(parent) = get_parent_expr(cx, expr)
78 && let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
79 && else_.hir_id == expr.hir_id
80 {
81 format!("{{{code} = {code}.saturating_add(1); }}")
82 } else {
83 format!("{code} = {code}.saturating_add(1);")
84 };
85 span_lint_and_sugg(
86 cx,
87 IMPLICIT_SATURATING_ADD,
88 expr.span,
89 "manual saturating add detected",
90 "use instead",
91 sugg,
92 app,
93 );
94 }
95 }
96 }
97
98 fn get_int_max(ty: Ty<'_>) -> Option<u128> {
99 match ty.peel_refs().kind() {
100 Int(IntTy::I8) => i8::MAX.try_into().ok(),
101 Int(IntTy::I16) => i16::MAX.try_into().ok(),
102 Int(IntTy::I32) => i32::MAX.try_into().ok(),
103 Int(IntTy::I64) => i64::MAX.try_into().ok(),
104 Int(IntTy::I128) => i128::MAX.try_into().ok(),
105 Int(IntTy::Isize) => isize::MAX.try_into().ok(),
106 Uint(UintTy::U8) => Some(u8::MAX.into()),
107 Uint(UintTy::U16) => Some(u16::MAX.into()),
108 Uint(UintTy::U32) => Some(u32::MAX.into()),
109 Uint(UintTy::U64) => Some(u64::MAX.into()),
110 Uint(UintTy::U128) => Some(u128::MAX),
111 Uint(UintTy::Usize) => usize::MAX.try_into().ok(),
112 _ => None,
113 }
114 }
115
116 fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> {
117 if let ExprKind::Binary(op, l, r) = expr.kind {
118 let tr = cx.typeck_results();
119 if let Some(Constant::Int(c)) = constant(cx, tr, r) {
120 return Some((c, op.node, l));
121 };
122 if let Some(Constant::Int(c)) = constant(cx, tr, l) {
123 return Some((c, invert_op(op.node)?, r));
124 }
125 }
126 None
127 }
128
129 fn invert_op(op: BinOpKind) -> Option<BinOpKind> {
130 use rustc_hir::BinOpKind::{Ge, Gt, Le, Lt, Ne};
131 match op {
132 Lt => Some(Gt),
133 Le => Some(Ge),
134 Ne => Some(Ne),
135 Ge => Some(Le),
136 Gt => Some(Lt),
137 _ => None,
138 }
139 }