diff --git a/CHANGELOG.md b/CHANGELOG.md
index e485c2c979e..09805e91d7c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1108,6 +1108,7 @@ Released 2018-09-13
 [`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
 [`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
 [`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
+[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
 [`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
 [`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
 [`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
diff --git a/README.md b/README.md
index 46fbc9dc9a4..be5433029de 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 
 A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
 
-[There are 340 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
+[There are 341 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
 
 We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
 
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 2e5ce50bf34..218928dfeae 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -240,6 +240,7 @@ pub mod missing_doc;
 pub mod missing_inline;
 pub mod mul_add;
 pub mod multiple_crate_versions;
+pub mod mut_key;
 pub mod mut_mut;
 pub mod mut_reference;
 pub mod mutable_debug_assertion;
@@ -667,6 +668,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
         &missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
         &mul_add::MANUAL_MUL_ADD,
         &multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
+        &mut_key::MUTABLE_KEY_TYPE,
         &mut_mut::MUT_MUT,
         &mut_reference::UNNECESSARY_MUT_PASSED,
         &mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
@@ -939,6 +941,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
     store.register_late_pass(|| box trait_bounds::TraitBounds);
     store.register_late_pass(|| box comparison_chain::ComparisonChain);
     store.register_late_pass(|| box mul_add::MulAddCheck);
+    store.register_late_pass(|| box mut_key::MutableKeyType);
     store.register_early_pass(|| box reference::DerefAddrOf);
     store.register_early_pass(|| box reference::RefInDeref);
     store.register_early_pass(|| box double_parens::DoubleParens);
@@ -1223,6 +1226,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
         LintId::of(&misc_early::UNNEEDED_FIELD_PATTERN),
         LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN),
         LintId::of(&misc_early::ZERO_PREFIXED_LITERAL),
+        LintId::of(&mut_key::MUTABLE_KEY_TYPE),
         LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED),
         LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
         LintId::of(&mutex_atomic::MUTEX_ATOMIC),
@@ -1532,6 +1536,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
         LintId::of(&misc::CMP_NAN),
         LintId::of(&misc::FLOAT_CMP),
         LintId::of(&misc::MODULO_ONE),
+        LintId::of(&mut_key::MUTABLE_KEY_TYPE),
         LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
         LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
         LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs
new file mode 100644
index 00000000000..3a0899032a5
--- /dev/null
+++ b/clippy_lints/src/mut_key.rs
@@ -0,0 +1,120 @@
+use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method, walk_ptrs_ty};
+use rustc::declare_lint_pass;
+use rustc::hir;
+use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
+use rustc::ty::{Adt, Dynamic, Opaque, Param, RawPtr, Ref, Ty, TypeAndMut};
+use rustc_session::declare_tool_lint;
+use syntax::source_map::Span;
+
+declare_clippy_lint! {
+    /// **What it does:** Checks for sets/maps with mutable key types.
+    ///
+    /// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and
+    /// `BtreeSet` rely on either the hash or the order of keys be unchanging,
+    /// so having types with interior mutability is a bad idea.
+    ///
+    /// **Known problems:** We don't currently account for `Rc` or `Arc`, so
+    /// this may yield false positives.
+    ///
+    /// **Example:**
+    /// ```rust
+    /// use std::cmp::{PartialEq, Eq};
+    /// use std::collections::HashSet;
+    /// use std::hash::{Hash, Hasher};
+    /// use std::sync::atomic::AtomicUsize;
+    ///# #[allow(unused)]
+    ///
+    /// struct Bad(AtomicUsize);
+    /// impl PartialEq for Bad {
+    ///     fn eq(&self, rhs: &Self) -> bool {
+    ///          ..
+    /// ; unimplemented!();
+    ///     }
+    /// }
+    ///
+    /// impl Eq for Bad {}
+    ///
+    /// impl Hash for Bad {
+    ///     fn hash<H: Hasher>(&self, h: &mut H) {
+    ///         ..
+    /// ; unimplemented!();
+    ///     }
+    /// }
+    ///
+    /// fn main() {
+    ///     let _: HashSet<Bad> = HashSet::new();
+    /// }
+    /// ```
+    pub MUTABLE_KEY_TYPE,
+    correctness,
+    "Check for mutable Map/Set key type"
+}
+
+declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
+
+impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MutableKeyType {
+    fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'tcx>) {
+        if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
+            check_sig(cx, item.hir_id, &sig.decl);
+        }
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
+        if let hir::ImplItemKind::Method(ref sig, ..) = item.kind {
+            if trait_ref_of_method(cx, item.hir_id).is_none() {
+                check_sig(cx, item.hir_id, &sig.decl);
+            }
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
+        if let hir::TraitItemKind::Method(ref sig, ..) = item.kind {
+            check_sig(cx, item.hir_id, &sig.decl);
+        }
+    }
+
+    fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &hir::Local) {
+        if let hir::PatKind::Wild = local.pat.kind {
+            return;
+        }
+        check_ty(cx, local.span, cx.tables.pat_ty(&*local.pat));
+    }
+}
+
+fn check_sig<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl) {
+    let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
+    let fn_sig = cx.tcx.fn_sig(fn_def_id);
+    for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) {
+        check_ty(cx, hir_ty.span, ty);
+    }
+    check_ty(
+        cx,
+        decl.output.span(),
+        cx.tcx.erase_late_bound_regions(&fn_sig.output()),
+    );
+}
+
+// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
+// generics (because the compiler cannot ensure immutability for unknown types).
+fn check_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, ty: Ty<'tcx>) {
+    let ty = walk_ptrs_ty(ty);
+    if let Adt(def, substs) = ty.kind {
+        if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET]
+            .iter()
+            .any(|path| match_def_path(cx, def.did, &**path))
+        {
+            let key_type = substs.type_at(0);
+            if is_concrete_type(key_type) && !key_type.is_freeze(cx.tcx, cx.param_env, span) {
+                span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
+            }
+        }
+    }
+}
+
+fn is_concrete_type(ty: Ty<'_>) -> bool {
+    match ty.kind {
+        RawPtr(TypeAndMut { ty: inner_ty, .. }) | Ref(_, inner_ty, _) => is_concrete_type(inner_ty),
+        Dynamic(..) | Opaque(..) | Param(..) => false,
+        _ => true,
+    }
+}
diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs
index 913e941660e..bdbe06c5f27 100644
--- a/src/lintlist/mod.rs
+++ b/src/lintlist/mod.rs
@@ -6,7 +6,7 @@ pub use lint::Lint;
 pub use lint::LINT_LEVELS;
 
 // begin lint list, do not remove this comment, it’s used in `update_lints`
-pub const ALL_LINTS: [Lint; 340] = [
+pub const ALL_LINTS: [Lint; 341] = [
     Lint {
         name: "absurd_extreme_comparisons",
         group: "correctness",
@@ -1239,6 +1239,13 @@ pub const ALL_LINTS: [Lint; 340] = [
         deprecation: None,
         module: "loops",
     },
+    Lint {
+        name: "mutable_key_type",
+        group: "correctness",
+        desc: "Check for mutable Map/Set key type",
+        deprecation: None,
+        module: "mut_key",
+    },
     Lint {
         name: "mutex_atomic",
         group: "perf",
diff --git a/tests/ui/mut_key.rs b/tests/ui/mut_key.rs
new file mode 100644
index 00000000000..5ec9b05f517
--- /dev/null
+++ b/tests/ui/mut_key.rs
@@ -0,0 +1,37 @@
+use std::collections::{HashMap, HashSet};
+use std::hash::{Hash, Hasher};
+use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
+
+struct Key(AtomicUsize);
+
+impl Clone for Key {
+    fn clone(&self) -> Self {
+        Key(AtomicUsize::new(self.0.load(Relaxed)))
+    }
+}
+
+impl PartialEq for Key {
+    fn eq(&self, other: &Self) -> bool {
+        self.0.load(Relaxed) == other.0.load(Relaxed)
+    }
+}
+
+impl Eq for Key {}
+
+impl Hash for Key {
+    fn hash<H: Hasher>(&self, h: &mut H) {
+        self.0.load(Relaxed).hash(h);
+    }
+}
+
+fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
+    let _other: HashMap<Key, bool> = HashMap::new();
+    m.keys().cloned().collect()
+}
+
+fn this_is_ok(m: &mut HashMap<usize, Key>) {}
+
+fn main() {
+    let _ = should_not_take_this_arg(&mut HashMap::new(), 1);
+    this_is_ok(&mut HashMap::new());
+}
diff --git a/tests/ui/mut_key.stderr b/tests/ui/mut_key.stderr
new file mode 100644
index 00000000000..ebdbfe99022
--- /dev/null
+++ b/tests/ui/mut_key.stderr
@@ -0,0 +1,22 @@
+error: mutable key type
+  --> $DIR/mut_key.rs:27:32
+   |
+LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
+   |                                ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[deny(clippy::mutable_key_type)]` on by default
+
+error: mutable key type
+  --> $DIR/mut_key.rs:27:72
+   |
+LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
+   |                                                                        ^^^^^^^^^^^^
+
+error: mutable key type
+  --> $DIR/mut_key.rs:28:5
+   |
+LL |     let _other: HashMap<Key, bool> = HashMap::new();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 3 previous errors
+