Skip to content

Commit 3d7cdd4

Browse files
authored
Merge pull request #2827 from 0ndorio/lint/cmp_operators_on_partial_cmp
Added lint to avoid negated comparisions on partially ordered types. (fixes #2626)
2 parents c6d53ad + 28f735b commit 3d7cdd4

File tree

7 files changed

+212
-2
lines changed

7 files changed

+212
-2
lines changed

clippy_lints/src/booleans.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc::hir::intravisit::*;
44
use syntax::ast::{LitKind, NodeId, DUMMY_NODE_ID};
55
use syntax::codemap::{dummy_spanned, Span, DUMMY_SP};
66
use syntax::util::ThinVec;
7-
use crate::utils::{in_macro, paths, match_type, snippet_opt, span_lint_and_then, SpanlessEq};
7+
use crate::utils::{in_macro, paths, match_type, snippet_opt, span_lint_and_then, SpanlessEq, get_trait_def_id, implements_trait};
88

99
/// **What it does:** Checks for boolean expressions that can be written more
1010
/// concisely.
@@ -122,6 +122,11 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
122122
}
123123
let negated = match e.node {
124124
ExprBinary(binop, ref lhs, ref rhs) => {
125+
126+
if !implements_ord(self.cx, lhs) {
127+
continue;
128+
}
129+
125130
let mk_expr = |op| {
126131
Expr {
127132
id: DUMMY_NODE_ID,
@@ -174,6 +179,11 @@ impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
174179
fn simplify_not(&self, expr: &Expr) -> Option<String> {
175180
match expr.node {
176181
ExprBinary(binop, ref lhs, ref rhs) => {
182+
183+
if !implements_ord(self.cx, lhs) {
184+
return None;
185+
}
186+
177187
match binop.node {
178188
BiEq => Some(" != "),
179189
BiNe => Some(" == "),
@@ -444,3 +454,10 @@ impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
444454
NestedVisitorMap::None
445455
}
446456
}
457+
458+
459+
fn implements_ord<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, expr: &Expr) -> bool {
460+
let ty = cx.tables.expr_ty(expr);
461+
get_trait_def_id(cx, &paths::ORD)
462+
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
463+
}

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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use rustc::hir::*;
2+
use rustc::lint::*;
3+
4+
use crate::utils::{self, paths};
5+
6+
/// **What it does:**
7+
/// Checks for the usage of negated comparision operators on types which only implement
8+
/// `PartialOrd` (e.g. `f64`).
9+
///
10+
/// **Why is this bad?**
11+
/// These operators make it easy to forget that the underlying types actually allow not only three
12+
/// potential Orderings (Less, Equal, Greater) but also a forth one (Uncomparable). Escpeccially if
13+
/// the operator based comparision result is negated it is easy to miss that fact.
14+
///
15+
/// **Known problems:** None.
16+
///
17+
/// **Example:**
18+
///
19+
/// ```rust
20+
/// use core::cmp::Ordering;
21+
///
22+
/// // Bad
23+
/// let a = 1.0;
24+
/// let b = std::f64::NAN;
25+
///
26+
/// let _not_less_or_equal = !(a <= b);
27+
///
28+
/// // Good
29+
/// let a = 1.0;
30+
/// let b = std::f64::NAN;
31+
///
32+
/// let _not_less_or_equal = match a.partial_cmp(&b) {
33+
/// None | Some(Ordering::Greater) => true,
34+
/// _ => false,
35+
/// };
36+
/// ```
37+
declare_clippy_lint! {
38+
pub NEG_CMP_OP_ON_PARTIAL_ORD,
39+
complexity,
40+
"The use of negated comparision operators on partially orded types may produce confusing code."
41+
}
42+
43+
pub struct NoNegCompOpForPartialOrd;
44+
45+
impl LintPass for NoNegCompOpForPartialOrd {
46+
fn get_lints(&self) -> LintArray {
47+
lint_array!(NEG_CMP_OP_ON_PARTIAL_ORD)
48+
}
49+
}
50+
51+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NoNegCompOpForPartialOrd {
52+
53+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
54+
if_chain! {
55+
56+
if let Expr_::ExprUnary(UnOp::UnNot, ref inner) = expr.node;
57+
if let Expr_::ExprBinary(ref op, ref left, _) = inner.node;
58+
if let BinOp_::BiLe | BinOp_::BiGe | BinOp_::BiLt | BinOp_::BiGt = op.node;
59+
60+
then {
61+
62+
let ty = cx.tables.expr_ty(left);
63+
64+
let implements_ord = {
65+
if let Some(id) = utils::get_trait_def_id(cx, &paths::ORD) {
66+
utils::implements_trait(cx, ty, id, &[])
67+
} else {
68+
return;
69+
}
70+
};
71+
72+
let implements_partial_ord = {
73+
if let Some(id) = utils::get_trait_def_id(cx, &paths::PARTIAL_ORD) {
74+
utils::implements_trait(cx, ty, id, &[])
75+
} else {
76+
return;
77+
}
78+
};
79+
80+
if implements_partial_ord && !implements_ord {
81+
cx.span_lint(
82+
NEG_CMP_OP_ON_PARTIAL_ORD,
83+
expr.span,
84+
"The use of negated comparision operators on partially orded \
85+
types produces code that is hard to read and refactor. Please \
86+
consider to use the `partial_cmp` instead, to make it clear \
87+
that the two values could be incomparable."
88+
)
89+
}
90+
}
91+
}
92+
}
93+
}

clippy_lints/src/utils/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
5757
pub const OPTION: [&str; 3] = ["core", "option", "Option"];
5858
pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
5959
pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
60+
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
61+
pub const PARTIAL_ORD: [&str; 3] = ["core", "cmp", "PartialOrd"];
6062
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
6163
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
6264
pub const RANGE: [&str; 3] = ["core", "ops", "Range"];

tests/ui/booleans.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,13 @@ fn warn_for_built_in_methods_with_negation() {
114114
if !res.is_some() { }
115115
if !res.is_none() { }
116116
}
117+
118+
#[allow(neg_cmp_op_on_partial_ord)]
119+
fn dont_warn_for_negated_partial_ord_comparision() {
120+
let a: f64 = unimplemented!();
121+
let b: f64 = unimplemented!();
122+
let _ = !(a < b);
123+
let _ = !(a <= b);
124+
let _ = !(a > b);
125+
let _ = !(a >= b);
126+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
#[warn(neg_cmp_op_on_partial_ord)]
8+
fn main() {
9+
10+
let a_value = 1.0;
11+
let another_value = 7.0;
12+
13+
// --- Bad ---
14+
15+
16+
// Not Less but potentially Greater, Equal or Uncomparable.
17+
let _not_less = !(a_value < another_value);
18+
19+
// Not Less or Equal but potentially Greater or Uncomparable.
20+
let _not_less_or_equal = !(a_value <= another_value);
21+
22+
// Not Greater but potentially Less, Equal or Uncomparable.
23+
let _not_greater = !(a_value > another_value);
24+
25+
// Not Greater or Equal but potentially Less or Uncomparable.
26+
let _not_greater_or_equal = !(a_value >= another_value);
27+
28+
29+
// --- Good ---
30+
31+
32+
let _not_less = match a_value.partial_cmp(&another_value) {
33+
None | Some(Ordering::Greater) | Some(Ordering::Equal) => true,
34+
_ => false,
35+
};
36+
let _not_less_or_equal = match a_value.partial_cmp(&another_value) {
37+
None | Some(Ordering::Greater) => true,
38+
_ => false,
39+
};
40+
let _not_greater = match a_value.partial_cmp(&another_value) {
41+
None | Some(Ordering::Less) | Some(Ordering::Equal) => true,
42+
_ => false,
43+
};
44+
let _not_greater_or_equal = match a_value.partial_cmp(&another_value) {
45+
None | Some(Ordering::Less) => true,
46+
_ => false,
47+
};
48+
49+
50+
// --- Should not trigger ---
51+
52+
53+
let _ = a_value < another_value;
54+
let _ = a_value <= another_value;
55+
let _ = a_value > another_value;
56+
let _ = a_value >= another_value;
57+
}
58+
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 orded types produces code that is hard to read and refactor. Please consider to use the `partial_cmp` instead, to make it clear that the two values could be incomparable.
2+
--> $DIR/neg_cmp_op_on_partial_ord.rs:17:21
3+
|
4+
17 | 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 orded types produces code that is hard to read and refactor. Please consider to use the `partial_cmp` instead, to make it clear that the two values could be incomparable.
10+
--> $DIR/neg_cmp_op_on_partial_ord.rs:20:30
11+
|
12+
20 | let _not_less_or_equal = !(a_value <= another_value);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: The use of negated comparision operators on partially orded types produces code that is hard to read and refactor. Please consider to use the `partial_cmp` instead, to make it clear that the two values could be incomparable.
16+
--> $DIR/neg_cmp_op_on_partial_ord.rs:23:24
17+
|
18+
23 | let _not_greater = !(a_value > another_value);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
error: The use of negated comparision operators on partially orded types produces code that is hard to read and refactor. Please consider to use the `partial_cmp` instead, to make it clear that the two values could be incomparable.
22+
--> $DIR/neg_cmp_op_on_partial_ord.rs:26:33
23+
|
24+
26 | 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)