11use crate :: consts:: {
2- constant, Constant ,
2+ constant, constant_simple , Constant ,
33 Constant :: { F32 , F64 } ,
44} ;
5- use crate :: utils:: { span_lint_and_sugg, sugg} ;
5+ use crate :: utils:: { higher , span_lint_and_sugg, sugg, SpanlessEq } ;
66use if_chain:: if_chain;
77use rustc:: ty;
88use rustc_errors:: Applicability ;
@@ -72,6 +72,16 @@ declare_clippy_lint! {
7272 /// let _ = a.log(E);
7373 /// let _ = a.powf(2.0);
7474 /// let _ = a * 2.0 + 4.0;
75+ /// let _ = if a < 0.0 {
76+ /// -a
77+ /// } else {
78+ /// a
79+ /// };
80+ /// let _ = if a < 0.0 {
81+ /// a
82+ /// } else {
83+ /// -a
84+ /// };
7585 /// ```
7686 ///
7787 /// is better expressed as
@@ -88,6 +98,8 @@ declare_clippy_lint! {
8898 /// let _ = a.ln();
8999 /// let _ = a.powi(2);
90100 /// let _ = a.mul_add(2.0, 4.0);
101+ /// let _ = a.abs();
102+ /// let _ = -a.abs();
91103 /// ```
92104 pub SUBOPTIMAL_FLOPS ,
93105 nursery,
@@ -359,6 +371,116 @@ fn check_mul_add(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
359371 }
360372}
361373
374+ /// Returns true iff expr is an expression which tests whether or not
375+ /// test is positive or an expression which tests whether or not test
376+ /// is nonnegative.
377+ /// Used for check-custom-abs function below
378+ fn is_testing_positive ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > , test : & Expr < ' _ > ) -> bool {
379+ if let ExprKind :: Binary ( Spanned { node : op, .. } , left, right) = expr. kind {
380+ match op {
381+ BinOpKind :: Gt | BinOpKind :: Ge => is_zero ( cx, right) && are_exprs_equal ( cx, left, test) ,
382+ BinOpKind :: Lt | BinOpKind :: Le => is_zero ( cx, left) && are_exprs_equal ( cx, right, test) ,
383+ _ => false ,
384+ }
385+ } else {
386+ false
387+ }
388+ }
389+
390+ /// See [`is_testing_positive`]
391+ fn is_testing_negative ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > , test : & Expr < ' _ > ) -> bool {
392+ if let ExprKind :: Binary ( Spanned { node : op, .. } , left, right) = expr. kind {
393+ match op {
394+ BinOpKind :: Gt | BinOpKind :: Ge => is_zero ( cx, left) && are_exprs_equal ( cx, right, test) ,
395+ BinOpKind :: Lt | BinOpKind :: Le => is_zero ( cx, right) && are_exprs_equal ( cx, left, test) ,
396+ _ => false ,
397+ }
398+ } else {
399+ false
400+ }
401+ }
402+
403+ fn are_exprs_equal ( cx : & LateContext < ' _ , ' _ > , expr1 : & Expr < ' _ > , expr2 : & Expr < ' _ > ) -> bool {
404+ SpanlessEq :: new ( cx) . ignore_fn ( ) . eq_expr ( expr1, expr2)
405+ }
406+
407+ /// Returns true iff expr is some zero literal
408+ fn is_zero ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > ) -> bool {
409+ match constant_simple ( cx, cx. tables , expr) {
410+ Some ( Constant :: Int ( i) ) => i == 0 ,
411+ Some ( Constant :: F32 ( f) ) => f == 0.0 ,
412+ Some ( Constant :: F64 ( f) ) => f == 0.0 ,
413+ _ => false ,
414+ }
415+ }
416+
417+ /// If the two expressions are negations of each other, then it returns
418+ /// a tuple, in which the first element is true iff expr1 is the
419+ /// positive expressions, and the second element is the positive
420+ /// one of the two expressions
421+ /// If the two expressions are not negations of each other, then it
422+ /// returns None.
423+ fn are_negated < ' a > ( cx : & LateContext < ' _ , ' _ > , expr1 : & ' a Expr < ' a > , expr2 : & ' a Expr < ' a > ) -> Option < ( bool , & ' a Expr < ' a > ) > {
424+ if let ExprKind :: Unary ( UnOp :: UnNeg , expr1_negated) = & expr1. kind {
425+ if are_exprs_equal ( cx, expr1_negated, expr2) {
426+ return Some ( ( false , expr2) ) ;
427+ }
428+ }
429+ if let ExprKind :: Unary ( UnOp :: UnNeg , expr2_negated) = & expr2. kind {
430+ if are_exprs_equal ( cx, expr1, expr2_negated) {
431+ return Some ( ( true , expr1) ) ;
432+ }
433+ }
434+ None
435+ }
436+
437+ fn check_custom_abs ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > ) {
438+ if_chain ! {
439+ if let Some ( ( cond, body, Some ( else_body) ) ) = higher:: if_block( & expr) ;
440+ if let ExprKind :: Block ( block, _) = body. kind;
441+ if block. stmts. is_empty( ) ;
442+ if let Some ( if_body_expr) = block. expr;
443+ if let ExprKind :: Block ( else_block, _) = else_body. kind;
444+ if else_block. stmts. is_empty( ) ;
445+ if let Some ( else_body_expr) = else_block. expr;
446+ if let Some ( ( if_expr_positive, body) ) = are_negated( cx, if_body_expr, else_body_expr) ;
447+ then {
448+ let positive_abs_sugg = (
449+ "manual implementation of `abs` method" ,
450+ format!( "{}.abs()" , Sugg :: hir( cx, body, ".." ) ) ,
451+ ) ;
452+ let negative_abs_sugg = (
453+ "manual implementation of negation of `abs` method" ,
454+ format!( "-{}.abs()" , Sugg :: hir( cx, body, ".." ) ) ,
455+ ) ;
456+ let sugg = if is_testing_positive( cx, cond, body) {
457+ if if_expr_positive {
458+ positive_abs_sugg
459+ } else {
460+ negative_abs_sugg
461+ }
462+ } else if is_testing_negative( cx, cond, body) {
463+ if if_expr_positive {
464+ negative_abs_sugg
465+ } else {
466+ positive_abs_sugg
467+ }
468+ } else {
469+ return ;
470+ } ;
471+ span_lint_and_sugg(
472+ cx,
473+ SUBOPTIMAL_FLOPS ,
474+ expr. span,
475+ sugg. 0 ,
476+ "try" ,
477+ sugg. 1 ,
478+ Applicability :: MachineApplicable ,
479+ ) ;
480+ }
481+ }
482+ }
483+
362484impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for FloatingPointArithmetic {
363485 fn check_expr ( & mut self , cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr < ' _ > ) {
364486 if let ExprKind :: MethodCall ( ref path, _, args) = & expr. kind {
@@ -375,6 +497,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
375497 } else {
376498 check_expm1 ( cx, expr) ;
377499 check_mul_add ( cx, expr) ;
500+ check_custom_abs ( cx, expr) ;
378501 }
379502 }
380503}
0 commit comments