about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-04-15 06:39:11 +0000
committerbors <bors@rust-lang.org>2020-04-15 06:39:11 +0000
commita96f8743014cb1e3c26ec6e6b45734fa537bda2e (patch)
tree35a56db186926350c9a875e5576c040b0392a1f9
parentc6cc07a85121107c3d268fc67671f1b3b4a529af (diff)
parent4449cc799b737327b773b900f3f9e3684373aaf5 (diff)
downloadrust-a96f8743014cb1e3c26ec6e6b45734fa537bda2e.tar.gz
rust-a96f8743014cb1e3c26ec6e6b45734fa537bda2e.zip
Auto merge of #5345 - Toxyxer:add-lint-for-float-in-array-comparison, r=flip1995
Add lint for float in array comparison

Fixes #4277
changelog:
- Added new handler for expression of index kind (e.g. `arr[i]`). It returns a constant when both array and index are constant, or when the array is constant and all values are equal.
- Trigger float_cmp and float_cmp_const lint when comparing arrays. Allow for comparison when one of the arrays contains only zeros or infinities.
- Added appropriate tests for such cases.
-rw-r--r--clippy_lints/src/consts.rs61
-rw-r--r--clippy_lints/src/misc.rs76
-rw-r--r--tests/ui/float_cmp.rs23
-rw-r--r--tests/ui/float_cmp.stderr48
-rw-r--r--tests/ui/float_cmp_const.rs13
-rw-r--r--tests/ui/float_cmp_const.stderr50
6 files changed, 200 insertions, 71 deletions
diff --git a/clippy_lints/src/consts.rs b/clippy_lints/src/consts.rs
index b1d540c9751..b9160712915 100644
--- a/clippy_lints/src/consts.rs
+++ b/clippy_lints/src/consts.rs
@@ -268,6 +268,7 @@ impl<'c, 'cc> ConstEvalLateContext<'c, 'cc> {
                     }
                 }
             },
+            ExprKind::Index(ref arr, ref index) => self.index(arr, index),
             // TODO: add other expressions.
             _ => None,
         }
@@ -345,6 +346,31 @@ impl<'c, 'cc> ConstEvalLateContext<'c, 'cc> {
         }
     }
 
+    fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant> {
+        let lhs = self.expr(lhs);
+        let index = self.expr(index);
+
+        match (lhs, index) {
+            (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec[index as usize] {
+                Constant::F32(x) => Some(Constant::F32(x)),
+                Constant::F64(x) => Some(Constant::F64(x)),
+                _ => None,
+            },
+            (Some(Constant::Vec(vec)), _) => {
+                if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) {
+                    match vec[0] {
+                        Constant::F32(x) => Some(Constant::F32(x)),
+                        Constant::F64(x) => Some(Constant::F64(x)),
+                        _ => None,
+                    }
+                } else {
+                    None
+                }
+            },
+            _ => None,
+        }
+    }
+
     /// A block can only yield a constant if it only has one constant expression.
     fn block(&mut self, block: &Block<'_>) -> Option<Constant> {
         if block.stmts.is_empty() {
@@ -492,6 +518,41 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option<Constant> {
             },
             _ => None,
         },
+        ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind {
+            ty::Array(sub_type, len) => match sub_type.kind {
+                ty::Float(FloatTy::F32) => match miri_to_const(len) {
+                    Some(Constant::Int(len)) => alloc
+                        .inspect_with_undef_and_ptr_outside_interpreter(0..(4 * len as usize))
+                        .to_owned()
+                        .chunks(4)
+                        .map(|chunk| {
+                            Some(Constant::F32(f32::from_le_bytes(
+                                chunk.try_into().expect("this shouldn't happen"),
+                            )))
+                        })
+                        .collect::<Option<Vec<Constant>>>()
+                        .map(Constant::Vec),
+                    _ => None,
+                },
+                ty::Float(FloatTy::F64) => match miri_to_const(len) {
+                    Some(Constant::Int(len)) => alloc
+                        .inspect_with_undef_and_ptr_outside_interpreter(0..(8 * len as usize))
+                        .to_owned()
+                        .chunks(8)
+                        .map(|chunk| {
+                            Some(Constant::F64(f64::from_le_bytes(
+                                chunk.try_into().expect("this shouldn't happen"),
+                            )))
+                        })
+                        .collect::<Option<Vec<Constant>>>()
+                        .map(Constant::Vec),
+                    _ => None,
+                },
+                // FIXME: implement other array type conversions.
+                _ => None,
+            },
+            _ => None,
+        },
         // FIXME: implement other conversions.
         _ => None,
     }
diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs
index cedd15e8daf..58a5a29eb16 100644
--- a/clippy_lints/src/misc.rs
+++ b/clippy_lints/src/misc.rs
@@ -369,26 +369,28 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MiscLints {
                             return;
                         }
                     }
-                    let (lint, msg) = if is_named_constant(cx, left) || is_named_constant(cx, right) {
-                        (FLOAT_CMP_CONST, "strict comparison of `f32` or `f64` constant")
-                    } else {
-                        (FLOAT_CMP, "strict comparison of `f32` or `f64`")
-                    };
+                    let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
+                    let (lint, msg) = get_lint_and_message(
+                        is_named_constant(cx, left) || is_named_constant(cx, right),
+                        is_comparing_arrays,
+                    );
                     span_lint_and_then(cx, lint, expr.span, msg, |db| {
                         let lhs = Sugg::hir(cx, left, "..");
                         let rhs = Sugg::hir(cx, right, "..");
 
-                        db.span_suggestion(
-                            expr.span,
-                            "consider comparing them within some error",
-                            format!(
-                                "({}).abs() {} error",
-                                lhs - rhs,
-                                if op == BinOpKind::Eq { '<' } else { '>' }
-                            ),
-                            Applicability::HasPlaceholders, // snippet
-                        );
-                        db.span_note(expr.span, "`f32::EPSILON` and `f64::EPSILON` are available.");
+                        if !is_comparing_arrays {
+                            db.span_suggestion(
+                                expr.span,
+                                "consider comparing them within some error",
+                                format!(
+                                    "({}).abs() {} error",
+                                    lhs - rhs,
+                                    if op == BinOpKind::Eq { '<' } else { '>' }
+                                ),
+                                Applicability::HasPlaceholders, // snippet
+                            );
+                        }
+                        db.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error`");
                     });
                 } else if op == BinOpKind::Rem && is_integer_const(cx, right, 1) {
                     span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
@@ -440,6 +442,31 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MiscLints {
     }
 }
 
+fn get_lint_and_message(
+    is_comparing_constants: bool,
+    is_comparing_arrays: bool,
+) -> (&'static rustc_lint::Lint, &'static str) {
+    if is_comparing_constants {
+        (
+            FLOAT_CMP_CONST,
+            if is_comparing_arrays {
+                "strict comparison of `f32` or `f64` constant arrays"
+            } else {
+                "strict comparison of `f32` or `f64` constant"
+            },
+        )
+    } else {
+        (
+            FLOAT_CMP,
+            if is_comparing_arrays {
+                "strict comparison of `f32` or `f64` arrays"
+            } else {
+                "strict comparison of `f32` or `f64`"
+            },
+        )
+    }
+}
+
 fn check_nan(cx: &LateContext<'_, '_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
     if_chain! {
         if !in_constant(cx, cmp_expr.hir_id);
@@ -475,6 +502,11 @@ fn is_allowed<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> boo
     match constant(cx, cx.tables, expr) {
         Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
         Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
+        Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
+            Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
+            Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
+            _ => false,
+        }),
         _ => false,
     }
 }
@@ -499,7 +531,17 @@ fn is_signum(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
 }
 
 fn is_float(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
-    matches!(walk_ptrs_ty(cx.tables.expr_ty(expr)).kind, ty::Float(_))
+    let value = &walk_ptrs_ty(cx.tables.expr_ty(expr)).kind;
+
+    if let ty::Array(arr_ty, _) = value {
+        return matches!(arr_ty.kind, ty::Float(_));
+    };
+
+    matches!(value, ty::Float(_))
+}
+
+fn is_array(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
+    matches!(&walk_ptrs_ty(cx.tables.expr_ty(expr)).kind, ty::Array(_, _))
 }
 
 fn check_to_owned(cx: &LateContext<'_, '_>, expr: &Expr<'_>, other: &Expr<'_>) {
diff --git a/tests/ui/float_cmp.rs b/tests/ui/float_cmp.rs
index c8248723bc9..9fa0e5f5c07 100644
--- a/tests/ui/float_cmp.rs
+++ b/tests/ui/float_cmp.rs
@@ -1,5 +1,11 @@
 #![warn(clippy::float_cmp)]
-#![allow(unused, clippy::no_effect, clippy::unnecessary_operation, clippy::cast_lossless)]
+#![allow(
+    unused,
+    clippy::no_effect,
+    clippy::unnecessary_operation,
+    clippy::cast_lossless,
+    clippy::many_single_char_names
+)]
 
 use std::ops::Add;
 
@@ -77,6 +83,21 @@ fn main() {
 
     assert_eq!(a, b); // no errors
 
+    const ZERO_ARRAY: [f32; 2] = [0.0, 0.0];
+    const NON_ZERO_ARRAY: [f32; 2] = [0.0, 0.1];
+
+    let i = 0;
+    let j = 1;
+
+    ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; // ok, because lhs is zero regardless of i
+    NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j];
+
+    let a1: [f32; 1] = [0.0];
+    let a2: [f32; 1] = [1.1];
+
+    a1 == a2;
+    a1[0] == a2[0];
+
     // no errors - comparing signums is ok
     let x32 = 3.21f32;
     1.23f32.signum() == x32.signum();
diff --git a/tests/ui/float_cmp.stderr b/tests/ui/float_cmp.stderr
index 90c25a6db37..2d454e8e70d 100644
--- a/tests/ui/float_cmp.stderr
+++ b/tests/ui/float_cmp.stderr
@@ -1,39 +1,51 @@
 error: strict comparison of `f32` or `f64`
-  --> $DIR/float_cmp.rs:59:5
+  --> $DIR/float_cmp.rs:65:5
    |
 LL |     ONE as f64 != 2.0;
    |     ^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(ONE as f64 - 2.0).abs() > error`
    |
    = note: `-D clippy::float-cmp` implied by `-D warnings`
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp.rs:59:5
-   |
-LL |     ONE as f64 != 2.0;
-   |     ^^^^^^^^^^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64`
-  --> $DIR/float_cmp.rs:64:5
+  --> $DIR/float_cmp.rs:70:5
    |
 LL |     x == 1.0;
    |     ^^^^^^^^ help: consider comparing them within some error: `(x - 1.0).abs() < error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp.rs:64:5
-   |
-LL |     x == 1.0;
-   |     ^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64`
-  --> $DIR/float_cmp.rs:67:5
+  --> $DIR/float_cmp.rs:73:5
    |
 LL |     twice(x) != twice(ONE as f64);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(twice(x) - twice(ONE as f64)).abs() > error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp.rs:67:5
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
+
+error: strict comparison of `f32` or `f64`
+  --> $DIR/float_cmp.rs:93:5
    |
-LL |     twice(x) != twice(ONE as f64);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j];
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(NON_ZERO_ARRAY[i] - NON_ZERO_ARRAY[j]).abs() < error`
+   |
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
+
+error: strict comparison of `f32` or `f64` arrays
+  --> $DIR/float_cmp.rs:98:5
+   |
+LL |     a1 == a2;
+   |     ^^^^^^^^
+   |
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
+
+error: strict comparison of `f32` or `f64`
+  --> $DIR/float_cmp.rs:99:5
+   |
+LL |     a1[0] == a2[0];
+   |     ^^^^^^^^^^^^^^ help: consider comparing them within some error: `(a1[0] - a2[0]).abs() < error`
+   |
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
-error: aborting due to 3 previous errors
+error: aborting due to 6 previous errors
 
diff --git a/tests/ui/float_cmp_const.rs b/tests/ui/float_cmp_const.rs
index a338040e19b..dfc025558a2 100644
--- a/tests/ui/float_cmp_const.rs
+++ b/tests/ui/float_cmp_const.rs
@@ -46,4 +46,17 @@ fn main() {
     v != w;
     v == 1.0;
     v != 1.0;
+
+    const ZERO_ARRAY: [f32; 3] = [0.0, 0.0, 0.0];
+    const ZERO_INF_ARRAY: [f32; 3] = [0.0, ::std::f32::INFINITY, ::std::f32::NEG_INFINITY];
+    const NON_ZERO_ARRAY: [f32; 3] = [0.0, 0.1, 0.2];
+    const NON_ZERO_ARRAY2: [f32; 3] = [0.2, 0.1, 0.0];
+
+    // no errors, zero and infinity values
+    NON_ZERO_ARRAY[0] == NON_ZERO_ARRAY2[1]; // lhs is 0.0
+    ZERO_ARRAY == NON_ZERO_ARRAY; // lhs is all zeros
+    ZERO_INF_ARRAY == NON_ZERO_ARRAY; // lhs is all zeros or infinities
+
+    // has errors
+    NON_ZERO_ARRAY == NON_ZERO_ARRAY2;
 }
diff --git a/tests/ui/float_cmp_const.stderr b/tests/ui/float_cmp_const.stderr
index 2dc43cf4e5f..19dc4a284b7 100644
--- a/tests/ui/float_cmp_const.stderr
+++ b/tests/ui/float_cmp_const.stderr
@@ -5,11 +5,7 @@ LL |     1f32 == ONE;
    |     ^^^^^^^^^^^ help: consider comparing them within some error: `(1f32 - ONE).abs() < error`
    |
    = note: `-D clippy::float-cmp-const` implied by `-D warnings`
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:20:5
-   |
-LL |     1f32 == ONE;
-   |     ^^^^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64` constant
   --> $DIR/float_cmp_const.rs:21:5
@@ -17,11 +13,7 @@ error: strict comparison of `f32` or `f64` constant
 LL |     TWO == ONE;
    |     ^^^^^^^^^^ help: consider comparing them within some error: `(TWO - ONE).abs() < error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:21:5
-   |
-LL |     TWO == ONE;
-   |     ^^^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64` constant
   --> $DIR/float_cmp_const.rs:22:5
@@ -29,11 +21,7 @@ error: strict comparison of `f32` or `f64` constant
 LL |     TWO != ONE;
    |     ^^^^^^^^^^ help: consider comparing them within some error: `(TWO - ONE).abs() > error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:22:5
-   |
-LL |     TWO != ONE;
-   |     ^^^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64` constant
   --> $DIR/float_cmp_const.rs:23:5
@@ -41,11 +29,7 @@ error: strict comparison of `f32` or `f64` constant
 LL |     ONE + ONE == TWO;
    |     ^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(ONE + ONE - TWO).abs() < error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:23:5
-   |
-LL |     ONE + ONE == TWO;
-   |     ^^^^^^^^^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64` constant
   --> $DIR/float_cmp_const.rs:25:5
@@ -53,11 +37,7 @@ error: strict comparison of `f32` or `f64` constant
 LL |     x as f32 == ONE;
    |     ^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(x as f32 - ONE).abs() < error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:25:5
-   |
-LL |     x as f32 == ONE;
-   |     ^^^^^^^^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64` constant
   --> $DIR/float_cmp_const.rs:28:5
@@ -65,11 +45,7 @@ error: strict comparison of `f32` or `f64` constant
 LL |     v == ONE;
    |     ^^^^^^^^ help: consider comparing them within some error: `(v - ONE).abs() < error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:28:5
-   |
-LL |     v == ONE;
-   |     ^^^^^^^^
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
 error: strict comparison of `f32` or `f64` constant
   --> $DIR/float_cmp_const.rs:29:5
@@ -77,11 +53,15 @@ error: strict comparison of `f32` or `f64` constant
 LL |     v != ONE;
    |     ^^^^^^^^ help: consider comparing them within some error: `(v - ONE).abs() > error`
    |
-note: `f32::EPSILON` and `f64::EPSILON` are available.
-  --> $DIR/float_cmp_const.rs:29:5
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
+
+error: strict comparison of `f32` or `f64` constant arrays
+  --> $DIR/float_cmp_const.rs:61:5
    |
-LL |     v != ONE;
-   |     ^^^^^^^^
+LL |     NON_ZERO_ARRAY == NON_ZERO_ARRAY2;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error`
 
-error: aborting due to 7 previous errors
+error: aborting due to 8 previous errors