| use clippy_utils::consts::{ConstEvalCtxt, Constant}; | |
| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::sugg::Sugg; | |
| use clippy_utils::{parent_item_name, sym}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; | |
| use rustc_lint::LateContext; | |
| use rustc_middle::ty; | |
| use super::{FLOAT_CMP, FLOAT_CMP_CONST}; | |
| pub(crate) fn check<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| expr: &'tcx Expr<'_>, | |
| op: BinOpKind, | |
| left: &'tcx Expr<'_>, | |
| right: &'tcx Expr<'_>, | |
| ) { | |
| if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) { | |
| let ecx = ConstEvalCtxt::new(cx); | |
| let ctxt = expr.span.ctxt(); | |
| let left_is_local = match ecx.eval_with_source(left, ctxt) { | |
| Some((c, s)) if !is_allowed(&c) => s.is_local(), | |
| Some(_) => return, | |
| None => true, | |
| }; | |
| let right_is_local = match ecx.eval_with_source(right, ctxt) { | |
| Some((c, s)) if !is_allowed(&c) => s.is_local(), | |
| Some(_) => return, | |
| None => true, | |
| }; | |
| // Allow comparing the results of signum() | |
| if is_signum(cx, left) && is_signum(cx, right) { | |
| return; | |
| } | |
| if let Some(name) = parent_item_name(cx, expr) { | |
| let name = name.as_str(); | |
| if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") { | |
| return; | |
| } | |
| } | |
| let is_comparing_arrays = is_array(cx, left) || is_array(cx, right); | |
| let (lint, msg) = get_lint_and_message(left_is_local && right_is_local, is_comparing_arrays); | |
| span_lint_and_then(cx, lint, expr.span, msg, |diag| { | |
| let lhs = Sugg::hir(cx, left, ".."); | |
| let rhs = Sugg::hir(cx, right, ".."); | |
| if !is_comparing_arrays { | |
| diag.span_suggestion( | |
| expr.span, | |
| "consider comparing them within some margin of error", | |
| format!( | |
| "({}).abs() {} error_margin", | |
| lhs - rhs, | |
| if op == BinOpKind::Eq { '<' } else { '>' } | |
| ), | |
| Applicability::HasPlaceholders, // snippet | |
| ); | |
| } | |
| }); | |
| } | |
| } | |
| fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static rustc_lint::Lint, &'static str) { | |
| if is_local { | |
| ( | |
| FLOAT_CMP, | |
| if is_comparing_arrays { | |
| "strict comparison of `f32` or `f64` arrays" | |
| } else { | |
| "strict comparison of `f32` or `f64`" | |
| }, | |
| ) | |
| } else { | |
| ( | |
| FLOAT_CMP_CONST, | |
| if is_comparing_arrays { | |
| "strict comparison of `f32` or `f64` constant arrays" | |
| } else { | |
| "strict comparison of `f32` or `f64` constant" | |
| }, | |
| ) | |
| } | |
| } | |
| fn is_allowed(val: &Constant) -> bool { | |
| match val { | |
| // FIXME(f16_f128): add when equality check is available on all platforms | |
| &Constant::F32(f) => f == 0.0 || f.is_infinite(), | |
| &Constant::F64(f) => f == 0.0 || f.is_infinite(), | |
| 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, | |
| } | |
| } | |
| // Return true if `expr` is the result of `signum()` invoked on a float value. | |
| fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
| // The negation of a signum is still a signum | |
| if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind { | |
| return is_signum(cx, child_expr); | |
| } | |
| if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind | |
| && method_name.ident.name == sym::signum | |
| // Check that the receiver of the signum() is a float (expressions[0] is the receiver of | |
| // the method call) | |
| { | |
| return is_float(cx, self_arg); | |
| } | |
| false | |
| } | |
| fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
| let value = &cx.typeck_results().expr_ty(expr).peel_refs().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!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _)) | |
| } |