about summary refs log tree commit diff
path: root/clippy_lints_internal
diff options
context:
space:
mode:
authorJason Newcomb <jsnewcomb@pm.me>2024-08-05 18:23:48 -0400
committerJason Newcomb <jsnewcomb@pm.me>2025-04-12 17:53:36 -0400
commit5b4b463d4925d2ccc56afbf83a2f0e4a50b0f56c (patch)
tree00ea17151e827168665748508d3b9c2dbd7236f0 /clippy_lints_internal
parentec105bab2f29abe73f55bbf9e9aefcf143e88383 (diff)
downloadrust-5b4b463d4925d2ccc56afbf83a2f0e4a50b0f56c.tar.gz
rust-5b4b463d4925d2ccc56afbf83a2f0e4a50b0f56c.zip
Move internal lints to their own crate
Diffstat (limited to 'clippy_lints_internal')
-rw-r--r--clippy_lints_internal/Cargo.toml14
-rw-r--r--clippy_lints_internal/src/almost_standard_lint_formulation.rs87
-rw-r--r--clippy_lints_internal/src/collapsible_calls.rs250
-rw-r--r--clippy_lints_internal/src/interning_defined_symbol.rs244
-rw-r--r--clippy_lints_internal/src/invalid_paths.rs108
-rw-r--r--clippy_lints_internal/src/lib.rs78
-rw-r--r--clippy_lints_internal/src/lint_without_lint_pass.rs282
-rw-r--r--clippy_lints_internal/src/msrv_attr_impl.rs59
-rw-r--r--clippy_lints_internal/src/outer_expn_data_pass.rs61
-rw-r--r--clippy_lints_internal/src/produce_ice.rs43
-rw-r--r--clippy_lints_internal/src/slow_symbol_comparisons.rs76
-rw-r--r--clippy_lints_internal/src/unnecessary_def_path.rs303
-rw-r--r--clippy_lints_internal/src/unsorted_clippy_utils_paths.rs54
13 files changed, 1659 insertions, 0 deletions
diff --git a/clippy_lints_internal/Cargo.toml b/clippy_lints_internal/Cargo.toml
new file mode 100644
index 00000000000..2a0ceac27a3
--- /dev/null
+++ b/clippy_lints_internal/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "clippy_lints_internal"
+version = "0.0.1"
+edition = "2021"
+
+[dependencies]
+clippy_config = { path = "../clippy_config" }
+clippy_utils = { path = "../clippy_utils" }
+regex = { version = "1.5" }
+rustc-semver = "1.1"
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
diff --git a/clippy_lints_internal/src/almost_standard_lint_formulation.rs b/clippy_lints_internal/src/almost_standard_lint_formulation.rs
new file mode 100644
index 00000000000..4fd5ea459a5
--- /dev/null
+++ b/clippy_lints_internal/src/almost_standard_lint_formulation.rs
@@ -0,0 +1,87 @@
+use crate::lint_without_lint_pass::is_lint_ref_type;
+use clippy_utils::diagnostics::span_lint_and_help;
+use regex::Regex;
+use rustc_hir::{Attribute, Item, ItemKind, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_session::impl_lint_pass;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks if lint formulations have a standardized format.
+    ///
+    /// ### Why is this bad?
+    /// It's not necessarily bad, but we try to enforce a standard in Clippy.
+    ///
+    /// ### Example
+    /// `Checks for use...` can be written as `Checks for usage...` .
+    pub clippy::ALMOST_STANDARD_LINT_FORMULATION,
+    Warn,
+    "lint formulations must have a standardized format.",
+    report_in_external_macro: true
+}
+
+impl_lint_pass!(AlmostStandardFormulation => [ALMOST_STANDARD_LINT_FORMULATION]);
+
+pub struct AlmostStandardFormulation {
+    standard_formulations: Vec<StandardFormulations<'static>>,
+}
+
+#[derive(Debug)]
+struct StandardFormulations<'a> {
+    wrong_pattern: Regex,
+    correction: &'a str,
+}
+
+impl AlmostStandardFormulation {
+    pub fn new() -> Self {
+        let standard_formulations = vec![StandardFormulations {
+            wrong_pattern: Regex::new("^(Check for|Detects? uses?)").unwrap(),
+            correction: "Checks for",
+        }];
+        Self { standard_formulations }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+        let mut check_next = false;
+        if let ItemKind::Static(_, ty, Mutability::Not, _) = item.kind {
+            let lines = cx
+                .tcx
+                .hir_attrs(item.hir_id())
+                .iter()
+                .filter_map(|attr| Attribute::doc_str(attr).map(|sym| (sym, attr)));
+            if is_lint_ref_type(cx, ty) {
+                for (line, attr) in lines {
+                    let cur_line = line.as_str().trim();
+                    if check_next && !cur_line.is_empty() {
+                        for formulation in &self.standard_formulations {
+                            let starts_with_correct_formulation = cur_line.starts_with(formulation.correction);
+                            if !starts_with_correct_formulation && formulation.wrong_pattern.is_match(cur_line) {
+                                if let Some(ident) = attr.ident() {
+                                    span_lint_and_help(
+                                        cx,
+                                        ALMOST_STANDARD_LINT_FORMULATION,
+                                        ident.span,
+                                        "non-standard lint formulation",
+                                        None,
+                                        format!("consider using `{}`", formulation.correction),
+                                    );
+                                }
+                                return;
+                            }
+                        }
+                        return;
+                    } else if cur_line.contains("What it does") {
+                        check_next = true;
+                    } else if cur_line.contains("Why is this bad") {
+                        // Formulation documentation is done. Can add check to ensure that missing formulation is added
+                        // and add a check if it matches no accepted formulation
+                        return;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/clippy_lints_internal/src/collapsible_calls.rs b/clippy_lints_internal/src/collapsible_calls.rs
new file mode 100644
index 00000000000..d7967a0cc02
--- /dev/null
+++ b/clippy_lints_internal/src/collapsible_calls.rs
@@ -0,0 +1,250 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::{SpanlessEq, is_expr_path_def_path, is_lint_allowed, peel_blocks_with_stmt};
+use rustc_errors::Applicability;
+use rustc_hir::{Closure, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_session::declare_lint_pass;
+use rustc_span::Span;
+
+use std::borrow::{Borrow, Cow};
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Lints `span_lint_and_then` function calls, where the
+    /// closure argument has only one statement and that statement is a method
+    /// call to `span_suggestion`, `span_help`, `span_note` (using the same
+    /// span), `help` or `note`.
+    ///
+    /// These usages of `span_lint_and_then` should be replaced with one of the
+    /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
+    /// `span_lint_and_note`.
+    ///
+    /// ### Why is this bad?
+    /// Using the wrapper `span_lint_and_*` functions, is more
+    /// convenient, readable and less error prone.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.span_suggestion(
+    ///         expr.span,
+    ///         help_msg,
+    ///         sugg.to_string(),
+    ///         Applicability::MachineApplicable,
+    ///     );
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.span_help(expr.span, help_msg);
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.help(help_msg);
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.span_note(expr.span, note_msg);
+    /// });
+    /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+    ///     diag.note(note_msg);
+    /// });
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// span_lint_and_sugg(
+    ///     cx,
+    ///     TEST_LINT,
+    ///     expr.span,
+    ///     lint_msg,
+    ///     help_msg,
+    ///     sugg.to_string(),
+    ///     Applicability::MachineApplicable,
+    /// );
+    /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+    /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+    /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+    /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+    /// ```
+    pub clippy::COLLAPSIBLE_SPAN_LINT_CALLS,
+    Warn,
+    "found collapsible `span_lint_and_then` calls",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
+
+impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        if is_lint_allowed(cx, COLLAPSIBLE_SPAN_LINT_CALLS, expr.hir_id) {
+            return;
+        }
+
+        if let ExprKind::Call(func, [call_cx, call_lint, call_sp, call_msg, call_f]) = expr.kind
+            && is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"])
+            && let ExprKind::Closure(&Closure { body, .. }) = call_f.kind
+            && let body = cx.tcx.hir_body(body)
+            && let only_expr = peel_blocks_with_stmt(body.value)
+            && let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind
+            && let ExprKind::Path(..) = recv.kind
+        {
+            let and_then_snippets =
+                get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span);
+            let mut sle = SpanlessEq::new(cx).deny_side_effects();
+            match ps.ident.as_str() {
+                "span_suggestion" if sle.eq_expr(call_sp, &span_call_args[0]) => {
+                    suggest_suggestion(
+                        cx,
+                        expr,
+                        &and_then_snippets,
+                        &span_suggestion_snippets(cx, span_call_args),
+                    );
+                },
+                "span_help" if sle.eq_expr(call_sp, &span_call_args[0]) => {
+                    let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+                    suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
+                },
+                "span_note" if sle.eq_expr(call_sp, &span_call_args[0]) => {
+                    let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+                    suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
+                },
+                "help" => {
+                    let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
+                    suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
+                },
+                "note" => {
+                    let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
+                    suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
+                },
+                _ => (),
+            }
+        }
+    }
+}
+
+struct AndThenSnippets<'a> {
+    cx: Cow<'a, str>,
+    lint: Cow<'a, str>,
+    span: Cow<'a, str>,
+    msg: Cow<'a, str>,
+}
+
+fn get_and_then_snippets(
+    cx: &LateContext<'_>,
+    cx_span: Span,
+    lint_span: Span,
+    span_span: Span,
+    msg_span: Span,
+) -> AndThenSnippets<'static> {
+    let cx_snippet = snippet(cx, cx_span, "cx");
+    let lint_snippet = snippet(cx, lint_span, "..");
+    let span_snippet = snippet(cx, span_span, "span");
+    let msg_snippet = snippet(cx, msg_span, r#""...""#);
+
+    AndThenSnippets {
+        cx: cx_snippet,
+        lint: lint_snippet,
+        span: span_snippet,
+        msg: msg_snippet,
+    }
+}
+
+struct SpanSuggestionSnippets<'a> {
+    help: Cow<'a, str>,
+    sugg: Cow<'a, str>,
+    applicability: Cow<'a, str>,
+}
+
+fn span_suggestion_snippets<'a, 'hir>(
+    cx: &LateContext<'_>,
+    span_call_args: &'hir [Expr<'hir>],
+) -> SpanSuggestionSnippets<'a> {
+    let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+    let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
+    let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
+
+    SpanSuggestionSnippets {
+        help: help_snippet,
+        sugg: sugg_snippet,
+        applicability: applicability_snippet,
+    }
+}
+
+fn suggest_suggestion(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    and_then_snippets: &AndThenSnippets<'_>,
+    span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
+) {
+    span_lint_and_sugg(
+        cx,
+        COLLAPSIBLE_SPAN_LINT_CALLS,
+        expr.span,
+        "this call is collapsible",
+        "collapse into",
+        format!(
+            "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
+            and_then_snippets.cx,
+            and_then_snippets.lint,
+            and_then_snippets.span,
+            and_then_snippets.msg,
+            span_suggestion_snippets.help,
+            span_suggestion_snippets.sugg,
+            span_suggestion_snippets.applicability
+        ),
+        Applicability::MachineApplicable,
+    );
+}
+
+fn suggest_help(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    and_then_snippets: &AndThenSnippets<'_>,
+    help: &str,
+    with_span: bool,
+) {
+    let option_span = if with_span {
+        format!("Some({})", and_then_snippets.span)
+    } else {
+        "None".to_string()
+    };
+
+    span_lint_and_sugg(
+        cx,
+        COLLAPSIBLE_SPAN_LINT_CALLS,
+        expr.span,
+        "this call is collapsible",
+        "collapse into",
+        format!(
+            "span_lint_and_help({}, {}, {}, {}, {}, {help})",
+            and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span,
+        ),
+        Applicability::MachineApplicable,
+    );
+}
+
+fn suggest_note(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    and_then_snippets: &AndThenSnippets<'_>,
+    note: &str,
+    with_span: bool,
+) {
+    let note_span = if with_span {
+        format!("Some({})", and_then_snippets.span)
+    } else {
+        "None".to_string()
+    };
+
+    span_lint_and_sugg(
+        cx,
+        COLLAPSIBLE_SPAN_LINT_CALLS,
+        expr.span,
+        "this call is collapsible",
+        "collapse into",
+        format!(
+            "span_lint_and_note({}, {}, {}, {}, {note_span}, {note})",
+            and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg,
+        ),
+        Applicability::MachineApplicable,
+    );
+}
diff --git a/clippy_lints_internal/src/interning_defined_symbol.rs b/clippy_lints_internal/src/interning_defined_symbol.rs
new file mode 100644
index 00000000000..831e8876330
--- /dev/null
+++ b/clippy_lints_internal/src/interning_defined_symbol.rs
@@ -0,0 +1,244 @@
+use clippy_utils::consts::{ConstEvalCtxt, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::match_type;
+use clippy_utils::{def_path_def_ids, is_expn_of, match_def_path, paths};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_middle::mir::ConstValue;
+use rustc_middle::ty;
+use rustc_session::impl_lint_pass;
+use rustc_span::sym;
+use rustc_span::symbol::Symbol;
+
+use std::borrow::Cow;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for interning symbols that have already been pre-interned and defined as constants.
+    ///
+    /// ### Why is this bad?
+    /// It's faster and easier to use the symbol constant.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// let _ = sym!(f32);
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// let _ = sym::f32;
+    /// ```
+    pub clippy::INTERNING_DEFINED_SYMBOL,
+    Warn,
+    "interning a symbol that is pre-interned and defined as a constant",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for unnecessary conversion from Symbol to a string.
+    ///
+    /// ### Why is this bad?
+    /// It's faster use symbols directly instead of strings.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// symbol.as_str() == "clippy";
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// symbol == sym::clippy;
+    /// ```
+    pub clippy::UNNECESSARY_SYMBOL_STR,
+    Warn,
+    "unnecessary conversion between Symbol and string",
+    report_in_external_macro: true
+}
+
+#[derive(Default)]
+pub struct InterningDefinedSymbol {
+    // Maps the symbol value to the constant DefId.
+    symbol_map: FxHashMap<u32, DefId>,
+}
+
+impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]);
+
+impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
+    fn check_crate(&mut self, cx: &LateContext<'_>) {
+        if !self.symbol_map.is_empty() {
+            return;
+        }
+
+        for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
+            for def_id in def_path_def_ids(cx.tcx, module) {
+                for item in cx.tcx.module_children(def_id) {
+                    if let Res::Def(DefKind::Const, item_def_id) = item.res
+                        && let ty = cx.tcx.type_of(item_def_id).instantiate_identity()
+                        && match_type(cx, ty, &paths::SYMBOL)
+                        && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id)
+                        && let Some(value) = value.to_u32().discard_err()
+                    {
+                        self.symbol_map.insert(value, item_def_id);
+                    }
+                }
+            }
+        }
+    }
+
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        if let ExprKind::Call(func, [arg]) = &expr.kind
+            && let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind()
+            && cx.tcx.is_diagnostic_item(sym::SymbolIntern, *def_id)
+            && let Some(Constant::Str(arg)) = ConstEvalCtxt::new(cx).eval_simple(arg)
+            && let value = Symbol::intern(&arg).as_u32()
+            && let Some(&def_id) = self.symbol_map.get(&value)
+        {
+            span_lint_and_sugg(
+                cx,
+                INTERNING_DEFINED_SYMBOL,
+                is_expn_of(expr.span, "sym").unwrap_or(expr.span),
+                "interning a defined symbol",
+                "try",
+                cx.tcx.def_path_str(def_id),
+                Applicability::MachineApplicable,
+            );
+        }
+        if let ExprKind::Binary(op, left, right) = expr.kind
+            && matches!(op.node, BinOpKind::Eq | BinOpKind::Ne)
+        {
+            let data = [
+                (left, self.symbol_str_expr(left, cx)),
+                (right, self.symbol_str_expr(right, cx)),
+            ];
+            match data {
+                // both operands are a symbol string
+                [(_, Some(left)), (_, Some(right))] => {
+                    span_lint_and_sugg(
+                        cx,
+                        UNNECESSARY_SYMBOL_STR,
+                        expr.span,
+                        "unnecessary `Symbol` to string conversion",
+                        "try",
+                        format!(
+                            "{} {} {}",
+                            left.as_symbol_snippet(cx),
+                            op.node.as_str(),
+                            right.as_symbol_snippet(cx),
+                        ),
+                        Applicability::MachineApplicable,
+                    );
+                },
+                // one of the operands is a symbol string
+                [(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => {
+                    // creating an owned string for comparison
+                    if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) {
+                        span_lint_and_sugg(
+                            cx,
+                            UNNECESSARY_SYMBOL_STR,
+                            expr.span,
+                            "unnecessary string allocation",
+                            "try",
+                            format!("{}.as_str()", symbol.as_symbol_snippet(cx)),
+                            Applicability::MachineApplicable,
+                        );
+                    }
+                },
+                // nothing found
+                [(_, None), (_, None)] => {},
+            }
+        }
+    }
+}
+
+impl InterningDefinedSymbol {
+    fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
+        static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR];
+        static SYMBOL_STR_PATHS: &[&[&str]] = &[&paths::SYMBOL_AS_STR, &paths::SYMBOL_TO_IDENT_STRING];
+        let call = if let ExprKind::AddrOf(_, _, e) = expr.kind
+            && let ExprKind::Unary(UnOp::Deref, e) = e.kind
+        {
+            e
+        } else {
+            expr
+        };
+        if let ExprKind::MethodCall(_, item, [], _) = call.kind
+            // is a method call
+            && let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id)
+            && let ty = cx.typeck_results().expr_ty(item)
+            // ...on either an Ident or a Symbol
+            && let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) {
+                Some(false)
+            } else if match_type(cx, ty, &paths::IDENT) {
+                Some(true)
+            } else {
+                None
+            }
+            // ...which converts it to a string
+            && let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS }
+            && let Some(is_to_owned) = paths
+                .iter()
+                .find_map(|path| if match_def_path(cx, did, path) {
+                    Some(path == &paths::SYMBOL_TO_IDENT_STRING)
+                } else {
+                    None
+                })
+                .or_else(|| if cx.tcx.is_diagnostic_item(sym::to_string_method, did) {
+                    Some(true)
+                } else {
+                    None
+                })
+        {
+            return Some(SymbolStrExpr::Expr {
+                item,
+                is_ident,
+                is_to_owned,
+            });
+        }
+        // is a string constant
+        if let Some(Constant::Str(s)) = ConstEvalCtxt::new(cx).eval_simple(expr) {
+            let value = Symbol::intern(&s).as_u32();
+            // ...which matches a symbol constant
+            if let Some(&def_id) = self.symbol_map.get(&value) {
+                return Some(SymbolStrExpr::Const(def_id));
+            }
+        }
+        None
+    }
+}
+
+enum SymbolStrExpr<'tcx> {
+    /// a string constant with a corresponding symbol constant
+    Const(DefId),
+    /// a "symbol to string" expression like `symbol.as_str()`
+    Expr {
+        /// part that evaluates to `Symbol` or `Ident`
+        item: &'tcx Expr<'tcx>,
+        is_ident: bool,
+        /// whether an owned `String` is created like `to_ident_string()`
+        is_to_owned: bool,
+    },
+}
+
+impl<'tcx> SymbolStrExpr<'tcx> {
+    /// Returns a snippet that evaluates to a `Symbol` and is const if possible
+    fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> {
+        match *self {
+            Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(),
+            Self::Expr { item, is_ident, .. } => {
+                let mut snip = snippet(cx, item.span.source_callsite(), "..");
+                if is_ident {
+                    // get `Ident.name`
+                    snip.to_mut().push_str(".name");
+                }
+                snip
+            },
+        }
+    }
+}
diff --git a/clippy_lints_internal/src/invalid_paths.rs b/clippy_lints_internal/src/invalid_paths.rs
new file mode 100644
index 00000000000..bee87efa3fc
--- /dev/null
+++ b/clippy_lints_internal/src/invalid_paths.rs
@@ -0,0 +1,108 @@
+use clippy_utils::consts::{ConstEvalCtxt, Constant};
+use clippy_utils::def_path_res;
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir as hir;
+use rustc_hir::Item;
+use rustc_hir::def::DefKind;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_middle::ty::fast_reject::SimplifiedType;
+use rustc_middle::ty::{self, FloatTy};
+use rustc_session::declare_lint_pass;
+use rustc_span::symbol::Symbol;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks the paths module for invalid paths.
+    ///
+    /// ### Why is this bad?
+    /// It indicates a bug in the code.
+    ///
+    /// ### Example
+    /// None.
+    pub clippy::INVALID_PATHS,
+    Warn,
+    "invalid path",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+        let local_def_id = &cx.tcx.parent_module(item.hir_id());
+        let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
+        if mod_name.as_str() == "paths"
+            && let hir::ItemKind::Const(.., body_id) = item.kind
+            && let Some(Constant::Vec(path)) = ConstEvalCtxt::with_env(
+                cx.tcx,
+                ty::TypingEnv::post_analysis(cx.tcx, item.owner_id),
+                cx.tcx.typeck(item.owner_id),
+            )
+            .eval_simple(cx.tcx.hir_body(body_id).value)
+            && let Some(path) = path
+                .iter()
+                .map(|x| {
+                    if let Constant::Str(s) = x {
+                        Some(s.as_str())
+                    } else {
+                        None
+                    }
+                })
+                .collect::<Option<Vec<&str>>>()
+            && !check_path(cx, &path[..])
+        {
+            span_lint(cx, INVALID_PATHS, item.span, "invalid path");
+        }
+    }
+}
+
+// This is not a complete resolver for paths. It works on all the paths currently used in the paths
+// module.  That's all it does and all it needs to do.
+pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
+    if !def_path_res(cx.tcx, path).is_empty() {
+        return true;
+    }
+
+    // Some implementations can't be found by `path_to_res`, particularly inherent
+    // implementations of native types. Check lang items.
+    let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
+    let lang_items = cx.tcx.lang_items();
+    // This list isn't complete, but good enough for our current list of paths.
+    let incoherent_impls = [
+        SimplifiedType::Float(FloatTy::F32),
+        SimplifiedType::Float(FloatTy::F64),
+        SimplifiedType::Slice,
+        SimplifiedType::Str,
+        SimplifiedType::Bool,
+        SimplifiedType::Char,
+    ]
+    .iter()
+    .flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter())
+    .copied();
+    for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) {
+        let lang_item_path = cx.get_def_path(item_def_id);
+        if path_syms.starts_with(&lang_item_path)
+            && let [item] = &path_syms[lang_item_path.len()..]
+        {
+            if matches!(
+                cx.tcx.def_kind(item_def_id),
+                DefKind::Mod | DefKind::Enum | DefKind::Trait
+            ) {
+                for child in cx.tcx.module_children(item_def_id) {
+                    if child.ident.name == *item {
+                        return true;
+                    }
+                }
+            } else {
+                for child in cx.tcx.associated_item_def_ids(item_def_id) {
+                    if cx.tcx.item_name(*child) == *item {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+
+    false
+}
diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs
new file mode 100644
index 00000000000..1e59a93dc9e
--- /dev/null
+++ b/clippy_lints_internal/src/lib.rs
@@ -0,0 +1,78 @@
+#![feature(let_chains, rustc_private)]
+#![allow(
+    clippy::missing_docs_in_private_items,
+    clippy::must_use_candidate,
+    rustc::diagnostic_outside_of_impl,
+    rustc::untranslatable_diagnostic
+)]
+#![warn(
+    trivial_casts,
+    trivial_numeric_casts,
+    rust_2018_idioms,
+    unused_lifetimes,
+    unused_qualifications,
+    rustc::internal
+)]
+// Disable this rustc lint for now, as it was also done in rustc
+#![allow(rustc::potential_query_instability)]
+// None of these lints need a version.
+#![allow(clippy::missing_clippy_version_attribute)]
+
+extern crate rustc_ast;
+extern crate rustc_attr_parsing;
+extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_lint_defs;
+extern crate rustc_middle;
+extern crate rustc_session;
+extern crate rustc_span;
+
+mod almost_standard_lint_formulation;
+mod collapsible_calls;
+mod interning_defined_symbol;
+mod invalid_paths;
+mod lint_without_lint_pass;
+mod msrv_attr_impl;
+mod outer_expn_data_pass;
+mod produce_ice;
+mod slow_symbol_comparisons;
+mod unnecessary_def_path;
+mod unsorted_clippy_utils_paths;
+
+use rustc_lint::{Lint, LintStore};
+
+static LINTS: &[&Lint] = &[
+    almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION,
+    collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
+    interning_defined_symbol::INTERNING_DEFINED_SYMBOL,
+    interning_defined_symbol::UNNECESSARY_SYMBOL_STR,
+    invalid_paths::INVALID_PATHS,
+    lint_without_lint_pass::DEFAULT_LINT,
+    lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
+    lint_without_lint_pass::LINT_WITHOUT_LINT_PASS,
+    lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE,
+    msrv_attr_impl::MISSING_MSRV_ATTR_IMPL,
+    outer_expn_data_pass::OUTER_EXPN_EXPN_DATA,
+    produce_ice::PRODUCE_ICE,
+    slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS,
+    unnecessary_def_path::UNNECESSARY_DEF_PATH,
+    unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS,
+];
+
+pub fn register_lints(store: &mut LintStore) {
+    store.register_lints(LINTS);
+
+    store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths));
+    store.register_early_pass(|| Box::new(produce_ice::ProduceIce));
+    store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls));
+    store.register_late_pass(|_| Box::new(invalid_paths::InvalidPaths));
+    store.register_late_pass(|_| Box::<interning_defined_symbol::InterningDefinedSymbol>::default());
+    store.register_late_pass(|_| Box::<lint_without_lint_pass::LintWithoutLintPass>::default());
+    store.register_late_pass(|_| Box::<unnecessary_def_path::UnnecessaryDefPath>::default());
+    store.register_late_pass(|_| Box::new(outer_expn_data_pass::OuterExpnDataPass));
+    store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl));
+    store.register_late_pass(|_| Box::new(almost_standard_lint_formulation::AlmostStandardFormulation::new()));
+    store.register_late_pass(|_| Box::new(slow_symbol_comparisons::SlowSymbolComparisons));
+}
diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs
new file mode 100644
index 00000000000..6a75defcce3
--- /dev/null
+++ b/clippy_lints_internal/src/lint_without_lint_pass.rs
@@ -0,0 +1,282 @@
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::macros::root_macro_call_first_node;
+use clippy_utils::{is_lint_allowed, match_def_path, paths};
+use rustc_ast::ast::LitKind;
+use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_middle::hir::nested_filter;
+use rustc_session::impl_lint_pass;
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::Symbol;
+use rustc_span::{Span, sym};
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Ensures every lint is associated to a `LintPass`.
+    ///
+    /// ### Why is this bad?
+    /// The compiler only knows lints via a `LintPass`. Without
+    /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not
+    /// know the name of the lint.
+    ///
+    /// ### Known problems
+    /// Only checks for lints associated using the `declare_lint_pass!` and
+    /// `impl_lint_pass!` macros.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// declare_lint! { pub LINT_1, ... }
+    /// declare_lint! { pub LINT_2, ... }
+    /// declare_lint! { pub FORGOTTEN_LINT, ... }
+    /// // ...
+    /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
+    /// // missing FORGOTTEN_LINT
+    /// ```
+    pub clippy::LINT_WITHOUT_LINT_PASS,
+    Warn,
+    "declaring a lint without associating it in a LintPass",
+    report_in_external_macro: true
+
+}
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for cases of an auto-generated lint without an updated description,
+    /// i.e. `default lint description`.
+    ///
+    /// ### Why is this bad?
+    /// Indicates that the lint is not finished.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
+    /// ```
+    pub clippy::DEFAULT_LINT,
+    Warn,
+    "found 'default lint description' in a lint declaration",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for invalid `clippy::version` attributes.
+    ///
+    /// Valid values are:
+    /// * "pre 1.29.0"
+    /// * any valid semantic version
+    pub clippy::INVALID_CLIPPY_VERSION_ATTRIBUTE,
+    Warn,
+    "found an invalid `clippy::version` attribute",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for declared clippy lints without the `clippy::version` attribute.
+    pub clippy::MISSING_CLIPPY_VERSION_ATTRIBUTE,
+    Warn,
+    "found clippy lint without `clippy::version` attribute",
+    report_in_external_macro: true
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct LintWithoutLintPass {
+    declared_lints: FxIndexMap<Symbol, Span>,
+    registered_lints: FxIndexSet<Symbol>,
+}
+
+impl_lint_pass!(LintWithoutLintPass => [
+    DEFAULT_LINT,
+    LINT_WITHOUT_LINT_PASS,
+    INVALID_CLIPPY_VERSION_ATTRIBUTE,
+    MISSING_CLIPPY_VERSION_ATTRIBUTE,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+        if let hir::ItemKind::Static(ident, ty, Mutability::Not, body_id) = item.kind {
+            if is_lint_ref_type(cx, ty) {
+                check_invalid_clippy_version_attribute(cx, item);
+
+                let expr = &cx.tcx.hir_body(body_id).value;
+                let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
+                    && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind
+                {
+                    struct_fields
+                } else {
+                    return;
+                };
+
+                let field = fields
+                    .iter()
+                    .find(|f| f.ident.as_str() == "desc")
+                    .expect("lints must have a description field");
+
+                if let ExprKind::Lit(Spanned {
+                    node: LitKind::Str(sym, _),
+                    ..
+                }) = field.expr.kind
+                {
+                    let sym_str = sym.as_str();
+                    if sym_str == "default lint description" {
+                        span_lint(
+                            cx,
+                            DEFAULT_LINT,
+                            item.span,
+                            format!("the lint `{}` has the default lint description", ident.name),
+                        );
+                    }
+                    self.declared_lints.insert(ident.name, item.span);
+                }
+            }
+        } else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
+            if !matches!(
+                cx.tcx.item_name(macro_call.def_id).as_str(),
+                "impl_lint_pass" | "declare_lint_pass"
+            ) {
+                return;
+            }
+            if let hir::ItemKind::Impl(hir::Impl {
+                of_trait: None,
+                items: impl_item_refs,
+                ..
+            }) = item.kind
+            {
+                let mut collector = LintCollector {
+                    output: &mut self.registered_lints,
+                    cx,
+                };
+                let body = cx.tcx.hir_body_owned_by(
+                    impl_item_refs
+                        .iter()
+                        .find(|iiref| iiref.ident.as_str() == "lint_vec")
+                        .expect("LintPass needs to implement lint_vec")
+                        .id
+                        .owner_id
+                        .def_id,
+                );
+                collector.visit_expr(body.value);
+            }
+        }
+    }
+
+    fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+        if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
+            return;
+        }
+
+        for (lint_name, &lint_span) in &self.declared_lints {
+            // When using the `declare_tool_lint!` macro, the original `lint_span`'s
+            // file points to "<rustc macros>".
+            // `compiletest-rs` thinks that's an error in a different file and
+            // just ignores it. This causes the test in compile-fail/lint_pass
+            // not able to capture the error.
+            // Therefore, we need to climb the macro expansion tree and find the
+            // actual span that invoked `declare_tool_lint!`:
+            let lint_span = lint_span.ctxt().outer_expn_data().call_site;
+
+            if !self.registered_lints.contains(lint_name) {
+                span_lint(
+                    cx,
+                    LINT_WITHOUT_LINT_PASS,
+                    lint_span,
+                    format!("the lint `{lint_name}` is not added to any `LintPass`"),
+                );
+            }
+        }
+    }
+}
+
+pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+    if let TyKind::Ref(
+        _,
+        MutTy {
+            ty: inner,
+            mutbl: Mutability::Not,
+        },
+    ) = ty.kind
+        && let TyKind::Path(ref path) = inner.kind
+        && let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id)
+    {
+        return match_def_path(cx, def_id, &paths::LINT);
+    }
+
+    false
+}
+
+fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
+    if let Some(value) = extract_clippy_version_value(cx, item) {
+        if value.as_str() == "pre 1.29.0" {
+            return;
+        }
+
+        if rustc_attr_parsing::parse_version(value).is_none() {
+            span_lint_and_help(
+                cx,
+                INVALID_CLIPPY_VERSION_ATTRIBUTE,
+                item.span,
+                "this item has an invalid `clippy::version` attribute",
+                None,
+                "please use a valid semantic version, see `doc/adding_lints.md`",
+            );
+        }
+    } else {
+        span_lint_and_help(
+            cx,
+            MISSING_CLIPPY_VERSION_ATTRIBUTE,
+            item.span,
+            "this lint is missing the `clippy::version` attribute or version value",
+            None,
+            "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
+        );
+    }
+}
+
+/// This function extracts the version value of a `clippy::version` attribute if the given value has
+/// one
+pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
+    let attrs = cx.tcx.hir_attrs(item.hir_id());
+    attrs.iter().find_map(|attr| {
+        if let hir::Attribute::Unparsed(attr_kind) = &attr
+            // Identify attribute
+            && let [tool_name, attr_name] = &attr_kind.path.segments[..]
+            && tool_name.name == sym::clippy
+            && attr_name.name == sym::version
+            && let Some(version) = attr.value_str()
+        {
+            Some(version)
+        } else {
+            None
+        }
+    })
+}
+
+struct LintCollector<'a, 'tcx> {
+    output: &'a mut FxIndexSet<Symbol>,
+    cx: &'a LateContext<'tcx>,
+}
+
+impl<'tcx> Visitor<'tcx> for LintCollector<'_, 'tcx> {
+    type NestedFilter = nested_filter::All;
+
+    fn visit_path(&mut self, path: &Path<'_>, _: HirId) {
+        if path.segments.len() == 1 {
+            self.output.insert(path.segments[0].ident.name);
+        }
+    }
+
+    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
+        self.cx.tcx
+    }
+}
diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs
new file mode 100644
index 00000000000..dda054546e2
--- /dev/null
+++ b/clippy_lints_internal/src/msrv_attr_impl.rs
@@ -0,0 +1,59 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::match_type;
+use clippy_utils::{match_def_path, paths};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_middle::ty::{self, EarlyBinder, GenericArgKind};
+use rustc_session::declare_lint_pass;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
+    pub clippy::MISSING_MSRV_ATTR_IMPL,
+    Warn,
+    "checking if all necessary steps were taken when adding a MSRV to a lint",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]);
+
+impl LateLintPass<'_> for MsrvAttrImpl {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if let hir::ItemKind::Impl(hir::Impl {
+            of_trait: Some(_),
+            items,
+            ..
+        }) = &item.kind
+            && let Some(trait_ref) = cx
+                .tcx
+                .impl_trait_ref(item.owner_id)
+                .map(EarlyBinder::instantiate_identity)
+            && match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS)
+            && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind()
+            && self_ty_def.is_struct()
+            && self_ty_def.all_fields().any(|f| {
+                cx.tcx
+                    .type_of(f.did)
+                    .instantiate_identity()
+                    .walk()
+                    .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
+                    .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV_STACK))
+            })
+            && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes")
+        {
+            let span = cx.sess().source_map().span_through_char(item.span, '{');
+            span_lint_and_sugg(
+                cx,
+                MISSING_MSRV_ATTR_IMPL,
+                span,
+                "`extract_msrv_attr!` macro missing from `EarlyLintPass` implementation",
+                "add `extract_msrv_attr!()` to the `EarlyLintPass` implementation",
+                format!("{}\n    extract_msrv_attr!();", snippet(cx, span, "..")),
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
diff --git a/clippy_lints_internal/src/outer_expn_data_pass.rs b/clippy_lints_internal/src/outer_expn_data_pass.rs
new file mode 100644
index 00000000000..e9441964797
--- /dev/null
+++ b/clippy_lints_internal/src/outer_expn_data_pass.rs
@@ -0,0 +1,61 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::match_type;
+use clippy_utils::{is_lint_allowed, method_calls, paths};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_session::declare_lint_pass;
+use rustc_span::symbol::Symbol;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for calls to `cx.outer().expn_data()` and suggests to use
+    /// the `cx.outer_expn_data()`
+    ///
+    /// ### Why is this bad?
+    /// `cx.outer_expn_data()` is faster and more concise.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// expr.span.ctxt().outer().expn_data()
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// expr.span.ctxt().outer_expn_data()
+    /// ```
+    pub clippy::OUTER_EXPN_EXPN_DATA,
+    Warn,
+    "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]);
+
+impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+        if is_lint_allowed(cx, OUTER_EXPN_EXPN_DATA, expr.hir_id) {
+            return;
+        }
+
+        let (method_names, arg_lists, spans) = method_calls(expr, 2);
+        let method_names: Vec<&str> = method_names.iter().map(Symbol::as_str).collect();
+        if let ["expn_data", "outer_expn"] = method_names.as_slice()
+            && let (self_arg, args) = arg_lists[1]
+            && args.is_empty()
+            && let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
+            && match_type(cx, self_ty, &paths::SYNTAX_CONTEXT)
+        {
+            span_lint_and_sugg(
+                cx,
+                OUTER_EXPN_EXPN_DATA,
+                spans[1].with_hi(expr.span.hi()),
+                "usage of `outer_expn().expn_data()`",
+                "try",
+                "outer_expn_data()".to_string(),
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
diff --git a/clippy_lints_internal/src/produce_ice.rs b/clippy_lints_internal/src/produce_ice.rs
new file mode 100644
index 00000000000..14e93dc6d5f
--- /dev/null
+++ b/clippy_lints_internal/src/produce_ice.rs
@@ -0,0 +1,43 @@
+use rustc_ast::ast::NodeId;
+use rustc_ast::visit::FnKind;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_session::declare_lint_pass;
+use rustc_span::Span;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Not an actual lint. This lint is only meant for testing our customized internal compiler
+    /// error message by calling `panic`.
+    ///
+    /// ### Why is this bad?
+    /// ICE in large quantities can damage your teeth
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// 🍦🍦🍦🍦🍦
+    /// ```
+    pub clippy::PRODUCE_ICE,
+    Warn,
+    "this message should not appear anywhere as we ICE before and don't emit the lint",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(ProduceIce => [PRODUCE_ICE]);
+
+impl EarlyLintPass for ProduceIce {
+    fn check_fn(&mut self, ctx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+        if is_trigger_fn(fn_kind) {
+            ctx.sess()
+                .dcx()
+                .span_delayed_bug(span, "Would you like some help with that?");
+        }
+    }
+}
+
+fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
+    match fn_kind {
+        FnKind::Fn(_, _, func) => func.ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy",
+        FnKind::Closure(..) => false,
+    }
+}
diff --git a/clippy_lints_internal/src/slow_symbol_comparisons.rs b/clippy_lints_internal/src/slow_symbol_comparisons.rs
new file mode 100644
index 00000000000..6776366a23b
--- /dev/null
+++ b/clippy_lints_internal/src/slow_symbol_comparisons.rs
@@ -0,0 +1,76 @@
+use clippy_utils::consts::{ConstEvalCtxt, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::paths;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::match_type;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_session::declare_lint_pass;
+use rustc_span::{Span, sym};
+
+declare_tool_lint! {
+    /// ### What it does
+    ///
+    /// Detects symbol comparison using `Symbol::intern`.
+    ///
+    /// ### Why is this bad?
+    ///
+    /// Comparison via `Symbol::as_str()` is faster if the interned symbols are not reused.
+    ///
+    /// ### Example
+    ///
+    /// None, see suggestion.
+    pub clippy::SLOW_SYMBOL_COMPARISONS,
+    Warn,
+    "detects slow comparisons of symbol",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(SlowSymbolComparisons => [SLOW_SYMBOL_COMPARISONS]);
+
+fn check_slow_comparison<'tcx>(
+    cx: &LateContext<'tcx>,
+    op1: &'tcx Expr<'tcx>,
+    op2: &'tcx Expr<'tcx>,
+) -> Option<(Span, String)> {
+    if match_type(cx, cx.typeck_results().expr_ty(op1), &paths::SYMBOL)
+        && let ExprKind::Call(fun, args) = op2.kind
+        && let ExprKind::Path(ref qpath) = fun.kind
+        && cx
+            .tcx
+            .is_diagnostic_item(sym::SymbolIntern, cx.qpath_res(qpath, fun.hir_id).opt_def_id()?)
+        && let [symbol_name_expr] = args
+        && let Some(Constant::Str(symbol_name)) = ConstEvalCtxt::new(cx).eval_simple(symbol_name_expr)
+    {
+        Some((op1.span, symbol_name))
+    } else {
+        None
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for SlowSymbolComparisons {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+        if let ExprKind::Binary(op, left, right) = expr.kind
+            && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne)
+            && let Some((symbol_span, symbol_name)) =
+                check_slow_comparison(cx, left, right).or_else(|| check_slow_comparison(cx, right, left))
+        {
+            let mut applicability = Applicability::MachineApplicable;
+            span_lint_and_sugg(
+                cx,
+                SLOW_SYMBOL_COMPARISONS,
+                expr.span,
+                "comparing `Symbol` via `Symbol::intern`",
+                "use `Symbol::as_str` and check the string instead",
+                format!(
+                    "{}.as_str() {} \"{symbol_name}\"",
+                    snippet_with_applicability(cx, symbol_span, "symbol", &mut applicability),
+                    op.node.as_str()
+                ),
+                applicability,
+            );
+        }
+    }
+}
diff --git a/clippy_lints_internal/src/unnecessary_def_path.rs b/clippy_lints_internal/src/unnecessary_def_path.rs
new file mode 100644
index 00000000000..6bdfbed55b0
--- /dev/null
+++ b/clippy_lints_internal/src/unnecessary_def_path.rs
@@ -0,0 +1,303 @@
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
+use rustc_ast::ast::LitKind;
+use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Expr, ExprKind, LetStmt, Mutability, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_middle::mir::ConstValue;
+use rustc_middle::mir::interpret::{Allocation, GlobalAlloc};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::impl_lint_pass;
+use rustc_span::Span;
+use rustc_span::symbol::Symbol;
+
+use std::str;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks for usage of def paths when a diagnostic item or a `LangItem` could be used.
+    ///
+    /// ### Why is this bad?
+    /// The path for an item is subject to change and is less efficient to look up than a
+    /// diagnostic item or a `LangItem`.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// utils::match_type(cx, ty, &paths::VEC)
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust,ignore
+    /// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
+    /// ```
+    pub clippy::UNNECESSARY_DEF_PATH,
+    Warn,
+    "using a def path when a diagnostic item or a `LangItem` is available",
+    report_in_external_macro: true
+}
+
+impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
+
+#[derive(Default)]
+pub struct UnnecessaryDefPath {
+    array_def_ids: FxIndexSet<(DefId, Span)>,
+    linted_def_ids: FxHashSet<DefId>,
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) {
+            return;
+        }
+
+        match expr.kind {
+            ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span),
+            ExprKind::Array(elements) => self.check_array(cx, elements, expr.span),
+            _ => {},
+        }
+    }
+
+    fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+        for &(def_id, span) in &self.array_def_ids {
+            if self.linted_def_ids.contains(&def_id) {
+                continue;
+            }
+
+            let (msg, sugg) = if let Some(sym) = cx.tcx.get_diagnostic_name(def_id) {
+                ("diagnostic item", format!("sym::{sym}"))
+            } else if let Some(sym) = get_lang_item_name(cx, def_id) {
+                ("language item", format!("LangItem::{sym}"))
+            } else {
+                continue;
+            };
+
+            span_lint_and_help(
+                cx,
+                UNNECESSARY_DEF_PATH,
+                span,
+                format!("hardcoded path to a {msg}"),
+                None,
+                format!("convert all references to use `{sugg}`"),
+            );
+        }
+    }
+}
+
+impl UnnecessaryDefPath {
+    #[allow(clippy::too_many_lines)]
+    fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) {
+        enum Item {
+            LangItem(&'static str),
+            DiagnosticItem(Symbol),
+        }
+        static PATHS: &[&[&str]] = &[
+            &["clippy_utils", "match_def_path"],
+            &["clippy_utils", "match_trait_method"],
+            &["clippy_utils", "ty", "match_type"],
+            &["clippy_utils", "is_expr_path_def_path"],
+        ];
+
+        if let [cx_arg, def_arg, args @ ..] = args
+            && let ExprKind::Path(path) = &func.kind
+            && let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id()
+            && let Some(which_path) = match_any_def_paths(cx, id, PATHS)
+            && let item_arg = if which_path == 4 { &args[1] } else { &args[0] }
+            // Extract the path to the matched type
+            && let Some(segments) = path_to_matched_type(cx, item_arg)
+            && let segments = segments.iter().map(|sym| &**sym).collect::<Vec<_>>()
+            && let Some(def_id) = def_path_def_ids(cx.tcx, &segments[..]).next()
+        {
+            // Check if the target item is a diagnostic item or LangItem.
+            #[rustfmt::skip]
+            let (msg, item) = if let Some(item_name)
+                = cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id)
+            {
+                (
+                    "use of a def path to a diagnostic item",
+                    Item::DiagnosticItem(*item_name),
+                )
+            } else if let Some(item_name) = get_lang_item_name(cx, def_id) {
+                (
+                    "use of a def path to a `LangItem`",
+                    Item::LangItem(item_name),
+                )
+            } else {
+                return;
+            };
+
+            let has_ctor = match cx.tcx.def_kind(def_id) {
+                DefKind::Struct => {
+                    let variant = cx.tcx.adt_def(def_id).non_enum_variant();
+                    variant.ctor.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
+                },
+                DefKind::Variant => {
+                    let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id);
+                    variant.ctor.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
+                },
+                _ => false,
+            };
+
+            let mut app = Applicability::MachineApplicable;
+            let cx_snip = snippet_with_applicability(cx, cx_arg.span, "..", &mut app);
+            let def_snip = snippet_with_applicability(cx, def_arg.span, "..", &mut app);
+            let (sugg, with_note) = match (which_path, item) {
+                // match_def_path
+                (0, Item::DiagnosticItem(item)) => (
+                    format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"),
+                    has_ctor,
+                ),
+                (0, Item::LangItem(item)) => (
+                    format!("{cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some({def_snip})"),
+                    has_ctor,
+                ),
+                // match_trait_method
+                (1, Item::DiagnosticItem(item)) => {
+                    (format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false)
+                },
+                // match_type
+                (2, Item::DiagnosticItem(item)) => (
+                    format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
+                    false,
+                ),
+                (2, Item::LangItem(item)) => (
+                    format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"),
+                    false,
+                ),
+                // is_expr_path_def_path
+                (3, Item::DiagnosticItem(item)) if has_ctor => (
+                    format!("is_res_diag_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), sym::{item})",),
+                    false,
+                ),
+                (3, Item::LangItem(item)) if has_ctor => (
+                    format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",),
+                    false,
+                ),
+                (3, Item::DiagnosticItem(item)) => (
+                    format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
+                    false,
+                ),
+                (3, Item::LangItem(item)) => (
+                    format!(
+                        "path_res({cx_snip}, {def_snip}).opt_def_id()\
+                            .map_or(false, |id| {cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some(id))",
+                    ),
+                    false,
+                ),
+                _ => return,
+            };
+
+            span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| {
+                diag.span_suggestion(span, "try", sugg, app);
+                if with_note {
+                    diag.help(
+                        "if this `DefId` came from a constructor expression or pattern then the \
+                                parent `DefId` should be used instead",
+                    );
+                }
+            });
+
+            self.linted_def_ids.insert(def_id);
+        }
+    }
+
+    fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) {
+        let Some(path) = path_from_array(elements) else { return };
+
+        for def_id in def_path_def_ids(cx.tcx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
+            self.array_def_ids.insert((def_id, span));
+        }
+    }
+}
+
+fn path_to_matched_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Vec<String>> {
+    match peel_hir_expr_refs(expr).0.kind {
+        ExprKind::Path(ref qpath) => match cx.qpath_res(qpath, expr.hir_id) {
+            Res::Local(hir_id) => {
+                if let Node::LetStmt(LetStmt { init: Some(init), .. }) = cx.tcx.parent_hir_node(hir_id) {
+                    path_to_matched_type(cx, init)
+                } else {
+                    None
+                }
+            },
+            Res::Def(DefKind::Static { .. }, def_id) => read_mir_alloc_def_path(
+                cx,
+                cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
+                cx.tcx.type_of(def_id).instantiate_identity(),
+            ),
+            Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
+                ConstValue::Indirect { alloc_id, offset } if offset.bytes() == 0 => {
+                    let alloc = cx.tcx.global_alloc(alloc_id).unwrap_memory();
+                    read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).instantiate_identity())
+                },
+                _ => None,
+            },
+            _ => None,
+        },
+        ExprKind::Array(exprs) => path_from_array(exprs),
+        _ => None,
+    }
+}
+
+fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation, ty: Ty<'_>) -> Option<Vec<String>> {
+    let (alloc, ty) = if let ty::Ref(_, ty, Mutability::Not) = *ty.kind() {
+        let &alloc = alloc.provenance().ptrs().values().next()?;
+        if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc.alloc_id()) {
+            (alloc.inner(), ty)
+        } else {
+            return None;
+        }
+    } else {
+        (alloc, ty)
+    };
+
+    if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind()
+        && let ty::Ref(_, ty, Mutability::Not) = *ty.kind()
+        && ty.is_str()
+    {
+        alloc
+            .provenance()
+            .ptrs()
+            .values()
+            .map(|&alloc| {
+                if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc.alloc_id()) {
+                    let alloc = alloc.inner();
+                    str::from_utf8(alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()))
+                        .ok()
+                        .map(ToOwned::to_owned)
+                } else {
+                    None
+                }
+            })
+            .collect()
+    } else {
+        None
+    }
+}
+
+fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> {
+    exprs
+        .iter()
+        .map(|expr| {
+            if let ExprKind::Lit(lit) = &expr.kind
+                && let LitKind::Str(sym, _) = lit.node
+            {
+                return Some((*sym.as_str()).to_owned());
+            }
+
+            None
+        })
+        .collect()
+}
+
+fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> {
+    if let Some((lang_item, _)) = cx.tcx.lang_items().iter().find(|(_, id)| *id == def_id) {
+        Some(lang_item.variant_name())
+    } else {
+        None
+    }
+}
diff --git a/clippy_lints_internal/src/unsorted_clippy_utils_paths.rs b/clippy_lints_internal/src/unsorted_clippy_utils_paths.rs
new file mode 100644
index 00000000000..8e281ecb2ee
--- /dev/null
+++ b/clippy_lints_internal/src/unsorted_clippy_utils_paths.rs
@@ -0,0 +1,54 @@
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast::{Crate, ItemKind, ModKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_lint_defs::declare_tool_lint;
+use rustc_session::declare_lint_pass;
+
+declare_tool_lint! {
+    /// ### What it does
+    /// Checks that [`clippy_utils::paths`] is sorted lexically
+    ///
+    /// ### Why is this bad?
+    /// We like to pretend we're an example of tidy code.
+    ///
+    /// ### Example
+    /// Wrong ordering of the util::paths constants.
+    pub clippy::UNSORTED_CLIPPY_UTILS_PATHS,
+    Warn,
+    "various things that will negatively affect your clippy experience",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(UnsortedClippyUtilsPaths => [UNSORTED_CLIPPY_UTILS_PATHS]);
+
+impl EarlyLintPass for UnsortedClippyUtilsPaths {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
+        if let Some(utils) = krate
+            .items
+            .iter()
+            .find(|item| item.kind.ident().is_some_and(|i| i.name.as_str() == "utils"))
+            && let ItemKind::Mod(_, _, ModKind::Loaded(ref items, ..)) = utils.kind
+            && let Some(paths) = items
+                .iter()
+                .find(|item| item.kind.ident().is_some_and(|i| i.name.as_str() == "paths"))
+            && let ItemKind::Mod(_, _, ModKind::Loaded(ref items, ..)) = paths.kind
+        {
+            let mut last_name: Option<String> = None;
+            for item in items {
+                let name = item.kind.ident().expect("const items have idents").to_string();
+                if let Some(last_name) = last_name
+                    && *last_name > *name
+                {
+                    span_lint(
+                        cx,
+                        UNSORTED_CLIPPY_UTILS_PATHS,
+                        item.span,
+                        "this constant should be before the previous constant due to lexical \
+                                         ordering",
+                    );
+                }
+                last_name = Some(name);
+            }
+        }
+    }
+}