about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-03-14 16:24:12 +0000
committerbors <bors@rust-lang.org>2022-03-14 16:24:12 +0000
commit285fa7ecd05dcbfdaf2faaf20400f5f92b39b3c6 (patch)
treea8b13585116a678e07763e47514130459f5e4347
parentbce19cf7f19ee5729defaccc86b068cc3c206c9c (diff)
parent706fa54456f42086a943992e03354c75e1754307 (diff)
downloadrust-285fa7ecd05dcbfdaf2faaf20400f5f92b39b3c6.tar.gz
rust-285fa7ecd05dcbfdaf2faaf20400f5f92b39b3c6.zip
Auto merge of #94929 - flip1995:clippyup, r=Manishearth
Update Clippy

r? `@Manishearth`

A few days delayed, because I recovered from a cold last week and couldn't get myself to do the sync, sorry. :upside_down_face:
-rw-r--r--Cargo.lock2
-rw-r--r--src/tools/clippy/.github/workflows/clippy.yml7
-rw-r--r--src/tools/clippy/.github/workflows/clippy_bors.yml7
-rw-r--r--src/tools/clippy/.github/workflows/clippy_dev.yml7
-rw-r--r--src/tools/clippy/CHANGELOG.md7
-rw-r--r--src/tools/clippy/Cargo.toml2
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs.rs60
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_slice_different_sizes.rs117
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/mod.rs48
-rw-r--r--src/tools/clippy/clippy_lints/src/dbg_macro.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_all.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_complexity.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_correctness.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_internal.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_lints.rs9
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_perf.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.register_restriction.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs56
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/mod.rs39
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/mod.rs47
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/needless_match.rs197
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs72
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/mod.rs68
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs9
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs26
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs91
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/utils.rs87
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs668
-rw-r--r--src/tools/clippy/clippy_lints/src/question_mark.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_clone.rs59
-rw-r--r--src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/mod.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs47
-rw-r--r--src/tools/clippy/clippy_lints/src/use_self.rs19
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints.rs54
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/write.rs6
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs54
-rw-r--r--src/tools/clippy/clippy_utils/src/consts.rs9
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs6
-rw-r--r--src/tools/clippy/clippy_utils/src/msrvs.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/paths.rs6
-rw-r--r--src/tools/clippy/clippy_utils/src/ty.rs6
-rw-r--r--src/tools/clippy/rust-toolchain2
-rw-r--r--src/tools/clippy/rustfmt.toml2
-rw-r--r--src/tools/clippy/tests/compile-test.rs9
-rw-r--r--src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.fixed40
-rw-r--r--src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.rs38
-rw-r--r--src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.stderr32
-rw-r--r--src/tools/clippy/tests/ui/allow_attributes_without_reason.rs14
-rw-r--r--src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr23
-rw-r--r--src/tools/clippy/tests/ui/cast_slice_different_sizes.rs39
-rw-r--r--src/tools/clippy/tests/ui/cast_slice_different_sizes.stderr52
-rw-r--r--src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs4
-rw-r--r--src/tools/clippy/tests/ui/dbg_macro.rs6
-rw-r--r--src/tools/clippy/tests/ui/dbg_macro.stderr20
-rw-r--r--src/tools/clippy/tests/ui/extend_with_drain.fixed4
-rw-r--r--src/tools/clippy/tests/ui/extend_with_drain.rs4
-rw-r--r--src/tools/clippy/tests/ui/iter_with_drain.fixed56
-rw-r--r--src/tools/clippy/tests/ui/iter_with_drain.rs56
-rw-r--r--src/tools/clippy/tests/ui/iter_with_drain.stderr40
-rw-r--r--src/tools/clippy/tests/ui/manual_map_option.fixed1
-rw-r--r--src/tools/clippy/tests/ui/manual_map_option.rs1
-rw-r--r--src/tools/clippy/tests/ui/missing_spin_loop.fixed28
-rw-r--r--src/tools/clippy/tests/ui/missing_spin_loop.rs28
-rw-r--r--src/tools/clippy/tests/ui/missing_spin_loop.stderr40
-rw-r--r--src/tools/clippy/tests/ui/missing_spin_loop_no_std.fixed23
-rw-r--r--src/tools/clippy/tests/ui/missing_spin_loop_no_std.rs23
-rw-r--r--src/tools/clippy/tests/ui/missing_spin_loop_no_std.stderr10
-rw-r--r--src/tools/clippy/tests/ui/needless_match.fixed83
-rw-r--r--src/tools/clippy/tests/ui/needless_match.rs122
-rw-r--r--src/tools/clippy/tests/ui/needless_match.stderr122
-rw-r--r--src/tools/clippy/tests/ui/only_used_in_recursion.rs122
-rw-r--r--src/tools/clippy/tests/ui/only_used_in_recursion.stderr82
-rw-r--r--src/tools/clippy/tests/ui/range_plus_minus_one.fixed2
-rw-r--r--src/tools/clippy/tests/ui/range_plus_minus_one.rs2
-rw-r--r--src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed4
-rw-r--r--src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs4
-rw-r--r--src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr4
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_filter_map.rs129
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_filter_map.stderr8
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_find_map.rs23
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_find_map.stderr38
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_sort_by.fixed10
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_sort_by.rs2
-rw-r--r--src/tools/clippy/tests/ui/unnecessary_sort_by.stderr32
-rw-r--r--src/tools/clippy/tests/ui/use_self.fixed25
-rw-r--r--src/tools/clippy/tests/ui/use_self.rs25
-rw-r--r--src/tools/clippy/tests/ui/use_self.stderr20
-rw-r--r--src/tools/clippy/util/gh-pages/index.html282
92 files changed, 3351 insertions, 332 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5d9edc5157d..dce5357b12d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -624,10 +624,10 @@ dependencies = [
  "futures 0.3.19",
  "if_chain",
  "itertools",
- "num_cpus",
  "parking_lot",
  "quote",
  "regex",
+ "rustc-semver",
  "rustc-workspace-hack",
  "rustc_tools_util 0.2.0",
  "semver",
diff --git a/src/tools/clippy/.github/workflows/clippy.yml b/src/tools/clippy/.github/workflows/clippy.yml
index 116ae031bb7..cd83bc9642b 100644
--- a/src/tools/clippy/.github/workflows/clippy.yml
+++ b/src/tools/clippy/.github/workflows/clippy.yml
@@ -74,10 +74,3 @@ jobs:
       run: bash .github/driver.sh
       env:
         OS: ${{ runner.os }}
-
-    - name: Test cargo dev new lint
-      run: |
-        cargo dev new_lint --name new_early_pass --pass early
-        cargo dev new_lint --name new_late_pass --pass late
-        cargo check
-        git reset --hard HEAD
diff --git a/src/tools/clippy/.github/workflows/clippy_bors.yml b/src/tools/clippy/.github/workflows/clippy_bors.yml
index 989667037c1..f571485e6d3 100644
--- a/src/tools/clippy/.github/workflows/clippy_bors.yml
+++ b/src/tools/clippy/.github/workflows/clippy_bors.yml
@@ -143,13 +143,6 @@ jobs:
       env:
         OS: ${{ runner.os }}
 
-    - name: Test cargo dev new lint
-      run: |
-        cargo dev new_lint --name new_early_pass --pass early
-        cargo dev new_lint --name new_late_pass --pass late
-        cargo check
-        git reset --hard HEAD
-
   integration_build:
     needs: changelog
     runs-on: ubuntu-latest
diff --git a/src/tools/clippy/.github/workflows/clippy_dev.yml b/src/tools/clippy/.github/workflows/clippy_dev.yml
index fe8bce00fa8..5dfab1d2ebc 100644
--- a/src/tools/clippy/.github/workflows/clippy_dev.yml
+++ b/src/tools/clippy/.github/workflows/clippy_dev.yml
@@ -36,6 +36,13 @@ jobs:
     - name: Test fmt
       run: cargo dev fmt --check
 
+    - name: Test cargo dev new lint
+      run: |
+        cargo dev new_lint --name new_early_pass --pass early
+        cargo dev new_lint --name new_late_pass --pass late
+        cargo check
+        git reset --hard HEAD
+
   # These jobs doesn't actually test anything, but they're only used to tell
   # bors the build completed, as there is no practical way to detect when a
   # workflow is successful listening to webhooks only.
diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md
index 35983b7fb50..2bc393d6042 100644
--- a/src/tools/clippy/CHANGELOG.md
+++ b/src/tools/clippy/CHANGELOG.md
@@ -3042,6 +3042,7 @@ Released 2018-09-13
 <!-- lint disable no-unused-definitions -->
 <!-- begin autogenerated links to lint list -->
 [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
+[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
 [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
 [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
 [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
@@ -3076,6 +3077,7 @@ Released 2018-09-13
 [`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
 [`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
 [`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
+[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
 [`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
 [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
 [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
@@ -3225,6 +3227,7 @@ Released 2018-09-13
 [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
 [`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
 [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
+[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
 [`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
 [`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
 [`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
@@ -3297,6 +3300,7 @@ Released 2018-09-13
 [`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
 [`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
 [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
+[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
 [`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
 [`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
 [`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
@@ -3327,6 +3331,7 @@ Released 2018-09-13
 [`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
 [`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
 [`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
+[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
 [`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
 [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
 [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
@@ -3351,6 +3356,7 @@ Released 2018-09-13
 [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
 [`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes
 [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
+[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
 [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
 [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
 [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
@@ -3498,6 +3504,7 @@ Released 2018-09-13
 [`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
 [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
 [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
+[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
 [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
 [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
 [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml
index d4ca9480bec..123af23881b 100644
--- a/src/tools/clippy/Cargo.toml
+++ b/src/tools/clippy/Cargo.toml
@@ -50,7 +50,7 @@ syn = { version = "1.0", features = ["full"] }
 futures = "0.3"
 parking_lot = "0.11.2"
 tokio = { version = "1", features = ["io-util"] }
-num_cpus = "1.13"
+rustc-semver = "1.1"
 
 [build-dependencies]
 rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
diff --git a/src/tools/clippy/clippy_lints/src/attrs.rs b/src/tools/clippy/clippy_lints/src/attrs.rs
index a58d12ddd6b..d0b03c0a9c2 100644
--- a/src/tools/clippy/clippy_lints/src/attrs.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs.rs
@@ -255,7 +255,38 @@ declare_clippy_lint! {
     "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for attributes that allow lints without a reason.
+    ///
+    /// (This requires the `lint_reasons` feature)
+    ///
+    /// ### Why is this bad?
+    /// Allowing a lint should always have a reason. This reason should be documented to
+    /// ensure that others understand the reasoning
+    ///
+    /// ### Example
+    /// Bad:
+    /// ```rust
+    /// #![feature(lint_reasons)]
+    ///
+    /// #![allow(clippy::some_lint)]
+    /// ```
+    ///
+    /// Good:
+    /// ```rust
+    /// #![feature(lint_reasons)]
+    ///
+    /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
+    restriction,
+    "ensures that all `allow` and `expect` attributes have a reason"
+}
+
 declare_lint_pass!(Attributes => [
+    ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
     DEPRECATED_SEMVER,
     USELESS_ATTRIBUTE,
@@ -269,6 +300,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
                 if is_lint_level(ident.name) {
                     check_clippy_lint_names(cx, ident.name, items);
                 }
+                if matches!(ident.name, sym::allow | sym::expect) {
+                    check_lint_reason(cx, ident.name, items, attr);
+                }
                 if items.is_empty() || !attr.has_name(sym::deprecated) {
                     return;
                 }
@@ -404,6 +438,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
     }
 }
 
+fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
+    // Check for the feature
+    if !cx.tcx.sess.features_untracked().lint_reasons {
+        return;
+    }
+
+    // Check if the reason is present
+    if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
+        && let MetaItemKind::NameValue(_) = &item.kind
+        && item.path == sym::reason
+    {
+        return;
+    }
+
+    span_lint_and_help(
+        cx,
+        ALLOW_ATTRIBUTES_WITHOUT_REASON,
+        attr.span,
+        &format!("`{}` attribute without specifying a reason", name.as_str()),
+        None,
+        "try adding a reason at the end with `, reason = \"..\"`",
+    );
+}
+
 fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
     if let ItemKind::Fn(_, _, eid) = item.kind {
         is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
@@ -659,5 +717,5 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
 }
 
 fn is_lint_level(symbol: Symbol) -> bool {
-    matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
+    matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
 }
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_slice_different_sizes.rs b/src/tools/clippy/clippy_lints/src/casts/cast_slice_different_sizes.rs
new file mode 100644
index 00000000000..3608c1654d5
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_slice_different_sizes.rs
@@ -0,0 +1,117 @@
+use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source::snippet_opt};
+use if_chain::if_chain;
+use rustc_ast::Mutability;
+use rustc_hir::{Expr, ExprKind, Node};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
+use rustc_semver::RustcVersion;
+
+use super::CAST_SLICE_DIFFERENT_SIZES;
+
+fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    let map = cx.tcx.hir();
+    if_chain! {
+        if let Some(parent_id) = map.find_parent_node(expr.hir_id);
+        if let Some(parent) = map.find(parent_id);
+        then {
+            let expr = match parent {
+                Node::Block(block) => {
+                    if let Some(parent_expr) = block.expr {
+                        parent_expr
+                    } else {
+                        return false;
+                    }
+                },
+                Node::Expr(expr) => expr,
+                _ => return false,
+            };
+
+            matches!(expr.kind, ExprKind::Cast(..))
+        } else {
+            false
+        }
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
+    // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
+    if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
+        return;
+    }
+
+    // if this cast is the child of another cast expression then don't emit something for it, the full
+    // chain will be analyzed
+    if is_child_of_cast(cx, expr) {
+        return;
+    }
+
+    if let Some((from_slice_ty, to_slice_ty)) = expr_cast_chain_tys(cx, expr) {
+        if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(from_slice_ty.ty), cx.layout_of(to_slice_ty.ty)) {
+            let from_size = from_layout.size.bytes();
+            let to_size = to_layout.size.bytes();
+            if from_size != to_size && from_size != 0 && to_size != 0 {
+                span_lint_and_then(
+                    cx,
+                    CAST_SLICE_DIFFERENT_SIZES,
+                    expr.span,
+                    &format!(
+                        "casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
+                        from_slice_ty, from_size, to_slice_ty, to_size,
+                    ),
+                    |diag| {
+                        let cast_expr = match expr.kind {
+                            ExprKind::Cast(cast_expr, ..) => cast_expr,
+                            _ => unreachable!("expr should be a cast as checked by expr_cast_chain_tys"),
+                        };
+                        let ptr_snippet = snippet_opt(cx, cast_expr.span).unwrap();
+
+                        let (mutbl_fn_str, mutbl_ptr_str) = match to_slice_ty.mutbl {
+                            Mutability::Mut => ("_mut", "mut"),
+                            Mutability::Not => ("", "const"),
+                        };
+                        let sugg = format!(
+                            "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {to_slice_ty}, ..)"
+                        );
+
+                        diag.span_suggestion(
+                            expr.span,
+                            &format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
+                            sugg,
+                            rustc_errors::Applicability::HasPlaceholders,
+                        );
+                    },
+                );
+            }
+        }
+    }
+}
+
+/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
+/// the type is one of those slices
+fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
+    match ty.kind() {
+        ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
+            ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts
+/// Returns None if the expr is not a Cast
+fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> {
+    if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
+        let cast_to = cx.typeck_results().expr_ty(expr);
+        let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
+        if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) {
+            Some((inner_from_ty, to_slice_ty))
+        } else {
+            let cast_from = cx.typeck_results().expr_ty(cast_expr);
+            let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
+            Some((from_slice_ty, to_slice_ty))
+        }
+    } else {
+        None
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs
index f2077c569c0..6a0eabd089d 100644
--- a/src/tools/clippy/clippy_lints/src/casts/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs
@@ -5,6 +5,7 @@ mod cast_precision_loss;
 mod cast_ptr_alignment;
 mod cast_ref_to_mut;
 mod cast_sign_loss;
+mod cast_slice_different_sizes;
 mod char_lit_as_u8;
 mod fn_to_numeric_cast;
 mod fn_to_numeric_cast_any;
@@ -409,6 +410,50 @@ declare_clippy_lint! {
     "casts from an enum type to an integral type which will truncate the value"
 }
 
+declare_clippy_lint! {
+    /// Checks for `as` casts between raw pointers to slices with differently sized elements.
+    ///
+    /// ### Why is this bad?
+    /// The produced raw pointer to a slice does not update its length metadata. The produced
+    /// pointer will point to a different number of bytes than the original pointer because the
+    /// length metadata of a raw slice pointer is in elements rather than bytes.
+    /// Producing a slice reference from the raw pointer will either create a slice with
+    /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
+    ///
+    /// ### Example
+    /// // Missing data
+    /// ```rust
+    /// let a = [1_i32, 2, 3, 4];
+    /// let p = &a as *const [i32] as *const [u8];
+    /// unsafe {
+    ///     println!("{:?}", &*p);
+    /// }
+    /// ```
+    /// // Undefined Behavior (note: also potential alignment issues)
+    /// ```rust
+    /// let a = [1_u8, 2, 3, 4];
+    /// let p = &a as *const [u8] as *const [u32];
+    /// unsafe {
+    ///     println!("{:?}", &*p);
+    /// }
+    /// ```
+    /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
+    /// ```rust
+    /// let a = [1_i32, 2, 3, 4];
+    /// let old_ptr = &a as *const [i32];
+    /// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
+    /// // The length comes from the known length of 4 i32s times the 4 bytes per i32
+    /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
+    /// unsafe {
+    ///     println!("{:?}", &*new_ptr);
+    /// }
+    /// ```
+    #[clippy::version = "1.60.0"]
+    pub CAST_SLICE_DIFFERENT_SIZES,
+    correctness,
+    "casting using `as` between raw pointers to slices of types with different sizes"
+}
+
 pub struct Casts {
     msrv: Option<RustcVersion>,
 }
@@ -428,6 +473,7 @@ impl_lint_pass!(Casts => [
     CAST_LOSSLESS,
     CAST_REF_TO_MUT,
     CAST_PTR_ALIGNMENT,
+    CAST_SLICE_DIFFERENT_SIZES,
     UNNECESSARY_CAST,
     FN_TO_NUMERIC_CAST_ANY,
     FN_TO_NUMERIC_CAST,
@@ -478,6 +524,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
         cast_ref_to_mut::check(cx, expr);
         cast_ptr_alignment::check(cx, expr);
         char_lit_as_u8::check(cx, expr);
+        ptr_as_ptr::check(cx, expr, &self.msrv);
+        cast_slice_different_sizes::check(cx, expr, &self.msrv);
     }
 
     extract_msrv_attr!(LateContext);
diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs
index df1a4128af3..a0e5d302633 100644
--- a/src/tools/clippy/clippy_lints/src/dbg_macro.rs
+++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs
@@ -1,4 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_in_test_function;
 use clippy_utils::macros::root_macro_call_first_node;
 use clippy_utils::source::snippet_with_applicability;
 use rustc_errors::Applicability;
@@ -35,6 +36,10 @@ impl LateLintPass<'_> for DbgMacro {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
         if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
+            // we make an exception for test code
+            if is_in_test_function(cx.tcx, expr.hir_id) {
+                return;
+            }
             let mut applicability = Applicability::MachineApplicable;
             let suggestion = match expr.peel_drop_temps().kind {
                 // dbg!()
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_all.rs b/src/tools/clippy/clippy_lints/src/lib.register_all.rs
index abfb4603537..23bca5a0eab 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_all.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_all.rs
@@ -25,6 +25,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(booleans::NONMINIMAL_BOOL),
     LintId::of(casts::CAST_ENUM_TRUNCATION),
     LintId::of(casts::CAST_REF_TO_MUT),
+    LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
     LintId::of(casts::CHAR_LIT_AS_U8),
     LintId::of(casts::FN_TO_NUMERIC_CAST),
     LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
@@ -109,6 +110,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(loops::ITER_NEXT_LOOP),
     LintId::of(loops::MANUAL_FLATTEN),
     LintId::of(loops::MANUAL_MEMCPY),
+    LintId::of(loops::MISSING_SPIN_LOOP),
     LintId::of(loops::MUT_RANGE_BOUND),
     LintId::of(loops::NEEDLESS_COLLECT),
     LintId::of(loops::NEEDLESS_RANGE_LOOP),
@@ -136,6 +138,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(matches::MATCH_OVERLAPPING_ARM),
     LintId::of(matches::MATCH_REF_PATS),
     LintId::of(matches::MATCH_SINGLE_BINDING),
+    LintId::of(matches::NEEDLESS_MATCH),
     LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
     LintId::of(matches::SINGLE_MATCH),
     LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
@@ -163,6 +166,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(methods::ITER_NTH_ZERO),
     LintId::of(methods::ITER_OVEREAGER_CLONED),
     LintId::of(methods::ITER_SKIP_NEXT),
+    LintId::of(methods::ITER_WITH_DRAIN),
     LintId::of(methods::MANUAL_FILTER_MAP),
     LintId::of(methods::MANUAL_FIND_MAP),
     LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
@@ -189,6 +193,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(methods::SUSPICIOUS_SPLITN),
     LintId::of(methods::UNINIT_ASSUMED_INIT),
     LintId::of(methods::UNNECESSARY_FILTER_MAP),
+    LintId::of(methods::UNNECESSARY_FIND_MAP),
     LintId::of(methods::UNNECESSARY_FOLD),
     LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
     LintId::of(methods::UNNECESSARY_TO_OWNED),
@@ -231,6 +236,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
     LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
     LintId::of(octal_escapes::OCTAL_ESCAPES),
+    LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
     LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
     LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
     LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs b/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs
index bd5ff613447..68d6c6ce5f7 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs
@@ -30,6 +30,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
     LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
     LintId::of(matches::MATCH_AS_REF),
     LintId::of(matches::MATCH_SINGLE_BINDING),
+    LintId::of(matches::NEEDLESS_MATCH),
     LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
     LintId::of(methods::BIND_INSTEAD_OF_MAP),
     LintId::of(methods::CLONE_ON_COPY),
@@ -49,6 +50,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
     LintId::of(methods::SEARCH_IS_SOME),
     LintId::of(methods::SKIP_WHILE_NEXT),
     LintId::of(methods::UNNECESSARY_FILTER_MAP),
+    LintId::of(methods::UNNECESSARY_FIND_MAP),
     LintId::of(methods::USELESS_ASREF),
     LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
     LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
@@ -63,6 +65,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
     LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
     LintId::of(no_effect::NO_EFFECT),
     LintId::of(no_effect::UNNECESSARY_OPERATION),
+    LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
     LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
     LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
     LintId::of(precedence::PRECEDENCE),
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs b/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs
index d7bf91eb692..df63f84463d 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs
@@ -13,6 +13,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
     LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
     LintId::of(booleans::LOGIC_BUG),
     LintId::of(casts::CAST_REF_TO_MUT),
+    LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
     LintId::of(copies::IFS_SAME_COND),
     LintId::of(copies::IF_SAME_THEN_ELSE),
     LintId::of(derive::DERIVE_HASH_XOR_EQ),
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_internal.rs b/src/tools/clippy/clippy_lints/src/lib.register_internal.rs
index 7d4c7d2adb5..4778f4fdfa7 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_internal.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_internal.rs
@@ -14,6 +14,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
     LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
     LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
     LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
+    LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL),
     LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
     LintId::of(utils::internal_lints::PRODUCE_ICE),
     LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_lints.rs b/src/tools/clippy/clippy_lints/src/lib.register_lints.rs
index 686482671b4..1a45763a869 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_lints.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_lints.rs
@@ -26,6 +26,8 @@ store.register_lints(&[
     #[cfg(feature = "internal")]
     utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
     #[cfg(feature = "internal")]
+    utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
+    #[cfg(feature = "internal")]
     utils::internal_lints::OUTER_EXPN_EXPN_DATA,
     #[cfg(feature = "internal")]
     utils::internal_lints::PRODUCE_ICE,
@@ -42,6 +44,7 @@ store.register_lints(&[
     assign_ops::ASSIGN_OP_PATTERN,
     assign_ops::MISREFACTORED_ASSIGN_OP,
     async_yields_async::ASYNC_YIELDS_ASYNC,
+    attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
     attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
     attrs::DEPRECATED_CFG_ATTR,
     attrs::DEPRECATED_SEMVER,
@@ -75,6 +78,7 @@ store.register_lints(&[
     casts::CAST_PTR_ALIGNMENT,
     casts::CAST_REF_TO_MUT,
     casts::CAST_SIGN_LOSS,
+    casts::CAST_SLICE_DIFFERENT_SIZES,
     casts::CHAR_LIT_AS_U8,
     casts::FN_TO_NUMERIC_CAST,
     casts::FN_TO_NUMERIC_CAST_ANY,
@@ -219,6 +223,7 @@ store.register_lints(&[
     loops::ITER_NEXT_LOOP,
     loops::MANUAL_FLATTEN,
     loops::MANUAL_MEMCPY,
+    loops::MISSING_SPIN_LOOP,
     loops::MUT_RANGE_BOUND,
     loops::NEEDLESS_COLLECT,
     loops::NEEDLESS_RANGE_LOOP,
@@ -255,6 +260,7 @@ store.register_lints(&[
     matches::MATCH_SINGLE_BINDING,
     matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
     matches::MATCH_WILD_ERR_ARM,
+    matches::NEEDLESS_MATCH,
     matches::REDUNDANT_PATTERN_MATCHING,
     matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
     matches::SINGLE_MATCH,
@@ -296,6 +302,7 @@ store.register_lints(&[
     methods::ITER_NTH_ZERO,
     methods::ITER_OVEREAGER_CLONED,
     methods::ITER_SKIP_NEXT,
+    methods::ITER_WITH_DRAIN,
     methods::MANUAL_FILTER_MAP,
     methods::MANUAL_FIND_MAP,
     methods::MANUAL_SATURATING_ARITHMETIC,
@@ -323,6 +330,7 @@ store.register_lints(&[
     methods::SUSPICIOUS_SPLITN,
     methods::UNINIT_ASSUMED_INIT,
     methods::UNNECESSARY_FILTER_MAP,
+    methods::UNNECESSARY_FIND_MAP,
     methods::UNNECESSARY_FOLD,
     methods::UNNECESSARY_LAZY_EVALUATIONS,
     methods::UNNECESSARY_TO_OWNED,
@@ -392,6 +400,7 @@ store.register_lints(&[
     non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
     nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
     octal_escapes::OCTAL_ESCAPES,
+    only_used_in_recursion::ONLY_USED_IN_RECURSION,
     open_options::NONSENSICAL_OPEN_OPTIONS,
     option_env_unwrap::OPTION_ENV_UNWRAP,
     option_if_let_else::OPTION_IF_LET_ELSE,
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_perf.rs b/src/tools/clippy/clippy_lints/src/lib.register_perf.rs
index c44ef124bfa..6e9c0ee33a1 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_perf.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_perf.rs
@@ -10,11 +10,13 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
     LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
     LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
     LintId::of(loops::MANUAL_MEMCPY),
+    LintId::of(loops::MISSING_SPIN_LOOP),
     LintId::of(loops::NEEDLESS_COLLECT),
     LintId::of(methods::EXPECT_FUN_CALL),
     LintId::of(methods::EXTEND_WITH_DRAIN),
     LintId::of(methods::ITER_NTH),
     LintId::of(methods::ITER_OVEREAGER_CLONED),
+    LintId::of(methods::ITER_WITH_DRAIN),
     LintId::of(methods::MANUAL_STR_REPEAT),
     LintId::of(methods::OR_FUN_CALL),
     LintId::of(methods::SINGLE_CHAR_PATTERN),
diff --git a/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs b/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs
index f89f35b885c..4e30fc38197 100644
--- a/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs
@@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
     LintId::of(as_conversions::AS_CONVERSIONS),
     LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
     LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
+    LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON),
     LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
     LintId::of(create_dir::CREATE_DIR),
     LintId::of(dbg_macro::DBG_MACRO),
diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs
index 230e2b2ccdf..504235d0d1e 100644
--- a/src/tools/clippy/clippy_lints/src/lib.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.rs
@@ -318,6 +318,7 @@ mod non_octal_unix_permissions;
 mod non_send_fields_in_send_ty;
 mod nonstandard_macro_braces;
 mod octal_escapes;
+mod only_used_in_recursion;
 mod open_options;
 mod option_env_unwrap;
 mod option_if_let_else;
@@ -505,6 +506,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
         store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
         store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass));
+        store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
     }
 
     store.register_late_pass(|| Box::new(utils::author::Author));
@@ -856,6 +858,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv)));
     store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
     store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
+    store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion));
     store.register_late_pass(|| Box::new(dbg_macro::DbgMacro));
     let cargo_ignore_publish = conf.cargo_ignore_publish;
     store.register_late_pass(move || {
diff --git a/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs b/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs
new file mode 100644
index 00000000000..0696afa3922
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs
@@ -0,0 +1,56 @@
+use super::MISSING_SPIN_LOOP;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_no_std_crate;
+use rustc_errors::Applicability;
+use rustc_hir::{Block, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+    match &cond.kind {
+        ExprKind::Block(
+            Block {
+                stmts: [],
+                expr: Some(e),
+                ..
+            },
+            _,
+        )
+        | ExprKind::Unary(_, e) => unpack_cond(e),
+        ExprKind::Binary(_, l, r) => {
+            let l = unpack_cond(l);
+            if let ExprKind::MethodCall(..) = l.kind {
+                l
+            } else {
+                unpack_cond(r)
+            }
+        },
+        _ => cond,
+    }
+}
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
+    if_chain! {
+        if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind;
+        if let ExprKind::MethodCall(method, [callee, ..], _) = unpack_cond(cond).kind;
+        if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name);
+        if let ty::Adt(def, _substs) = cx.typeck_results().expr_ty(callee).kind();
+        if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did());
+        then {
+            span_lint_and_sugg(
+                cx,
+                MISSING_SPIN_LOOP,
+                body.span,
+                "busy-waiting loop should at least have a spin loop hint",
+                "try this",
+                (if is_no_std_crate(cx) {
+                    "{ core::hint::spin_loop() }"
+                } else {
+                    "{ std::hint::spin_loop() }"
+                }).into(),
+                Applicability::MachineApplicable
+            );
+        }
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs
index 5bc32acf56e..f029067d367 100644
--- a/src/tools/clippy/clippy_lints/src/loops/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs
@@ -7,6 +7,7 @@ mod for_loops_over_fallibles;
 mod iter_next_loop;
 mod manual_flatten;
 mod manual_memcpy;
+mod missing_spin_loop;
 mod mut_range_bound;
 mod needless_collect;
 mod needless_range_loop;
@@ -560,6 +561,42 @@ declare_clippy_lint! {
     "for loops over `Option`s or `Result`s with a single expression can be simplified"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Check for empty spin loops
+    ///
+    /// ### Why is this bad?
+    /// The loop body should have something like `thread::park()` or at least
+    /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve
+    /// energy. Perhaps even better use an actual lock, if possible.
+    ///
+    /// ### Known problems
+    /// This lint doesn't currently trigger on `while let` or
+    /// `loop { match .. { .. } }` loops, which would be considered idiomatic in
+    /// combination with e.g. `AtomicBool::compare_exchange_weak`.
+    ///
+    /// ### Example
+    ///
+    /// ```ignore
+    /// use core::sync::atomic::{AtomicBool, Ordering};
+    /// let b = AtomicBool::new(true);
+    /// // give a ref to `b` to another thread,wait for it to become false
+    /// while b.load(Ordering::Acquire) {};
+    /// ```
+    /// Use instead:
+    /// ```rust,no_run
+    ///# use core::sync::atomic::{AtomicBool, Ordering};
+    ///# let b = AtomicBool::new(true);
+    /// while b.load(Ordering::Acquire) {
+    ///     std::hint::spin_loop()
+    /// }
+    /// ```
+    #[clippy::version = "1.59.0"]
+    pub MISSING_SPIN_LOOP,
+    perf,
+    "An empty busy waiting loop"
+}
+
 declare_lint_pass!(Loops => [
     MANUAL_MEMCPY,
     MANUAL_FLATTEN,
@@ -579,6 +616,7 @@ declare_lint_pass!(Loops => [
     WHILE_IMMUTABLE_CONDITION,
     SAME_ITEM_PUSH,
     SINGLE_ELEMENT_LOOP,
+    MISSING_SPIN_LOOP,
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -628,6 +666,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
 
         if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
             while_immutable_condition::check(cx, condition, body);
+            missing_spin_loop::check(cx, condition, body);
         }
 
         needless_collect::check(expr, cx);
diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs
index 92179eb6f0e..ff85623acf4 100644
--- a/src/tools/clippy/clippy_lints/src/matches/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs
@@ -16,6 +16,7 @@ mod match_same_arms;
 mod match_single_binding;
 mod match_wild_enum;
 mod match_wild_err_arm;
+mod needless_match;
 mod overlapping_arms;
 mod redundant_pattern_match;
 mod rest_pat_in_fully_bound_struct;
@@ -566,6 +567,49 @@ declare_clippy_lint! {
     "`match` with identical arm bodies"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
+    /// when function signatures are the same.
+    ///
+    /// ### Why is this bad?
+    /// This `match` block does nothing and might not be what the coder intended.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// fn foo() -> Result<(), i32> {
+    ///     match result {
+    ///         Ok(val) => Ok(val),
+    ///         Err(err) => Err(err),
+    ///     }
+    /// }
+    ///
+    /// fn bar() -> Option<i32> {
+    ///     if let Some(val) = option {
+    ///         Some(val)
+    ///     } else {
+    ///         None
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// Could be replaced as
+    ///
+    /// ```rust,ignore
+    /// fn foo() -> Result<(), i32> {
+    ///     result
+    /// }
+    ///
+    /// fn bar() -> Option<i32> {
+    ///     option
+    /// }
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub NEEDLESS_MATCH,
+    complexity,
+    "`match` or match-like `if let` that are unnecessary"
+}
+
 #[derive(Default)]
 pub struct Matches {
     msrv: Option<RustcVersion>,
@@ -599,6 +643,7 @@ impl_lint_pass!(Matches => [
     REDUNDANT_PATTERN_MATCHING,
     MATCH_LIKE_MATCHES_MACRO,
     MATCH_SAME_ARMS,
+    NEEDLESS_MATCH,
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Matches {
@@ -622,6 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                     overlapping_arms::check(cx, ex, arms);
                     match_wild_enum::check(cx, ex, arms);
                     match_as_ref::check(cx, ex, arms, expr);
+                    needless_match::check_match(cx, ex, arms);
 
                     if self.infallible_destructuring_match_linted {
                         self.infallible_destructuring_match_linted = false;
@@ -640,6 +686,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                 match_like_matches::check(cx, expr);
             }
             redundant_pattern_match::check(cx, expr);
+            needless_match::check(cx, expr);
         }
     }
 
diff --git a/src/tools/clippy/clippy_lints/src/matches/needless_match.rs b/src/tools/clippy/clippy_lints/src/matches/needless_match.rs
new file mode 100644
index 00000000000..76131d307d7
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/matches/needless_match.rs
@@ -0,0 +1,197 @@
+use super::NEEDLESS_MATCH;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+    // This is for avoiding collision with `match_single_binding`.
+    if arms.len() < 2 {
+        return;
+    }
+
+    for arm in arms {
+        if let PatKind::Wild = arm.pat.kind {
+            let ret_expr = strip_return(arm.body);
+            if !eq_expr_value(cx, ex, ret_expr) {
+                return;
+            }
+        } else if !pat_same_as_expr(arm.pat, arm.body) {
+            return;
+        }
+    }
+
+    if let Some(match_expr) = get_parent_expr(cx, ex) {
+        let mut applicability = Applicability::MachineApplicable;
+        span_lint_and_sugg(
+            cx,
+            NEEDLESS_MATCH,
+            match_expr.span,
+            "this match expression is unnecessary",
+            "replace it with",
+            snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
+            applicability,
+        );
+    }
+}
+
+/// Check for nop `if let` expression that assembled as unnecessary match
+///
+/// ```rust,ignore
+/// if let Some(a) = option {
+///     Some(a)
+/// } else {
+///     None
+/// }
+/// ```
+/// OR
+/// ```rust,ignore
+/// if let SomeEnum::A = some_enum {
+///     SomeEnum::A
+/// } else if let SomeEnum::B = some_enum {
+///     SomeEnum::B
+/// } else {
+///     some_enum
+/// }
+/// ```
+pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
+    if_chain! {
+        if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
+        if !is_else_clause(cx.tcx, ex);
+        if check_if_let(cx, if_let);
+        then {
+            let mut applicability = Applicability::MachineApplicable;
+            span_lint_and_sugg(
+                cx,
+                NEEDLESS_MATCH,
+                ex.span,
+                "this if-let expression is unnecessary",
+                "replace it with",
+                snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
+                applicability,
+            );
+        }
+    }
+}
+
+fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
+    if let Some(if_else) = if_let.if_else {
+        if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
+            return false;
+        }
+
+        // Recurrsively check for each `else if let` phrase,
+        if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
+            return check_if_let(cx, nested_if_let);
+        }
+
+        if matches!(if_else.kind, ExprKind::Block(..)) {
+            let else_expr = peel_blocks_with_stmt(if_else);
+            let ret = strip_return(else_expr);
+            let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
+            if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
+                if let ExprKind::Path(ref qpath) = ret.kind {
+                    return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
+                }
+            } else {
+                return eq_expr_value(cx, if_let.let_expr, ret);
+            }
+            return true;
+        }
+    }
+    false
+}
+
+/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
+fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+    if let ExprKind::Ret(Some(ret)) = expr.kind {
+        ret
+    } else {
+        expr
+    }
+}
+
+fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
+    let expr = strip_return(expr);
+    match (&pat.kind, &expr.kind) {
+        // Example: `Some(val) => Some(val)`
+        (
+            PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
+            ExprKind::Call(call_expr, [first_param, ..]),
+        ) => {
+            if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
+                if has_identical_segments(path.segments, call_path.segments)
+                    && has_same_non_ref_symbol(first_pat, first_param)
+                {
+                    return true;
+                }
+            }
+        },
+        // Example: `val => val`, or `ref val => *val`
+        (PatKind::Binding(annot, _, pat_ident, _), _) => {
+            let new_expr = if let (
+                BindingAnnotation::Ref | BindingAnnotation::RefMut,
+                ExprKind::Unary(UnOp::Deref, operand_expr),
+            ) = (annot, &expr.kind)
+            {
+                operand_expr
+            } else {
+                expr
+            };
+
+            if let ExprKind::Path(QPath::Resolved(
+                _,
+                Path {
+                    segments: [first_seg, ..],
+                    ..
+                },
+            )) = new_expr.kind
+            {
+                return pat_ident.name == first_seg.ident.name;
+            }
+        },
+        // Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
+        (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
+            return has_identical_segments(p_path.segments, e_path.segments);
+        },
+        // Example: `5 => 5`
+        (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
+            if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
+                return pat_spanned.node == expr_spanned.node;
+            }
+        },
+        _ => {},
+    }
+
+    false
+}
+
+fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
+    if left_segs.len() != right_segs.len() {
+        return false;
+    }
+    for i in 0..left_segs.len() {
+        if left_segs[i].ident.name != right_segs[i].ident.name {
+            return false;
+        }
+    }
+    true
+}
+
+fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
+    if_chain! {
+        if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
+        if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
+        if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
+        then {
+            return pat_ident.name == first_seg.ident.name;
+        }
+    }
+
+    false
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs b/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
new file mode 100644
index 00000000000..958c3773087
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
@@ -0,0 +1,72 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_integer_const;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{
+    higher::{self, Range},
+    SpanlessEq,
+};
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::LateContext;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::Span;
+
+use super::ITER_WITH_DRAIN;
+
+const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
+    let ty = cx.typeck_results().expr_ty(recv).peel_refs();
+    if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
+        // Refuse to emit `into_iter` suggestion on draining struct fields due
+        // to the strong possibility of processing unmovable field.
+        if let ExprKind::Field(..) = recv.kind {
+            return;
+        }
+
+        if let Some(range) = higher::Range::hir(arg) {
+            let left_full = match range {
+                Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
+                Range { start: None, .. } => true,
+                _ => false,
+            };
+            let full = left_full
+                && match range {
+                    Range {
+                        end: Some(end),
+                        limits: RangeLimits::HalfOpen,
+                        ..
+                    } => {
+                        // `x.drain(..x.len())` call
+                        if_chain! {
+                            if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
+                            if len_path.ident.name == sym::len && len_args.len() == 1;
+                            if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
+                            if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
+                            if SpanlessEq::new(cx).eq_path(drain_path, len_path);
+                            then { true }
+                            else { false }
+                        }
+                    },
+                    Range {
+                        end: None,
+                        limits: RangeLimits::HalfOpen,
+                        ..
+                    } => true,
+                    _ => false,
+                };
+            if full {
+                span_lint_and_sugg(
+                    cx,
+                    ITER_WITH_DRAIN,
+                    span.with_hi(expr.span.hi()),
+                    &format!("`drain(..)` used on a `{}`", drained_type),
+                    "try this",
+                    "into_iter()".to_string(),
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        }
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs
index 3021a40fae1..5edd22cd14c 100644
--- a/src/tools/clippy/clippy_lints/src/methods/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs
@@ -32,6 +32,7 @@ mod iter_nth;
 mod iter_nth_zero;
 mod iter_overeager_cloned;
 mod iter_skip_next;
+mod iter_with_drain;
 mod iterator_step_by_zero;
 mod manual_saturating_arithmetic;
 mod manual_str_repeat;
@@ -1120,6 +1121,31 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
+    /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
+    ///
+    /// ### Why is this bad?
+    /// `.into_iter()` is simpler with better performance.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # use std::collections::HashSet;
+    /// let mut foo = vec![0, 1, 2, 3];
+    /// let bar: HashSet<usize> = foo.drain(..).collect();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// # use std::collections::HashSet;
+    /// let foo = vec![0, 1, 2, 3];
+    /// let bar: HashSet<usize> = foo.into_iter().collect();
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub ITER_WITH_DRAIN,
+    perf,
+    "replace `.drain(..)` with `.into_iter()`"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
     /// Checks for use of `.get().unwrap()` (or
     /// `.get_mut().unwrap`) on a standard library type which implements `Index`
     ///
@@ -1309,7 +1335,7 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
-    /// Checks for `filter_map` calls which could be replaced by `filter` or `map`.
+    /// Checks for `filter_map` calls that could be replaced by `filter` or `map`.
     /// More specifically it checks if the closure provided is only performing one of the
     /// filter or map operations and suggests the appropriate option.
     ///
@@ -1339,6 +1365,36 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
+    /// Checks for `find_map` calls that could be replaced by `find` or `map`. More
+    /// specifically it checks if the closure provided is only performing one of the
+    /// find or map operations and suggests the appropriate option.
+    ///
+    /// ### Why is this bad?
+    /// Complexity. The intent is also clearer if only a single
+    /// operation is being performed.
+    ///
+    /// ### Example
+    /// ```rust
+    /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });
+    ///
+    /// // As there is no transformation of the argument this could be written as:
+    /// let _ = (0..3).find(|&x| x > 2);
+    /// ```
+    ///
+    /// ```rust
+    /// let _ = (0..4).find_map(|x| Some(x + 1));
+    ///
+    /// // As there is no conditional check on the argument this could be written as:
+    /// let _ = (0..4).map(|x| x + 1).next();
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub UNNECESSARY_FIND_MAP,
+    complexity,
+    "using `find_map` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
     /// Checks for `into_iter` calls on references which should be replaced by `iter`
     /// or `iter_mut`.
     ///
@@ -2017,9 +2073,11 @@ impl_lint_pass!(Methods => [
     GET_UNWRAP,
     STRING_EXTEND_CHARS,
     ITER_CLONED_COLLECT,
+    ITER_WITH_DRAIN,
     USELESS_ASREF,
     UNNECESSARY_FOLD,
     UNNECESSARY_FILTER_MAP,
+    UNNECESSARY_FIND_MAP,
     INTO_ITER_ON_REF,
     SUSPICIOUS_MAP,
     UNINIT_ASSUMED_INIT,
@@ -2296,6 +2354,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                 Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
                 _ => {},
             },
+            ("drain", [arg]) => {
+                iter_with_drain::check(cx, expr, recv, span, arg);
+            },
             ("expect", [_]) => match method_call(recv) {
                 Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
                 _ => expect_used::check(cx, expr, recv),
@@ -2305,9 +2366,12 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                 extend_with_drain::check(cx, expr, recv, arg);
             },
             ("filter_map", [arg]) => {
-                unnecessary_filter_map::check(cx, expr, arg);
+                unnecessary_filter_map::check(cx, expr, arg, name);
                 filter_map_identity::check(cx, expr, arg, span);
             },
+            ("find_map", [arg]) => {
+                unnecessary_filter_map::check(cx, expr, arg, name);
+            },
             ("flat_map", [arg]) => {
                 flat_map_identity::check(cx, expr, arg, span);
                 flat_map_option::check(cx, expr, arg, span);
diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
index bdf8cea1207..2a5ab6e625c 100644
--- a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
@@ -14,10 +14,7 @@ use super::RESULT_MAP_OR_INTO_OPTION;
 
 // The expression inside a closure may or may not have surrounding braces
 // which causes problems when generating a suggestion.
-fn reduce_unit_expression<'a>(
-    cx: &LateContext<'_>,
-    expr: &'a hir::Expr<'_>,
-) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
+fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
     match expr.kind {
         hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
         hir::ExprKind::Block(block, _) => {
@@ -25,7 +22,7 @@ fn reduce_unit_expression<'a>(
                 (&[], Some(inner_expr)) => {
                     // If block only contains an expression,
                     // reduce `|x| { x + 1 }` to `|x| x + 1`
-                    reduce_unit_expression(cx, inner_expr)
+                    reduce_unit_expression(inner_expr)
                 },
                 _ => None,
             }
@@ -77,7 +74,7 @@ pub(super) fn check<'tcx>(
         if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
             let arg_snippet = snippet(cx, span, "..");
             let body = cx.tcx.hir().body(id);
-                if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value);
+                if let Some((func, [arg_char])) = reduce_unit_expression(&body.value);
                 if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id));
                 if Some(id) == cx.tcx.lang_items().option_some_variant();
                 then {
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs
index a307e33875e..2fda254ca98 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs
@@ -1,4 +1,6 @@
+use super::utils::clone_or_copy_needed;
 use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_copy;
 use clippy_utils::usage::mutated_variables;
 use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
 use rustc_hir as hir;
@@ -9,8 +11,9 @@ use rustc_middle::ty;
 use rustc_span::sym;
 
 use super::UNNECESSARY_FILTER_MAP;
+use super::UNNECESSARY_FIND_MAP;
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
     if !is_trait_method(cx, expr, sym::Iterator) {
         return;
     }
@@ -20,6 +23,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
         let arg_id = body.params[0].pat.hir_id;
         let mutates_arg =
             mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
+        let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, &body.value);
 
         let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value);
 
@@ -28,13 +32,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
         found_mapping |= return_visitor.found_mapping;
         found_filtering |= return_visitor.found_filtering;
 
+        let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
         let sugg = if !found_filtering {
-            "map"
-        } else if !found_mapping && !mutates_arg {
-            let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
+            if name == "filter_map" { "map" } else { "map(..).next()" }
+        } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
             match cx.typeck_results().expr_ty(&body.value).kind() {
-                ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => {
-                    "filter"
+                ty::Adt(adt, subst)
+                    if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) =>
+                {
+                    if name == "filter_map" { "filter" } else { "find" }
                 },
                 _ => return,
             }
@@ -43,9 +49,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
         };
         span_lint(
             cx,
-            UNNECESSARY_FILTER_MAP,
+            if name == "filter_map" {
+                UNNECESSARY_FILTER_MAP
+            } else {
+                UNNECESSARY_FIND_MAP
+            },
             expr.span,
-            &format!("this `.filter_map` can be written more simply using `.{}`", sugg),
+            &format!("this `.{}` can be written more simply using `.{}`", name, sugg),
         );
     }
 }
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
index 65e94c5f44a..7a39557ad57 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
@@ -1,14 +1,12 @@
+use super::utils::clone_or_copy_needed;
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::higher::ForLoop;
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
-use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage};
+use clippy_utils::{fn_def_id, get_parent_expr};
 use rustc_errors::Applicability;
-use rustc_hir::intravisit::{walk_expr, Visitor};
-use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat};
+use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem};
 use rustc_lint::LateContext;
-use rustc_middle::hir::nested_filter;
-use rustc_middle::ty;
 use rustc_span::{sym, Symbol};
 
 use super::UNNECESSARY_TO_OWNED;
@@ -100,89 +98,6 @@ pub fn check_for_loop_iter(
     false
 }
 
-/// The core logic of `check_for_loop_iter` above, this function wraps a use of
-/// `CloneOrCopyVisitor`.
-fn clone_or_copy_needed<'tcx>(
-    cx: &LateContext<'tcx>,
-    pat: &Pat<'tcx>,
-    body: &'tcx Expr<'tcx>,
-) -> (bool, Vec<&'tcx Expr<'tcx>>) {
-    let mut visitor = CloneOrCopyVisitor {
-        cx,
-        binding_hir_ids: pat_bindings(pat),
-        clone_or_copy_needed: false,
-        addr_of_exprs: Vec::new(),
-    };
-    visitor.visit_expr(body);
-    (visitor.clone_or_copy_needed, visitor.addr_of_exprs)
-}
-
-/// Returns a vector of all `HirId`s bound by the pattern.
-fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
-    let mut collector = usage::ParamBindingIdCollector {
-        binding_hir_ids: Vec::new(),
-    };
-    collector.visit_pat(pat);
-    collector.binding_hir_ids
-}
-
-/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
-/// operations performed on `binding_hir_ids` are:
-/// * to take non-mutable references to them
-/// * to use them as non-mutable `&self` in method calls
-/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
-/// when `CloneOrCopyVisitor` is done visiting.
-struct CloneOrCopyVisitor<'cx, 'tcx> {
-    cx: &'cx LateContext<'tcx>,
-    binding_hir_ids: Vec<HirId>,
-    clone_or_copy_needed: bool,
-    addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
-}
-
-impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
-    type NestedFilter = nested_filter::OnlyBodies;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.cx.tcx.hir()
-    }
-
-    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
-        walk_expr(self, expr);
-        if self.is_binding(expr) {
-            if let Some(parent) = get_parent_expr(self.cx, expr) {
-                match parent.kind {
-                    ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
-                        self.addr_of_exprs.push(parent);
-                        return;
-                    },
-                    ExprKind::MethodCall(_, args, _) => {
-                        if_chain! {
-                            if args.iter().skip(1).all(|arg| !self.is_binding(arg));
-                            if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
-                            let method_ty = self.cx.tcx.type_of(method_def_id);
-                            let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
-                            if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
-                            then {
-                                return;
-                            }
-                        }
-                    },
-                    _ => {},
-                }
-            }
-            self.clone_or_copy_needed = true;
-        }
-    }
-}
-
-impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
-    fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
-        self.binding_hir_ids
-            .iter()
-            .any(|hir_id| path_to_local_id(expr, *hir_id))
-    }
-}
-
 /// Returns true if the named method is `IntoIterator::into_iter`.
 pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
     cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)
diff --git a/src/tools/clippy/clippy_lints/src/methods/utils.rs b/src/tools/clippy/clippy_lints/src/methods/utils.rs
index 63c3273bd68..3015531e843 100644
--- a/src/tools/clippy/clippy_lints/src/methods/utils.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/utils.rs
@@ -1,10 +1,14 @@
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_parent_expr, path_to_local_id, usage};
 use if_chain::if_chain;
 use rustc_ast::ast;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat};
 use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::{self, Ty};
 use rustc_span::symbol::sym;
 
@@ -79,3 +83,86 @@ pub(super) fn get_hint_if_single_char_arg(
         }
     }
 }
+
+/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a
+/// use of `CloneOrCopyVisitor`.
+pub(super) fn clone_or_copy_needed<'tcx>(
+    cx: &LateContext<'tcx>,
+    pat: &Pat<'tcx>,
+    body: &'tcx Expr<'tcx>,
+) -> (bool, Vec<&'tcx Expr<'tcx>>) {
+    let mut visitor = CloneOrCopyVisitor {
+        cx,
+        binding_hir_ids: pat_bindings(pat),
+        clone_or_copy_needed: false,
+        addr_of_exprs: Vec::new(),
+    };
+    visitor.visit_expr(body);
+    (visitor.clone_or_copy_needed, visitor.addr_of_exprs)
+}
+
+/// Returns a vector of all `HirId`s bound by the pattern.
+fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
+    let mut collector = usage::ParamBindingIdCollector {
+        binding_hir_ids: Vec::new(),
+    };
+    collector.visit_pat(pat);
+    collector.binding_hir_ids
+}
+
+/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
+/// operations performed on `binding_hir_ids` are:
+/// * to take non-mutable references to them
+/// * to use them as non-mutable `&self` in method calls
+/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
+/// when `CloneOrCopyVisitor` is done visiting.
+struct CloneOrCopyVisitor<'cx, 'tcx> {
+    cx: &'cx LateContext<'tcx>,
+    binding_hir_ids: Vec<HirId>,
+    clone_or_copy_needed: bool,
+    addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
+}
+
+impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
+    type NestedFilter = nested_filter::OnlyBodies;
+
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.cx.tcx.hir()
+    }
+
+    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+        walk_expr(self, expr);
+        if self.is_binding(expr) {
+            if let Some(parent) = get_parent_expr(self.cx, expr) {
+                match parent.kind {
+                    ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
+                        self.addr_of_exprs.push(parent);
+                        return;
+                    },
+                    ExprKind::MethodCall(_, args, _) => {
+                        if_chain! {
+                            if args.iter().skip(1).all(|arg| !self.is_binding(arg));
+                            if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
+                            let method_ty = self.cx.tcx.type_of(method_def_id);
+                            let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
+                            if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
+                            then {
+                                return;
+                            }
+                        }
+                    },
+                    _ => {},
+                }
+            }
+            self.clone_or_copy_needed = true;
+        }
+    }
+}
+
+impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
+    fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
+        self.binding_hir_ids
+            .iter()
+            .any(|hir_id| path_to_local_id(expr, *hir_id))
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
index a57dc2b2798..5eb7b0f0521 100644
--- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
@@ -199,7 +199,12 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
                     let sugg = |diag: &mut Diagnostic| {
                         if let ty::Adt(def, ..) = ty.kind() {
                             if let Some(span) = cx.tcx.hir().span_if_local(def.did()) {
-                                if can_type_implement_copy(cx.tcx, cx.param_env, ty, traits::ObligationCause::dummy_with_span(span)).is_ok() {
+                                if can_type_implement_copy(
+                                    cx.tcx,
+                                    cx.param_env,
+                                    ty,
+                                    traits::ObligationCause::dummy_with_span(span),
+                                ).is_ok() {
                                     diag.span_help(span, "consider marking this type as `Copy`");
                                 }
                             }
diff --git a/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs b/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
new file mode 100644
index 00000000000..b828d9334ee
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
@@ -0,0 +1,668 @@
+use std::collections::VecDeque;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use itertools::{izip, Itertools};
+use rustc_ast::{walk_list, Label, Mutability};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
+use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::{
+    Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment,
+    QPath, Stmt, StmtKind, TyKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_middle::ty::{Ty, TyCtxt, TypeckResults};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for arguments that are only used in recursion with no side-effects.
+    ///
+    /// ### Why is this bad?
+    /// It could contain a useless calculation and can make function simpler.
+    ///
+    /// The arguments can be involved in calculations and assignments but as long as
+    /// the calculations have no side-effects (function calls or mutating dereference)
+    /// and the assigned variables are also only in recursion, it is useless.
+    ///
+    /// ### Known problems
+    /// In some cases, this would not catch all useless arguments.
+    ///
+    /// ```rust
+    /// fn foo(a: usize, b: usize) -> usize {
+    ///     let f = |x| x + 1;
+    ///
+    ///     if a == 0 {
+    ///         1
+    ///     } else {
+    ///         foo(a - 1, f(b))
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// For example, the argument `b` is only used in recursion, but the lint would not catch it.
+    ///
+    /// List of some examples that can not be caught:
+    /// - binary operation of non-primitive types
+    /// - closure usage
+    /// - some `break` relative operations
+    /// - struct pattern binding
+    ///
+    /// Also, when you recurse the function name with path segments, it is not possible to detect.
+    ///
+    /// ### Example
+    /// ```rust
+    /// fn f(a: usize, b: usize) -> usize {
+    ///     if a == 0 {
+    ///         1
+    ///     } else {
+    ///         f(a - 1, b + 1)
+    ///     }
+    /// }
+    /// # fn main() {
+    /// #     print!("{}", f(1, 1));
+    /// # }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// fn f(a: usize) -> usize {
+    ///     if a == 0 {
+    ///         1
+    ///     } else {
+    ///         f(a - 1)
+    ///     }
+    /// }
+    /// # fn main() {
+    /// #     print!("{}", f(1));
+    /// # }
+    /// ```
+    #[clippy::version = "1.60.0"]
+    pub ONLY_USED_IN_RECURSION,
+    complexity,
+    "arguments that is only used in recursion can be removed"
+}
+declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
+
+impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        kind: FnKind<'tcx>,
+        decl: &'tcx rustc_hir::FnDecl<'tcx>,
+        body: &'tcx Body<'tcx>,
+        _: Span,
+        id: HirId,
+    ) {
+        if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind {
+            let def_id = id.owner.to_def_id();
+            let data = cx.tcx.def_path(def_id).data;
+
+            if data.len() > 1 {
+                match data.get(data.len() - 2) {
+                    Some(DisambiguatedDefPathData {
+                        data: DefPathData::Impl,
+                        disambiguator,
+                    }) if *disambiguator != 0 => return,
+                    _ => {},
+                }
+            }
+
+            let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None);
+
+            let ty_res = cx.typeck_results();
+            let param_span = body
+                .params
+                .iter()
+                .flat_map(|param| {
+                    let mut v = Vec::new();
+                    param.pat.each_binding(|_, hir_id, span, ident| {
+                        v.push((hir_id, span, ident));
+                    });
+                    v
+                })
+                .skip(if has_self { 1 } else { 0 })
+                .filter(|(_, _, ident)| !ident.name.as_str().starts_with('_'))
+                .collect_vec();
+
+            let params = body.params.iter().map(|param| param.pat).collect();
+
+            let mut visitor = SideEffectVisit {
+                graph: FxHashMap::default(),
+                has_side_effect: FxHashSet::default(),
+                ret_vars: Vec::new(),
+                contains_side_effect: false,
+                break_vars: FxHashMap::default(),
+                params,
+                fn_ident: ident,
+                fn_def_id: def_id,
+                is_method: matches!(kind, FnKind::Method(..)),
+                has_self,
+                ty_res,
+                ty_ctx: cx.tcx,
+            };
+
+            visitor.visit_expr(&body.value);
+            let vars = std::mem::take(&mut visitor.ret_vars);
+            // this would set the return variables to side effect
+            visitor.add_side_effect(vars);
+
+            let mut queue = visitor.has_side_effect.iter().copied().collect::<VecDeque<_>>();
+
+            // a simple BFS to check all the variables that have side effect
+            while let Some(id) = queue.pop_front() {
+                if let Some(next) = visitor.graph.get(&id) {
+                    for i in next {
+                        if !visitor.has_side_effect.contains(i) {
+                            visitor.has_side_effect.insert(*i);
+                            queue.push_back(*i);
+                        }
+                    }
+                }
+            }
+
+            for (id, span, ident) in param_span {
+                // if the variable is not used in recursion, it would be marked as unused
+                if !visitor.has_side_effect.contains(&id) {
+                    let mut queue = VecDeque::new();
+                    let mut visited = FxHashSet::default();
+
+                    queue.push_back(id);
+
+                    // a simple BFS to check the graph can reach to itself
+                    // if it can't, it means the variable is never used in recursion
+                    while let Some(id) = queue.pop_front() {
+                        if let Some(next) = visitor.graph.get(&id) {
+                            for i in next {
+                                if !visited.contains(i) {
+                                    visited.insert(id);
+                                    queue.push_back(*i);
+                                }
+                            }
+                        }
+                    }
+
+                    if visited.contains(&id) {
+                        span_lint_and_sugg(
+                            cx,
+                            ONLY_USED_IN_RECURSION,
+                            span,
+                            "parameter is only used in recursion",
+                            "if this is intentional, prefix with an underscore",
+                            format!("_{}", ident.name.as_str()),
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
+
+pub fn is_primitive(ty: Ty<'_>) -> bool {
+    match ty.kind() {
+        ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
+        ty::Ref(_, t, _) => is_primitive(*t),
+        _ => false,
+    }
+}
+
+pub fn is_array(ty: Ty<'_>) -> bool {
+    match ty.kind() {
+        ty::Array(..) | ty::Slice(..) => true,
+        ty::Ref(_, t, _) => is_array(*t),
+        _ => false,
+    }
+}
+
+/// This builds the graph of side effect.
+/// The edge `a -> b` means if `a` has side effect, `b` will have side effect.
+///
+/// There are some exmaple in following code:
+/// ```rust, ignore
+/// let b = 1;
+/// let a = b; // a -> b
+/// let (c, d) = (a, b); // c -> b, d -> b
+///
+/// let e = if a == 0 { // e -> a
+///     c // e -> c
+/// } else {
+///     d // e -> d
+/// };
+/// ```
+pub struct SideEffectVisit<'tcx> {
+    graph: FxHashMap<HirId, FxHashSet<HirId>>,
+    has_side_effect: FxHashSet<HirId>,
+    // bool for if the variable was dereferenced from mutable reference
+    ret_vars: Vec<(HirId, bool)>,
+    contains_side_effect: bool,
+    // break label
+    break_vars: FxHashMap<Ident, Vec<(HirId, bool)>>,
+    params: Vec<&'tcx Pat<'tcx>>,
+    fn_ident: Ident,
+    fn_def_id: DefId,
+    is_method: bool,
+    has_self: bool,
+    ty_res: &'tcx TypeckResults<'tcx>,
+    ty_ctx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
+    fn visit_block(&mut self, b: &'tcx Block<'tcx>) {
+        b.stmts.iter().for_each(|stmt| {
+            self.visit_stmt(stmt);
+            self.ret_vars.clear();
+        });
+        walk_list!(self, visit_expr, b.expr);
+    }
+
+    fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
+        match s.kind {
+            StmtKind::Local(Local {
+                pat, init: Some(init), ..
+            }) => {
+                self.visit_pat_expr(pat, init, false);
+                self.ret_vars.clear();
+            },
+            StmtKind::Item(i) => {
+                let item = self.ty_ctx.hir().item(i);
+                self.visit_item(item);
+                self.ret_vars.clear();
+            },
+            StmtKind::Expr(e) | StmtKind::Semi(e) => {
+                self.visit_expr(e);
+                self.ret_vars.clear();
+            },
+            StmtKind::Local(_) => {},
+        }
+    }
+
+    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+        match ex.kind {
+            ExprKind::Array(exprs) | ExprKind::Tup(exprs) => {
+                self.ret_vars = exprs
+                    .iter()
+                    .flat_map(|expr| {
+                        self.visit_expr(expr);
+                        std::mem::take(&mut self.ret_vars)
+                    })
+                    .collect();
+            },
+            ExprKind::Call(callee, args) => self.visit_fn(callee, args),
+            ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args),
+            ExprKind::Binary(_, lhs, rhs) => {
+                self.visit_bin_op(lhs, rhs);
+            },
+            ExprKind::Unary(op, expr) => self.visit_un_op(op, expr),
+            ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false),
+            ExprKind::If(bind, then_expr, else_expr) => {
+                self.visit_if(bind, then_expr, else_expr);
+            },
+            ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms),
+            // since analysing the closure is not easy, just set all variables in it to side-effect
+            ExprKind::Closure(_, _, body_id, _, _) => {
+                let body = self.ty_ctx.hir().body(body_id);
+                self.visit_body(body);
+                let vars = std::mem::take(&mut self.ret_vars);
+                self.add_side_effect(vars);
+            },
+            ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => {
+                self.visit_block_label(block, label);
+            },
+            ExprKind::Assign(bind, expr, _) => {
+                self.visit_assign(bind, expr);
+            },
+            ExprKind::AssignOp(_, bind, expr) => {
+                self.visit_assign(bind, expr);
+                self.visit_bin_op(bind, expr);
+            },
+            ExprKind::Field(expr, _) => {
+                self.visit_expr(expr);
+                if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
+                    self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
+                }
+            },
+            ExprKind::Index(expr, index) => {
+                self.visit_expr(expr);
+                let mut vars = std::mem::take(&mut self.ret_vars);
+                self.visit_expr(index);
+                self.ret_vars.append(&mut vars);
+
+                if !is_array(self.ty_res.expr_ty(expr)) {
+                    self.add_side_effect(self.ret_vars.clone());
+                } else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
+                    self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
+                }
+            },
+            ExprKind::Break(dest, Some(expr)) => {
+                self.visit_expr(expr);
+                if let Some(label) = dest.label {
+                    self.break_vars
+                        .entry(label.ident)
+                        .or_insert(Vec::new())
+                        .append(&mut self.ret_vars);
+                }
+                self.contains_side_effect = true;
+            },
+            ExprKind::Ret(Some(expr)) => {
+                self.visit_expr(expr);
+                let vars = std::mem::take(&mut self.ret_vars);
+                self.add_side_effect(vars);
+                self.contains_side_effect = true;
+            },
+            ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => {
+                self.contains_side_effect = true;
+            },
+            ExprKind::Struct(_, exprs, expr) => {
+                let mut ret_vars = exprs
+                    .iter()
+                    .flat_map(|field| {
+                        self.visit_expr(field.expr);
+                        std::mem::take(&mut self.ret_vars)
+                    })
+                    .collect();
+
+                walk_list!(self, visit_expr, expr);
+                self.ret_vars.append(&mut ret_vars);
+            },
+            _ => walk_expr(self, ex),
+        }
+    }
+
+    fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) {
+        if let Res::Local(id) = path.res {
+            self.ret_vars.push((id, false));
+        }
+    }
+}
+
+impl<'tcx> SideEffectVisit<'tcx> {
+    fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
+        // Just support array and tuple unwrapping for now.
+        //
+        // ex) `(a, b) = (c, d);`
+        // The graph would look like this:
+        //   a -> c
+        //   b -> d
+        //
+        // This would minimize the connection of the side-effect graph.
+        match (&lhs.kind, &rhs.kind) {
+            (ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => {
+                // if not, it is a compile error
+                debug_assert!(lhs.len() == rhs.len());
+                izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs));
+            },
+            // in other assigns, we have to connect all each other
+            // because they can be connected somehow
+            _ => {
+                self.visit_expr(lhs);
+                let lhs_vars = std::mem::take(&mut self.ret_vars);
+                self.visit_expr(rhs);
+                let rhs_vars = std::mem::take(&mut self.ret_vars);
+                self.connect_assign(&lhs_vars, &rhs_vars, false);
+            },
+        }
+    }
+
+    fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option<Label>) {
+        self.visit_block(block);
+        let _ = label.and_then(|label| {
+            self.break_vars
+                .remove(&label.ident)
+                .map(|mut break_vars| self.ret_vars.append(&mut break_vars))
+        });
+    }
+
+    fn visit_bin_op(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
+        self.visit_expr(lhs);
+        let mut ret_vars = std::mem::take(&mut self.ret_vars);
+        self.visit_expr(rhs);
+        self.ret_vars.append(&mut ret_vars);
+
+        // the binary operation between non primitive values are overloaded operators
+        // so they can have side-effects
+        if !is_primitive(self.ty_res.expr_ty(lhs)) || !is_primitive(self.ty_res.expr_ty(rhs)) {
+            self.ret_vars.iter().for_each(|id| {
+                self.has_side_effect.insert(id.0);
+            });
+            self.contains_side_effect = true;
+        }
+    }
+
+    fn visit_un_op(&mut self, op: UnOp, expr: &'tcx Expr<'tcx>) {
+        self.visit_expr(expr);
+        let ty = self.ty_res.expr_ty(expr);
+        // dereferencing a reference has no side-effect
+        if !is_primitive(ty) && !matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(..))) {
+            self.add_side_effect(self.ret_vars.clone());
+        }
+
+        if matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(_, _, Mutability::Mut))) {
+            self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
+        }
+    }
+
+    fn visit_pat_expr(&mut self, pat: &'tcx Pat<'tcx>, expr: &'tcx Expr<'tcx>, connect_self: bool) {
+        match (&pat.kind, &expr.kind) {
+            (PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) => {
+                self.ret_vars = izip!(*pats, *exprs)
+                    .flat_map(|(pat, expr)| {
+                        self.visit_pat_expr(pat, expr, connect_self);
+                        std::mem::take(&mut self.ret_vars)
+                    })
+                    .collect();
+            },
+            (PatKind::Slice(front_exprs, _, back_exprs), ExprKind::Array(exprs)) => {
+                let mut vars = izip!(*front_exprs, *exprs)
+                    .flat_map(|(pat, expr)| {
+                        self.visit_pat_expr(pat, expr, connect_self);
+                        std::mem::take(&mut self.ret_vars)
+                    })
+                    .collect();
+                self.ret_vars = izip!(back_exprs.iter().rev(), exprs.iter().rev())
+                    .flat_map(|(pat, expr)| {
+                        self.visit_pat_expr(pat, expr, connect_self);
+                        std::mem::take(&mut self.ret_vars)
+                    })
+                    .collect();
+                self.ret_vars.append(&mut vars);
+            },
+            _ => {
+                let mut lhs_vars = Vec::new();
+                pat.each_binding(|_, id, _, _| lhs_vars.push((id, false)));
+                self.visit_expr(expr);
+                let rhs_vars = std::mem::take(&mut self.ret_vars);
+                self.connect_assign(&lhs_vars, &rhs_vars, connect_self);
+                self.ret_vars = rhs_vars;
+            },
+        }
+    }
+
+    fn visit_fn(&mut self, callee: &'tcx Expr<'tcx>, args: &'tcx [Expr<'tcx>]) {
+        self.visit_expr(callee);
+        let mut ret_vars = std::mem::take(&mut self.ret_vars);
+        self.add_side_effect(ret_vars.clone());
+
+        let mut is_recursive = false;
+
+        if_chain! {
+            if !self.has_self;
+            if let ExprKind::Path(QPath::Resolved(_, path)) = callee.kind;
+            if let Res::Def(DefKind::Fn, def_id) = path.res;
+            if self.fn_def_id == def_id;
+            then {
+                is_recursive = true;
+            }
+        }
+
+        if_chain! {
+            if !self.has_self && self.is_method;
+            if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = callee.kind;
+            if segment.ident == self.fn_ident;
+            if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
+            if let Res::SelfTy{ .. } = path.res;
+            then {
+                is_recursive = true;
+            }
+        }
+
+        if is_recursive {
+            izip!(self.params.clone(), args).for_each(|(pat, expr)| {
+                self.visit_pat_expr(pat, expr, true);
+                self.ret_vars.clear();
+            });
+        } else {
+            // This would set arguments used in closure that does not have side-effect.
+            // Closure itself can be detected whether there is a side-effect, but the
+            // value of variable that is holding closure can change.
+            // So, we just check the variables.
+            self.ret_vars = args
+                .iter()
+                .flat_map(|expr| {
+                    self.visit_expr(expr);
+                    std::mem::take(&mut self.ret_vars)
+                })
+                .collect_vec()
+                .into_iter()
+                .map(|id| {
+                    self.has_side_effect.insert(id.0);
+                    id
+                })
+                .collect();
+            self.contains_side_effect = true;
+        }
+
+        self.ret_vars.append(&mut ret_vars);
+    }
+
+    fn visit_method_call(&mut self, path: &'tcx PathSegment<'tcx>, args: &'tcx [Expr<'tcx>]) {
+        if_chain! {
+            if self.is_method;
+            if path.ident == self.fn_ident;
+            if let ExprKind::Path(QPath::Resolved(_, path)) = args.first().unwrap().kind;
+            if let Res::Local(..) = path.res;
+            let ident = path.segments.last().unwrap().ident;
+            if ident.name == kw::SelfLower;
+            then {
+                izip!(self.params.clone(), args.iter())
+                    .for_each(|(pat, expr)| {
+                        self.visit_pat_expr(pat, expr, true);
+                        self.ret_vars.clear();
+                    });
+            } else {
+                self.ret_vars = args
+                    .iter()
+                    .flat_map(|expr| {
+                        self.visit_expr(expr);
+                        std::mem::take(&mut self.ret_vars)
+                    })
+                    .collect_vec()
+                    .into_iter()
+                    .map(|a| {
+                        self.has_side_effect.insert(a.0);
+                        a
+                    })
+                    .collect();
+                self.contains_side_effect = true;
+            }
+        }
+    }
+
+    fn visit_if(&mut self, bind: &'tcx Expr<'tcx>, then_expr: &'tcx Expr<'tcx>, else_expr: Option<&'tcx Expr<'tcx>>) {
+        let contains_side_effect = self.contains_side_effect;
+        self.contains_side_effect = false;
+        self.visit_expr(bind);
+        let mut vars = std::mem::take(&mut self.ret_vars);
+        self.visit_expr(then_expr);
+        let mut then_vars = std::mem::take(&mut self.ret_vars);
+        walk_list!(self, visit_expr, else_expr);
+        if self.contains_side_effect {
+            self.add_side_effect(vars.clone());
+        }
+        self.contains_side_effect |= contains_side_effect;
+        self.ret_vars.append(&mut vars);
+        self.ret_vars.append(&mut then_vars);
+    }
+
+    fn visit_match(&mut self, expr: &'tcx Expr<'tcx>, arms: &'tcx [Arm<'tcx>]) {
+        self.visit_expr(expr);
+        let mut expr_vars = std::mem::take(&mut self.ret_vars);
+        self.ret_vars = arms
+            .iter()
+            .flat_map(|arm| {
+                let contains_side_effect = self.contains_side_effect;
+                self.contains_side_effect = false;
+                // this would visit `expr` multiple times
+                // but couldn't think of a better way
+                self.visit_pat_expr(arm.pat, expr, false);
+                let mut vars = std::mem::take(&mut self.ret_vars);
+                let _ = arm.guard.as_ref().map(|guard| {
+                    self.visit_expr(match guard {
+                        Guard::If(expr) | Guard::IfLet(_, expr) => expr,
+                    });
+                    vars.append(&mut self.ret_vars);
+                });
+                self.visit_expr(arm.body);
+                if self.contains_side_effect {
+                    self.add_side_effect(vars.clone());
+                    self.add_side_effect(expr_vars.clone());
+                }
+                self.contains_side_effect |= contains_side_effect;
+                vars.append(&mut self.ret_vars);
+                vars
+            })
+            .collect();
+        self.ret_vars.append(&mut expr_vars);
+    }
+
+    fn connect_assign(&mut self, lhs: &[(HirId, bool)], rhs: &[(HirId, bool)], connect_self: bool) {
+        // if mutable dereference is on assignment it can have side-effect
+        // (this can lead to parameter mutable dereference and change the original value)
+        // too hard to detect whether this value is from parameter, so this would all
+        // check mutable dereference assignment to side effect
+        lhs.iter().filter(|(_, b)| *b).for_each(|(id, _)| {
+            self.has_side_effect.insert(*id);
+            self.contains_side_effect = true;
+        });
+
+        // there is no connection
+        if lhs.is_empty() || rhs.is_empty() {
+            return;
+        }
+
+        // by connected rhs in cycle, the connections would decrease
+        // from `n * m` to `n + m`
+        // where `n` and `m` are length of `lhs` and `rhs`.
+
+        // unwrap is possible since rhs is not empty
+        let rhs_first = rhs.first().unwrap();
+        for (id, _) in lhs.iter() {
+            if connect_self || *id != rhs_first.0 {
+                self.graph
+                    .entry(*id)
+                    .or_insert_with(FxHashSet::default)
+                    .insert(rhs_first.0);
+            }
+        }
+
+        let rhs = rhs.iter();
+        izip!(rhs.clone().cycle().skip(1), rhs).for_each(|(from, to)| {
+            if connect_self || from.0 != to.0 {
+                self.graph.entry(from.0).or_insert_with(FxHashSet::default).insert(to.0);
+            }
+        });
+    }
+
+    fn add_side_effect(&mut self, v: Vec<(HirId, bool)>) {
+        for (id, _) in v {
+            self.has_side_effect.insert(id);
+            self.contains_side_effect = true;
+        }
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/question_mark.rs b/src/tools/clippy/clippy_lints/src/question_mark.rs
index 6f634ded5fe..899568a933f 100644
--- a/src/tools/clippy/clippy_lints/src/question_mark.rs
+++ b/src/tools/clippy/clippy_lints/src/question_mark.rs
@@ -123,7 +123,7 @@ impl QuestionMark {
     }
 
     fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
-        Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
+        Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr)
     }
 
     fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
@@ -156,9 +156,9 @@ impl QuestionMark {
         }
     }
 
-    fn expression_returns_unmodified_err(cx: &LateContext<'_>, expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
+    fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
         match peel_blocks_with_stmt(expr).kind {
-            ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(cx, ret_expr, cond_expr),
+            ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr),
             ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
             _ => false,
         }
diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs
index 1f134be2cbc..1507c75ff61 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_clone.rs
+++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs
@@ -3,7 +3,7 @@ use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
 use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
 use if_chain::if_chain;
-use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation};
+use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Applicability;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{def_id, Body, FnDecl, HirId};
@@ -512,7 +512,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
 /// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
 /// possible borrowers of `a`.
 struct PossibleBorrowerVisitor<'a, 'tcx> {
-    possible_borrower: TransitiveRelation<mir::Local>,
+    possible_borrower: TransitiveRelation,
     body: &'a mir::Body<'tcx>,
     cx: &'a LateContext<'tcx>,
     possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
@@ -543,18 +543,10 @@ impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> {
                 continue;
             }
 
-            let borrowers = self.possible_borrower.reachable_from(row);
+            let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
+            borrowers.remove(mir::Local::from_usize(0));
             if !borrowers.is_empty() {
-                let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
-                for c in borrowers {
-                    if c != mir::Local::from_usize(0) {
-                        bs.insert(c);
-                    }
-                }
-
-                if !bs.is_empty() {
-                    map.insert(row, bs);
-                }
+                map.insert(row, borrowers);
             }
         }
 
@@ -644,7 +636,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
 /// For exampel, `_1 = &mut _2` generate _1: {_2,...}
 /// Known Problems: not sure all borrowed are tracked
 struct PossibleOriginVisitor<'a, 'tcx> {
-    possible_origin: TransitiveRelation<mir::Local>,
+    possible_origin: TransitiveRelation,
     body: &'a mir::Body<'tcx>,
 }
 
@@ -663,18 +655,10 @@ impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
                 continue;
             }
 
-            let borrowers = self.possible_origin.reachable_from(row);
+            let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
+            borrowers.remove(mir::Local::from_usize(0));
             if !borrowers.is_empty() {
-                let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
-                for c in borrowers {
-                    if c != mir::Local::from_usize(0) {
-                        bs.insert(c);
-                    }
-                }
-
-                if !bs.is_empty() {
-                    map.insert(row, bs);
-                }
+                map.insert(row, borrowers);
             }
         }
         map
@@ -766,3 +750,28 @@ impl PossibleBorrowerMap<'_, '_> {
         self.maybe_live.contains(local)
     }
 }
+
+#[derive(Default)]
+struct TransitiveRelation {
+    relations: FxHashMap<mir::Local, Vec<mir::Local>>,
+}
+impl TransitiveRelation {
+    fn add(&mut self, a: mir::Local, b: mir::Local) {
+        self.relations.entry(a).or_default().push(b);
+    }
+
+    fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
+        let mut seen = HybridBitSet::new_empty(domain_size);
+        let mut stack = vec![a];
+        while let Some(u) = stack.pop() {
+            if let Some(edges) = self.relations.get(&u) {
+                for &v in edges {
+                    if seen.insert(v) {
+                        stack.push(v);
+                    }
+                }
+            }
+        }
+        seen
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
index c3d984fb5ae..940a8428f77 100644
--- a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
+++ b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
@@ -399,9 +399,9 @@ fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
 
 fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
     match (target_opt, source_opt) {
-        (Some(mut target), Some(mut source)) => {
+        (Some(mut target), Some(source)) => {
             target.reserve(source.len());
-            for op in source.drain(..) {
+            for op in source {
                 target.push(op);
             }
             Some(target)
@@ -436,9 +436,9 @@ fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Exp
             chained_binops_helper(left_left, left_right),
             chained_binops_helper(right_left, right_right),
         ) {
-            (Some(mut left_ops), Some(mut right_ops)) => {
+            (Some(mut left_ops), Some(right_ops)) => {
                 left_ops.reserve(right_ops.len());
-                for op in right_ops.drain(..) {
+                for op in right_ops {
                     left_ops.push(op);
                 }
                 Some(left_ops)
diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs
index 1da3b765904..23cb9d40dfd 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs
@@ -364,6 +364,10 @@ declare_clippy_lint! {
     /// ### Why is this bad?
     /// The results of such a transmute are not defined.
     ///
+    /// ### Known problems
+    /// This lint has had multiple problems in the past and was moved to `nursery`. See issue
+    /// [#8496](https://github.com/rust-lang/rust-clippy/issues/8496) for more details.
+    ///
     /// ### Example
     /// ```rust
     /// struct Foo<T>(u32, T);
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs b/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs
index e6c260ed96a..d371cafb16b 100644
--- a/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs
+++ b/src/tools/clippy/clippy_lints/src/unnecessary_sort_by.rs
@@ -67,59 +67,51 @@ struct SortByKeyDetection {
 
 /// Detect if the two expressions are mirrored (identical, except one
 /// contains a and the other replaces it with b)
-fn mirrored_exprs(
-    cx: &LateContext<'_>,
-    a_expr: &Expr<'_>,
-    a_ident: &Ident,
-    b_expr: &Expr<'_>,
-    b_ident: &Ident,
-) -> bool {
+fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool {
     match (&a_expr.kind, &b_expr.kind) {
         // Two boxes with mirrored contents
         (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => {
-            mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
+            mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
         },
         // Two arrays with mirrored contents
         (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => {
-            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // The two exprs are function calls.
         // Check to see that the function itself and its arguments are mirrored
         (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => {
-            mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
-                && iter::zip(*left_args, *right_args)
-                    .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+            mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
+                && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // The two exprs are method calls.
         // Check to see that the function is the same and the arguments are mirrored
         // This is enough because the receiver of the method is listed in the arguments
         (ExprKind::MethodCall(left_segment, left_args, _), ExprKind::MethodCall(right_segment, right_args, _)) => {
             left_segment.ident == right_segment.ident
-                && iter::zip(*left_args, *right_args)
-                    .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+                && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // Two tuples with mirrored contents
         (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => {
-            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
+            iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
         },
         // Two binary ops, which are the same operation and which have mirrored arguments
         (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => {
             left_op.node == right_op.node
-                && mirrored_exprs(cx, left_left, a_ident, right_left, b_ident)
-                && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident)
+                && mirrored_exprs(left_left, a_ident, right_left, b_ident)
+                && mirrored_exprs(left_right, a_ident, right_right, b_ident)
         },
         // Two unary ops, which are the same operation and which have the same argument
         (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => {
-            left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
+            left_op == right_op && mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
         },
         // The two exprs are literals of some kind
         (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node,
-        (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(cx, left, a_ident, right, b_ident),
+        (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, a_ident, right, b_ident),
         (ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => {
-            mirrored_exprs(cx, left_block, a_ident, right_block, b_ident)
+            mirrored_exprs(left_block, a_ident, right_block, b_ident)
         },
         (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => {
-            left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident)
+            left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, right_ident)
         },
         // Two paths: either one is a and the other is b, or they're identical to each other
         (
@@ -151,11 +143,9 @@ fn mirrored_exprs(
         (
             ExprKind::AddrOf(left_kind, Mutability::Not, left_expr),
             ExprKind::AddrOf(right_kind, Mutability::Not, right_expr),
-        ) => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident),
-        (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => {
-            mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident)
-        },
-        (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident),
+        ) => left_kind == right_kind && mirrored_exprs(left_expr, a_ident, right_expr, b_ident),
+        (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => mirrored_exprs(a_expr, a_ident, right_expr, b_ident),
+        (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(left_expr, a_ident, b_expr, b_ident),
         _ => false,
     }
 }
@@ -176,14 +166,13 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
         if method_path.ident.name == sym::cmp;
         then {
             let (closure_body, closure_arg, reverse) = if mirrored_exprs(
-                cx,
                 left_expr,
                 left_ident,
                 right_expr,
                 right_ident
             ) {
                 (Sugg::hir(cx, left_expr, "..").to_string(), left_ident.name.to_string(), false)
-            } else if mirrored_exprs(cx, left_expr, right_ident, right_expr, left_ident) {
+            } else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) {
                 (Sugg::hir(cx, left_expr, "..").to_string(), right_ident.name.to_string(), true)
             } else {
                 return None;
@@ -239,7 +228,7 @@ impl LateLintPass<'_> for UnnecessarySortBy {
                     if trigger.unstable { "_unstable" } else { "" },
                     trigger.closure_arg,
                     if trigger.reverse {
-                        format!("Reverse({})", trigger.closure_body)
+                        format!("std::cmp::Reverse({})", trigger.closure_body)
                     } else {
                         trigger.closure_body.to_string()
                     },
diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs
index 80164c59ba7..09d671e1118 100644
--- a/src/tools/clippy/clippy_lints/src/use_self.rs
+++ b/src/tools/clippy/clippy_lints/src/use_self.rs
@@ -9,7 +9,8 @@ use rustc_hir::{
     def::{CtorOf, DefKind, Res},
     def_id::LocalDefId,
     intravisit::{walk_inf, walk_ty, Visitor},
-    Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Path, QPath, TyKind,
+    Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath,
+    TyKind,
 };
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_semver::RustcVersion;
@@ -252,6 +253,22 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
         }
     }
 
+    fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
+        if_chain! {
+            if !pat.span.from_expansion();
+            if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+            if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+            if let PatKind::Path(QPath::Resolved(_, path)) = pat.kind;
+            if !matches!(path.res, Res::SelfTy { .. } | Res::Def(DefKind::TyParam, _));
+            if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id);
+            if let [first, ..] = path.segments;
+            if let Some(hir_id) = first.hir_id;
+            then {
+                span_lint(cx, cx.tcx.hir().span(hir_id));
+            }
+        }
+    }
+
     extract_msrv_attr!(LateContext);
 }
 
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs
index 4433d5f5bf1..b3b241392fe 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs
@@ -25,7 +25,7 @@ use rustc_hir::{
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::mir::interpret::ConstValue;
-use rustc_middle::ty;
+use rustc_middle::ty::{self, subst::GenericArgKind};
 use rustc_semver::RustcVersion;
 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::Spanned;
@@ -337,6 +337,15 @@ declare_clippy_lint! {
     "found clippy lint without `clippy::version` attribute"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
+    ///
+    pub MISSING_MSRV_ATTR_IMPL,
+    internal,
+    "checking if all necessary steps were taken when adding a MSRV to a lint"
+}
+
 declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
 
 impl EarlyLintPass for ClippyLintsInternal {
@@ -1314,3 +1323,46 @@ fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: S
         span.parent(),
     )
 }
+
+declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]);
+
+impl LateLintPass<'_> for MsrvAttrImpl {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if_chain! {
+            if let hir::ItemKind::Impl(hir::Impl {
+                of_trait: Some(lint_pass_trait_ref),
+                self_ty,
+                items,
+                ..
+            }) = &item.kind;
+            if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
+            let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
+            if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
+            let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
+            if let ty::Adt(self_ty_def, _) = self_ty.kind();
+            if self_ty_def.is_struct();
+            if self_ty_def.all_fields().any(|f| {
+                cx.tcx
+                    .type_of(f.did)
+                    .walk()
+                    .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
+                    .any(|t| match_type(cx, t.expect_ty(), &paths::RUSTC_VERSION))
+            });
+            if !items.iter().any(|item| item.ident.name == sym!(enter_lint_attrs));
+            then {
+                let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
+                let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
+                let span = cx.sess().source_map().span_through_char(item.span, '{');
+                span_lint_and_sugg(
+                    cx,
+                    MISSING_MSRV_ATTR_IMPL,
+                    span,
+                    &format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
+                    &format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
+                    format!("{}\n    extract_msrv_attr!({context});", snippet(cx, span, "..")),
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+    }
+}
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
index 56633490eaa..a617422bbeb 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
@@ -473,7 +473,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
     /// ```
     fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
         if let Some(args) = match_lint_emission(cx, expr) {
-            let mut emission_info = extract_emission_info(cx, args);
+            let emission_info = extract_emission_info(cx, args);
             if emission_info.is_empty() {
                 // See:
                 // - src/misc.rs:734:9
@@ -483,7 +483,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
                 return;
             }
 
-            for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
+            for (lint_name, applicability, is_multi_part) in emission_info {
                 let app_info = self.applicability_info.entry(lint_name).or_default();
                 app_info.applicability = applicability;
                 app_info.is_multi_part_suggestion = is_multi_part;
@@ -693,7 +693,7 @@ fn extract_emission_info<'hir>(
     }
 
     lints
-        .drain(..)
+        .into_iter()
         .map(|lint_name| (lint_name, applicability, multi_part))
         .collect()
 }
diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs
index a328ddda5ae..532bd810a2e 100644
--- a/src/tools/clippy/clippy_lints/src/write.rs
+++ b/src/tools/clippy/clippy_lints/src/write.rs
@@ -7,7 +7,7 @@ use clippy_utils::source::{snippet_opt, snippet_with_applicability};
 use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
 use rustc_ast::token::{self, LitKind};
 use rustc_ast::tokenstream::TokenStream;
-use rustc_errors::Applicability;
+use rustc_errors::{Applicability, DiagnosticBuilder};
 use rustc_lexer::unescape::{self, EscapeError};
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_parse::parser;
@@ -534,7 +534,7 @@ impl Write {
             match parser
                 .parse_expr()
                 .map(rustc_ast::ptr::P::into_inner)
-                .map_err(|e| e.cancel())
+                .map_err(DiagnosticBuilder::cancel)
             {
                 // write!(e, ...)
                 Ok(p) if parser.eat(&token::Comma) => Some(p),
@@ -563,7 +563,7 @@ impl Write {
             }
 
             let comma_span = parser.prev_token.span;
-            let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|err| err.cancel()) {
+            let token_expr = if let Ok(expr) = parser.parse_expr().map_err(DiagnosticBuilder::cancel) {
                 expr
             } else {
                 return (Some(fmtstr), None);
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 3a47845ec82..3fce4987679 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -279,8 +279,22 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
         (ForeignMod(l), ForeignMod(r)) => {
             both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
         },
-        (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt, .. }),
-         TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt, .. })) => {
+        (
+            TyAlias(box ast::TyAlias {
+                defaultness: ld,
+                generics: lg,
+                bounds: lb,
+                ty: lt,
+                ..
+            }),
+            TyAlias(box ast::TyAlias {
+                defaultness: rd,
+                generics: rg,
+                bounds: rb,
+                ty: rt,
+                ..
+            }),
+        ) => {
             eq_defaultness(*ld, *rd)
                 && eq_generics(lg, rg)
                 && over(lb, rb, eq_generic_bound)
@@ -370,8 +384,22 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
         ) => {
             eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
         },
-        (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt, .. }),
-         TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt, .. })) => {
+        (
+            TyAlias(box ast::TyAlias {
+                defaultness: ld,
+                generics: lg,
+                bounds: lb,
+                ty: lt,
+                ..
+            }),
+            TyAlias(box ast::TyAlias {
+                defaultness: rd,
+                generics: rg,
+                bounds: rb,
+                ty: rt,
+                ..
+            }),
+        ) => {
             eq_defaultness(*ld, *rd)
                 && eq_generics(lg, rg)
                 && over(lb, rb, eq_generic_bound)
@@ -402,8 +430,22 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
         ) => {
             eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
         },
-        (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt, .. }),
-         TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt, .. })) => {
+        (
+            TyAlias(box ast::TyAlias {
+                defaultness: ld,
+                generics: lg,
+                bounds: lb,
+                ty: lt,
+                ..
+            }),
+            TyAlias(box ast::TyAlias {
+                defaultness: rd,
+                generics: rg,
+                bounds: rb,
+                ty: rt,
+                ..
+            }),
+        ) => {
             eq_defaultness(*ld, *rd)
                 && eq_generics(lg, rg)
                 && over(lb, rb, eq_generic_bound)
diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs
index 42b9e692d3f..1d6f7acab13 100644
--- a/src/tools/clippy/clippy_utils/src/consts.rs
+++ b/src/tools/clippy/clippy_utils/src/consts.rs
@@ -593,7 +593,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
         ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty().kind() {
             ty::Ref(_, tam, _) => match tam.kind() {
                 ty::Str => String::from_utf8(
-                    data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end)
+                    data.inner()
+                        .inspect_with_uninit_and_ptr_outside_interpreter(start..end)
                         .to_owned(),
                 )
                 .ok()
@@ -605,7 +606,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
         ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty().kind() {
             ty::Array(sub_type, len) => match sub_type.kind() {
                 ty::Float(FloatTy::F32) => match miri_to_const(*len) {
-                    Some(Constant::Int(len)) => alloc.inner()
+                    Some(Constant::Int(len)) => alloc
+                        .inner()
                         .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
                         .to_owned()
                         .chunks(4)
@@ -619,7 +621,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
                     _ => None,
                 },
                 ty::Float(FloatTy::F64) => match miri_to_const(*len) {
-                    Some(Constant::Int(len)) => alloc.inner()
+                    Some(Constant::Int(len)) => alloc
+                        .inner()
                         .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
                         .to_owned()
                         .chunks(8)
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index 9654895060f..00594f4d42a 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -74,6 +74,10 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
         self.inter_expr().eq_expr(left, right)
     }
 
+    pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+        self.inter_expr().eq_path(left, right)
+    }
+
     pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
         self.inter_expr().eq_path_segment(left, right)
     }
@@ -362,7 +366,7 @@ impl HirEqInterExpr<'_, '_, '_> {
         }
     }
 
-    fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+    pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
         match (left.res, right.res) {
             (Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
             (Res::Local(_), _) | (_, Res::Local(_)) => false,
diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs
index a5b409ad96b..fce93153d96 100644
--- a/src/tools/clippy/clippy_utils/src/msrvs.rs
+++ b/src/tools/clippy/clippy_utils/src/msrvs.rs
@@ -20,7 +20,7 @@ msrv_aliases! {
     1,46,0 { CONST_IF_MATCH }
     1,45,0 { STR_STRIP_PREFIX }
     1,43,0 { LOG2_10, LOG10_2 }
-    1,42,0 { MATCHES_MACRO, SLICE_PATTERNS }
+    1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
     1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
     1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
     1,38,0 { POINTER_CAST }
diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs
index 2778e30388e..6f56f8d5136 100644
--- a/src/tools/clippy/clippy_utils/src/paths.rs
+++ b/src/tools/clippy/clippy_utils/src/paths.rs
@@ -32,6 +32,8 @@ pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
 pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
 #[cfg(feature = "internal")]
 pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+#[cfg(feature = "internal")]
+pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
 pub const EXIT: [&str; 3] = ["std", "process", "exit"];
 pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
 pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
@@ -67,6 +69,8 @@ pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
 #[cfg(feature = "internal")]
 pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
 #[cfg(feature = "internal")]
+pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
+#[cfg(feature = "internal")]
 pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
 pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
 pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
@@ -126,6 +130,8 @@ pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet",
 pub const RESULT: [&str; 3] = ["core", "result", "Result"];
 pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
 pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+#[cfg(feature = "internal")]
+pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
 pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
 pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
 pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs
index 3645a9a5228..e3fc76f4e1a 100644
--- a/src/tools/clippy/clippy_utils/src/ty.rs
+++ b/src/tools/clippy/clippy_utils/src/ty.rs
@@ -294,7 +294,11 @@ pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symb
 /// Returns `false` if the `LangItem` is not defined.
 pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
     match ty.kind() {
-        ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).map_or(false, |li| li == adt.did()),
+        ty::Adt(adt, _) => cx
+            .tcx
+            .lang_items()
+            .require(lang_item)
+            .map_or(false, |li| li == adt.did()),
         _ => false,
     }
 }
diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain
index 4d2c5761991..9d5da4ed68f 100644
--- a/src/tools/clippy/rust-toolchain
+++ b/src/tools/clippy/rust-toolchain
@@ -1,3 +1,3 @@
 [toolchain]
-channel = "nightly-2022-02-24"
+channel = "nightly-2022-03-14"
 components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
diff --git a/src/tools/clippy/rustfmt.toml b/src/tools/clippy/rustfmt.toml
index 4b415a31b27..10d39762043 100644
--- a/src/tools/clippy/rustfmt.toml
+++ b/src/tools/clippy/rustfmt.toml
@@ -2,6 +2,6 @@ max_width = 120
 comment_width = 100
 match_block_trailing_comma = true
 wrap_comments = true
-edition = "2018"
+edition = "2021"
 error_on_line_overflow = true
 version = "Two"
diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs
index 6bc74bc1e9a..c9710e3db8e 100644
--- a/src/tools/clippy/tests/compile-test.rs
+++ b/src/tools/clippy/tests/compile-test.rs
@@ -34,6 +34,7 @@ static TEST_DEPENDENCIES: &[&str] = &[
     "syn",
     "tokio",
     "parking_lot",
+    "rustc_semver",
 ];
 
 // Test dependencies may need an `extern crate` here to ensure that they show up
@@ -53,6 +54,8 @@ extern crate parking_lot;
 #[allow(unused_extern_crates)]
 extern crate quote;
 #[allow(unused_extern_crates)]
+extern crate rustc_semver;
+#[allow(unused_extern_crates)]
 extern crate syn;
 #[allow(unused_extern_crates)]
 extern crate tokio;
@@ -165,7 +168,11 @@ fn run_ui() {
     let _threads = VarGuard::set(
         "RUST_TEST_THREADS",
         // if RUST_TEST_THREADS is set, adhere to it, otherwise override it
-        env::var("RUST_TEST_THREADS").unwrap_or_else(|_| num_cpus::get().to_string()),
+        env::var("RUST_TEST_THREADS").unwrap_or_else(|_| {
+            std::thread::available_parallelism()
+                .map_or(1, std::num::NonZeroUsize::get)
+                .to_string()
+        }),
     );
     compiletest::run_tests(&config);
 }
diff --git a/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.fixed b/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.fixed
new file mode 100644
index 00000000000..900a8fffd40
--- /dev/null
+++ b/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.fixed
@@ -0,0 +1,40 @@
+// run-rustfix
+
+#![deny(clippy::internal)]
+#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_ast;
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+use clippy_utils::extract_msrv_attr;
+use rustc_hir::Expr;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+
+declare_lint! {
+    pub TEST_LINT,
+    Warn,
+    ""
+}
+
+struct Pass {
+    msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(Pass => [TEST_LINT]);
+
+impl LateLintPass<'_> for Pass {
+    extract_msrv_attr!(LateContext);
+    fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
+}
+
+impl EarlyLintPass for Pass {
+    extract_msrv_attr!(EarlyContext);
+    fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
+}
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.rs b/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.rs
new file mode 100644
index 00000000000..4bc8164db67
--- /dev/null
+++ b/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.rs
@@ -0,0 +1,38 @@
+// run-rustfix
+
+#![deny(clippy::internal)]
+#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_ast;
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+use clippy_utils::extract_msrv_attr;
+use rustc_hir::Expr;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+
+declare_lint! {
+    pub TEST_LINT,
+    Warn,
+    ""
+}
+
+struct Pass {
+    msrv: Option<RustcVersion>,
+}
+
+impl_lint_pass!(Pass => [TEST_LINT]);
+
+impl LateLintPass<'_> for Pass {
+    fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
+}
+
+impl EarlyLintPass for Pass {
+    fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
+}
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.stderr b/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.stderr
new file mode 100644
index 00000000000..ddc06f0be1b
--- /dev/null
+++ b/src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.stderr
@@ -0,0 +1,32 @@
+error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation
+  --> $DIR/invalid_msrv_attr_impl.rs:30:1
+   |
+LL | impl LateLintPass<'_> for Pass {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/invalid_msrv_attr_impl.rs:3:9
+   |
+LL | #![deny(clippy::internal)]
+   |         ^^^^^^^^^^^^^^^^
+   = note: `#[deny(clippy::missing_msrv_attr_impl)]` implied by `#[deny(clippy::internal)]`
+help: add `extract_msrv_attr!(LateContext)` to the `LateLintPass` implementation
+   |
+LL + impl LateLintPass<'_> for Pass {
+LL +     extract_msrv_attr!(LateContext);
+   |
+
+error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
+  --> $DIR/invalid_msrv_attr_impl.rs:34:1
+   |
+LL | impl EarlyLintPass for Pass {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: add `extract_msrv_attr!(EarlyContext)` to the `EarlyLintPass` implementation
+   |
+LL + impl EarlyLintPass for Pass {
+LL +     extract_msrv_attr!(EarlyContext);
+   |
+
+error: aborting due to 2 previous errors
+
diff --git a/src/tools/clippy/tests/ui/allow_attributes_without_reason.rs b/src/tools/clippy/tests/ui/allow_attributes_without_reason.rs
new file mode 100644
index 00000000000..1a0d4e88657
--- /dev/null
+++ b/src/tools/clippy/tests/ui/allow_attributes_without_reason.rs
@@ -0,0 +1,14 @@
+#![feature(lint_reasons)]
+#![deny(clippy::allow_attributes_without_reason)]
+
+// These should trigger the lint
+#[allow(dead_code)]
+#[allow(dead_code, deprecated)]
+// These should be fine
+#[allow(dead_code, reason = "This should be allowed")]
+#[warn(dyn_drop, reason = "Warnings can also have reasons")]
+#[warn(deref_nullptr)]
+#[deny(deref_nullptr)]
+#[forbid(deref_nullptr)]
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr b/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr
new file mode 100644
index 00000000000..cd040a144aa
--- /dev/null
+++ b/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr
@@ -0,0 +1,23 @@
+error: `allow` attribute without specifying a reason
+  --> $DIR/allow_attributes_without_reason.rs:5:1
+   |
+LL | #[allow(dead_code)]
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/allow_attributes_without_reason.rs:2:9
+   |
+LL | #![deny(clippy::allow_attributes_without_reason)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: try adding a reason at the end with `, reason = ".."`
+
+error: `allow` attribute without specifying a reason
+  --> $DIR/allow_attributes_without_reason.rs:6:1
+   |
+LL | #[allow(dead_code, deprecated)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: try adding a reason at the end with `, reason = ".."`
+
+error: aborting due to 2 previous errors
+
diff --git a/src/tools/clippy/tests/ui/cast_slice_different_sizes.rs b/src/tools/clippy/tests/ui/cast_slice_different_sizes.rs
new file mode 100644
index 00000000000..cfe1cca2eba
--- /dev/null
+++ b/src/tools/clippy/tests/ui/cast_slice_different_sizes.rs
@@ -0,0 +1,39 @@
+fn main() {
+    let x: [i32; 3] = [1_i32, 2, 3];
+    let r_x = &x;
+    // Check casting through multiple bindings
+    // Because it's separate, it does not check the cast back to something of the same size
+    let a = r_x as *const [i32];
+    let b = a as *const [u8];
+    let c = b as *const [u32];
+
+    // loses data
+    let loss = r_x as *const [i32] as *const [u8];
+
+    // Cast back to same size but different type loses no data, just type conversion
+    // This is weird code but there's no reason for this lint specifically to fire *twice* on it
+    let restore = r_x as *const [i32] as *const [u8] as *const [u32];
+
+    // Check casting through blocks is detected
+    let loss_block_1 = { r_x as *const [i32] } as *const [u8];
+    let loss_block_2 = {
+        let _ = ();
+        r_x as *const [i32]
+    } as *const [u8];
+
+    // Check that resores of the same size are detected through blocks
+    let restore_block_1 = { r_x as *const [i32] } as *const [u8] as *const [u32];
+    let restore_block_2 = { ({ r_x as *const [i32] }) as *const [u8] } as *const [u32];
+    let restore_block_3 = {
+        let _ = ();
+        ({
+            let _ = ();
+            r_x as *const [i32]
+        }) as *const [u8]
+    } as *const [u32];
+
+    // Check that the result of a long chain of casts is detected
+    let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
+    let long_chain_restore =
+        r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8] as *const [u32];
+}
diff --git a/src/tools/clippy/tests/ui/cast_slice_different_sizes.stderr b/src/tools/clippy/tests/ui/cast_slice_different_sizes.stderr
new file mode 100644
index 00000000000..a37cec7cb3b
--- /dev/null
+++ b/src/tools/clippy/tests/ui/cast_slice_different_sizes.stderr
@@ -0,0 +1,52 @@
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:7:13
+   |
+LL |     let b = a as *const [u8];
+   |             ^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(a as *const u8, ..)`
+   |
+   = note: `#[deny(clippy::cast_slice_different_sizes)]` on by default
+
+error: casting between raw pointers to `[u8]` (element size 1) and `[u32]` (element size 4) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:8:13
+   |
+LL |     let c = b as *const [u32];
+   |             ^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(b as *const u32, ..)`
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:11:16
+   |
+LL |     let loss = r_x as *const [i32] as *const [u8];
+   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const u8, ..)`
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:18:24
+   |
+LL |     let loss_block_1 = { r_x as *const [i32] } as *const [u8];
+   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts({ r_x as *const [i32] } as *const u8, ..)`
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:19:24
+   |
+LL |       let loss_block_2 = {
+   |  ________________________^
+LL | |         let _ = ();
+LL | |         r_x as *const [i32]
+LL | |     } as *const [u8];
+   | |____________________^
+   |
+help: replace with `ptr::slice_from_raw_parts`
+   |
+LL ~     let loss_block_2 = core::ptr::slice_from_raw_parts({
+LL +         let _ = ();
+LL +         r_x as *const [i32]
+LL ~     } as *const u8, ..);
+   |
+
+error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
+  --> $DIR/cast_slice_different_sizes.rs:36:27
+   |
+LL |     let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const u8, ..)`
+
+error: aborting due to 6 previous errors
+
diff --git a/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs
index be4348e2bb7..4a5c597dda5 100644
--- a/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs
+++ b/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs
@@ -12,12 +12,12 @@ static N: AtomicUsize = AtomicUsize::new(0);
 
 #[warn(clippy::main_recursion)]
 #[start]
-fn main(argc: isize, argv: *const *const u8) -> isize {
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
     let x = N.load(Ordering::Relaxed);
     N.store(x + 1, Ordering::Relaxed);
 
     if x < 3 {
-        main(argc, argv);
+        main(_argc, _argv);
     }
 
     0
diff --git a/src/tools/clippy/tests/ui/dbg_macro.rs b/src/tools/clippy/tests/ui/dbg_macro.rs
index 9b03c9b4783..baf01174b67 100644
--- a/src/tools/clippy/tests/ui/dbg_macro.rs
+++ b/src/tools/clippy/tests/ui/dbg_macro.rs
@@ -1,3 +1,4 @@
+// compile-flags: --test
 #![warn(clippy::dbg_macro)]
 
 fn foo(n: u32) -> u32 {
@@ -40,3 +41,8 @@ mod issue7274 {
         dbg!(2);
     });
 }
+
+#[test]
+pub fn issue8481() {
+    dbg!(1);
+}
diff --git a/src/tools/clippy/tests/ui/dbg_macro.stderr b/src/tools/clippy/tests/ui/dbg_macro.stderr
index 8ee1b328720..a3e7a7162e5 100644
--- a/src/tools/clippy/tests/ui/dbg_macro.stderr
+++ b/src/tools/clippy/tests/ui/dbg_macro.stderr
@@ -1,5 +1,5 @@
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:4:22
+  --> $DIR/dbg_macro.rs:5:22
    |
 LL |     if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
    |                      ^^^^^^^^^^^^^^^^^^^^^^
@@ -11,7 +11,7 @@ LL |     if let Some(n) = n.checked_sub(4) { n } else { n }
    |                      ~~~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:8:8
+  --> $DIR/dbg_macro.rs:9:8
    |
 LL |     if dbg!(n <= 1) {
    |        ^^^^^^^^^^^^
@@ -22,7 +22,7 @@ LL |     if n <= 1 {
    |        ~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:9:9
+  --> $DIR/dbg_macro.rs:10:9
    |
 LL |         dbg!(1)
    |         ^^^^^^^
@@ -33,7 +33,7 @@ LL |         1
    |
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:11:9
+  --> $DIR/dbg_macro.rs:12:9
    |
 LL |         dbg!(n * factorial(n - 1))
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -44,7 +44,7 @@ LL |         n * factorial(n - 1)
    |
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:16:5
+  --> $DIR/dbg_macro.rs:17:5
    |
 LL |     dbg!(42);
    |     ^^^^^^^^
@@ -55,7 +55,7 @@ LL |     42;
    |     ~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:17:5
+  --> $DIR/dbg_macro.rs:18:5
    |
 LL |     dbg!(dbg!(dbg!(42)));
    |     ^^^^^^^^^^^^^^^^^^^^
@@ -66,7 +66,7 @@ LL |     dbg!(dbg!(42));
    |     ~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:18:14
+  --> $DIR/dbg_macro.rs:19:14
    |
 LL |     foo(3) + dbg!(factorial(4));
    |              ^^^^^^^^^^^^^^^^^^
@@ -77,7 +77,7 @@ LL |     foo(3) + factorial(4);
    |              ~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:19:5
+  --> $DIR/dbg_macro.rs:20:5
    |
 LL |     dbg!(1, 2, dbg!(3, 4));
    |     ^^^^^^^^^^^^^^^^^^^^^^
@@ -88,7 +88,7 @@ LL |     (1, 2, dbg!(3, 4));
    |     ~~~~~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:20:5
+  --> $DIR/dbg_macro.rs:21:5
    |
 LL |     dbg!(1, 2, 3, 4, 5);
    |     ^^^^^^^^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL |     (1, 2, 3, 4, 5);
    |     ~~~~~~~~~~~~~~~
 
 error: `dbg!` macro is intended as a debugging tool
-  --> $DIR/dbg_macro.rs:40:9
+  --> $DIR/dbg_macro.rs:41:9
    |
 LL |         dbg!(2);
    |         ^^^^^^^
diff --git a/src/tools/clippy/tests/ui/extend_with_drain.fixed b/src/tools/clippy/tests/ui/extend_with_drain.fixed
index e863870e7d6..71ebad24c16 100644
--- a/src/tools/clippy/tests/ui/extend_with_drain.fixed
+++ b/src/tools/clippy/tests/ui/extend_with_drain.fixed
@@ -1,11 +1,11 @@
 // run-rustfix
 #![warn(clippy::extend_with_drain)]
+#![allow(clippy::iter_with_drain)]
 use std::collections::BinaryHeap;
 fn main() {
     //gets linted
     let mut vec1 = vec![0u8; 1024];
     let mut vec2: std::vec::Vec<u8> = Vec::new();
-
     vec2.append(&mut vec1);
 
     let mut vec3 = vec![0u8; 1024];
@@ -17,7 +17,7 @@ fn main() {
 
     vec11.append(&mut return_vector());
 
-    //won't get linted it dosen't move the entire content of a vec into another
+    //won't get linted it doesn't move the entire content of a vec into another
     let mut test1 = vec![0u8, 10];
     let mut test2: std::vec::Vec<u8> = Vec::new();
 
diff --git a/src/tools/clippy/tests/ui/extend_with_drain.rs b/src/tools/clippy/tests/ui/extend_with_drain.rs
index dcb36b5951c..e9f011abb0e 100644
--- a/src/tools/clippy/tests/ui/extend_with_drain.rs
+++ b/src/tools/clippy/tests/ui/extend_with_drain.rs
@@ -1,11 +1,11 @@
 // run-rustfix
 #![warn(clippy::extend_with_drain)]
+#![allow(clippy::iter_with_drain)]
 use std::collections::BinaryHeap;
 fn main() {
     //gets linted
     let mut vec1 = vec![0u8; 1024];
     let mut vec2: std::vec::Vec<u8> = Vec::new();
-
     vec2.extend(vec1.drain(..));
 
     let mut vec3 = vec![0u8; 1024];
@@ -17,7 +17,7 @@ fn main() {
 
     vec11.extend(return_vector().drain(..));
 
-    //won't get linted it dosen't move the entire content of a vec into another
+    //won't get linted it doesn't move the entire content of a vec into another
     let mut test1 = vec![0u8, 10];
     let mut test2: std::vec::Vec<u8> = Vec::new();
 
diff --git a/src/tools/clippy/tests/ui/iter_with_drain.fixed b/src/tools/clippy/tests/ui/iter_with_drain.fixed
new file mode 100644
index 00000000000..aea4dba9dd5
--- /dev/null
+++ b/src/tools/clippy/tests/ui/iter_with_drain.fixed
@@ -0,0 +1,56 @@
+// run-rustfix
+// will emits unused mut warnings after fixing
+#![allow(unused_mut)]
+// will emits needless collect warnings after fixing
+#![allow(clippy::needless_collect)]
+#![warn(clippy::iter_with_drain)]
+use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
+
+fn full() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.into_iter().collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.into_iter().collect();
+    let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn closed() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.into_iter().collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.into_iter().collect();
+    let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn should_not_help() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(1..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
+    let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+
+    let mut b = vec!["aaa".to_string(), "bbb".to_string()];
+    let _: Vec<_> = b.drain(0..a.len()).collect();
+}
+
+#[derive(Default)]
+struct Bomb {
+    fire: Vec<u8>,
+}
+
+fn should_not_help_0(bomb: &mut Bomb) {
+    let _: Vec<u8> = bomb.fire.drain(..).collect();
+}
+
+fn main() {
+    full();
+    closed();
+    should_not_help();
+    should_not_help_0(&mut Bomb::default());
+}
diff --git a/src/tools/clippy/tests/ui/iter_with_drain.rs b/src/tools/clippy/tests/ui/iter_with_drain.rs
new file mode 100644
index 00000000000..271878cffb4
--- /dev/null
+++ b/src/tools/clippy/tests/ui/iter_with_drain.rs
@@ -0,0 +1,56 @@
+// run-rustfix
+// will emits unused mut warnings after fixing
+#![allow(unused_mut)]
+// will emits needless collect warnings after fixing
+#![allow(clippy::needless_collect)]
+#![warn(clippy::iter_with_drain)]
+use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
+
+fn full() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..).collect();
+    let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn closed() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(0..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..a.len()).collect();
+    let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+}
+
+fn should_not_help() {
+    let mut a = vec!["aaa".to_string(), "bbb".to_string()];
+    let mut a: BinaryHeap<_> = a.drain(1..).collect();
+    let mut a: HashSet<_> = a.drain().collect();
+    let mut a: VecDeque<_> = a.drain().collect();
+    let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
+    let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
+    let _: Vec<(String, String)> = a.drain().collect();
+
+    let mut b = vec!["aaa".to_string(), "bbb".to_string()];
+    let _: Vec<_> = b.drain(0..a.len()).collect();
+}
+
+#[derive(Default)]
+struct Bomb {
+    fire: Vec<u8>,
+}
+
+fn should_not_help_0(bomb: &mut Bomb) {
+    let _: Vec<u8> = bomb.fire.drain(..).collect();
+}
+
+fn main() {
+    full();
+    closed();
+    should_not_help();
+    should_not_help_0(&mut Bomb::default());
+}
diff --git a/src/tools/clippy/tests/ui/iter_with_drain.stderr b/src/tools/clippy/tests/ui/iter_with_drain.stderr
new file mode 100644
index 00000000000..aa394439fa6
--- /dev/null
+++ b/src/tools/clippy/tests/ui/iter_with_drain.stderr
@@ -0,0 +1,40 @@
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:11:34
+   |
+LL |     let mut a: BinaryHeap<_> = a.drain(..).collect();
+   |                                  ^^^^^^^^^ help: try this: `into_iter()`
+   |
+   = note: `-D clippy::iter-with-drain` implied by `-D warnings`
+
+error: `drain(..)` used on a `VecDeque`
+  --> $DIR/iter_with_drain.rs:14:27
+   |
+LL |     let mut a: Vec<_> = a.drain(..).collect();
+   |                           ^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:15:34
+   |
+LL |     let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
+   |                                  ^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:21:34
+   |
+LL |     let mut a: BinaryHeap<_> = a.drain(0..).collect();
+   |                                  ^^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `VecDeque`
+  --> $DIR/iter_with_drain.rs:24:27
+   |
+LL |     let mut a: Vec<_> = a.drain(..a.len()).collect();
+   |                           ^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
+
+error: `drain(..)` used on a `Vec`
+  --> $DIR/iter_with_drain.rs:25:34
+   |
+LL |     let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
+   |                                  ^^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
+
+error: aborting due to 6 previous errors
+
diff --git a/src/tools/clippy/tests/ui/manual_map_option.fixed b/src/tools/clippy/tests/ui/manual_map_option.fixed
index 294d79abc04..fc6a7abca0e 100644
--- a/src/tools/clippy/tests/ui/manual_map_option.fixed
+++ b/src/tools/clippy/tests/ui/manual_map_option.fixed
@@ -148,6 +148,7 @@ fn main() {
 
     // #7077
     let s = &String::new();
+    #[allow(clippy::needless_match)]
     let _: Option<&str> = match Some(s) {
         Some(s) => Some(s),
         None => None,
diff --git a/src/tools/clippy/tests/ui/manual_map_option.rs b/src/tools/clippy/tests/ui/manual_map_option.rs
index d11bf5ecb82..16508270f64 100644
--- a/src/tools/clippy/tests/ui/manual_map_option.rs
+++ b/src/tools/clippy/tests/ui/manual_map_option.rs
@@ -214,6 +214,7 @@ fn main() {
 
     // #7077
     let s = &String::new();
+    #[allow(clippy::needless_match)]
     let _: Option<&str> = match Some(s) {
         Some(s) => Some(s),
         None => None,
diff --git a/src/tools/clippy/tests/ui/missing_spin_loop.fixed b/src/tools/clippy/tests/ui/missing_spin_loop.fixed
new file mode 100644
index 00000000000..aa89e04d26e
--- /dev/null
+++ b/src/tools/clippy/tests/ui/missing_spin_loop.fixed
@@ -0,0 +1,28 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![allow(clippy::bool_comparison)]
+#![allow(unused_braces)]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+fn main() {
+    let b = AtomicBool::new(true);
+    // Those should lint
+    while b.load(Ordering::Acquire) { std::hint::spin_loop() }
+
+    while !b.load(Ordering::SeqCst) { std::hint::spin_loop() }
+
+    while b.load(Ordering::Acquire) == false { std::hint::spin_loop() }
+
+    while { true == b.load(Ordering::Acquire) } { std::hint::spin_loop() }
+
+    while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) { std::hint::spin_loop() }
+
+    while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { std::hint::spin_loop() }
+
+    // This is OK, as the body is not empty
+    while b.load(Ordering::Acquire) {
+        std::hint::spin_loop()
+    }
+    // TODO: also match on loop+match or while let
+}
diff --git a/src/tools/clippy/tests/ui/missing_spin_loop.rs b/src/tools/clippy/tests/ui/missing_spin_loop.rs
new file mode 100644
index 00000000000..88745e47732
--- /dev/null
+++ b/src/tools/clippy/tests/ui/missing_spin_loop.rs
@@ -0,0 +1,28 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![allow(clippy::bool_comparison)]
+#![allow(unused_braces)]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+fn main() {
+    let b = AtomicBool::new(true);
+    // Those should lint
+    while b.load(Ordering::Acquire) {}
+
+    while !b.load(Ordering::SeqCst) {}
+
+    while b.load(Ordering::Acquire) == false {}
+
+    while { true == b.load(Ordering::Acquire) } {}
+
+    while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) {}
+
+    while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {}
+
+    // This is OK, as the body is not empty
+    while b.load(Ordering::Acquire) {
+        std::hint::spin_loop()
+    }
+    // TODO: also match on loop+match or while let
+}
diff --git a/src/tools/clippy/tests/ui/missing_spin_loop.stderr b/src/tools/clippy/tests/ui/missing_spin_loop.stderr
new file mode 100644
index 00000000000..485da00dc64
--- /dev/null
+++ b/src/tools/clippy/tests/ui/missing_spin_loop.stderr
@@ -0,0 +1,40 @@
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:11:37
+   |
+LL |     while b.load(Ordering::Acquire) {}
+   |                                     ^^ help: try this: `{ std::hint::spin_loop() }`
+   |
+   = note: `-D clippy::missing-spin-loop` implied by `-D warnings`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:13:37
+   |
+LL |     while !b.load(Ordering::SeqCst) {}
+   |                                     ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:15:46
+   |
+LL |     while b.load(Ordering::Acquire) == false {}
+   |                                              ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:17:49
+   |
+LL |     while { true == b.load(Ordering::Acquire) } {}
+   |                                                 ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:19:93
+   |
+LL |     while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) {}
+   |                                                                                             ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop.rs:21:94
+   |
+LL |     while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {}
+   |                                                                                              ^^ help: try this: `{ std::hint::spin_loop() }`
+
+error: aborting due to 6 previous errors
+
diff --git a/src/tools/clippy/tests/ui/missing_spin_loop_no_std.fixed b/src/tools/clippy/tests/ui/missing_spin_loop_no_std.fixed
new file mode 100644
index 00000000000..bb4b4795516
--- /dev/null
+++ b/src/tools/clippy/tests/ui/missing_spin_loop_no_std.fixed
@@ -0,0 +1,23 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![feature(lang_items, start, libc)]
+#![no_std]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+#[start]
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
+    // This should trigger the lint
+    let b = AtomicBool::new(true);
+    // This should lint with `core::hint::spin_loop()`
+    while b.load(Ordering::Acquire) { core::hint::spin_loop() }
+    0
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[lang = "eh_personality"]
+extern "C" fn eh_personality() {}
diff --git a/src/tools/clippy/tests/ui/missing_spin_loop_no_std.rs b/src/tools/clippy/tests/ui/missing_spin_loop_no_std.rs
new file mode 100644
index 00000000000..a19bc72baf8
--- /dev/null
+++ b/src/tools/clippy/tests/ui/missing_spin_loop_no_std.rs
@@ -0,0 +1,23 @@
+// run-rustfix
+#![warn(clippy::missing_spin_loop)]
+#![feature(lang_items, start, libc)]
+#![no_std]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+#[start]
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
+    // This should trigger the lint
+    let b = AtomicBool::new(true);
+    // This should lint with `core::hint::spin_loop()`
+    while b.load(Ordering::Acquire) {}
+    0
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
+
+#[lang = "eh_personality"]
+extern "C" fn eh_personality() {}
diff --git a/src/tools/clippy/tests/ui/missing_spin_loop_no_std.stderr b/src/tools/clippy/tests/ui/missing_spin_loop_no_std.stderr
new file mode 100644
index 00000000000..2b3b6873c3c
--- /dev/null
+++ b/src/tools/clippy/tests/ui/missing_spin_loop_no_std.stderr
@@ -0,0 +1,10 @@
+error: busy-waiting loop should at least have a spin loop hint
+  --> $DIR/missing_spin_loop_no_std.rs:13:37
+   |
+LL |     while b.load(Ordering::Acquire) {}
+   |                                     ^^ help: try this: `{ core::hint::spin_loop() }`
+   |
+   = note: `-D clippy::missing-spin-loop` implied by `-D warnings`
+
+error: aborting due to previous error
+
diff --git a/src/tools/clippy/tests/ui/needless_match.fixed b/src/tools/clippy/tests/ui/needless_match.fixed
new file mode 100644
index 00000000000..ece18ad737f
--- /dev/null
+++ b/src/tools/clippy/tests/ui/needless_match.fixed
@@ -0,0 +1,83 @@
+// run-rustfix
+#![warn(clippy::needless_match)]
+#![allow(clippy::manual_map)]
+#![allow(dead_code)]
+
+#[derive(Clone, Copy)]
+enum Choice {
+    A,
+    B,
+    C,
+    D,
+}
+
+#[allow(unused_mut)]
+fn useless_match() {
+    let mut i = 10;
+    let _: i32 = i;
+    let _: i32 = i;
+    let mut _i_mut = i;
+
+    let s = "test";
+    let _: &str = s;
+}
+
+fn custom_type_match(se: Choice) {
+    let _: Choice = se;
+    // Don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::A,
+        Choice::B => Choice::B,
+        _ => Choice::C,
+    };
+    // Mingled, don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::B,
+        Choice::B => Choice::C,
+        Choice::C => Choice::D,
+        Choice::D => Choice::A,
+    };
+}
+
+fn option_match(x: Option<i32>) {
+    let _: Option<i32> = x;
+    // Don't trigger, this is the case for manual_map_option
+    let _: Option<i32> = match x {
+        Some(a) => Some(-a),
+        None => None,
+    };
+}
+
+fn func_ret_err<T>(err: T) -> Result<i32, T> {
+    Err(err)
+}
+
+fn result_match() {
+    let _: Result<i32, i32> = Ok(1);
+    let _: Result<i32, i32> = func_ret_err(0_i32);
+}
+
+fn if_let_option() -> Option<i32> {
+    Some(1)
+}
+
+fn if_let_result(x: Result<(), i32>) {
+    let _: Result<(), i32> = x;
+    let _: Result<(), i32> = x;
+    // Input type mismatch, don't trigger
+    let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
+}
+
+fn if_let_custom_enum(x: Choice) {
+    let _: Choice = x;
+    // Don't trigger
+    let _: Choice = if let Choice::A = x {
+        Choice::A
+    } else if true {
+        Choice::B
+    } else {
+        x
+    };
+}
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui/needless_match.rs b/src/tools/clippy/tests/ui/needless_match.rs
new file mode 100644
index 00000000000..36649de35a6
--- /dev/null
+++ b/src/tools/clippy/tests/ui/needless_match.rs
@@ -0,0 +1,122 @@
+// run-rustfix
+#![warn(clippy::needless_match)]
+#![allow(clippy::manual_map)]
+#![allow(dead_code)]
+
+#[derive(Clone, Copy)]
+enum Choice {
+    A,
+    B,
+    C,
+    D,
+}
+
+#[allow(unused_mut)]
+fn useless_match() {
+    let mut i = 10;
+    let _: i32 = match i {
+        0 => 0,
+        1 => 1,
+        2 => 2,
+        _ => i,
+    };
+    let _: i32 = match i {
+        0 => 0,
+        1 => 1,
+        ref i => *i,
+    };
+    let mut _i_mut = match i {
+        0 => 0,
+        1 => 1,
+        ref mut i => *i,
+    };
+
+    let s = "test";
+    let _: &str = match s {
+        "a" => "a",
+        "b" => "b",
+        s => s,
+    };
+}
+
+fn custom_type_match(se: Choice) {
+    let _: Choice = match se {
+        Choice::A => Choice::A,
+        Choice::B => Choice::B,
+        Choice::C => Choice::C,
+        Choice::D => Choice::D,
+    };
+    // Don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::A,
+        Choice::B => Choice::B,
+        _ => Choice::C,
+    };
+    // Mingled, don't trigger
+    let _: Choice = match se {
+        Choice::A => Choice::B,
+        Choice::B => Choice::C,
+        Choice::C => Choice::D,
+        Choice::D => Choice::A,
+    };
+}
+
+fn option_match(x: Option<i32>) {
+    let _: Option<i32> = match x {
+        Some(a) => Some(a),
+        None => None,
+    };
+    // Don't trigger, this is the case for manual_map_option
+    let _: Option<i32> = match x {
+        Some(a) => Some(-a),
+        None => None,
+    };
+}
+
+fn func_ret_err<T>(err: T) -> Result<i32, T> {
+    Err(err)
+}
+
+fn result_match() {
+    let _: Result<i32, i32> = match Ok(1) {
+        Ok(a) => Ok(a),
+        Err(err) => Err(err),
+    };
+    let _: Result<i32, i32> = match func_ret_err(0_i32) {
+        Err(err) => Err(err),
+        Ok(a) => Ok(a),
+    };
+}
+
+fn if_let_option() -> Option<i32> {
+    if let Some(a) = Some(1) { Some(a) } else { None }
+}
+
+fn if_let_result(x: Result<(), i32>) {
+    let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
+    let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
+    // Input type mismatch, don't trigger
+    let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
+}
+
+fn if_let_custom_enum(x: Choice) {
+    let _: Choice = if let Choice::A = x {
+        Choice::A
+    } else if let Choice::B = x {
+        Choice::B
+    } else if let Choice::C = x {
+        Choice::C
+    } else {
+        x
+    };
+    // Don't trigger
+    let _: Choice = if let Choice::A = x {
+        Choice::A
+    } else if true {
+        Choice::B
+    } else {
+        x
+    };
+}
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui/needless_match.stderr b/src/tools/clippy/tests/ui/needless_match.stderr
new file mode 100644
index 00000000000..ad1525406ad
--- /dev/null
+++ b/src/tools/clippy/tests/ui/needless_match.stderr
@@ -0,0 +1,122 @@
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:17:18
+   |
+LL |       let _: i32 = match i {
+   |  __________________^
+LL | |         0 => 0,
+LL | |         1 => 1,
+LL | |         2 => 2,
+LL | |         _ => i,
+LL | |     };
+   | |_____^ help: replace it with: `i`
+   |
+   = note: `-D clippy::needless-match` implied by `-D warnings`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:23:18
+   |
+LL |       let _: i32 = match i {
+   |  __________________^
+LL | |         0 => 0,
+LL | |         1 => 1,
+LL | |         ref i => *i,
+LL | |     };
+   | |_____^ help: replace it with: `i`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:28:22
+   |
+LL |       let mut _i_mut = match i {
+   |  ______________________^
+LL | |         0 => 0,
+LL | |         1 => 1,
+LL | |         ref mut i => *i,
+LL | |     };
+   | |_____^ help: replace it with: `i`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:35:19
+   |
+LL |       let _: &str = match s {
+   |  ___________________^
+LL | |         "a" => "a",
+LL | |         "b" => "b",
+LL | |         s => s,
+LL | |     };
+   | |_____^ help: replace it with: `s`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:43:21
+   |
+LL |       let _: Choice = match se {
+   |  _____________________^
+LL | |         Choice::A => Choice::A,
+LL | |         Choice::B => Choice::B,
+LL | |         Choice::C => Choice::C,
+LL | |         Choice::D => Choice::D,
+LL | |     };
+   | |_____^ help: replace it with: `se`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:65:26
+   |
+LL |       let _: Option<i32> = match x {
+   |  __________________________^
+LL | |         Some(a) => Some(a),
+LL | |         None => None,
+LL | |     };
+   | |_____^ help: replace it with: `x`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:81:31
+   |
+LL |       let _: Result<i32, i32> = match Ok(1) {
+   |  _______________________________^
+LL | |         Ok(a) => Ok(a),
+LL | |         Err(err) => Err(err),
+LL | |     };
+   | |_____^ help: replace it with: `Ok(1)`
+
+error: this match expression is unnecessary
+  --> $DIR/needless_match.rs:85:31
+   |
+LL |       let _: Result<i32, i32> = match func_ret_err(0_i32) {
+   |  _______________________________^
+LL | |         Err(err) => Err(err),
+LL | |         Ok(a) => Ok(a),
+LL | |     };
+   | |_____^ help: replace it with: `func_ret_err(0_i32)`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:92:5
+   |
+LL |     if let Some(a) = Some(1) { Some(a) } else { None }
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(1)`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:96:30
+   |
+LL |     let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
+   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:97:30
+   |
+LL |     let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
+   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
+
+error: this if-let expression is unnecessary
+  --> $DIR/needless_match.rs:103:21
+   |
+LL |       let _: Choice = if let Choice::A = x {
+   |  _____________________^
+LL | |         Choice::A
+LL | |     } else if let Choice::B = x {
+LL | |         Choice::B
+...  |
+LL | |         x
+LL | |     };
+   | |_____^ help: replace it with: `x`
+
+error: aborting due to 12 previous errors
+
diff --git a/src/tools/clippy/tests/ui/only_used_in_recursion.rs b/src/tools/clippy/tests/ui/only_used_in_recursion.rs
new file mode 100644
index 00000000000..5768434f988
--- /dev/null
+++ b/src/tools/clippy/tests/ui/only_used_in_recursion.rs
@@ -0,0 +1,122 @@
+#![warn(clippy::only_used_in_recursion)]
+
+fn simple(a: usize, b: usize) -> usize {
+    if a == 0 { 1 } else { simple(a - 1, b) }
+}
+
+fn with_calc(a: usize, b: isize) -> usize {
+    if a == 0 { 1 } else { with_calc(a - 1, -b + 1) }
+}
+
+fn tuple((a, b): (usize, usize)) -> usize {
+    if a == 0 { 1 } else { tuple((a - 1, b + 1)) }
+}
+
+fn let_tuple(a: usize, b: usize) -> usize {
+    let (c, d) = (a, b);
+    if c == 0 { 1 } else { let_tuple(c - 1, d + 1) }
+}
+
+fn array([a, b]: [usize; 2]) -> usize {
+    if a == 0 { 1 } else { array([a - 1, b + 1]) }
+}
+
+fn index(a: usize, mut b: &[usize], c: usize) -> usize {
+    if a == 0 { 1 } else { index(a - 1, b, c + b[0]) }
+}
+
+fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
+    let c = loop {
+        b += 1;
+        c += 1;
+        if c == 10 {
+            break b;
+        }
+    };
+
+    if a == 0 { 1 } else { break_(a - 1, c, c) }
+}
+
+// this has a side effect
+fn mut_ref(a: usize, b: &mut usize) -> usize {
+    *b = 1;
+    if a == 0 { 1 } else { mut_ref(a - 1, b) }
+}
+
+fn mut_ref2(a: usize, b: &mut usize) -> usize {
+    let mut c = *b;
+    if a == 0 { 1 } else { mut_ref2(a - 1, &mut c) }
+}
+
+fn not_primitive(a: usize, b: String) -> usize {
+    if a == 0 { 1 } else { not_primitive(a - 1, b) }
+}
+
+// this doesn't have a side effect,
+// but `String` is not primitive.
+fn not_primitive_op(a: usize, b: String, c: &str) -> usize {
+    if a == 1 { 1 } else { not_primitive_op(a, b + c, c) }
+}
+
+struct A;
+
+impl A {
+    fn method(a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { A::method(a - 1, b - 1) }
+    }
+
+    fn method2(&self, a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { self.method2(a - 1, b + 1) }
+    }
+}
+
+trait B {
+    fn hello(a: usize, b: usize) -> usize;
+
+    fn hello2(&self, a: usize, b: usize) -> usize;
+}
+
+impl B for A {
+    fn hello(a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { A::hello(a - 1, b + 1) }
+    }
+
+    fn hello2(&self, a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
+    }
+}
+
+trait C {
+    fn hello(a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { Self::hello(a - 1, b + 1) }
+    }
+
+    fn hello2(&self, a: usize, b: usize) -> usize {
+        if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
+    }
+}
+
+fn ignore(a: usize, _: usize) -> usize {
+    if a == 1 { 1 } else { ignore(a - 1, 0) }
+}
+
+fn ignore2(a: usize, _b: usize) -> usize {
+    if a == 1 { 1 } else { ignore2(a - 1, _b) }
+}
+
+fn f1(a: u32) -> u32 {
+    a
+}
+
+fn f2(a: u32) -> u32 {
+    f1(a)
+}
+
+fn inner_fn(a: u32) -> u32 {
+    fn inner_fn(a: u32) -> u32 {
+        a
+    }
+    inner_fn(a)
+}
+
+fn main() {}
diff --git a/src/tools/clippy/tests/ui/only_used_in_recursion.stderr b/src/tools/clippy/tests/ui/only_used_in_recursion.stderr
new file mode 100644
index 00000000000..6fe9361bf5f
--- /dev/null
+++ b/src/tools/clippy/tests/ui/only_used_in_recursion.stderr
@@ -0,0 +1,82 @@
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:3:21
+   |
+LL | fn simple(a: usize, b: usize) -> usize {
+   |                     ^ help: if this is intentional, prefix with an underscore: `_b`
+   |
+   = note: `-D clippy::only-used-in-recursion` implied by `-D warnings`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:7:24
+   |
+LL | fn with_calc(a: usize, b: isize) -> usize {
+   |                        ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:11:14
+   |
+LL | fn tuple((a, b): (usize, usize)) -> usize {
+   |              ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:15:24
+   |
+LL | fn let_tuple(a: usize, b: usize) -> usize {
+   |                        ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:20:14
+   |
+LL | fn array([a, b]: [usize; 2]) -> usize {
+   |              ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:24:20
+   |
+LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
+   |                    ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:24:37
+   |
+LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
+   |                                     ^ help: if this is intentional, prefix with an underscore: `_c`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:28:21
+   |
+LL | fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
+   |                     ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:46:23
+   |
+LL | fn mut_ref2(a: usize, b: &mut usize) -> usize {
+   |                       ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:51:28
+   |
+LL | fn not_primitive(a: usize, b: String) -> usize {
+   |                            ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:68:33
+   |
+LL |     fn method2(&self, a: usize, b: usize) -> usize {
+   |                                 ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:90:24
+   |
+LL |     fn hello(a: usize, b: usize) -> usize {
+   |                        ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: parameter is only used in recursion
+  --> $DIR/only_used_in_recursion.rs:94:32
+   |
+LL |     fn hello2(&self, a: usize, b: usize) -> usize {
+   |                                ^ help: if this is intentional, prefix with an underscore: `_b`
+
+error: aborting due to 13 previous errors
+
diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.fixed b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed
index 19b253b0fe2..40d7791df28 100644
--- a/src/tools/clippy/tests/ui/range_plus_minus_one.fixed
+++ b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed
@@ -1,7 +1,7 @@
 // run-rustfix
 
 #![allow(unused_parens)]
-
+#![allow(clippy::iter_with_drain)]
 fn f() -> usize {
     42
 }
diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.rs b/src/tools/clippy/tests/ui/range_plus_minus_one.rs
index 7d034117547..a8ddd9b5f75 100644
--- a/src/tools/clippy/tests/ui/range_plus_minus_one.rs
+++ b/src/tools/clippy/tests/ui/range_plus_minus_one.rs
@@ -1,7 +1,7 @@
 // run-rustfix
 
 #![allow(unused_parens)]
-
+#![allow(clippy::iter_with_drain)]
 fn f() -> usize {
     42
 }
diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed
index 9ae0ed0b13f..b425cdd6cbf 100644
--- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed
+++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed
@@ -25,8 +25,8 @@ fn main() {
     let slice_ptr = &[0, 1, 2, 3] as *const [i32];
 
     // ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
-    let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u16] };
-    let _ptr_to_unsized = slice_ptr as *const [u16];
+    let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] };
+    let _ptr_to_unsized = slice_ptr as *const [u32];
     // TODO: We could try testing vtable casts here too, but maybe
     // we should wait until std::raw::TraitObject is stabilized?
 
diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs
index 985cf9a075d..8fd57c59652 100644
--- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs
+++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs
@@ -25,8 +25,8 @@ fn main() {
     let slice_ptr = &[0, 1, 2, 3] as *const [i32];
 
     // ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
-    let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
-    let _ptr_to_unsized = slice_ptr as *const [u16];
+    let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
+    let _ptr_to_unsized = slice_ptr as *const [u32];
     // TODO: We could try testing vtable casts here too, but maybe
     // we should wait until std::raw::TraitObject is stabilized?
 
diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr
index e8496a325d6..d9b64a0ed7b 100644
--- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr
+++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr
@@ -17,8 +17,8 @@ LL |     let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr
 error: transmute from a pointer to a pointer
   --> $DIR/transmutes_expressible_as_ptr_casts.rs:28:46
    |
-LL |     let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u16]`
+LL |     let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u32]`
 
 error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead
   --> $DIR/transmutes_expressible_as_ptr_casts.rs:34:50
diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.rs b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs
index c58181f518d..8e01c2674f1 100644
--- a/src/tools/clippy/tests/ui/unnecessary_filter_map.rs
+++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs
@@ -1,3 +1,5 @@
+#![allow(dead_code)]
+
 fn main() {
     let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
     let _ = (0..4).filter_map(|x| {
@@ -19,3 +21,130 @@ fn main() {
 fn filter_map_none_changes_item_type() -> impl Iterator<Item = bool> {
     "".chars().filter_map(|_| None)
 }
+
+// https://github.com/rust-lang/rust-clippy/issues/4433#issue-483920107
+mod comment_483920107 {
+    enum Severity {
+        Warning,
+        Other,
+    }
+
+    struct ServerError;
+
+    impl ServerError {
+        fn severity(&self) -> Severity {
+            Severity::Warning
+        }
+    }
+
+    struct S {
+        warnings: Vec<ServerError>,
+    }
+
+    impl S {
+        fn foo(&mut self, server_errors: Vec<ServerError>) {
+            #[allow(unused_variables)]
+            let errors: Vec<ServerError> = server_errors
+                .into_iter()
+                .filter_map(|se| match se.severity() {
+                    Severity::Warning => {
+                        self.warnings.push(se);
+                        None
+                    },
+                    _ => Some(se),
+                })
+                .collect();
+        }
+    }
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-611006622
+mod comment_611006622 {
+    struct PendingRequest {
+        reply_to: u8,
+        token: u8,
+        expires: u8,
+        group_id: u8,
+    }
+
+    enum Value {
+        Null,
+    }
+
+    struct Node;
+
+    impl Node {
+        fn send_response(&self, _reply_to: u8, _token: u8, _value: Value) -> &Self {
+            self
+        }
+        fn on_error_warn(&self) -> &Self {
+            self
+        }
+    }
+
+    struct S {
+        pending_requests: Vec<PendingRequest>,
+    }
+
+    impl S {
+        fn foo(&mut self, node: Node, now: u8, group_id: u8) {
+            // "drain_filter"
+            self.pending_requests = self
+                .pending_requests
+                .drain(..)
+                .filter_map(|pending| {
+                    if pending.expires <= now {
+                        return None; // Expired, remove
+                    }
+
+                    if pending.group_id == group_id {
+                        // Matched - reuse strings and remove
+                        node.send_response(pending.reply_to, pending.token, Value::Null)
+                            .on_error_warn();
+                        None
+                    } else {
+                        // Keep waiting
+                        Some(pending)
+                    }
+                })
+                .collect();
+        }
+    }
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-621925270
+// This extrapolation doesn't reproduce the false positive. Additional context seems necessary.
+mod comment_621925270 {
+    struct Signature(u8);
+
+    fn foo(sig_packets: impl Iterator<Item = Result<Signature, ()>>) -> impl Iterator<Item = u8> {
+        sig_packets.filter_map(|res| match res {
+            Ok(Signature(sig_packet)) => Some(sig_packet),
+            _ => None,
+        })
+    }
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-1052978898
+mod comment_1052978898 {
+    #![allow(clippy::redundant_closure)]
+
+    pub struct S(u8);
+
+    impl S {
+        pub fn consume(self) {
+            println!("yum");
+        }
+    }
+
+    pub fn filter_owned() -> impl Iterator<Item = S> {
+        (0..10).map(|i| S(i)).filter_map(|s| {
+            if s.0 & 1 == 0 {
+                s.consume();
+                None
+            } else {
+                Some(s)
+            }
+        })
+    }
+}
diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr
index 041829c3c78..5585b10ab90 100644
--- a/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr
+++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr
@@ -1,5 +1,5 @@
 error: this `.filter_map` can be written more simply using `.filter`
-  --> $DIR/unnecessary_filter_map.rs:2:13
+  --> $DIR/unnecessary_filter_map.rs:4:13
    |
 LL |     let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -7,7 +7,7 @@ LL |     let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
    = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings`
 
 error: this `.filter_map` can be written more simply using `.filter`
-  --> $DIR/unnecessary_filter_map.rs:3:13
+  --> $DIR/unnecessary_filter_map.rs:5:13
    |
 LL |       let _ = (0..4).filter_map(|x| {
    |  _____________^
@@ -19,7 +19,7 @@ LL | |     });
    | |______^
 
 error: this `.filter_map` can be written more simply using `.filter`
-  --> $DIR/unnecessary_filter_map.rs:9:13
+  --> $DIR/unnecessary_filter_map.rs:11:13
    |
 LL |       let _ = (0..4).filter_map(|x| match x {
    |  _____________^
@@ -29,7 +29,7 @@ LL | |     });
    | |______^
 
 error: this `.filter_map` can be written more simply using `.map`
-  --> $DIR/unnecessary_filter_map.rs:14:13
+  --> $DIR/unnecessary_filter_map.rs:16:13
    |
 LL |     let _ = (0..4).filter_map(|x| Some(x + 1));
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tools/clippy/tests/ui/unnecessary_find_map.rs b/src/tools/clippy/tests/ui/unnecessary_find_map.rs
new file mode 100644
index 00000000000..a52390861b4
--- /dev/null
+++ b/src/tools/clippy/tests/ui/unnecessary_find_map.rs
@@ -0,0 +1,23 @@
+#![allow(dead_code)]
+
+fn main() {
+    let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None });
+    let _ = (0..4).find_map(|x| {
+        if x > 1 {
+            return Some(x);
+        };
+        None
+    });
+    let _ = (0..4).find_map(|x| match x {
+        0 | 1 => None,
+        _ => Some(x),
+    });
+
+    let _ = (0..4).find_map(|x| Some(x + 1));
+
+    let _ = (0..4).find_map(i32::checked_abs);
+}
+
+fn find_map_none_changes_item_type() -> Option<bool> {
+    "".chars().find_map(|_| None)
+}
diff --git a/src/tools/clippy/tests/ui/unnecessary_find_map.stderr b/src/tools/clippy/tests/ui/unnecessary_find_map.stderr
new file mode 100644
index 00000000000..fb33c122fe3
--- /dev/null
+++ b/src/tools/clippy/tests/ui/unnecessary_find_map.stderr
@@ -0,0 +1,38 @@
+error: this `.find_map` can be written more simply using `.find`
+  --> $DIR/unnecessary_find_map.rs:4:13
+   |
+LL |     let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None });
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::unnecessary-find-map` implied by `-D warnings`
+
+error: this `.find_map` can be written more simply using `.find`
+  --> $DIR/unnecessary_find_map.rs:5:13
+   |
+LL |       let _ = (0..4).find_map(|x| {
+   |  _____________^
+LL | |         if x > 1 {
+LL | |             return Some(x);
+LL | |         };
+LL | |         None
+LL | |     });
+   | |______^
+
+error: this `.find_map` can be written more simply using `.find`
+  --> $DIR/unnecessary_find_map.rs:11:13
+   |
+LL |       let _ = (0..4).find_map(|x| match x {
+   |  _____________^
+LL | |         0 | 1 => None,
+LL | |         _ => Some(x),
+LL | |     });
+   | |______^
+
+error: this `.find_map` can be written more simply using `.map(..).next()`
+  --> $DIR/unnecessary_find_map.rs:16:13
+   |
+LL |     let _ = (0..4).find_map(|x| Some(x + 1));
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 4 previous errors
+
diff --git a/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed b/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed
index d806d620b17..21e2da474a8 100644
--- a/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed
+++ b/src/tools/clippy/tests/ui/unnecessary_sort_by.fixed
@@ -3,7 +3,6 @@
 #![allow(clippy::stable_sort_primitive)]
 
 use std::cell::Ref;
-use std::cmp::Reverse;
 
 fn unnecessary_sort_by() {
     fn id(x: isize) -> isize {
@@ -18,8 +17,8 @@ fn unnecessary_sort_by() {
     vec.sort_unstable_by_key(|a| id(-a));
     // Reverse examples
     vec.sort_by(|a, b| b.cmp(a)); // not linted to avoid suggesting `Reverse(b)` which would borrow
-    vec.sort_by_key(|b| Reverse((b + 5).abs()));
-    vec.sort_unstable_by_key(|b| Reverse(id(-b)));
+    vec.sort_by_key(|b| std::cmp::Reverse((b + 5).abs()));
+    vec.sort_unstable_by_key(|b| std::cmp::Reverse(id(-b)));
     // Negative examples (shouldn't be changed)
     let c = &7;
     vec.sort_by(|a, b| (b - a).cmp(&(a - b)));
@@ -76,7 +75,6 @@ mod issue_5754 {
 
 // The closure parameter is not dereferenced anymore, so non-Copy types can be linted
 mod issue_6001 {
-    use super::*;
     struct Test(String);
 
     impl Test {
@@ -93,8 +91,8 @@ mod issue_6001 {
         args.sort_by_key(|a| a.name());
         args.sort_unstable_by_key(|a| a.name());
         // Reverse
-        args.sort_by_key(|b| Reverse(b.name()));
-        args.sort_unstable_by_key(|b| Reverse(b.name()));
+        args.sort_by_key(|b| std::cmp::Reverse(b.name()));
+        args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name()));
     }
 }
 
diff --git a/src/tools/clippy/tests/ui/unnecessary_sort_by.rs b/src/tools/clippy/tests/ui/unnecessary_sort_by.rs
index 6ee9c3af455..3365bf6e119 100644
--- a/src/tools/clippy/tests/ui/unnecessary_sort_by.rs
+++ b/src/tools/clippy/tests/ui/unnecessary_sort_by.rs
@@ -3,7 +3,6 @@
 #![allow(clippy::stable_sort_primitive)]
 
 use std::cell::Ref;
-use std::cmp::Reverse;
 
 fn unnecessary_sort_by() {
     fn id(x: isize) -> isize {
@@ -76,7 +75,6 @@ mod issue_5754 {
 
 // The closure parameter is not dereferenced anymore, so non-Copy types can be linted
 mod issue_6001 {
-    use super::*;
     struct Test(String);
 
     impl Test {
diff --git a/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr b/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr
index ca9641e8803..89da5e7ea8b 100644
--- a/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr
+++ b/src/tools/clippy/tests/ui/unnecessary_sort_by.stderr
@@ -1,5 +1,5 @@
 error: use Vec::sort here instead
-  --> $DIR/unnecessary_sort_by.rs:15:5
+  --> $DIR/unnecessary_sort_by.rs:14:5
    |
 LL |     vec.sort_by(|a, b| a.cmp(b));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort()`
@@ -7,70 +7,70 @@ LL |     vec.sort_by(|a, b| a.cmp(b));
    = note: `-D clippy::unnecessary-sort-by` implied by `-D warnings`
 
 error: use Vec::sort here instead
-  --> $DIR/unnecessary_sort_by.rs:16:5
+  --> $DIR/unnecessary_sort_by.rs:15:5
    |
 LL |     vec.sort_unstable_by(|a, b| a.cmp(b));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable()`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:17:5
+  --> $DIR/unnecessary_sort_by.rs:16:5
    |
 LL |     vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs()));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (a + 5).abs())`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:18:5
+  --> $DIR/unnecessary_sort_by.rs:17:5
    |
 LL |     vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b)));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| id(-a))`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:21:5
+  --> $DIR/unnecessary_sort_by.rs:20:5
    |
 LL |     vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs()));
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| Reverse((b + 5).abs()))`
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| std::cmp::Reverse((b + 5).abs()))`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:22:5
+  --> $DIR/unnecessary_sort_by.rs:21:5
    |
 LL |     vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a)));
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|b| Reverse(id(-b)))`
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|b| std::cmp::Reverse(id(-b)))`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:32:5
+  --> $DIR/unnecessary_sort_by.rs:31:5
    |
 LL |     vec.sort_by(|a, b| (***a).abs().cmp(&(***b).abs()));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (***a).abs())`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:33:5
+  --> $DIR/unnecessary_sort_by.rs:32:5
    |
 LL |     vec.sort_unstable_by(|a, b| (***a).abs().cmp(&(***b).abs()));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| (***a).abs())`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:93:9
+  --> $DIR/unnecessary_sort_by.rs:91:9
    |
 LL |         args.sort_by(|a, b| a.name().cmp(&b.name()));
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|a| a.name())`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:94:9
+  --> $DIR/unnecessary_sort_by.rs:92:9
    |
 LL |         args.sort_unstable_by(|a, b| a.name().cmp(&b.name()));
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|a| a.name())`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:96:9
+  --> $DIR/unnecessary_sort_by.rs:94:9
    |
 LL |         args.sort_by(|a, b| b.name().cmp(&a.name()));
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|b| Reverse(b.name()))`
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|b| std::cmp::Reverse(b.name()))`
 
 error: use Vec::sort_by_key here instead
-  --> $DIR/unnecessary_sort_by.rs:97:9
+  --> $DIR/unnecessary_sort_by.rs:95:9
    |
 LL |         args.sort_unstable_by(|a, b| b.name().cmp(&a.name()));
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| Reverse(b.name()))`
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name()))`
 
 error: aborting due to 12 previous errors
 
diff --git a/src/tools/clippy/tests/ui/use_self.fixed b/src/tools/clippy/tests/ui/use_self.fixed
index 4e33e343ce0..9d216f56ae6 100644
--- a/src/tools/clippy/tests/ui/use_self.fixed
+++ b/src/tools/clippy/tests/ui/use_self.fixed
@@ -2,7 +2,7 @@
 // aux-build:proc_macro_derive.rs
 
 #![warn(clippy::use_self)]
-#![allow(dead_code)]
+#![allow(dead_code, unreachable_code)]
 #![allow(
     clippy::should_implement_trait,
     clippy::upper_case_acronyms,
@@ -519,3 +519,26 @@ mod self_is_ty_param {
         }
     }
 }
+
+mod use_self_in_pat {
+    enum Foo {
+        Bar,
+        Baz,
+    }
+
+    impl Foo {
+        fn do_stuff(self) {
+            match self {
+                Self::Bar => unimplemented!(),
+                Self::Baz => unimplemented!(),
+            }
+            match Some(1) {
+                Some(_) => unimplemented!(),
+                None => unimplemented!(),
+            }
+            if let Self::Bar = self {
+                unimplemented!()
+            }
+        }
+    }
+}
diff --git a/src/tools/clippy/tests/ui/use_self.rs b/src/tools/clippy/tests/ui/use_self.rs
index 7b621ff9bca..5f604fe25e4 100644
--- a/src/tools/clippy/tests/ui/use_self.rs
+++ b/src/tools/clippy/tests/ui/use_self.rs
@@ -2,7 +2,7 @@
 // aux-build:proc_macro_derive.rs
 
 #![warn(clippy::use_self)]
-#![allow(dead_code)]
+#![allow(dead_code, unreachable_code)]
 #![allow(
     clippy::should_implement_trait,
     clippy::upper_case_acronyms,
@@ -519,3 +519,26 @@ mod self_is_ty_param {
         }
     }
 }
+
+mod use_self_in_pat {
+    enum Foo {
+        Bar,
+        Baz,
+    }
+
+    impl Foo {
+        fn do_stuff(self) {
+            match self {
+                Foo::Bar => unimplemented!(),
+                Foo::Baz => unimplemented!(),
+            }
+            match Some(1) {
+                Some(_) => unimplemented!(),
+                None => unimplemented!(),
+            }
+            if let Foo::Bar = self {
+                unimplemented!()
+            }
+        }
+    }
+}
diff --git a/src/tools/clippy/tests/ui/use_self.stderr b/src/tools/clippy/tests/ui/use_self.stderr
index ecb78b3f972..34d98618253 100644
--- a/src/tools/clippy/tests/ui/use_self.stderr
+++ b/src/tools/clippy/tests/ui/use_self.stderr
@@ -168,5 +168,23 @@ error: unnecessary structure name repetition
 LL |             S2::new()
    |             ^^ help: use the applicable keyword: `Self`
 
-error: aborting due to 28 previous errors
+error: unnecessary structure name repetition
+  --> $DIR/use_self.rs:532:17
+   |
+LL |                 Foo::Bar => unimplemented!(),
+   |                 ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+  --> $DIR/use_self.rs:533:17
+   |
+LL |                 Foo::Baz => unimplemented!(),
+   |                 ^^^ help: use the applicable keyword: `Self`
+
+error: unnecessary structure name repetition
+  --> $DIR/use_self.rs:539:20
+   |
+LL |             if let Foo::Bar = self {
+   |                    ^^^ help: use the applicable keyword: `Self`
+
+error: aborting due to 31 previous errors
 
diff --git a/src/tools/clippy/util/gh-pages/index.html b/src/tools/clippy/util/gh-pages/index.html
index 83a200ca3c4..97c974003c6 100644
--- a/src/tools/clippy/util/gh-pages/index.html
+++ b/src/tools/clippy/util/gh-pages/index.html
@@ -25,7 +25,56 @@ Otherwise, have a great day =^.^=
         blockquote { font-size: 1em; }
         [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; }
 
-        .form-inline .checkbox { margin-right: 0.6em }
+        .dropdown-menu {
+            color: var(--fg);
+            background: var(--theme-popup-bg);
+            border: 1px solid var(--theme-popup-border);
+        }
+
+        .dropdown-menu .divider {
+            background-color: var(--theme-popup-border);
+        }
+
+        .dropdown-menu .checkbox {
+            display: block;
+            white-space: nowrap;
+            margin: 0;
+        }
+        .dropdown-menu .checkbox label {
+            padding: 3px 20px;
+            width: 100%;
+        }
+
+        .dropdown-menu .checkbox input {
+            position: relative;
+            margin: 0 0.5rem 0;
+            padding: 0;
+        }
+
+        .dropdown-menu .checkbox:hover {
+            background-color: var(--theme-hover);
+        }
+
+        div.panel div.panel-body button.dropdown-toggle {
+            background: var(--searchbar-bg);
+            color: var(--searchbar-fg);
+            border-color: var(--theme-popup-border);
+        }
+
+        div.panel div.panel-body button.dropdown-toggle:hover {
+            box-shadow: 0 0 3px var(--searchbar-shadow-color);
+        }
+
+        div.panel div.panel-body .open button.dropdown-toggle {
+            background: var(--searchbar-bg);
+            color: var(--searchbar-fg);
+            border-color: var(--theme-popup-border);
+            filter: brightness(90%);
+        }
+
+        .dropdown-toggle .badge {
+            background-color: #777;
+        }
 
         .panel-heading { cursor: pointer; }
 
@@ -38,6 +87,16 @@ Otherwise, have a great day =^.^=
         .panel .panel-title-name .anchor { display: none; }
         .panel:hover .panel-title-name .anchor { display: inline;}
 
+        .search-control {
+            margin-top: 15px;
+        }
+
+        @media (min-width: 992px) {
+            .search-control {
+                margin-top: 0;
+            }
+        }
+
         .label {
             padding-top: 0.3em;
             padding-bottom: 0.3em;
@@ -143,13 +202,17 @@ Otherwise, have a great day =^.^=
             --inline-code-bg: #191f26;
         }
 
+        .theme-dropdown {
+            position: absolute;
+            margin: 0.7em;
+            z-index: 10;
+        }
+
         /* Applying the mdBook theme */
         .theme-icon {
-            position: absolute;
             text-align: center;
             width: 2em;
             height: 2em;
-            margin: 0.7em;
             line-height: 2em;
             border: solid 1px var(--icons);
             border-radius: 5px;
@@ -160,23 +223,28 @@ Otherwise, have a great day =^.^=
             background: var(--theme-hover);
         }
         .theme-choice {
-            position: absolute;
-            margin-top: calc(2em + 0.7em);
-            margin-left: 0.7em;
+            display: none;
             list-style: none;
             border: 1px solid var(--theme-popup-border);
             border-radius: 5px;
             color: var(--fg);
             background: var(--theme-popup-bg);
             padding: 0 0;
+            overflow: hidden;
         }
+
+        .theme-dropdown.open .theme-choice {
+            display: block;
+        }
+
         .theme-choice > li {
             padding: 5px 10px;
             font-size: 0.8em;
             user-select: none;
             cursor: pointer;
         }
-        .theme-choice > li:hover {
+
+        .theme-choice>li:hover {
             background: var(--theme-hover);
         }
 
@@ -240,17 +308,15 @@ Otherwise, have a great day =^.^=
 
     </style>
 </head>
-<body>
-    <div id="theme-icon" class="theme-icon">&#128396;</div>
-    <ul id="theme-menu" class="theme-choice" style="display: none;">
-        <li id="light">Light</li>
-        <li id="rust">Rust</li>
-        <li id="coal">Coal</li>
-        <li id="navy">Navy</li>
-        <li id="ayu">Ayu</li>
-    </ul>
-
-    <div class="container" ng-app="clippy" ng-controller="lintList">
+<body ng-app="clippy" ng-controller="lintList">
+    <div theme-dropdown class="theme-dropdown">
+        <div id="theme-icon" class="theme-icon">&#128396;</div>
+        <ul id="theme-menu" class="theme-choice">
+            <li id="{{id}}" ng-repeat="(id, name) in themes" ng-click="selectTheme(id)">{{name}}</li>
+        </ul>
+    </div>
+
+    <div class="container">
         <div class="page-header">
             <h1>Clippy Lints</h1>
         </div>
@@ -271,38 +337,62 @@ Otherwise, have a great day =^.^=
             </div>
 
             <div class="panel panel-default" ng-show="data">
-                <div class="panel-body row filter-panel">
-                    <div class="col-md-6 form-inline">
-                        <div class="form-group form-group-lg">
-                            <p class="h4">
-                                Lint levels
-                                <a href="https://doc.rust-lang.org/rustc/lints/levels.html">(?)</a>
-                            </p>
-                            <div class="checkbox" ng-repeat="(level, enabled) in levels">
-                                <label class="text-capitalize">
-                                    <input type="checkbox" ng-model="levels[level]" />
-                                    {{level}}
-                                </label>
-                            </div>
+                <div class="panel-body row">
+                    <div class="col-12 col-md-4">
+                        <div class="btn-group" filter-dropdown>
+                            <button type="button" class="btn btn-default dropdown-toggle">
+                                Lint levels <span class="badge">{{selectedValuesCount(levels)}}</span> <span class="caret"></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                <li class="checkbox">
+                                    <label ng-click="toggleLevels(true)">
+                                        <input type="checkbox" class="invisible" />
+                                        All
+                                    </label>
+                                </li>
+                                <li class="checkbox">
+                                    <label ng-click="toggleLevels(false)">
+                                        <input type="checkbox" class="invisible" />
+                                        None
+                                    </label>
+                                </li>
+                                <li role="separator" class="divider"></li>
+                                <li class="checkbox" ng-repeat="(level, enabled) in levels">
+                                    <label class="text-capitalize">
+                                        <input type="checkbox" ng-model="levels[level]" />
+                                        {{level}}
+                                    </label>
+                                </li>
+                            </ul>
                         </div>
-                    </div>
-                    <div class="col-md-6 form-inline">
-                        <div class="form-group form-group-lg">
-                            <p class="h4">
-                                Lint groups
-                                <a href="https://github.com/rust-lang/rust-clippy/#clippy">(?)</a>
-                            </p>
-                            <div class="checkbox" ng-repeat="(group, enabled) in groups">
-                                <label class="text-capitalize">
-                                    <input type="checkbox" ng-model="groups[group]" />
-                                    {{group}}
-                                </label>
-                            </div>
+                        <div class="btn-group" filter-dropdown>
+                            <button type="button" class="btn btn-default dropdown-toggle">
+                                Lint groups <span class="badge">{{selectedValuesCount(groups)}}</span> <span class="caret"></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                <li class="checkbox">
+                                    <label ng-click="toggleGroups(true)">
+                                        <input type="checkbox" class="invisible" />
+                                        All
+                                    </label>
+                                </li>
+                                <li class="checkbox">
+                                    <label ng-click="toggleGroups(false)">
+                                        <input type="checkbox" class="invisible" />
+                                        None
+                                    </label>
+                                </li>
+                                <li role="separator" class="divider"></li>
+                                <li class="checkbox" ng-repeat="(group, enabled) in groups">
+                                    <label class="text-capitalize">
+                                        <input type="checkbox" ng-model="groups[group]" />
+                                        {{group}}
+                                    </label>
+                                </li>
+                            </ul>
                         </div>
                     </div>
-                </div>
-                <div class="panel-body row">
-                    <div class="col-md-12 form-horizontal">
+                    <div class="col-12 col-md-8 search-control">
                         <div class="input-group">
                             <label class="input-group-addon" id="filter-label" for="filter-input">Filter:</label>
                             <input type="text" class="form-control" placeholder="Keywords or search string" id="filter-input" ng-model="search" ng-model-options="{debounce: 50}"/>
@@ -336,7 +426,7 @@ Otherwise, have a great day =^.^=
                     </h2>
                 </header>
 
-                <div class="list-group lint-docs" ng-class="{collapse: true, in: open[lint.id]}">
+                <div class="list-group lint-docs" ng-if="open[lint.id]" ng-class="{collapse: true, in: open[lint.id]}">
                     <div class="list-group-item lint-doc-md" ng-bind-html="lint.docs | markdown"></div>
                     <div class="lint-additional-info-container">
                         <!-- Applicability -->
@@ -365,7 +455,7 @@ Otherwise, have a great day =^.^=
     </div>
 
     <a href="https://github.com/rust-lang/rust-clippy">
-        <img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
+        <img style="position: absolute; top: 0; right: 0; border: 0; clip-path: polygon(0% 0%, 100% 0%, 100% 100%);" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
     </a>
 
     <script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.3.2/markdown-it.min.js"></script>
@@ -429,6 +519,46 @@ Otherwise, have a great day =^.^=
                 );
             };
         })
+        .directive('themeDropdown', function ($document) {
+            return {
+                restrict: 'A',
+                link: function ($scope, $element, $attr) {
+                    $element.bind('click', function () {
+                        $element.toggleClass('open');
+                        $element.addClass('open-recent');
+                    });
+
+                    $document.bind('click', function () {
+                        if (!$element.hasClass('open-recent')) {
+                            $element.removeClass('open');
+                        }
+                        $element.removeClass('open-recent');
+                    })
+                }
+            }
+        })
+        .directive('filterDropdown', function ($document) {
+            return {
+                restrict: 'A',
+                link: function ($scope, $element, $attr) {
+                    $element.bind('click', function (event) {
+                        if (event.target.closest('button')) {
+                            $element.toggleClass('open');
+                        } else {
+                            $element.addClass('open');
+                        }
+                        $element.addClass('open-recent');
+                    });
+
+                    $document.bind('click', function () {
+                        if (!$element.hasClass('open-recent')) {
+                            $element.removeClass('open');
+                        }
+                        $element.removeClass('open-recent');
+                    })
+                }
+            }
+        })
         .directive('onFinishRender', function ($timeout) {
             return {
                 restrict: 'A',
@@ -462,6 +592,38 @@ Otherwise, have a great day =^.^=
                 suspicious: true,
             };
             $scope.groups = GROUPS_FILTER_DEFAULT;
+            const THEMES_DEFAULT = {
+                light: "Light",
+                rust: "Rust",
+                coal: "Coal",
+                navy: "Navy",
+                ayu: "Ayu"
+            };
+            $scope.themes = THEMES_DEFAULT;
+
+            $scope.selectTheme = function (theme) {
+                setTheme(theme, true);
+            }
+
+            $scope.toggleLevels = function (value) {
+                const levels = $scope.levels;
+                for (const key in levels) {
+                    if (levels.hasOwnProperty(key)) {
+                        levels[key] = value;
+                    }
+                }
+            };
+            $scope.toggleGroups = function (value) {
+                const groups = $scope.groups;
+                for (const key in groups) {
+                    if (groups.hasOwnProperty(key)) {
+                        groups[key] = value;
+                    }
+                }
+            };
+            $scope.selectedValuesCount = function (obj) {
+                return Object.values(obj).filter(x => x).length;
+            }
             $scope.byGroups = function (lint) {
                 return $scope.groups[lint.group];
             };
@@ -558,28 +720,6 @@ Otherwise, have a great day =^.^=
         }
     }
 
-    function setupListeners() {
-        let themeIcon = document.getElementById("theme-icon");
-        let themeMenu = document.getElementById("theme-menu");
-        themeIcon.addEventListener("click", function(e) {
-            if (themeMenu.style.display == "none") {
-                themeMenu.style.display = "block";
-            } else {
-                themeMenu.style.display = "none";
-            }
-        });
-
-        let children = themeMenu.children;
-        for (let index = 0; index < children.length; index++) {
-            let child = children[index];
-            child.addEventListener("click", function(e) {
-                setTheme(child.id, true);
-            });
-        }
-    }
-
-    setupListeners();
-
     function setTheme(theme, store) {
         let enableHighlight = false;
         let enableNight = false;