|
1 |
| -use clippy_utils::diagnostics::span_lint_and_sugg; |
2 |
| -use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq}; |
| 1 | +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; |
| 2 | +use clippy_utils::source::snippet_opt; |
| 3 | +use clippy_utils::{higher, is_integer_literal, path_to_local, peel_blocks, peel_blocks_with_stmt, SpanlessEq}; |
3 | 4 | use rustc_ast::ast::LitKind;
|
4 | 5 | use rustc_data_structures::packed::Pu128;
|
5 | 6 | use rustc_errors::Applicability;
|
6 |
| -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; |
| 7 | +use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, HirId, QPath}; |
7 | 8 | use rustc_lint::{LateContext, LateLintPass};
|
8 | 9 | use rustc_session::declare_lint_pass;
|
| 10 | +use rustc_span::Span; |
9 | 11 |
|
10 | 12 | declare_clippy_lint! {
|
11 | 13 | /// ### What it does
|
@@ -50,73 +52,229 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
50 | 52 |
|
51 | 53 | // Check if the conditional expression is a binary operation
|
52 | 54 | && let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
|
| 55 | + { |
| 56 | + check_with_condition(cx, expr, cond_op.node, cond_left, cond_right, then); |
| 57 | + } else if let Some(higher::If { |
| 58 | + cond, |
| 59 | + then: if_block, |
| 60 | + r#else: Some(else_block), |
| 61 | + }) = higher::If::hir(expr) |
| 62 | + && let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind |
| 63 | + { |
| 64 | + check_manual_check(cx, expr, cond_op, cond_left, cond_right, if_block, else_block); |
| 65 | + } |
| 66 | + } |
| 67 | +} |
53 | 68 |
|
54 |
| - // Ensure that the binary operator is >, !=, or < |
55 |
| - && (BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node) |
| 69 | +fn check_manual_check<'tcx>( |
| 70 | + cx: &LateContext<'tcx>, |
| 71 | + expr: &Expr<'tcx>, |
| 72 | + condition: &BinOp, |
| 73 | + left_hand: &Expr<'tcx>, |
| 74 | + right_hand: &Expr<'tcx>, |
| 75 | + if_block: &Expr<'tcx>, |
| 76 | + else_block: &Expr<'tcx>, |
| 77 | +) { |
| 78 | + let ty = cx.typeck_results().expr_ty(left_hand); |
| 79 | + if ty.is_numeric() && !ty.is_signed() { |
| 80 | + match condition.node { |
| 81 | + BinOpKind::Gt | BinOpKind::Ge => check_gt( |
| 82 | + cx, |
| 83 | + condition.span, |
| 84 | + expr.span, |
| 85 | + left_hand, |
| 86 | + right_hand, |
| 87 | + if_block, |
| 88 | + else_block, |
| 89 | + ), |
| 90 | + BinOpKind::Lt | BinOpKind::Le => check_gt( |
| 91 | + cx, |
| 92 | + condition.span, |
| 93 | + expr.span, |
| 94 | + right_hand, |
| 95 | + left_hand, |
| 96 | + if_block, |
| 97 | + else_block, |
| 98 | + ), |
| 99 | + _ => {}, |
| 100 | + } |
| 101 | + } |
| 102 | +} |
56 | 103 |
|
57 |
| - // Check if assign operation is done |
58 |
| - && let Some(target) = subtracts_one(cx, then) |
| 104 | +fn check_gt( |
| 105 | + cx: &LateContext<'_>, |
| 106 | + condition_span: Span, |
| 107 | + expr_span: Span, |
| 108 | + big_var: &Expr<'_>, |
| 109 | + little_var: &Expr<'_>, |
| 110 | + if_block: &Expr<'_>, |
| 111 | + else_block: &Expr<'_>, |
| 112 | +) { |
| 113 | + if let Some(big_var) = Var::new(big_var) |
| 114 | + && let Some(little_var) = Var::new(little_var) |
| 115 | + { |
| 116 | + check_subtraction(cx, condition_span, expr_span, big_var, little_var, if_block, else_block); |
| 117 | + } |
| 118 | +} |
59 | 119 |
|
60 |
| - // Extracting out the variable name |
61 |
| - && let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind |
| 120 | +struct Var { |
| 121 | + span: Span, |
| 122 | + hir_id: HirId, |
| 123 | +} |
| 124 | + |
| 125 | +impl Var { |
| 126 | + fn new(expr: &Expr<'_>) -> Option<Self> { |
| 127 | + path_to_local(expr).map(|hir_id| Self { |
| 128 | + span: expr.span, |
| 129 | + hir_id, |
| 130 | + }) |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +fn check_subtraction( |
| 135 | + cx: &LateContext<'_>, |
| 136 | + condition_span: Span, |
| 137 | + expr_span: Span, |
| 138 | + big_var: Var, |
| 139 | + little_var: Var, |
| 140 | + if_block: &Expr<'_>, |
| 141 | + else_block: &Expr<'_>, |
| 142 | +) { |
| 143 | + let if_block = peel_blocks(if_block); |
| 144 | + let else_block = peel_blocks(else_block); |
| 145 | + if is_integer_literal(if_block, 0) { |
| 146 | + // We need to check this case as well to prevent infinite recursion. |
| 147 | + if is_integer_literal(else_block, 0) { |
| 148 | + // Well, seems weird but who knows? |
| 149 | + return; |
| 150 | + } |
| 151 | + // If the subtraction is done in the `else` block, then we need to also revert the two |
| 152 | + // variables as it means that the check was reverted too. |
| 153 | + check_subtraction(cx, condition_span, expr_span, little_var, big_var, else_block, if_block); |
| 154 | + return; |
| 155 | + } |
| 156 | + if is_integer_literal(else_block, 0) |
| 157 | + && let ExprKind::Binary(op, left, right) = if_block.kind |
| 158 | + && let BinOpKind::Sub = op.node |
| 159 | + { |
| 160 | + let local_left = path_to_local(left); |
| 161 | + let local_right = path_to_local(right); |
| 162 | + if Some(big_var.hir_id) == local_left && Some(little_var.hir_id) == local_right { |
| 163 | + // This part of the condition is voluntarily split from the one before to ensure that |
| 164 | + // if `snippet_opt` fails, it won't try the next conditions. |
| 165 | + if let Some(big_var_snippet) = snippet_opt(cx, big_var.span) |
| 166 | + && let Some(little_var_snippet) = snippet_opt(cx, little_var.span) |
| 167 | + { |
| 168 | + span_lint_and_sugg( |
| 169 | + cx, |
| 170 | + IMPLICIT_SATURATING_SUB, |
| 171 | + expr_span, |
| 172 | + "manual arithmetic check found", |
| 173 | + "replace it with", |
| 174 | + format!("{big_var_snippet}.saturating_sub({little_var_snippet})"), |
| 175 | + Applicability::MachineApplicable, |
| 176 | + ); |
| 177 | + } |
| 178 | + } else if Some(little_var.hir_id) == local_left |
| 179 | + && Some(big_var.hir_id) == local_right |
| 180 | + && let Some(big_var_snippet) = snippet_opt(cx, big_var.span) |
| 181 | + && let Some(little_var_snippet) = snippet_opt(cx, little_var.span) |
62 | 182 | {
|
63 |
| - // Handle symmetric conditions in the if statement |
64 |
| - let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) { |
65 |
| - if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node { |
66 |
| - (cond_left, cond_right) |
67 |
| - } else { |
68 |
| - return; |
69 |
| - } |
70 |
| - } else if SpanlessEq::new(cx).eq_expr(cond_right, target) { |
71 |
| - if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node { |
72 |
| - (cond_right, cond_left) |
73 |
| - } else { |
74 |
| - return; |
75 |
| - } |
| 183 | + span_lint_and_then( |
| 184 | + cx, |
| 185 | + IMPLICIT_SATURATING_SUB, |
| 186 | + condition_span, |
| 187 | + "inverted arithmetic check before subtraction", |
| 188 | + |diag| { |
| 189 | + diag.span_note( |
| 190 | + if_block.span, |
| 191 | + format!("this subtraction underflows when `{little_var_snippet} < {big_var_snippet}`"), |
| 192 | + ); |
| 193 | + diag.span_suggestion( |
| 194 | + if_block.span, |
| 195 | + "try replacing it with", |
| 196 | + format!("{big_var_snippet} - {little_var_snippet}"), |
| 197 | + Applicability::MaybeIncorrect, |
| 198 | + ); |
| 199 | + }, |
| 200 | + ); |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +fn check_with_condition<'tcx>( |
| 206 | + cx: &LateContext<'tcx>, |
| 207 | + expr: &Expr<'tcx>, |
| 208 | + cond_op: BinOpKind, |
| 209 | + cond_left: &Expr<'tcx>, |
| 210 | + cond_right: &Expr<'tcx>, |
| 211 | + then: &Expr<'tcx>, |
| 212 | +) { |
| 213 | + // Ensure that the binary operator is >, !=, or < |
| 214 | + if (BinOpKind::Ne == cond_op || BinOpKind::Gt == cond_op || BinOpKind::Lt == cond_op) |
| 215 | + |
| 216 | + // Check if assign operation is done |
| 217 | + && let Some(target) = subtracts_one(cx, then) |
| 218 | + |
| 219 | + // Extracting out the variable name |
| 220 | + && let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind |
| 221 | + { |
| 222 | + // Handle symmetric conditions in the if statement |
| 223 | + let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) { |
| 224 | + if BinOpKind::Gt == cond_op || BinOpKind::Ne == cond_op { |
| 225 | + (cond_left, cond_right) |
76 | 226 | } else {
|
77 | 227 | return;
|
78 |
| - }; |
79 |
| - |
80 |
| - // Check if the variable in the condition statement is an integer |
81 |
| - if !cx.typeck_results().expr_ty(cond_var).is_integral() { |
| 228 | + } |
| 229 | + } else if SpanlessEq::new(cx).eq_expr(cond_right, target) { |
| 230 | + if BinOpKind::Lt == cond_op || BinOpKind::Ne == cond_op { |
| 231 | + (cond_right, cond_left) |
| 232 | + } else { |
82 | 233 | return;
|
83 | 234 | }
|
| 235 | + } else { |
| 236 | + return; |
| 237 | + }; |
84 | 238 |
|
85 |
| - // Get the variable name |
86 |
| - let var_name = ares_path.segments[0].ident.name.as_str(); |
87 |
| - match cond_num_val.kind { |
88 |
| - ExprKind::Lit(cond_lit) => { |
89 |
| - // Check if the constant is zero |
90 |
| - if let LitKind::Int(Pu128(0), _) = cond_lit.node { |
91 |
| - if cx.typeck_results().expr_ty(cond_left).is_signed() { |
92 |
| - } else { |
93 |
| - print_lint_and_sugg(cx, var_name, expr); |
94 |
| - }; |
95 |
| - } |
96 |
| - }, |
97 |
| - ExprKind::Path(QPath::TypeRelative(_, name)) => { |
98 |
| - if name.ident.as_str() == "MIN" |
99 |
| - && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) |
100 |
| - && let Some(impl_id) = cx.tcx.impl_of_method(const_id) |
101 |
| - && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl |
102 |
| - && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() |
103 |
| - { |
104 |
| - print_lint_and_sugg(cx, var_name, expr); |
105 |
| - } |
106 |
| - }, |
107 |
| - ExprKind::Call(func, []) => { |
108 |
| - if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind |
109 |
| - && name.ident.as_str() == "min_value" |
110 |
| - && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) |
111 |
| - && let Some(impl_id) = cx.tcx.impl_of_method(func_id) |
112 |
| - && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl |
113 |
| - && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() |
114 |
| - { |
| 239 | + // Check if the variable in the condition statement is an integer |
| 240 | + if !cx.typeck_results().expr_ty(cond_var).is_integral() { |
| 241 | + return; |
| 242 | + } |
| 243 | + |
| 244 | + // Get the variable name |
| 245 | + let var_name = ares_path.segments[0].ident.name.as_str(); |
| 246 | + match cond_num_val.kind { |
| 247 | + ExprKind::Lit(cond_lit) => { |
| 248 | + // Check if the constant is zero |
| 249 | + if let LitKind::Int(Pu128(0), _) = cond_lit.node { |
| 250 | + if cx.typeck_results().expr_ty(cond_left).is_signed() { |
| 251 | + } else { |
115 | 252 | print_lint_and_sugg(cx, var_name, expr);
|
116 |
| - } |
117 |
| - }, |
118 |
| - _ => (), |
119 |
| - } |
| 253 | + }; |
| 254 | + } |
| 255 | + }, |
| 256 | + ExprKind::Path(QPath::TypeRelative(_, name)) => { |
| 257 | + if name.ident.as_str() == "MIN" |
| 258 | + && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) |
| 259 | + && let Some(impl_id) = cx.tcx.impl_of_method(const_id) |
| 260 | + && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl |
| 261 | + && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() |
| 262 | + { |
| 263 | + print_lint_and_sugg(cx, var_name, expr); |
| 264 | + } |
| 265 | + }, |
| 266 | + ExprKind::Call(func, []) => { |
| 267 | + if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind |
| 268 | + && name.ident.as_str() == "min_value" |
| 269 | + && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) |
| 270 | + && let Some(impl_id) = cx.tcx.impl_of_method(func_id) |
| 271 | + && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl |
| 272 | + && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() |
| 273 | + { |
| 274 | + print_lint_and_sugg(cx, var_name, expr); |
| 275 | + } |
| 276 | + }, |
| 277 | + _ => (), |
120 | 278 | }
|
121 | 279 | }
|
122 | 280 | }
|
|
0 commit comments