Skip to content

Commit 4b4d617

Browse files
committed
Added lint to avoid negated comparisions on partially ordered types.
1 parent 1ba6587 commit 4b4d617

File tree

4 files changed

+185
-1
lines changed

4 files changed

+185
-1
lines changed

clippy_lints/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub mod needless_borrowed_ref;
169169
pub mod needless_continue;
170170
pub mod needless_pass_by_value;
171171
pub mod needless_update;
172+
pub mod neg_cmp_op_on_partial_ord;
172173
pub mod neg_multiply;
173174
pub mod new_without_default;
174175
pub mod no_effect;
@@ -419,7 +420,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
419420
reg.register_late_lint_pass(box map_unit_fn::Pass);
420421
reg.register_late_lint_pass(box infallible_destructuring_match::Pass);
421422
reg.register_late_lint_pass(box inherent_impl::Pass::default());
422-
423+
reg.register_late_lint_pass(box neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd);
423424

424425
reg.register_lint_group("clippy_restriction", vec![
425426
arithmetic::FLOAT_ARITHMETIC,
@@ -501,6 +502,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
501502
booleans::LOGIC_BUG,
502503
booleans::NONMINIMAL_BOOL,
503504
bytecount::NAIVE_BYTECOUNT,
505+
neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
504506
collapsible_if::COLLAPSIBLE_IF,
505507
const_static_lifetime::CONST_STATIC_LIFETIME,
506508
copies::IF_SAME_THEN_ELSE,
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use rustc::hir::*;
2+
use rustc::lint::*;
3+
4+
use crate::utils;
5+
6+
const ORD: [&str; 3] = ["core", "cmp", "Ord"];
7+
const PARTIAL_ORD: [&str; 3] = ["core", "cmp", "PartialOrd"];
8+
9+
/// **What it does:**
10+
/// Checks for the usage of negated comparision operators on types which only implement
11+
/// `PartialOrd` (e.g. `f64`).
12+
///
13+
/// **Why is this bad?**
14+
/// These operators make it easy to forget that the underlying types actually allow not only three
15+
/// potential Orderings (Less, Equal, Greater) but also a forth one (Uncomparable). Escpeccially if
16+
/// the operator based comparision result is negated it is easy to miss that fact.
17+
///
18+
/// **Known problems:** None.
19+
///
20+
/// **Example:**
21+
///
22+
/// ```rust
23+
/// use core::cmp::Ordering;
24+
///
25+
/// // Bad
26+
/// let a = 1.0;
27+
/// let b = std::f64::NAN;
28+
///
29+
/// let _not_less_or_equal = !(a <= b);
30+
///
31+
/// // Good
32+
/// let a = 1.0;
33+
/// let b = std::f64::NAN;
34+
///
35+
/// let _not_less_or_equal = match a.partial_cmp(&b) {
36+
/// None | Some(Ordering::Greater) => true,
37+
/// _ => false,
38+
/// };
39+
/// ```
40+
declare_lint! {
41+
pub NEG_CMP_OP_ON_PARTIAL_ORD, Warn,
42+
"The use of negated comparision operators on partially orded types may produce confusing code."
43+
}
44+
45+
pub struct NoNegCompOpForPartialOrd;
46+
47+
impl LintPass for NoNegCompOpForPartialOrd {
48+
fn get_lints(&self) -> LintArray {
49+
lint_array!(NEG_CMP_OP_ON_PARTIAL_ORD)
50+
}
51+
}
52+
53+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NoNegCompOpForPartialOrd {
54+
55+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
56+
if_chain! {
57+
58+
if let Expr_::ExprUnary(UnOp::UnNot, ref inner) = expr.node;
59+
if let Expr_::ExprBinary(ref op, ref left, _) = inner.node;
60+
if let BinOp_::BiLe | BinOp_::BiGe | BinOp_::BiLt | BinOp_::BiGt = op.node;
61+
62+
then {
63+
64+
let ty = cx.tables.expr_ty(left);
65+
66+
let implements_ord = {
67+
if let Some(id) = utils::get_trait_def_id(cx, &ORD) {
68+
utils::implements_trait(cx, ty, id, &[])
69+
} else {
70+
return;
71+
}
72+
};
73+
74+
let implements_partial_ord = {
75+
if let Some(id) = utils::get_trait_def_id(cx, &PARTIAL_ORD) {
76+
utils::implements_trait(cx, ty, id, &[])
77+
} else {
78+
return;
79+
}
80+
};
81+
82+
if implements_partial_ord && !implements_ord {
83+
cx.span_lint(
84+
NEG_CMP_OP_ON_PARTIAL_ORD,
85+
expr.span,
86+
"The use of negated comparision operators on partially orded\
87+
types produces code that is hard to read and refactor. Please\
88+
consider to use the partial_cmp() instead, to make it clear\
89+
that the two values could be incomparable."
90+
)
91+
}
92+
}
93+
}
94+
}
95+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/// This test case utilizes `f64` an easy example for `PartialOrd` only types
2+
/// but the lint itself actually validates any expression where the left
3+
/// operand implements `PartialOrd` but not `Ord`.
4+
5+
use std::cmp::Ordering;
6+
7+
#[allow(nonminimal_bool)]
8+
#[warn(neg_cmp_op_on_partial_ord)]
9+
fn main() {
10+
11+
let a_value = 1.0;
12+
let another_value = 7.0;
13+
14+
// --- Bad ---
15+
16+
17+
// Not Less but potentially Greater, Equal or Uncomparable.
18+
let _not_less = !(a_value < another_value);
19+
20+
// Not Less or Equal but potentially Greater or Uncomparable.
21+
let _not_less_or_equal = !(a_value <= another_value);
22+
23+
// Not Greater but potentially Less, Equal or Uncomparable.
24+
let _not_greater = !(a_value > another_value);
25+
26+
// Not Greater or Equal but potentially Less or Uncomparable.
27+
let _not_greater_or_equal = !(a_value >= another_value);
28+
29+
30+
// --- Good ---
31+
32+
33+
let _not_less = match a_value.partial_cmp(&another_value) {
34+
None | Some(Ordering::Greater) | Some(Ordering::Equal) => true,
35+
_ => false,
36+
};
37+
let _not_less_or_equal = match a_value.partial_cmp(&another_value) {
38+
None | Some(Ordering::Greater) => true,
39+
_ => false,
40+
};
41+
let _not_greater = match a_value.partial_cmp(&another_value) {
42+
None | Some(Ordering::Less) | Some(Ordering::Equal) => true,
43+
_ => false,
44+
};
45+
let _not_greater_or_equal = match a_value.partial_cmp(&another_value) {
46+
None | Some(Ordering::Less) => true,
47+
_ => false,
48+
};
49+
50+
51+
// --- Should not trigger ---
52+
53+
54+
let _ = a_value < another_value;
55+
let _ = a_value <= another_value;
56+
let _ = a_value > another_value;
57+
let _ = a_value >= another_value;
58+
}
59+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: The use of negated comparision operators on partially ordedtypes produces code that is hard to read and refactor. Pleaseconsider to use the partial_cmp() instead, to make it clearthat the two values could be incomparable.
2+
--> $DIR/neg_cmp_op_on_partial_ord.rs:18:21
3+
|
4+
18 | let _not_less = !(a_value < another_value);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D neg-cmp-op-on-partial-ord` implied by `-D warnings`
8+
9+
error: The use of negated comparision operators on partially ordedtypes produces code that is hard to read and refactor. Pleaseconsider to use the partial_cmp() instead, to make it clearthat the two values could be incomparable.
10+
--> $DIR/neg_cmp_op_on_partial_ord.rs:21:30
11+
|
12+
21 | let _not_less_or_equal = !(a_value <= another_value);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: The use of negated comparision operators on partially ordedtypes produces code that is hard to read and refactor. Pleaseconsider to use the partial_cmp() instead, to make it clearthat the two values could be incomparable.
16+
--> $DIR/neg_cmp_op_on_partial_ord.rs:24:24
17+
|
18+
24 | let _not_greater = !(a_value > another_value);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
error: The use of negated comparision operators on partially ordedtypes produces code that is hard to read and refactor. Pleaseconsider to use the partial_cmp() instead, to make it clearthat the two values could be incomparable.
22+
--> $DIR/neg_cmp_op_on_partial_ord.rs:27:33
23+
|
24+
27 | let _not_greater_or_equal = !(a_value >= another_value);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)