]>
Commit | Line | Data |
---|---|---|
2b03887a FG |
1 | use clippy_utils::consts::{constant, Constant}; |
2 | use clippy_utils::diagnostics::span_lint_and_sugg; | |
3 | use clippy_utils::get_parent_expr; | |
353b0b11 | 4 | use clippy_utils::source::snippet_with_context; |
2b03887a FG |
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 | /// ``` | |
9c376795 | 34 | #[clippy::version = "1.66.0"] |
2b03887a FG |
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); | |
353b0b11 FG |
58 | let ctxt = expr.span.ctxt(); |
59 | if ex.span.ctxt() == ctxt; | |
60 | if expr1.span.ctxt() == ctxt; | |
2b03887a FG |
61 | if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target); |
62 | if BinOpKind::Add == op1.node; | |
49aad941 | 63 | if let ExprKind::Lit(lit) = value.kind; |
2b03887a FG |
64 | if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node; |
65 | if block.expr.is_none(); | |
66 | then { | |
67 | let mut app = Applicability::MachineApplicable; | |
353b0b11 FG |
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 | }; | |
2b03887a FG |
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(); | |
49aad941 | 104 | if let Some(Constant::Int(c)) = constant(cx, tr, r) { |
2b03887a FG |
105 | return Some((c, op.node, l)); |
106 | }; | |
49aad941 | 107 | if let Some(Constant::Int(c)) = constant(cx, tr, l) { |
2b03887a FG |
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 | } |