Move internal lints to their own crate (#13223)

This makes it so switching the internal feature on/off no longer
rebuilds `clippy_lints`.

r? @flip1995

changelog: none
This commit is contained in:
Timo 2025-04-18 12:04:08 +00:00 committed by GitHub
commit 781fdab9a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 295 additions and 333 deletions

View file

@ -1,11 +0,0 @@
pub mod almost_standard_lint_formulation;
pub mod collapsible_calls;
pub mod interning_defined_symbol;
pub mod invalid_paths;
pub mod lint_without_lint_pass;
pub mod msrv_attr_impl;
pub mod outer_expn_data_pass;
pub mod produce_ice;
pub mod slow_symbol_comparisons;
pub mod unnecessary_def_path;
pub mod unsorted_clippy_utils_paths;

View file

@ -1,85 +0,0 @@
use crate::utils::internal_lints::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_session::impl_lint_pass;
declare_clippy_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 ALMOST_STANDARD_LINT_FORMULATION,
internal,
"lint formulations must have a standardized format."
}
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;
}
}
}
}
}
}

View file

@ -1,248 +0,0 @@
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_session::declare_lint_pass;
use rustc_span::Span;
use std::borrow::{Borrow, Cow};
declare_clippy_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 COLLAPSIBLE_SPAN_LINT_CALLS,
internal,
"found collapsible `span_lint_and_then` calls"
}
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,
);
}

View file

@ -1,241 +0,0 @@
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_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_clippy_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 INTERNING_DEFINED_SYMBOL,
internal,
"interning a symbol that is pre-interned and defined as a constant"
}
declare_clippy_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 UNNECESSARY_SYMBOL_STR,
internal,
"unnecessary conversion between Symbol and string"
}
#[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
},
}
}
}

View file

@ -1,106 +0,0 @@
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_middle::ty::fast_reject::SimplifiedType;
use rustc_middle::ty::{self, FloatTy};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::Symbol;
declare_clippy_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 INVALID_PATHS,
internal,
"invalid path"
}
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
}

View file

@ -1,281 +0,0 @@
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_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_clippy_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 LINT_WITHOUT_LINT_PASS,
internal,
"declaring a lint without associating it in a LintPass"
}
declare_clippy_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 DEFAULT_LINT,
internal,
"found 'default lint description' in a lint declaration"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for invalid `clippy::version` attributes.
///
/// Valid values are:
/// * "pre 1.29.0"
/// * any valid semantic version
pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
internal,
"found an invalid `clippy::version` attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for declared clippy lints without the `clippy::version` attribute.
///
pub MISSING_CLIPPY_VERSION_ATTRIBUTE,
internal,
"found clippy lint without `clippy::version` attribute"
}
#[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 is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) {
return;
}
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
}
}

View file

@ -1,58 +0,0 @@
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_middle::ty::{self, EarlyBinder, GenericArgKind};
use rustc_session::declare_lint_pass;
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!(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,
);
}
}
}

View file

@ -1,59 +0,0 @@
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_session::declare_lint_pass;
use rustc_span::symbol::Symbol;
declare_clippy_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 OUTER_EXPN_EXPN_DATA,
internal,
"using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`"
}
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,
);
}
}
}

View file

@ -1,41 +0,0 @@
use rustc_ast::ast::NodeId;
use rustc_ast::visit::FnKind;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_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 PRODUCE_ICE,
internal,
"this message should not appear anywhere as we ICE before and don't emit the lint"
}
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,
}
}

View file

@ -1,74 +0,0 @@
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_session::declare_lint_pass;
use rustc_span::{Span, sym};
declare_clippy_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 SLOW_SYMBOL_COMPARISONS,
internal,
"detects slow comparisons of symbol"
}
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,
);
}
}
}

View file

@ -1,301 +0,0 @@
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_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_clippy_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 UNNECESSARY_DEF_PATH,
internal,
"using a def path when a diagnostic item or a `LangItem` is available"
}
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
}
}

View file

@ -1,52 +0,0 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::{Crate, ItemKind, ModKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_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 UNSORTED_CLIPPY_UTILS_PATHS,
internal,
"various things that will negatively affect your clippy experience"
}
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);
}
}
}
}

View file

@ -2,6 +2,3 @@ pub mod attr_collector;
pub mod author;
pub mod dump_hir;
pub mod format_args_collector;
#[cfg(feature = "internal")]
pub mod internal_lints;