@@ -85,6 +85,7 @@ mod needless_option_as_deref;
8585mod needless_option_take;
8686mod new_ret_no_self;
8787mod no_effect_replace;
88+ mod non_canonical_float_cmp;
8889mod obfuscated_if_else;
8990mod ok_expect;
9091mod open_options;
@@ -2708,6 +2709,72 @@ declare_clippy_lint! {
27082709 "replace with no effect"
27092710}
27102711
2712+ declare_clippy_lint ! {
2713+ /// ### What it does
2714+ ///
2715+ /// Checks for manual attempts to force a total ordering on floating-point numbers by
2716+ /// using `.partial_cmp(..).unwrap_or(Ordering)`.
2717+ ///
2718+ /// ### Why is this bad?
2719+ ///
2720+ /// Floating-point numbers do not have a total order by default because of `NaN` values
2721+ /// and the existence of both `-0.0` and `0.0`. When developers use `.unwrap_or(..)`
2722+ /// with `partial_cmp`, they are usually trying to implement a total order manually,
2723+ /// likely because they do not know about the existence of `total_cmp`.
2724+ ///
2725+ /// This is problematic because:
2726+ ///
2727+ /// - Inconsistency: Manual unwrapping often fails to handle the nuances of IEEE 754
2728+ /// total ordering (e.g., the sign of `NaN` or the difference between `-0.0` and `0.0`).
2729+ ///
2730+ /// - Broken invariants: In contexts like sorting or searching (e.g., `BTreeMap`),
2731+ /// an inconsistent ordering function can lead to non-deterministic results,
2732+ /// infinite loops, or panics.
2733+ ///
2734+ /// - Readability: `total_cmp` is the canonical, standard library method for this
2735+ /// exact purpose, making the code more idiomatic and making the intent clear.
2736+ ///
2737+ /// While sorting is the most common place this pattern appears, it also affects
2738+ /// any logic relying on a stable comparison, such as finding a minimum/maximum
2739+ /// value in a collection.
2740+ ///
2741+ /// ### Known problems
2742+ ///
2743+ /// - Performance: On some architectures (like x86), `total_cmp` *may* be
2744+ /// slightly slower than hardware-accelerated float comparisons if the values
2745+ /// are already in floating-point registers.
2746+ ///
2747+ /// - Semantic Change: `total_cmp` considers `-0.0` to be strictly less than `0.0`.
2748+ /// If your logic relies on them being equal (the default behavior of `==`),
2749+ /// this lint's suggestion will change your program's behavior.
2750+ ///
2751+ /// ### Example
2752+ ///
2753+ /// ```rs
2754+ /// use std::cmp;
2755+ ///
2756+ /// let x = 0.0;
2757+ /// let y = -0.0;
2758+ /// let is_less = x.partial_cmp(&y).unwrap_or(cmp::Ordering::Greater);
2759+ ///
2760+ /// vec.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal));
2761+ /// ```
2762+ ///
2763+ /// Use instead:
2764+ ///
2765+ /// ```rs
2766+ /// let x = 0.0;
2767+ /// let y = -0.0;
2768+ /// let is_less = x.total_cmp(&y);
2769+ ///
2770+ /// vec.sort_by(|a, b| a.total_cmp(b));
2771+ /// ```
2772+ #[ clippy:: version = "1.97.0" ]
2773+ pub NON_CANONICAL_TOTAL_FLOAT_CMP ,
2774+ suspicious,
2775+ "non-canonical total float comparison which is likely a mistake"
2776+ }
2777+
27112778declare_clippy_lint ! {
27122779 /// ### What it does
27132780 /// Checks for duplicate open options as well as combinations
@@ -4867,6 +4934,7 @@ impl_lint_pass!(Methods => [
48674934 NEEDLESS_SPLITN ,
48684935 NEW_RET_NO_SELF ,
48694936 NONSENSICAL_OPEN_OPTIONS ,
4937+ NON_CANONICAL_TOTAL_FLOAT_CMP ,
48704938 NO_EFFECT_REPLACE ,
48714939 OBFUSCATED_IF_ELSE ,
48724940 OK_EXPECT ,
@@ -5685,6 +5753,9 @@ impl Methods {
56855753 Some ( ( sym:: map, m_recv, [ m_arg] , span, _) ) => {
56865754 map_unwrap_or:: check ( cx, expr, m_recv, m_arg, recv, u_arg, span, self . msrv ) ;
56875755 } ,
5756+ Some ( ( sym:: partial_cmp, m_recv, [ m_arg] , _, _) ) => {
5757+ non_canonical_float_cmp:: check ( cx, expr, m_recv, m_arg, u_arg) ;
5758+ } ,
56885759 Some ( ( then_method @ ( sym:: then | sym:: then_some) , t_recv, [ t_arg] , _, _) ) => {
56895760 obfuscated_if_else:: check (
56905761 cx,
0 commit comments