Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add derive_ord_xor_partial_ord lint #5848

Merged
merged 12 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,7 @@ Released 2018-09-13
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
Expand Down
116 changes: 114 additions & 2 deletions clippy_lints/src/derive.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::utils::paths;
use crate::utils::{
is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note, span_lint_and_then,
get_trait_def_id, is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note,
span_lint_and_then,
};
use if_chain::if_chain;
use rustc_hir::def_id::DefId;
Expand Down Expand Up @@ -43,6 +44,57 @@ declare_clippy_lint! {
"deriving `Hash` but implementing `PartialEq` explicitly"
}

declare_clippy_lint! {
/// **What it does:** Checks for deriving `Ord` but implementing `PartialOrd`
/// explicitly or vice versa.
///
/// **Why is this bad?** The implementation of these traits must agree (for
/// example for use with `sort`) so it’s probably a bad idea to use a
/// default-generated `Ord` implementation with an explicitly defined
/// `PartialOrd`. In particular, the following must hold for any type
/// implementing `Ord`:
///
/// ```text
/// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
/// ```
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust,ignore
/// #[derive(Ord, PartialEq, Eq)]
/// struct Foo;
///
/// impl PartialOrd for Foo {
/// ...
/// }
/// ```
/// Use instead:
/// ```rust,ignore
/// #[derive(PartialEq, Eq)]
/// struct Foo;
///
/// impl PartialOrd for Foo {
/// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
/// Some(self.cmp(other))
/// }
/// }
///
/// impl Ord for Foo {
/// ...
/// }
/// ```
/// or, if you don't need a custom ordering:
/// ```rust,ignore
/// #[derive(Ord, PartialOrd, PartialEq, Eq)]
/// struct Foo;
/// ```
pub DERIVE_ORD_XOR_PARTIAL_ORD,
correctness,
"deriving `Ord` but implementing `PartialOrd` explicitly"
}

declare_clippy_lint! {
/// **What it does:** Checks for explicit `Clone` implementations for `Copy`
/// types.
Expand Down Expand Up @@ -103,7 +155,12 @@ declare_clippy_lint! {
"deriving `serde::Deserialize` on a type that has methods using `unsafe`"
}

declare_lint_pass!(Derive => [EXPL_IMPL_CLONE_ON_COPY, DERIVE_HASH_XOR_EQ, UNSAFE_DERIVE_DESERIALIZE]);
declare_lint_pass!(Derive => [
EXPL_IMPL_CLONE_ON_COPY,
DERIVE_HASH_XOR_EQ,
DERIVE_ORD_XOR_PARTIAL_ORD,
UNSAFE_DERIVE_DESERIALIZE
]);

impl<'tcx> LateLintPass<'tcx> for Derive {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
Expand All @@ -116,6 +173,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
let is_automatically_derived = is_automatically_derived(&*item.attrs);

check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);

if is_automatically_derived {
check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
Expand Down Expand Up @@ -180,6 +238,60 @@ fn check_hash_peq<'tcx>(
}
}

/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
fn check_ord_partial_ord<'tcx>(
cx: &LateContext<'tcx>,
span: Span,
trait_ref: &TraitRef<'_>,
ty: Ty<'tcx>,
ord_is_automatically_derived: bool,
) {
if_chain! {
if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD);
if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
if let Some(def_id) = &trait_ref.trait_def_id();
if *def_id == ord_trait_def_id;
then {
// Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));

if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
}

let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");

// Only care about `impl PartialOrd<Foo> for Foo`
// For `impl PartialOrd<B> for A, input_types is [A, B]
if trait_ref.substs.type_at(1) == ty {
let mess = if partial_ord_is_automatically_derived {
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
} else {
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
};

span_lint_and_then(
cx,
DERIVE_ORD_XOR_PARTIAL_ORD,
span,
mess,
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().as_local_hir_id(local_def_id);
diag.span_note(
cx.tcx.hir().span(hir_id),
"`PartialOrd` implemented here"
);
}
}
);
}
});
}
}
}

/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
if match_path(&trait_ref.path, &paths::CLONE_TRAIT) {
Expand Down
3 changes: 3 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&default_trait_access::DEFAULT_TRAIT_ACCESS,
&dereference::EXPLICIT_DEREF_METHODS,
&derive::DERIVE_HASH_XOR_EQ,
&derive::DERIVE_ORD_XOR_PARTIAL_ORD,
&derive::EXPL_IMPL_CLONE_ON_COPY,
&derive::UNSAFE_DERIVE_DESERIALIZE,
&doc::DOC_MARKDOWN,
Expand Down Expand Up @@ -1230,6 +1231,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&copies::IFS_SAME_COND),
LintId::of(&copies::IF_SAME_THEN_ELSE),
LintId::of(&derive::DERIVE_HASH_XOR_EQ),
LintId::of(&derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(&doc::MISSING_SAFETY_DOC),
LintId::of(&doc::NEEDLESS_DOCTEST_MAIN),
LintId::of(&double_comparison::DOUBLE_COMPARISONS),
Expand Down Expand Up @@ -1648,6 +1650,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&copies::IFS_SAME_COND),
LintId::of(&copies::IF_SAME_THEN_ELSE),
LintId::of(&derive::DERIVE_HASH_XOR_EQ),
LintId::of(&derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(&drop_bounds::DROP_BOUNDS),
LintId::of(&drop_forget_ref::DROP_COPY),
LintId::of(&drop_forget_ref::DROP_REF),
Expand Down
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None,
module: "derive",
},
Lint {
name: "derive_ord_xor_partial_ord",
group: "correctness",
desc: "deriving `Ord` but implementing `PartialOrd` explicitly",
deprecation: None,
module: "derive",
},
Lint {
name: "diverging_sub_expression",
group: "complexity",
Expand Down
68 changes: 68 additions & 0 deletions tests/ui/derive_ord_xor_partial_ord.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#![warn(clippy::derive_ord_xor_partial_ord)]

use std::cmp::Ordering;

#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct DeriveBoth;

impl PartialEq<u64> for DeriveBoth {
fn eq(&self, _: &u64) -> bool {
true
}
}

impl PartialOrd<u64> for DeriveBoth {
fn partial_cmp(&self, _: &u64) -> Option<Ordering> {
Some(Ordering::Equal)
}
}

#[derive(Ord, PartialEq, Eq)]
struct DeriveOrd;

impl PartialOrd for DeriveOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(other.cmp(self))
}
}

#[derive(Ord, PartialEq, Eq)]
struct DeriveOrdWithExplicitTypeVariable;

impl PartialOrd<DeriveOrdWithExplicitTypeVariable> for DeriveOrdWithExplicitTypeVariable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(other.cmp(self))
}
}

#[derive(PartialOrd, PartialEq, Eq)]
struct DerivePartialOrd;

impl std::cmp::Ord for DerivePartialOrd {
fn cmp(&self, other: &Self) -> Ordering {
Ordering::Less
}
}

#[derive(PartialOrd, PartialEq, Eq)]
struct ImplUserOrd;

trait Ord {}

// We don't want to lint on user-defined traits called `Ord`
impl Ord for ImplUserOrd {}

mod use_ord {
use std::cmp::{Ord, Ordering};

#[derive(PartialOrd, PartialEq, Eq)]
struct DerivePartialOrdInUseOrd;

impl Ord for DerivePartialOrdInUseOrd {
fn cmp(&self, other: &Self) -> Ordering {
Ordering::Less
}
}
}

fn main() {}
71 changes: 71 additions & 0 deletions tests/ui/derive_ord_xor_partial_ord.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
error: you are deriving `Ord` but have implemented `PartialOrd` explicitly
--> $DIR/derive_ord_xor_partial_ord.rs:20:10
|
LL | #[derive(Ord, PartialEq, Eq)]
| ^^^
|
= note: `-D clippy::derive-ord-xor-partial-ord` implied by `-D warnings`
note: `PartialOrd` implemented here
--> $DIR/derive_ord_xor_partial_ord.rs:23:1
|
LL | / impl PartialOrd for DeriveOrd {
LL | | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
LL | | Some(other.cmp(self))
LL | | }
LL | | }
| |_^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: you are deriving `Ord` but have implemented `PartialOrd` explicitly
--> $DIR/derive_ord_xor_partial_ord.rs:29:10
|
LL | #[derive(Ord, PartialEq, Eq)]
| ^^^
|
note: `PartialOrd` implemented here
--> $DIR/derive_ord_xor_partial_ord.rs:32:1
|
LL | / impl PartialOrd<DeriveOrdWithExplicitTypeVariable> for DeriveOrdWithExplicitTypeVariable {
LL | | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
LL | | Some(other.cmp(self))
LL | | }
LL | | }
| |_^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: you are implementing `Ord` explicitly but have derived `PartialOrd`
--> $DIR/derive_ord_xor_partial_ord.rs:41:1
|
LL | / impl std::cmp::Ord for DerivePartialOrd {
LL | | fn cmp(&self, other: &Self) -> Ordering {
LL | | Ordering::Less
LL | | }
LL | | }
| |_^
|
note: `PartialOrd` implemented here
--> $DIR/derive_ord_xor_partial_ord.rs:38:10
|
LL | #[derive(PartialOrd, PartialEq, Eq)]
| ^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: you are implementing `Ord` explicitly but have derived `PartialOrd`
--> $DIR/derive_ord_xor_partial_ord.rs:61:5
|
LL | / impl Ord for DerivePartialOrdInUseOrd {
LL | | fn cmp(&self, other: &Self) -> Ordering {
LL | | Ordering::Less
LL | | }
LL | | }
| |_____^
|
note: `PartialOrd` implemented here
--> $DIR/derive_ord_xor_partial_ord.rs:58:14
|
LL | #[derive(PartialOrd, PartialEq, Eq)]
| ^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 4 previous errors