Skip to content

Commit c6c0edb

Browse files
committed
Add a lint about deriving Hash and implementing PartialEq
1 parent c570fc6 commit c6c0edb

File tree

5 files changed

+133
-1
lines changed

5 files changed

+133
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
66
[Jump to usage instructions](#usage)
77

88
##Lints
9-
There are 95 lints included in this crate:
9+
There are 96 lints included in this crate:
1010

1111
name | default | meaning
1212
---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -26,6 +26,7 @@ name
2626
[collapsible_if](https://github.com/Manishearth/rust-clippy/wiki#collapsible_if) | warn | two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` can be written as `if x && y { foo() }`
2727
[cyclomatic_complexity](https://github.com/Manishearth/rust-clippy/wiki#cyclomatic_complexity) | warn | finds functions that should be split up into multiple functions
2828
[deprecated_semver](https://github.com/Manishearth/rust-clippy/wiki#deprecated_semver) | warn | `Warn` on `#[deprecated(since = "x")]` where x is not semver
29+
[derive_hash_not_eq](https://github.com/Manishearth/rust-clippy/wiki#derive_hash_not_eq) | warn | deriving `Hash` but implementing `PartialEq` explicitly
2930
[duplicate_underscore_argument](https://github.com/Manishearth/rust-clippy/wiki#duplicate_underscore_argument) | warn | Function arguments having names which only differ by an underscore
3031
[empty_loop](https://github.com/Manishearth/rust-clippy/wiki#empty_loop) | warn | empty `loop {}` detected
3132
[eq_op](https://github.com/Manishearth/rust-clippy/wiki#eq_op) | warn | equal operands on both sides of a comparison or bitwise combination (e.g. `x == x`)

src/derive.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use rustc::lint::*;
2+
use rustc_front::hir::*;
3+
use syntax::ast::{Attribute, MetaItem_};
4+
use utils::{match_path, span_lint_and_then};
5+
use utils::HASH_PATH;
6+
7+
use rustc::middle::ty::fast_reject::simplify_type;
8+
9+
/// **What it does:** This lint warns about deriving `Hash` but implementing `PartialEq`
10+
/// explicitely.
11+
///
12+
/// **Why is this bad?** The implementation of these traits must agree (for example for use with
13+
/// `HashMap`) so it’s probably a bad idea to use a default-generated `Hash` implementation with
14+
/// an explicitely defined `PartialEq`. In particular, the following must hold for any type:
15+
///
16+
/// ```rust
17+
/// k1 == k2 -> hash(k1) == hash(k2)
18+
/// ```
19+
///
20+
/// **Known problems:** None.
21+
///
22+
/// **Example:**
23+
/// ```rust
24+
/// #[derive(Hash)]
25+
/// struct Foo;
26+
///
27+
/// impl PartialEq for Foo {
28+
/// ..
29+
/// }
30+
declare_lint! {
31+
pub DERIVE_HASH_NOT_EQ,
32+
Warn,
33+
"deriving `Hash` but implementing `PartialEq` explicitly"
34+
}
35+
36+
pub struct Derive;
37+
38+
impl LintPass for Derive {
39+
fn get_lints(&self) -> LintArray {
40+
lint_array!(DERIVE_HASH_NOT_EQ)
41+
}
42+
}
43+
44+
impl LateLintPass for Derive {
45+
fn check_item(&mut self, cx: &LateContext, item: &Item) {
46+
/// A `#[derive]`d implementation has a `#[automatically_derived]` attribute.
47+
fn is_automatically_derived(attr: &Attribute) -> bool {
48+
if let MetaItem_::MetaWord(ref word) = attr.node.value.node {
49+
word == &"automatically_derived"
50+
}
51+
else {
52+
false
53+
}
54+
}
55+
56+
// If `item` is an automatically derived `Hash` implementation
57+
if_let_chain! {[
58+
let ItemImpl(_, _, _, Some(ref trait_ref), ref ast_ty, _) = item.node,
59+
match_path(&trait_ref.path, &HASH_PATH),
60+
item.attrs.iter().any(is_automatically_derived),
61+
let Some(peq_trait_def_id) = cx.tcx.lang_items.eq_trait()
62+
], {
63+
let peq_trait_def = cx.tcx.lookup_trait_def(peq_trait_def_id);
64+
65+
cx.tcx.populate_implementations_for_trait_if_necessary(peq_trait_def.trait_ref.def_id);
66+
let peq_impls = peq_trait_def.borrow_impl_lists(cx.tcx).1;
67+
let ast_ty_to_ty_cache = cx.tcx.ast_ty_to_ty_cache.borrow();
68+
69+
70+
// Look for the PartialEq implementations for `ty`
71+
if_let_chain! {[
72+
let Some(ty) = ast_ty_to_ty_cache.get(&ast_ty.id),
73+
let Some(simpl_ty) = simplify_type(cx.tcx, ty, false),
74+
let Some(impl_ids) = peq_impls.get(&simpl_ty)
75+
], {
76+
for &impl_id in impl_ids {
77+
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
78+
79+
// Only care about `impl PartialEq<Foo> for Foo`
80+
if trait_ref.input_types()[0] == *ty &&
81+
!cx.tcx.get_attrs(impl_id).iter().any(is_automatically_derived) {
82+
span_lint_and_then(
83+
cx, DERIVE_HASH_NOT_EQ, item.span,
84+
&format!("you are deriving `Hash` but have implemented \
85+
`PartialEq` explicitely"), |db| {
86+
if let Some(node_id) = cx.tcx.map.as_local_node_id(impl_id) {
87+
db.span_note(
88+
cx.tcx.map.span(node_id),
89+
"`PartialEq` implemented here"
90+
);
91+
}
92+
});
93+
}
94+
}
95+
}}
96+
}}
97+
}
98+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub mod entry;
7575
pub mod misc_early;
7676
pub mod array_indexing;
7777
pub mod panic;
78+
pub mod derive;
7879

7980
mod reexport {
8081
pub use syntax::ast::{Name, NodeId};
@@ -136,6 +137,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
136137
reg.register_late_lint_pass(box array_indexing::ArrayIndexing);
137138
reg.register_late_lint_pass(box panic::PanicPass);
138139
reg.register_late_lint_pass(box strings::StringLitAsBytes);
140+
reg.register_late_lint_pass(box derive::Derive);
139141

140142

141143
reg.register_lint_group("clippy_pedantic", vec![
@@ -168,6 +170,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
168170
block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT,
169171
collapsible_if::COLLAPSIBLE_IF,
170172
cyclomatic_complexity::CYCLOMATIC_COMPLEXITY,
173+
derive::DERIVE_HASH_NOT_EQ,
171174
entry::MAP_ENTRY,
172175
eq_op::EQ_OP,
173176
escape::BOXED_LOCAL,

src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub const COW_PATH: [&'static str; 3] = ["collections", "borrow", "Cow"];
2727
pub const DEFAULT_TRAIT_PATH: [&'static str; 3] = ["core", "default", "Default"];
2828
pub const HASHMAP_ENTRY_PATH: [&'static str; 5] = ["std", "collections", "hash", "map", "Entry"];
2929
pub const HASHMAP_PATH: [&'static str; 5] = ["std", "collections", "hash", "map", "HashMap"];
30+
pub const HASH_PATH: [&'static str; 2] = ["hash", "Hash"];
3031
pub const LL_PATH: [&'static str; 3] = ["collections", "linked_list", "LinkedList"];
3132
pub const MUTEX_PATH: [&'static str; 4] = ["std", "sync", "mutex", "Mutex"];
3233
pub const OPEN_OPTIONS_PATH: [&'static str; 3] = ["std", "fs", "OpenOptions"];

tests/compile-fail/derive.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#![deny(warnings)]
5+
6+
#[derive(PartialEq, Hash)]
7+
struct Foo;
8+
9+
impl PartialEq<u64> for Foo {
10+
fn eq(&self, _: &u64) -> bool { true }
11+
}
12+
13+
#[derive(Hash)]
14+
//~^ ERROR you are deriving `Hash` but have implemented `PartialEq` explicitely
15+
struct Bar;
16+
17+
impl PartialEq for Bar {
18+
fn eq(&self, _: &Bar) -> bool { true }
19+
}
20+
21+
#[derive(Hash)]
22+
//~^ ERROR you are deriving `Hash` but have implemented `PartialEq` explicitely
23+
struct Baz;
24+
25+
impl PartialEq<Baz> for Baz {
26+
fn eq(&self, _: &Baz) -> bool { true }
27+
}
28+
29+
fn main() {}

0 commit comments

Comments
 (0)