]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::consts::{ |
2 | constant, constant_simple, Constant, | |
3 | Constant::{Int, F32, F64}, | |
4 | }; | |
5 | use crate::utils::{eq_expr_value, get_parent_expr, numeric_literal, span_lint_and_sugg, sugg}; | |
6 | use if_chain::if_chain; | |
7 | use rustc_errors::Applicability; | |
8 | use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; | |
9 | use rustc_lint::{LateContext, LateLintPass}; | |
10 | use rustc_middle::ty; | |
11 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
12 | use rustc_span::source_map::Spanned; | |
13 | ||
14 | use rustc_ast::ast; | |
15 | use std::f32::consts as f32_consts; | |
16 | use std::f64::consts as f64_consts; | |
17 | use sugg::Sugg; | |
18 | ||
19 | declare_clippy_lint! { | |
20 | /// **What it does:** Looks for floating-point expressions that | |
21 | /// can be expressed using built-in methods to improve accuracy | |
22 | /// at the cost of performance. | |
23 | /// | |
24 | /// **Why is this bad?** Negatively impacts accuracy. | |
25 | /// | |
26 | /// **Known problems:** None | |
27 | /// | |
28 | /// **Example:** | |
29 | /// | |
30 | /// ```rust | |
31 | /// let a = 3f32; | |
32 | /// let _ = a.powf(1.0 / 3.0); | |
33 | /// let _ = (1.0 + a).ln(); | |
34 | /// let _ = a.exp() - 1.0; | |
35 | /// ``` | |
36 | /// | |
37 | /// is better expressed as | |
38 | /// | |
39 | /// ```rust | |
40 | /// let a = 3f32; | |
41 | /// let _ = a.cbrt(); | |
42 | /// let _ = a.ln_1p(); | |
43 | /// let _ = a.exp_m1(); | |
44 | /// ``` | |
45 | pub IMPRECISE_FLOPS, | |
46 | nursery, | |
47 | "usage of imprecise floating point operations" | |
48 | } | |
49 | ||
50 | declare_clippy_lint! { | |
51 | /// **What it does:** Looks for floating-point expressions that | |
52 | /// can be expressed using built-in methods to improve both | |
53 | /// accuracy and performance. | |
54 | /// | |
55 | /// **Why is this bad?** Negatively impacts accuracy and performance. | |
56 | /// | |
57 | /// **Known problems:** None | |
58 | /// | |
59 | /// **Example:** | |
60 | /// | |
61 | /// ```rust | |
62 | /// use std::f32::consts::E; | |
63 | /// | |
64 | /// let a = 3f32; | |
65 | /// let _ = (2f32).powf(a); | |
66 | /// let _ = E.powf(a); | |
67 | /// let _ = a.powf(1.0 / 2.0); | |
68 | /// let _ = a.log(2.0); | |
69 | /// let _ = a.log(10.0); | |
70 | /// let _ = a.log(E); | |
71 | /// let _ = a.powf(2.0); | |
72 | /// let _ = a * 2.0 + 4.0; | |
73 | /// let _ = if a < 0.0 { | |
74 | /// -a | |
75 | /// } else { | |
76 | /// a | |
77 | /// }; | |
78 | /// let _ = if a < 0.0 { | |
79 | /// a | |
80 | /// } else { | |
81 | /// -a | |
82 | /// }; | |
83 | /// ``` | |
84 | /// | |
85 | /// is better expressed as | |
86 | /// | |
87 | /// ```rust | |
88 | /// use std::f32::consts::E; | |
89 | /// | |
90 | /// let a = 3f32; | |
91 | /// let _ = a.exp2(); | |
92 | /// let _ = a.exp(); | |
93 | /// let _ = a.sqrt(); | |
94 | /// let _ = a.log2(); | |
95 | /// let _ = a.log10(); | |
96 | /// let _ = a.ln(); | |
97 | /// let _ = a.powi(2); | |
98 | /// let _ = a.mul_add(2.0, 4.0); | |
99 | /// let _ = a.abs(); | |
100 | /// let _ = -a.abs(); | |
101 | /// ``` | |
102 | pub SUBOPTIMAL_FLOPS, | |
103 | nursery, | |
104 | "usage of sub-optimal floating point operations" | |
105 | } | |
106 | ||
107 | declare_lint_pass!(FloatingPointArithmetic => [ | |
108 | IMPRECISE_FLOPS, | |
109 | SUBOPTIMAL_FLOPS | |
110 | ]); | |
111 | ||
112 | // Returns the specialized log method for a given base if base is constant | |
113 | // and is one of 2, 10 and e | |
114 | fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> { | |
115 | if let Some((value, _)) = constant(cx, cx.typeck_results(), base) { | |
116 | if F32(2.0) == value || F64(2.0) == value { | |
117 | return Some("log2"); | |
118 | } else if F32(10.0) == value || F64(10.0) == value { | |
119 | return Some("log10"); | |
120 | } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value { | |
121 | return Some("ln"); | |
122 | } | |
123 | } | |
124 | ||
125 | None | |
126 | } | |
127 | ||
128 | // Adds type suffixes and parenthesis to method receivers if necessary | |
129 | fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> { | |
130 | let mut suggestion = Sugg::hir(cx, expr, ".."); | |
131 | ||
132 | if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { | |
133 | expr = &inner_expr; | |
134 | } | |
135 | ||
136 | if_chain! { | |
137 | // if the expression is a float literal and it is unsuffixed then | |
138 | // add a suffix so the suggestion is valid and unambiguous | |
139 | if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind(); | |
140 | if let ExprKind::Lit(lit) = &expr.kind; | |
141 | if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node; | |
142 | then { | |
143 | let op = format!( | |
144 | "{}{}{}", | |
145 | suggestion, | |
146 | // Check for float literals without numbers following the decimal | |
147 | // separator such as `2.` and adds a trailing zero | |
148 | if sym.as_str().ends_with('.') { | |
149 | "0" | |
150 | } else { | |
151 | "" | |
152 | }, | |
153 | float_ty.name_str() | |
154 | ).into(); | |
155 | ||
156 | suggestion = match suggestion { | |
157 | Sugg::MaybeParen(_) => Sugg::MaybeParen(op), | |
158 | _ => Sugg::NonParen(op) | |
159 | }; | |
160 | } | |
161 | } | |
162 | ||
163 | suggestion.maybe_par() | |
164 | } | |
165 | ||
166 | fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { | |
167 | if let Some(method) = get_specialized_log_method(cx, &args[1]) { | |
168 | span_lint_and_sugg( | |
169 | cx, | |
170 | SUBOPTIMAL_FLOPS, | |
171 | expr.span, | |
172 | "logarithm for bases 2, 10 and e can be computed more accurately", | |
173 | "consider using", | |
174 | format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method), | |
175 | Applicability::MachineApplicable, | |
176 | ); | |
177 | } | |
178 | } | |
179 | ||
180 | // TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and | |
181 | // suggest usage of `(x + (y - 1)).ln_1p()` instead | |
182 | fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { | |
183 | if let ExprKind::Binary( | |
184 | Spanned { | |
185 | node: BinOpKind::Add, .. | |
186 | }, | |
187 | lhs, | |
188 | rhs, | |
189 | ) = &args[0].kind | |
190 | { | |
191 | let recv = match ( | |
192 | constant(cx, cx.typeck_results(), lhs), | |
193 | constant(cx, cx.typeck_results(), rhs), | |
194 | ) { | |
195 | (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs, | |
196 | (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs, | |
197 | _ => return, | |
198 | }; | |
199 | ||
200 | span_lint_and_sugg( | |
201 | cx, | |
202 | IMPRECISE_FLOPS, | |
203 | expr.span, | |
204 | "ln(1 + x) can be computed more accurately", | |
205 | "consider using", | |
206 | format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)), | |
207 | Applicability::MachineApplicable, | |
208 | ); | |
209 | } | |
210 | } | |
211 | ||
212 | // Returns an integer if the float constant is a whole number and it can be | |
213 | // converted to an integer without loss of precision. For now we only check | |
214 | // ranges [-16777215, 16777216) for type f32 as whole number floats outside | |
215 | // this range are lossy and ambiguous. | |
216 | #[allow(clippy::cast_possible_truncation)] | |
217 | fn get_integer_from_float_constant(value: &Constant) -> Option<i32> { | |
218 | match value { | |
219 | F32(num) if num.fract() == 0.0 => { | |
220 | if (-16_777_215.0..16_777_216.0).contains(num) { | |
221 | Some(num.round() as i32) | |
222 | } else { | |
223 | None | |
224 | } | |
225 | }, | |
226 | F64(num) if num.fract() == 0.0 => { | |
227 | if (-2_147_483_648.0..2_147_483_648.0).contains(num) { | |
228 | Some(num.round() as i32) | |
229 | } else { | |
230 | None | |
231 | } | |
232 | }, | |
233 | _ => None, | |
234 | } | |
235 | } | |
236 | ||
237 | fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { | |
238 | // Check receiver | |
239 | if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) { | |
240 | let method = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { | |
241 | "exp" | |
242 | } else if F32(2.0) == value || F64(2.0) == value { | |
243 | "exp2" | |
244 | } else { | |
245 | return; | |
246 | }; | |
247 | ||
248 | span_lint_and_sugg( | |
249 | cx, | |
250 | SUBOPTIMAL_FLOPS, | |
251 | expr.span, | |
252 | "exponent for bases 2 and e can be computed more accurately", | |
253 | "consider using", | |
254 | format!("{}.{}()", prepare_receiver_sugg(cx, &args[1]), method), | |
255 | Applicability::MachineApplicable, | |
256 | ); | |
257 | } | |
258 | ||
259 | // Check argument | |
260 | if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) { | |
261 | let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { | |
262 | ( | |
263 | SUBOPTIMAL_FLOPS, | |
264 | "square-root of a number can be computed more efficiently and accurately", | |
265 | format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")), | |
266 | ) | |
267 | } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { | |
268 | ( | |
269 | IMPRECISE_FLOPS, | |
270 | "cube-root of a number can be computed more accurately", | |
271 | format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")), | |
272 | ) | |
273 | } else if let Some(exponent) = get_integer_from_float_constant(&value) { | |
274 | ( | |
275 | SUBOPTIMAL_FLOPS, | |
276 | "exponentiation with integer powers can be computed more efficiently", | |
277 | format!( | |
278 | "{}.powi({})", | |
279 | Sugg::hir(cx, &args[0], ".."), | |
280 | numeric_literal::format(&exponent.to_string(), None, false) | |
281 | ), | |
282 | ) | |
283 | } else { | |
284 | return; | |
285 | }; | |
286 | ||
287 | span_lint_and_sugg( | |
288 | cx, | |
289 | lint, | |
290 | expr.span, | |
291 | help, | |
292 | "consider using", | |
293 | suggestion, | |
294 | Applicability::MachineApplicable, | |
295 | ); | |
296 | } | |
297 | } | |
298 | ||
299 | fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { | |
300 | if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) { | |
301 | if value == Int(2) { | |
302 | if let Some(parent) = get_parent_expr(cx, expr) { | |
303 | if let Some(grandparent) = get_parent_expr(cx, parent) { | |
304 | if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = grandparent.kind { | |
305 | if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() { | |
306 | return; | |
307 | } | |
308 | } | |
309 | } | |
310 | ||
311 | if let ExprKind::Binary( | |
312 | Spanned { | |
313 | node: BinOpKind::Add, .. | |
314 | }, | |
315 | ref lhs, | |
316 | ref rhs, | |
317 | ) = parent.kind | |
318 | { | |
319 | let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; | |
320 | ||
321 | span_lint_and_sugg( | |
322 | cx, | |
323 | SUBOPTIMAL_FLOPS, | |
324 | parent.span, | |
325 | "square can be computed more efficiently", | |
326 | "consider using", | |
327 | format!( | |
328 | "{}.mul_add({}, {})", | |
329 | Sugg::hir(cx, &args[0], ".."), | |
330 | Sugg::hir(cx, &args[0], ".."), | |
331 | Sugg::hir(cx, &other_addend, ".."), | |
332 | ), | |
333 | Applicability::MachineApplicable, | |
334 | ); | |
335 | ||
336 | return; | |
337 | } | |
338 | } | |
339 | ||
340 | span_lint_and_sugg( | |
341 | cx, | |
342 | SUBOPTIMAL_FLOPS, | |
343 | expr.span, | |
344 | "square can be computed more efficiently", | |
345 | "consider using", | |
346 | format!("{} * {}", Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], "..")), | |
347 | Applicability::MachineApplicable, | |
348 | ); | |
349 | } | |
350 | } | |
351 | } | |
352 | ||
353 | fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> { | |
354 | if let ExprKind::Binary( | |
355 | Spanned { | |
356 | node: BinOpKind::Add, .. | |
357 | }, | |
358 | ref add_lhs, | |
359 | ref add_rhs, | |
360 | ) = args[0].kind | |
361 | { | |
362 | // check if expression of the form x * x + y * y | |
363 | if_chain! { | |
364 | if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind; | |
365 | if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind; | |
366 | if eq_expr_value(cx, lmul_lhs, lmul_rhs); | |
367 | if eq_expr_value(cx, rmul_lhs, rmul_rhs); | |
368 | then { | |
369 | return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, ".."))); | |
370 | } | |
371 | } | |
372 | ||
373 | // check if expression of the form x.powi(2) + y.powi(2) | |
374 | if_chain! { | |
375 | if let ExprKind::MethodCall( | |
376 | PathSegment { ident: lmethod_name, .. }, | |
377 | ref _lspan, | |
378 | ref largs, | |
379 | _ | |
380 | ) = add_lhs.kind; | |
381 | if let ExprKind::MethodCall( | |
382 | PathSegment { ident: rmethod_name, .. }, | |
383 | ref _rspan, | |
384 | ref rargs, | |
385 | _ | |
386 | ) = add_rhs.kind; | |
387 | if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"; | |
388 | if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), &largs[1]); | |
389 | if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), &rargs[1]); | |
390 | if Int(2) == lvalue && Int(2) == rvalue; | |
391 | then { | |
392 | return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."))); | |
393 | } | |
394 | } | |
395 | } | |
396 | ||
397 | None | |
398 | } | |
399 | ||
400 | fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { | |
401 | if let Some(message) = detect_hypot(cx, args) { | |
402 | span_lint_and_sugg( | |
403 | cx, | |
404 | IMPRECISE_FLOPS, | |
405 | expr.span, | |
406 | "hypotenuse can be computed more accurately", | |
407 | "consider using", | |
408 | message, | |
409 | Applicability::MachineApplicable, | |
410 | ); | |
411 | } | |
412 | } | |
413 | ||
414 | // TODO: Lint expressions of the form `x.exp() - y` where y > 1 | |
415 | // and suggest usage of `x.exp_m1() - (y - 1)` instead | |
416 | fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
417 | if_chain! { | |
418 | if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, ref lhs, ref rhs) = expr.kind; | |
419 | if cx.typeck_results().expr_ty(lhs).is_floating_point(); | |
420 | if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs); | |
421 | if F32(1.0) == value || F64(1.0) == value; | |
422 | if let ExprKind::MethodCall(ref path, _, ref method_args, _) = lhs.kind; | |
423 | if cx.typeck_results().expr_ty(&method_args[0]).is_floating_point(); | |
424 | if path.ident.name.as_str() == "exp"; | |
425 | then { | |
426 | span_lint_and_sugg( | |
427 | cx, | |
428 | IMPRECISE_FLOPS, | |
429 | expr.span, | |
430 | "(e.pow(x) - 1) can be computed more accurately", | |
431 | "consider using", | |
432 | format!( | |
433 | "{}.exp_m1()", | |
434 | Sugg::hir(cx, &method_args[0], "..") | |
435 | ), | |
436 | Applicability::MachineApplicable, | |
437 | ); | |
438 | } | |
439 | } | |
440 | } | |
441 | ||
442 | fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { | |
443 | if_chain! { | |
444 | if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lhs, ref rhs) = &expr.kind; | |
445 | if cx.typeck_results().expr_ty(lhs).is_floating_point(); | |
446 | if cx.typeck_results().expr_ty(rhs).is_floating_point(); | |
447 | then { | |
448 | return Some((lhs, rhs)); | |
449 | } | |
450 | } | |
451 | ||
452 | None | |
453 | } | |
454 | ||
455 | // TODO: Fix rust-lang/rust-clippy#4735 | |
456 | fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
457 | if let ExprKind::Binary( | |
458 | Spanned { | |
459 | node: BinOpKind::Add, .. | |
460 | }, | |
461 | lhs, | |
462 | rhs, | |
463 | ) = &expr.kind | |
464 | { | |
465 | if let Some(parent) = get_parent_expr(cx, expr) { | |
466 | if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = parent.kind { | |
467 | if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() { | |
468 | return; | |
469 | } | |
470 | } | |
471 | } | |
472 | ||
473 | let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) { | |
474 | (inner_lhs, inner_rhs, rhs) | |
475 | } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) { | |
476 | (inner_lhs, inner_rhs, lhs) | |
477 | } else { | |
478 | return; | |
479 | }; | |
480 | ||
481 | span_lint_and_sugg( | |
482 | cx, | |
483 | SUBOPTIMAL_FLOPS, | |
484 | expr.span, | |
485 | "multiply and add expressions can be calculated more efficiently and accurately", | |
486 | "consider using", | |
487 | format!( | |
488 | "{}.mul_add({}, {})", | |
489 | prepare_receiver_sugg(cx, recv), | |
490 | Sugg::hir(cx, arg1, ".."), | |
491 | Sugg::hir(cx, arg2, ".."), | |
492 | ), | |
493 | Applicability::MachineApplicable, | |
494 | ); | |
495 | } | |
496 | } | |
497 | ||
498 | /// Returns true iff expr is an expression which tests whether or not | |
499 | /// test is positive or an expression which tests whether or not test | |
500 | /// is nonnegative. | |
501 | /// Used for check-custom-abs function below | |
502 | fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { | |
503 | if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { | |
504 | match op { | |
505 | BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test), | |
506 | BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test), | |
507 | _ => false, | |
508 | } | |
509 | } else { | |
510 | false | |
511 | } | |
512 | } | |
513 | ||
514 | /// See [`is_testing_positive`] | |
515 | fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { | |
516 | if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { | |
517 | match op { | |
518 | BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test), | |
519 | BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test), | |
520 | _ => false, | |
521 | } | |
522 | } else { | |
523 | false | |
524 | } | |
525 | } | |
526 | ||
527 | /// Returns true iff expr is some zero literal | |
528 | fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
529 | match constant_simple(cx, cx.typeck_results(), expr) { | |
530 | Some(Constant::Int(i)) => i == 0, | |
531 | Some(Constant::F32(f)) => f == 0.0, | |
532 | Some(Constant::F64(f)) => f == 0.0, | |
533 | _ => false, | |
534 | } | |
535 | } | |
536 | ||
537 | /// If the two expressions are negations of each other, then it returns | |
538 | /// a tuple, in which the first element is true iff expr1 is the | |
539 | /// positive expressions, and the second element is the positive | |
540 | /// one of the two expressions | |
541 | /// If the two expressions are not negations of each other, then it | |
542 | /// returns None. | |
543 | fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { | |
544 | if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind { | |
545 | if eq_expr_value(cx, expr1_negated, expr2) { | |
546 | return Some((false, expr2)); | |
547 | } | |
548 | } | |
549 | if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind { | |
550 | if eq_expr_value(cx, expr1, expr2_negated) { | |
551 | return Some((true, expr1)); | |
552 | } | |
553 | } | |
554 | None | |
555 | } | |
556 | ||
557 | fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
558 | if_chain! { | |
559 | if let ExprKind::If(cond, body, else_body) = expr.kind; | |
560 | if let ExprKind::Block(block, _) = body.kind; | |
561 | if block.stmts.is_empty(); | |
562 | if let Some(if_body_expr) = block.expr; | |
563 | if let Some(ExprKind::Block(else_block, _)) = else_body.map(|el| &el.kind); | |
564 | if else_block.stmts.is_empty(); | |
565 | if let Some(else_body_expr) = else_block.expr; | |
566 | if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr); | |
567 | then { | |
568 | let positive_abs_sugg = ( | |
569 | "manual implementation of `abs` method", | |
570 | format!("{}.abs()", Sugg::hir(cx, body, "..")), | |
571 | ); | |
572 | let negative_abs_sugg = ( | |
573 | "manual implementation of negation of `abs` method", | |
574 | format!("-{}.abs()", Sugg::hir(cx, body, "..")), | |
575 | ); | |
576 | let sugg = if is_testing_positive(cx, cond, body) { | |
577 | if if_expr_positive { | |
578 | positive_abs_sugg | |
579 | } else { | |
580 | negative_abs_sugg | |
581 | } | |
582 | } else if is_testing_negative(cx, cond, body) { | |
583 | if if_expr_positive { | |
584 | negative_abs_sugg | |
585 | } else { | |
586 | positive_abs_sugg | |
587 | } | |
588 | } else { | |
589 | return; | |
590 | }; | |
591 | span_lint_and_sugg( | |
592 | cx, | |
593 | SUBOPTIMAL_FLOPS, | |
594 | expr.span, | |
595 | sugg.0, | |
596 | "try", | |
597 | sugg.1, | |
598 | Applicability::MachineApplicable, | |
599 | ); | |
600 | } | |
601 | } | |
602 | } | |
603 | ||
604 | fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { | |
605 | if_chain! { | |
606 | if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, ref args_a, _) = expr_a.kind; | |
607 | if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, ref args_b, _) = expr_b.kind; | |
608 | then { | |
609 | return method_name_a.as_str() == method_name_b.as_str() && | |
610 | args_a.len() == args_b.len() && | |
611 | ( | |
612 | ["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) || | |
613 | method_name_a.as_str() == "log" && args_a.len() == 2 && eq_expr_value(cx, &args_a[1], &args_b[1]) | |
614 | ); | |
615 | } | |
616 | } | |
617 | ||
618 | false | |
619 | } | |
620 | ||
621 | fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
622 | // check if expression of the form x.logN() / y.logN() | |
623 | if_chain! { | |
624 | if let ExprKind::Binary( | |
625 | Spanned { | |
626 | node: BinOpKind::Div, .. | |
627 | }, | |
628 | lhs, | |
629 | rhs, | |
630 | ) = &expr.kind; | |
631 | if are_same_base_logs(cx, lhs, rhs); | |
632 | if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind; | |
633 | if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind; | |
634 | then { | |
635 | span_lint_and_sugg( | |
636 | cx, | |
637 | SUBOPTIMAL_FLOPS, | |
638 | expr.span, | |
639 | "log base can be expressed more clearly", | |
640 | "consider using", | |
641 | format!("{}.log({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."),), | |
642 | Applicability::MachineApplicable, | |
643 | ); | |
644 | } | |
645 | } | |
646 | } | |
647 | ||
648 | fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
649 | if_chain! { | |
650 | if let ExprKind::Binary( | |
651 | Spanned { | |
652 | node: BinOpKind::Div, .. | |
653 | }, | |
654 | div_lhs, | |
655 | div_rhs, | |
656 | ) = &expr.kind; | |
657 | if let ExprKind::Binary( | |
658 | Spanned { | |
659 | node: BinOpKind::Mul, .. | |
660 | }, | |
661 | mul_lhs, | |
662 | mul_rhs, | |
663 | ) = &div_lhs.kind; | |
664 | if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs); | |
665 | if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs); | |
666 | then { | |
667 | // TODO: also check for constant values near PI/180 or 180/PI | |
668 | if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && | |
669 | (F32(180_f32) == lvalue || F64(180_f64) == lvalue) | |
670 | { | |
671 | span_lint_and_sugg( | |
672 | cx, | |
673 | SUBOPTIMAL_FLOPS, | |
674 | expr.span, | |
675 | "conversion to degrees can be done more accurately", | |
676 | "consider using", | |
677 | format!("{}.to_degrees()", Sugg::hir(cx, &mul_lhs, "..")), | |
678 | Applicability::MachineApplicable, | |
679 | ); | |
680 | } else if | |
681 | (F32(180_f32) == rvalue || F64(180_f64) == rvalue) && | |
682 | (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) | |
683 | { | |
684 | span_lint_and_sugg( | |
685 | cx, | |
686 | SUBOPTIMAL_FLOPS, | |
687 | expr.span, | |
688 | "conversion to radians can be done more accurately", | |
689 | "consider using", | |
690 | format!("{}.to_radians()", Sugg::hir(cx, &mul_lhs, "..")), | |
691 | Applicability::MachineApplicable, | |
692 | ); | |
693 | } | |
694 | } | |
695 | } | |
696 | } | |
697 | ||
698 | impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { | |
699 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
700 | if let ExprKind::MethodCall(ref path, _, args, _) = &expr.kind { | |
701 | let recv_ty = cx.typeck_results().expr_ty(&args[0]); | |
702 | ||
703 | if recv_ty.is_floating_point() { | |
704 | match &*path.ident.name.as_str() { | |
705 | "ln" => check_ln1p(cx, expr, args), | |
706 | "log" => check_log_base(cx, expr, args), | |
707 | "powf" => check_powf(cx, expr, args), | |
708 | "powi" => check_powi(cx, expr, args), | |
709 | "sqrt" => check_hypot(cx, expr, args), | |
710 | _ => {}, | |
711 | } | |
712 | } | |
713 | } else { | |
714 | check_expm1(cx, expr); | |
715 | check_mul_add(cx, expr); | |
716 | check_custom_abs(cx, expr); | |
717 | check_log_division(cx, expr); | |
718 | check_radians(cx, expr); | |
719 | } | |
720 | } | |
721 | } |