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