diff options
| author | Jason Newcomb <jsnewcomb@pm.me> | 2022-06-01 02:06:12 -0400 |
|---|---|---|
| committer | Jason Newcomb <jsnewcomb@pm.me> | 2022-06-28 12:51:30 -0400 |
| commit | c0b0ee5bdcbf9005cdd7f48e0ad350b42387e1c6 (patch) | |
| tree | edef4f3565f01eada156d73d12e8ff46f6a6fb9a | |
| parent | a8df16ae1df9084dc8e20e835f3de981e32d110c (diff) | |
| download | rust-c0b0ee5bdcbf9005cdd7f48e0ad350b42387e1c6.tar.gz rust-c0b0ee5bdcbf9005cdd7f48e0ad350b42387e1c6.zip | |
Move some lints from `Misc` to `Operators`
| -rw-r--r-- | clippy_lints/src/lib.register_all.rs | 6 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_correctness.rs | 4 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_lints.rs | 10 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_pedantic.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_perf.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/lib.register_restriction.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/misc.rs | 483 | ||||
| -rw-r--r-- | clippy_lints/src/operators/cmp_nan.rs | 30 | ||||
| -rw-r--r-- | clippy_lints/src/operators/cmp_owned.rs | 147 | ||||
| -rw-r--r-- | clippy_lints/src/operators/float_cmp.rs | 139 | ||||
| -rw-r--r-- | clippy_lints/src/operators/mod.rs | 163 | ||||
| -rw-r--r-- | clippy_lints/src/operators/modulo_one.rs | 26 |
12 files changed, 524 insertions, 490 deletions
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index fb08d7d112d..06f8a6325ff 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -212,9 +212,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::WRONG_SELF_CONVENTION), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), - LintId::of(misc::CMP_NAN), - LintId::of(misc::CMP_OWNED), - LintId::of(misc::MODULO_ONE), LintId::of(misc::SHORT_CIRCUIT_STATEMENT), LintId::of(misc::TOPLEVEL_REF_ARG), LintId::of(misc::ZERO_PTR), @@ -251,6 +248,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::ASSIGN_OP_PATTERN), LintId::of(operators::BAD_BIT_MASK), + LintId::of(operators::CMP_NAN), + LintId::of(operators::CMP_OWNED), LintId::of(operators::DOUBLE_COMPARISONS), LintId::of(operators::DURATION_SUBSEC), LintId::of(operators::EQ_OP), @@ -259,6 +258,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(operators::IDENTITY_OP), LintId::of(operators::INEFFECTIVE_BIT_MASK), LintId::of(operators::MISREFACTORED_ASSIGN_OP), + LintId::of(operators::MODULO_ONE), LintId::of(operators::OP_REF), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index b8362d5a72d..7133b6cdd88 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -42,15 +42,15 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(methods::UNINIT_ASSUMED_INIT), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), - LintId::of(misc::CMP_NAN), - LintId::of(misc::MODULO_ONE), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::BAD_BIT_MASK), + LintId::of(operators::CMP_NAN), LintId::of(operators::EQ_OP), LintId::of(operators::ERASING_OP), LintId::of(operators::INEFFECTIVE_BIT_MASK), + LintId::of(operators::MODULO_ONE), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 7acfdf56840..139b55e965f 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -356,11 +356,6 @@ store.register_lints(&[ methods::WRONG_SELF_CONVENTION, methods::ZST_OFFSET, minmax::MIN_MAX, - misc::CMP_NAN, - misc::CMP_OWNED, - misc::FLOAT_CMP, - misc::FLOAT_CMP_CONST, - misc::MODULO_ONE, misc::SHORT_CIRCUIT_STATEMENT, misc::TOPLEVEL_REF_ARG, misc::USED_UNDERSCORE_BINDING, @@ -424,17 +419,22 @@ store.register_lints(&[ operators::ABSURD_EXTREME_COMPARISONS, operators::ASSIGN_OP_PATTERN, operators::BAD_BIT_MASK, + operators::CMP_NAN, + operators::CMP_OWNED, operators::DOUBLE_COMPARISONS, operators::DURATION_SUBSEC, operators::EQ_OP, operators::ERASING_OP, operators::FLOAT_ARITHMETIC, + operators::FLOAT_CMP, + operators::FLOAT_CMP_CONST, operators::FLOAT_EQUALITY_WITHOUT_ABS, operators::IDENTITY_OP, operators::INEFFECTIVE_BIT_MASK, operators::INTEGER_ARITHMETIC, operators::INTEGER_DIVISION, operators::MISREFACTORED_ASSIGN_OP, + operators::MODULO_ONE, operators::OP_REF, operators::VERBOSE_BIT_MASK, option_env_unwrap::OPTION_ENV_UNWRAP, diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index b1603afa25f..0c02c2bef56 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -64,7 +64,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(methods::INEFFICIENT_TO_STRING), LintId::of(methods::MAP_UNWRAP_OR), LintId::of(methods::UNNECESSARY_JOIN), - LintId::of(misc::FLOAT_CMP), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER), LintId::of(mut_mut::MUT_MUT), @@ -75,6 +74,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING), LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES), LintId::of(non_expressive_names::SIMILAR_NAMES), + LintId::of(operators::FLOAT_CMP), LintId::of(operators::VERBOSE_BIT_MASK), LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 2233b118849..6bf519c24e8 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -22,7 +22,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(methods::OR_FUN_CALL), LintId::of(methods::SINGLE_CHAR_PATTERN), LintId::of(methods::UNNECESSARY_TO_OWNED), - LintId::of(misc::CMP_OWNED), + LintId::of(operators::CMP_OWNED), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), LintId::of(types::BOX_COLLECTION), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 53d968c4710..e66dc67f3dc 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -38,7 +38,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(methods::FILETYPE_IS_FILE), LintId::of(methods::GET_UNWRAP), LintId::of(methods::UNWRAP_USED), - LintId::of(misc::FLOAT_CMP_CONST), LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX), LintId::of(misc_early::UNNEEDED_FIELD_PATTERN), LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX), @@ -50,6 +49,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(module_style::SELF_NAMED_MODULE_FILES), LintId::of(modulo_arithmetic::MODULO_ARITHMETIC), LintId::of(operators::FLOAT_ARITHMETIC), + LintId::of(operators::FLOAT_CMP_CONST), LintId::of(operators::INTEGER_ARITHMETIC), LintId::of(operators::INTEGER_DIVISION), LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN), diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 01bf871198a..df2430ced6b 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -1,28 +1,21 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_opt}; -use clippy_utils::ty::{implements_trait, is_copy}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt, - StmtKind, TyKind, UnOp, + StmtKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::{ExpnKind, Span}; -use rustc_span::symbol::sym; -use clippy_utils::consts::{constant, Constant}; use clippy_utils::sugg::Sugg; -use clippy_utils::{ - get_item_name, get_parent_expr, in_constant, is_integer_const, iter_input_pats, last_path_segment, - match_any_def_paths, path_def_id, paths, unsext, SpanlessEq, -}; +use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq}; declare_clippy_lint! { /// ### What it does @@ -58,122 +51,6 @@ declare_clippy_lint! { style, "an entire binding declared as `ref`, in a function argument or a `let` statement" } - -declare_clippy_lint! { - /// ### What it does - /// Checks for comparisons to NaN. - /// - /// ### Why is this bad? - /// NaN does not compare meaningfully to anything – not - /// even itself – so those comparisons are simply wrong. - /// - /// ### Example - /// ```rust - /// # let x = 1.0; - /// if x == f32::NAN { } - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = 1.0f32; - /// if x.is_nan() { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_NAN, - correctness, - "comparisons to `NAN`, which will always return false, probably not intended" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for (in-)equality comparisons on floating-point - /// values (apart from zero), except in functions called `*eq*` (which probably - /// implement equality for a type involving floats). - /// - /// ### Why is this bad? - /// Floating point calculations are usually imprecise, so - /// asking if two values are *exactly* equal is asking for trouble. For a good - /// guide on what to do, see [the floating point - /// guide](http://www.floating-point-gui.de/errors/comparison). - /// - /// ### Example - /// ```rust - /// let x = 1.2331f64; - /// let y = 1.2332f64; - /// - /// if y == 1.23f64 { } - /// if y != x {} // where both are floats - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = 1.2331f64; - /// # let y = 1.2332f64; - /// let error_margin = f64::EPSILON; // Use an epsilon for comparison - /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. - /// // let error_margin = std::f64::EPSILON; - /// if (y - 1.23f64).abs() < error_margin { } - /// if (y - x).abs() > error_margin { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FLOAT_CMP, - pedantic, - "using `==` or `!=` on float values instead of comparing difference with an epsilon" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for conversions to owned values just for the sake - /// of a comparison. - /// - /// ### Why is this bad? - /// The comparison can operate on a reference, so creating - /// an owned value effectively throws it away directly afterwards, which is - /// needlessly consuming code and heap space. - /// - /// ### Example - /// ```rust - /// # let x = "foo"; - /// # let y = String::from("foo"); - /// if x.to_owned() == y {} - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = "foo"; - /// # let y = String::from("foo"); - /// if x == y {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_OWNED, - perf, - "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for getting the remainder of a division by one or minus - /// one. - /// - /// ### Why is this bad? - /// The result for a divisor of one can only ever be zero; for - /// minus one it can cause panic/overflow (if the left operand is the minimal value of - /// the respective integer type) or results in zero. No one will write such code - /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that - /// contest, it's probably a bad idea. Use something more underhanded. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// let a = x % 1; - /// let a = x % -1; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MODULO_ONE, - correctness, - "taking a number modulo +/-1, which can either panic/overflow or always returns 0" -} - declare_clippy_lint! { /// ### What it does /// Checks for the use of bindings with a single leading @@ -244,51 +121,11 @@ declare_clippy_lint! { "using `0 as *{const, mut} T`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for (in-)equality comparisons on floating-point - /// value and constant, except in functions called `*eq*` (which probably - /// implement equality for a type involving floats). - /// - /// ### Why is this bad? - /// Floating point calculations are usually imprecise, so - /// asking if two values are *exactly* equal is asking for trouble. For a good - /// guide on what to do, see [the floating point - /// guide](http://www.floating-point-gui.de/errors/comparison). - /// - /// ### Example - /// ```rust - /// let x: f64 = 1.0; - /// const ONE: f64 = 1.00; - /// - /// if x == ONE { } // where both are floats - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x: f64 = 1.0; - /// # const ONE: f64 = 1.00; - /// let error_margin = f64::EPSILON; // Use an epsilon for comparison - /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. - /// // let error_margin = std::f64::EPSILON; - /// if (x - ONE).abs() < error_margin { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FLOAT_CMP_CONST, - restriction, - "using `==` or `!=` on float constants instead of comparing difference with an epsilon" -} - declare_lint_pass!(MiscLints => [ TOPLEVEL_REF_ARG, - CMP_NAN, - FLOAT_CMP, - CMP_OWNED, - MODULO_ONE, USED_UNDERSCORE_BINDING, SHORT_CIRCUIT_STATEMENT, ZERO_PTR, - FLOAT_CMP_CONST ]); impl<'tcx> LateLintPass<'tcx> for MiscLints { @@ -398,16 +235,9 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - match expr.kind { - ExprKind::Cast(e, ty) => { - check_cast(cx, expr.span, e, ty); - return; - }, - ExprKind::Binary(ref cmp, left, right) => { - check_binary(cx, expr, cmp, left, right); - return; - }, - _ => {}, + if let ExprKind::Cast(e, ty) = expr.kind { + check_cast(cx, expr.span, e, ty); + return; } if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) { // Don't lint things expanded by #[derive(...)], etc or `await` desugaring @@ -455,236 +285,6 @@ impl<'tcx> LateLintPass<'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); - if let Some((value, _)) = constant(cx, cx.typeck_results(), expr); - if match value { - Constant::F32(num) => num.is_nan(), - Constant::F64(num) => num.is_nan(), - _ => false, - }; - then { - span_lint( - cx, - CMP_NAN, - cmp_expr.span, - "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead", - ); - } - } -} - -fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) { - res - } else { - false - } -} - -fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - match constant(cx, cx.typeck_results(), 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, - } -} - -// 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_chain! { - if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind; - if sym!(signum) == method_name.ident.name; - // Check that the receiver of the signum() is a float (expressions[0] is the receiver of - // the method call) - then { - 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(_, _)) -} - -#[expect(clippy::too_many_lines)] -fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) { - #[derive(Default)] - struct EqImpl { - ty_eq_other: bool, - other_eq_ty: bool, - } - - impl EqImpl { - fn is_implemented(&self) -> bool { - self.ty_eq_other || self.other_eq_ty - } - } - - fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> { - cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl { - ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]), - other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]), - }) - } - - let typeck = cx.typeck_results(); - let (arg, arg_span) = match expr.kind { - ExprKind::MethodCall(.., [arg], _) - if typeck - .type_dependent_def_id(expr.hir_id) - .and_then(|id| cx.tcx.trait_of_item(id)) - .map_or(false, |id| { - matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned)) - }) => - { - (arg, arg.span) - }, - ExprKind::Call(path, [arg]) - if path_def_id(cx, path) - .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM])) - .map_or(false, |idx| match idx { - 0 => true, - 1 => !is_copy(cx, typeck.expr_ty(expr)), - _ => false, - }) => - { - (arg, arg.span) - }, - _ => return, - }; - - let arg_ty = typeck.expr_ty(arg); - let other_ty = typeck.expr_ty(other); - - let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default(); - let with_deref = arg_ty - .builtin_deref(true) - .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty)) - .unwrap_or_default(); - - if !with_deref.is_implemented() && !without_deref.is_implemented() { - return; - } - - let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _)); - - let lint_span = if other_gets_derefed { - expr.span.to(other.span) - } else { - expr.span - }; - - span_lint_and_then( - cx, - CMP_OWNED, - lint_span, - "this creates an owned instance just for comparison", - |diag| { - // This also catches `PartialEq` implementations that call `to_owned`. - if other_gets_derefed { - diag.span_label(lint_span, "try implementing the comparison without allocating"); - return; - } - - let arg_snip = snippet(cx, arg_span, ".."); - let expr_snip; - let eq_impl; - if with_deref.is_implemented() { - expr_snip = format!("*{}", arg_snip); - eq_impl = with_deref; - } else { - expr_snip = arg_snip.to_string(); - eq_impl = without_deref; - }; - - let span; - let hint; - if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { - span = expr.span; - hint = expr_snip; - } else { - span = expr.span.to(other.span); - - let cmp_span = if other.span < expr.span { - other.span.between(expr.span) - } else { - expr.span.between(other.span) - }; - if eq_impl.ty_eq_other { - hint = format!( - "{}{}{}", - expr_snip, - snippet(cx, cmp_span, ".."), - snippet(cx, other.span, "..") - ); - } else { - hint = format!( - "{}{}{}", - snippet(cx, other.span, ".."), - snippet(cx, cmp_span, ".."), - expr_snip - ); - } - } - - diag.span_suggestion( - span, - "try", - hint, - Applicability::MachineApplicable, // snippet - ); - }, - ); -} - /// Heuristic to see if an expression is used. Should be compatible with /// `unused_variables`'s idea /// of what it means for an expression to be "used". @@ -740,74 +340,3 @@ fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) } } } - -fn check_binary<'a>( - cx: &LateContext<'a>, - expr: &Expr<'_>, - cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>, - left: &'a Expr<'_>, - right: &'a Expr<'_>, -) { - let op = cmp.node; - if op.is_comparison() { - check_nan(cx, left, expr); - check_nan(cx, right, expr); - check_to_owned(cx, left, right, true); - check_to_owned(cx, right, left, false); - } - if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) { - if is_allowed(cx, left) || is_allowed(cx, right) { - return; - } - - // Allow comparing the results of signum() - if is_signum(cx, left) && is_signum(cx, right) { - return; - } - - if let Some(name) = get_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( - is_named_constant(cx, left) || is_named_constant(cx, right), - 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 - ); - } - diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`"); - }); - } else if op == BinOpKind::Rem { - if is_integer_const(cx, right, 1) { - span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); - } - - if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() { - if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) { - span_lint( - cx, - MODULO_ONE, - expr.span, - "any number modulo -1 will panic/overflow or result in 0", - ); - } - }; - } -} diff --git a/clippy_lints/src/operators/cmp_nan.rs b/clippy_lints/src/operators/cmp_nan.rs new file mode 100644 index 00000000000..786ae1552ad --- /dev/null +++ b/clippy_lints/src/operators/cmp_nan.rs @@ -0,0 +1,30 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::in_constant; +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; + +use super::CMP_NAN; + +pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) { + if op.is_comparison() && !in_constant(cx, e.hir_id) && (is_nan(cx, lhs) || is_nan(cx, rhs)) { + span_lint( + cx, + CMP_NAN, + e.span, + "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead", + ); + } +} + +fn is_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + if let Some((value, _)) = constant(cx, cx.typeck_results(), e) { + match value { + Constant::F32(num) => num.is_nan(), + Constant::F64(num) => num.is_nan(), + _ => false, + } + } else { + false + } +} diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs new file mode 100644 index 00000000000..e1f9b5906f6 --- /dev/null +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -0,0 +1,147 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use clippy_utils::ty::{implements_trait, is_copy}; +use clippy_utils::{match_any_def_paths, path_def_id, paths}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::symbol::sym; + +use super::CMP_OWNED; + +pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) { + if op.is_comparison() { + check_op(cx, lhs, rhs, true); + check_op(cx, rhs, lhs, false); + } +} + +#[derive(Default)] +struct EqImpl { + ty_eq_other: bool, + other_eq_ty: bool, +} +impl EqImpl { + fn is_implemented(&self) -> bool { + self.ty_eq_other || self.other_eq_ty + } +} + +fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> { + cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl { + ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]), + other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]), + }) +} + +fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) { + let typeck = cx.typeck_results(); + let (arg, arg_span) = match expr.kind { + ExprKind::MethodCall(.., [arg], _) + if typeck + .type_dependent_def_id(expr.hir_id) + .and_then(|id| cx.tcx.trait_of_item(id)) + .map_or(false, |id| { + matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned)) + }) => + { + (arg, arg.span) + }, + ExprKind::Call(path, [arg]) + if path_def_id(cx, path) + .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM])) + .map_or(false, |idx| match idx { + 0 => true, + 1 => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, + }) => + { + (arg, arg.span) + }, + _ => return, + }; + + let arg_ty = typeck.expr_ty(arg); + let other_ty = typeck.expr_ty(other); + + let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default(); + let with_deref = arg_ty + .builtin_deref(true) + .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty)) + .unwrap_or_default(); + + if !with_deref.is_implemented() && !without_deref.is_implemented() { + return; + } + + let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _)); + + let lint_span = if other_gets_derefed { + expr.span.to(other.span) + } else { + expr.span + }; + + span_lint_and_then( + cx, + CMP_OWNED, + lint_span, + "this creates an owned instance just for comparison", + |diag| { + // This also catches `PartialEq` implementations that call `to_owned`. + if other_gets_derefed { + diag.span_label(lint_span, "try implementing the comparison without allocating"); + return; + } + + let arg_snip = snippet(cx, arg_span, ".."); + let expr_snip; + let eq_impl; + if with_deref.is_implemented() { + expr_snip = format!("*{}", arg_snip); + eq_impl = with_deref; + } else { + expr_snip = arg_snip.to_string(); + eq_impl = without_deref; + }; + + let span; + let hint; + if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { + span = expr.span; + hint = expr_snip; + } else { + span = expr.span.to(other.span); + + let cmp_span = if other.span < expr.span { + other.span.between(expr.span) + } else { + expr.span.between(other.span) + }; + if eq_impl.ty_eq_other { + hint = format!( + "{}{}{}", + expr_snip, + snippet(cx, cmp_span, ".."), + snippet(cx, other.span, "..") + ); + } else { + hint = format!( + "{}{}{}", + snippet(cx, other.span, ".."), + snippet(cx, cmp_span, ".."), + expr_snip + ); + } + } + + diag.span_suggestion( + span, + "try", + hint, + Applicability::MachineApplicable, // snippet + ); + }, + ); +} diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs new file mode 100644 index 00000000000..0ef793443ff --- /dev/null +++ b/clippy_lints/src/operators/float_cmp.rs @@ -0,0 +1,139 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_item_name; +use clippy_utils::sugg::Sugg; +use if_chain::if_chain; +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) || is_float(cx, right)) { + if is_allowed(cx, left) || is_allowed(cx, right) { + return; + } + + // Allow comparing the results of signum() + if is_signum(cx, left) && is_signum(cx, right) { + return; + } + + if let Some(name) = get_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( + is_named_constant(cx, left) || is_named_constant(cx, right), + 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 + ); + } + diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`"); + }); + } +} + +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 is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) { + res + } else { + false + } +} + +fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + match constant(cx, cx.typeck_results(), 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, + } +} + +// 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_chain! { + if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind; + if sym!(signum) == method_name.ident.name; + // Check that the receiver of the signum() is a float (expressions[0] is the receiver of + // the method call) + then { + 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(_, _)) +} diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index ea6a0083bc8..e4aff267fef 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -5,14 +5,18 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; mod absurd_extreme_comparisons; mod assign_op_pattern; mod bit_mask; +mod cmp_nan; +mod cmp_owned; mod double_comparison; mod duration_subsec; mod eq_op; mod erasing_op; +mod float_cmp; mod float_equality_without_abs; mod identity_op; mod integer_division; mod misrefactored_assign_op; +mod modulo_one; mod numeric_arithmetic; mod op_ref; mod verbose_bit_mask; @@ -464,6 +468,156 @@ declare_clippy_lint! { "integer division may cause loss of precision" } +declare_clippy_lint! { + /// ### What it does + /// Checks for comparisons to NaN. + /// + /// ### Why is this bad? + /// NaN does not compare meaningfully to anything – not + /// even itself – so those comparisons are simply wrong. + /// + /// ### Example + /// ```rust + /// # let x = 1.0; + /// if x == f32::NAN { } + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = 1.0f32; + /// if x.is_nan() { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_NAN, + correctness, + "comparisons to `NAN`, which will always return false, probably not intended" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for conversions to owned values just for the sake + /// of a comparison. + /// + /// ### Why is this bad? + /// The comparison can operate on a reference, so creating + /// an owned value effectively throws it away directly afterwards, which is + /// needlessly consuming code and heap space. + /// + /// ### Example + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x.to_owned() == y {} + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x == y {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_OWNED, + perf, + "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for (in-)equality comparisons on floating-point + /// values (apart from zero), except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// ### Why is this bad? + /// Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// ### Example + /// ```rust + /// let x = 1.2331f64; + /// let y = 1.2332f64; + /// + /// if y == 1.23f64 { } + /// if y != x {} // where both are floats + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = 1.2331f64; + /// # let y = 1.2332f64; + /// let error_margin = f64::EPSILON; // Use an epsilon for comparison + /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. + /// // let error_margin = std::f64::EPSILON; + /// if (y - 1.23f64).abs() < error_margin { } + /// if (y - x).abs() > error_margin { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub FLOAT_CMP, + pedantic, + "using `==` or `!=` on float values instead of comparing difference with an epsilon" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for (in-)equality comparisons on floating-point + /// value and constant, except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// ### Why is this bad? + /// Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// ### Example + /// ```rust + /// let x: f64 = 1.0; + /// const ONE: f64 = 1.00; + /// + /// if x == ONE { } // where both are floats + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x: f64 = 1.0; + /// # const ONE: f64 = 1.00; + /// let error_margin = f64::EPSILON; // Use an epsilon for comparison + /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. + /// // let error_margin = std::f64::EPSILON; + /// if (x - ONE).abs() < error_margin { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub FLOAT_CMP_CONST, + restriction, + "using `==` or `!=` on float constants instead of comparing difference with an epsilon" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for getting the remainder of a division by one or minus + /// one. + /// + /// ### Why is this bad? + /// The result for a divisor of one can only ever be zero; for + /// minus one it can cause panic/overflow (if the left operand is the minimal value of + /// the respective integer type) or results in zero. No one will write such code + /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that + /// contest, it's probably a bad idea. Use something more underhanded. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// let a = x % 1; + /// let a = x % -1; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MODULO_ONE, + correctness, + "taking a number modulo +/-1, which can either panic/overflow or always returns 0" +} + pub struct Operators { arithmetic_context: numeric_arithmetic::Context, verbose_bit_mask_threshold: u64, @@ -485,6 +639,11 @@ impl_lint_pass!(Operators => [ FLOAT_EQUALITY_WITHOUT_ABS, IDENTITY_OP, INTEGER_DIVISION, + CMP_NAN, + CMP_OWNED, + FLOAT_CMP, + FLOAT_CMP_CONST, + MODULO_ONE, ]); impl Operators { pub fn new(verbose_bit_mask_threshold: u64) -> Self { @@ -515,6 +674,10 @@ impl<'tcx> LateLintPass<'tcx> for Operators { duration_subsec::check(cx, e, op.node, lhs, rhs); float_equality_without_abs::check(cx, e, op.node, lhs, rhs); integer_division::check(cx, e, op.node, lhs, rhs); + cmp_nan::check(cx, e, op.node, lhs, rhs); + cmp_owned::check(cx, op.node, lhs, rhs); + float_cmp::check(cx, e, op.node, lhs, rhs); + modulo_one::check(cx, e, op.node, rhs); }, ExprKind::AssignOp(op, lhs, rhs) => { self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); diff --git a/clippy_lints/src/operators/modulo_one.rs b/clippy_lints/src/operators/modulo_one.rs new file mode 100644 index 00000000000..54eea14833f --- /dev/null +++ b/clippy_lints/src/operators/modulo_one.rs @@ -0,0 +1,26 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::{is_integer_const, unsext}; +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MODULO_ONE; + +pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, right: &Expr<'_>) { + if op == BinOpKind::Rem { + if is_integer_const(cx, right, 1) { + span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); + } + + if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() { + if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) { + span_lint( + cx, + MODULO_ONE, + expr.span, + "any number modulo -1 will panic/overflow or result in 0", + ); + } + }; + } +} |
