blob: cb4c5077a5263bd58424e99e25d09e950946d1a4 [file] [log] [blame] [edit]
//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
//! Clippy.
use rustc_ast::{Pat, PatKind, Path};
use rustc_hir::def::Res;
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind, HirId, find_attr};
use rustc_middle::ty::{self, GenericArgsRef, PredicatePolarity};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::{Span, sym};
use {rustc_ast as ast, rustc_hir as hir};
use crate::lints::{
AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag,
ImplicitSysrootCrateImportDiag, LintPassByHand, NonGlobImportTypeIrInherent, QueryInstability,
QueryUntracked, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, TyQualified, TykindDiag,
TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage,
};
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
declare_tool_lint! {
/// The `default_hash_type` lint detects use of [`std::collections::HashMap`] and
/// [`std::collections::HashSet`], suggesting the use of `FxHashMap`/`FxHashSet`.
///
/// This can help as `FxHasher` can perform better than the default hasher. DOS protection is
/// not required as input is assumed to be trusted.
pub rustc::DEFAULT_HASH_TYPES,
Allow,
"forbid HashMap and HashSet and suggest the FxHash* variants",
report_in_external_macro: true
}
declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
impl LateLintPass<'_> for DefaultHashTypes {
fn check_path(&mut self, cx: &LateContext<'_>, path: &hir::Path<'_>, hir_id: HirId) {
let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
if matches!(
cx.tcx.hir_node(hir_id),
hir::Node::Item(hir::Item { kind: hir::ItemKind::Use(..), .. })
) {
// Don't lint imports, only actual usages.
return;
}
let preferred = match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::HashMap) => "FxHashMap",
Some(sym::HashSet) => "FxHashSet",
_ => return,
};
cx.emit_span_lint(
DEFAULT_HASH_TYPES,
path.span,
DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) },
);
}
}
declare_tool_lint! {
/// The `potential_query_instability` lint detects use of methods which can lead to
/// potential query instability, such as iterating over a `HashMap`.
///
/// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model,
/// queries must return deterministic, stable results. `HashMap` iteration order can change
/// between compilations, and will introduce instability if query results expose the order.
pub rustc::POTENTIAL_QUERY_INSTABILITY,
Allow,
"require explicit opt-in when using potentially unstable methods or functions",
report_in_external_macro: true
}
declare_tool_lint! {
/// The `untracked_query_information` lint detects use of methods which leak information not
/// tracked by the query system, such as whether a `Steal<T>` value has already been stolen. In
/// order not to break incremental compilation, such methods must be used very carefully or not
/// at all.
pub rustc::UNTRACKED_QUERY_INFORMATION,
Allow,
"require explicit opt-in when accessing information not tracked by the query system",
report_in_external_macro: true
}
declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY, UNTRACKED_QUERY_INFORMATION]);
impl<'tcx> LateLintPass<'tcx> for QueryStability {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some((callee_def_id, span, generic_args, _recv, _args)) =
get_callee_span_generic_args_and_args(cx, expr)
&& let Ok(Some(instance)) =
ty::Instance::try_resolve(cx.tcx, cx.typing_env(), callee_def_id, generic_args)
{
let def_id = instance.def_id();
if find_attr!(cx.tcx, def_id, RustcLintQueryInstability) {
cx.emit_span_lint(
POTENTIAL_QUERY_INSTABILITY,
span,
QueryInstability { query: cx.tcx.item_name(def_id) },
);
} else if has_unstable_into_iter_predicate(cx, callee_def_id, generic_args) {
let call_span = span.with_hi(expr.span.hi());
cx.emit_span_lint(
POTENTIAL_QUERY_INSTABILITY,
call_span,
QueryInstability { query: sym::into_iter },
);
}
if find_attr!(cx.tcx, def_id, RustcLintUntrackedQueryInformation) {
cx.emit_span_lint(
UNTRACKED_QUERY_INFORMATION,
span,
QueryUntracked { method: cx.tcx.item_name(def_id) },
);
}
}
}
}
fn has_unstable_into_iter_predicate<'tcx>(
cx: &LateContext<'tcx>,
callee_def_id: DefId,
generic_args: GenericArgsRef<'tcx>,
) -> bool {
let Some(into_iterator_def_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else {
return false;
};
let Some(into_iter_fn_def_id) = cx.tcx.lang_items().into_iter_fn() else {
return false;
};
let predicates = cx.tcx.predicates_of(callee_def_id).instantiate(cx.tcx, generic_args);
for (predicate, _) in predicates {
let Some(trait_pred) = predicate.as_trait_clause() else {
continue;
};
if trait_pred.def_id() != into_iterator_def_id
|| trait_pred.polarity() != PredicatePolarity::Positive
{
continue;
}
// `IntoIterator::into_iter` has no additional method args.
let into_iter_fn_args =
cx.tcx.instantiate_bound_regions_with_erased(trait_pred).trait_ref.args;
let Ok(Some(instance)) = ty::Instance::try_resolve(
cx.tcx,
cx.typing_env(),
into_iter_fn_def_id,
into_iter_fn_args,
) else {
continue;
};
// Does the input type's `IntoIterator` implementation have the
// `rustc_lint_query_instability` attribute on its `into_iter` method?
if find_attr!(cx.tcx, instance.def_id(), RustcLintQueryInstability) {
return true;
}
}
false
}
/// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
/// `Span`, `GenericArgs`, and arguments. This is a slight augmentation of a similarly named Clippy
/// function, `get_callee_generic_args_and_args`.
fn get_callee_span_generic_args_and_args<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> Option<(DefId, Span, GenericArgsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
if let ExprKind::Call(callee, args) = expr.kind
&& let callee_ty = cx.typeck_results().expr_ty(callee)
&& let ty::FnDef(callee_def_id, generic_args) = callee_ty.kind()
{
return Some((*callee_def_id, callee.span, generic_args, None, args));
}
if let ExprKind::MethodCall(segment, recv, args, _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
{
let generic_args = cx.typeck_results().node_args(expr.hir_id);
return Some((method_def_id, segment.ident.span, generic_args, Some(recv), args));
}
None
}
declare_tool_lint! {
/// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`,
/// where `ty::<kind>` would suffice.
pub rustc::USAGE_OF_TY_TYKIND,
Allow,
"usage of `ty::TyKind` outside of the `ty::sty` module",
report_in_external_macro: true
}
declare_tool_lint! {
/// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`,
/// where `Ty` should be used instead.
pub rustc::USAGE_OF_QUALIFIED_TY,
Allow,
"using `ty::{Ty,TyCtxt}` instead of importing it",
report_in_external_macro: true
}
declare_lint_pass!(TyTyKind => [
USAGE_OF_TY_TYKIND,
USAGE_OF_QUALIFIED_TY,
]);
impl<'tcx> LateLintPass<'tcx> for TyTyKind {
fn check_path(
&mut self,
cx: &LateContext<'tcx>,
path: &rustc_hir::Path<'tcx>,
_: rustc_hir::HirId,
) {
if let Some(segment) = path.segments.iter().nth_back(1)
&& lint_ty_kind_usage(cx, &segment.res)
{
let span =
path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi());
cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span });
}
}
fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
match &ty.kind {
hir::TyKind::Path(hir::QPath::Resolved(_, path)) => {
if lint_ty_kind_usage(cx, &path.res) {
let span = match cx.tcx.parent_hir_node(ty.hir_id) {
hir::Node::PatExpr(hir::PatExpr {
kind: hir::PatExprKind::Path(qpath),
..
})
| hir::Node::Pat(hir::Pat {
kind:
hir::PatKind::TupleStruct(qpath, ..) | hir::PatKind::Struct(qpath, ..),
..
})
| hir::Node::Expr(
hir::Expr { kind: hir::ExprKind::Path(qpath), .. }
| &hir::Expr { kind: hir::ExprKind::Struct(qpath, ..), .. },
) => {
if let hir::QPath::TypeRelative(qpath_ty, ..) = qpath
&& qpath_ty.hir_id == ty.hir_id
{
Some(path.span)
} else {
None
}
}
_ => None,
};
match span {
Some(span) => {
cx.emit_span_lint(
USAGE_OF_TY_TYKIND,
path.span,
TykindKind { suggestion: span },
);
}
None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
}
} else if !ty.span.from_expansion()
&& path.segments.len() > 1
&& let Some(ty) = is_ty_or_ty_ctxt(cx, path)
{
cx.emit_span_lint(
USAGE_OF_QUALIFIED_TY,
path.span,
TyQualified { ty, suggestion: path.span },
);
}
}
_ => {}
}
}
}
fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
if let Some(did) = res.opt_def_id() {
cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
} else {
false
}
}
fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &hir::Path<'_>) -> Option<String> {
match path.res {
Res::Def(_, def_id) => {
if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id) {
return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
}
}
// Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind()
&& let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
{
return Some(format!("{}<{}>", name, args[0]));
}
}
_ => (),
}
None
}
fn gen_args(segment: &hir::PathSegment<'_>) -> String {
if let Some(args) = &segment.args {
let lifetimes = args
.args
.iter()
.filter_map(|arg| {
if let hir::GenericArg::Lifetime(lt) = arg {
Some(lt.ident.to_string())
} else {
None
}
})
.collect::<Vec<_>>();
if !lifetimes.is_empty() {
return format!("<{}>", lifetimes.join(", "));
}
}
String::new()
}
declare_tool_lint! {
/// The `non_glob_import_of_type_ir_inherent_item` lint detects
/// non-glob imports of module `rustc_type_ir::inherent`.
pub rustc::NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
Allow,
"non-glob import of `rustc_type_ir::inherent`",
report_in_external_macro: true
}
declare_tool_lint! {
/// The `usage_of_type_ir_inherent` lint detects usage of `rustc_type_ir::inherent`.
///
/// This module should only be used within the trait solver.
pub rustc::USAGE_OF_TYPE_IR_INHERENT,
Allow,
"usage `rustc_type_ir::inherent` outside of trait system",
report_in_external_macro: true
}
declare_tool_lint! {
/// The `usage_of_type_ir_traits` lint detects usage of `rustc_type_ir::Interner`,
/// or `rustc_infer::InferCtxtLike`.
///
/// Methods of this trait should only be used within the type system abstraction layer,
/// and in the generic next trait solver implementation. Look for an analogously named
/// method on `TyCtxt` or `InferCtxt` (respectively).
pub rustc::USAGE_OF_TYPE_IR_TRAITS,
Allow,
"usage `rustc_type_ir`-specific abstraction traits outside of trait system",
report_in_external_macro: true
}
declare_tool_lint! {
/// The `direct_use_of_rustc_type_ir` lint detects usage of `rustc_type_ir`.
///
/// This module should only be used within the trait solver and some desirable
/// crates like rustc_middle.
pub rustc::DIRECT_USE_OF_RUSTC_TYPE_IR,
Allow,
"usage `rustc_type_ir` abstraction outside of trait system",
report_in_external_macro: true
}
declare_lint_pass!(TypeIr => [DIRECT_USE_OF_RUSTC_TYPE_IR, NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_TRAITS]);
impl<'tcx> LateLintPass<'tcx> for TypeIr {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
let res_def_id = match expr.kind {
hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => path.res.opt_def_id(),
hir::ExprKind::Path(hir::QPath::TypeRelative(..)) | hir::ExprKind::MethodCall(..) => {
cx.typeck_results().type_dependent_def_id(expr.hir_id)
}
_ => return,
};
let Some(res_def_id) = res_def_id else {
return;
};
if let Some(assoc_item) = cx.tcx.opt_associated_item(res_def_id)
&& let Some(trait_def_id) = assoc_item.trait_container(cx.tcx)
&& (cx.tcx.is_diagnostic_item(sym::type_ir_interner, trait_def_id)
| cx.tcx.is_diagnostic_item(sym::type_ir_infer_ctxt_like, trait_def_id))
{
cx.emit_span_lint(USAGE_OF_TYPE_IR_TRAITS, expr.span, TypeIrTraitUsage);
}
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
let rustc_hir::ItemKind::Use(path, kind) = item.kind else { return };
let is_mod_inherent = |res: Res| {
res.opt_def_id()
.is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id))
};
// Path segments except for the final.
if let Some(seg) = path.segments.iter().find(|seg| is_mod_inherent(seg.res)) {
cx.emit_span_lint(USAGE_OF_TYPE_IR_INHERENT, seg.ident.span, TypeIrInherentUsage);
}
// Final path resolutions, like `use rustc_type_ir::inherent`
else if let Some(type_ns) = path.res.type_ns
&& is_mod_inherent(type_ns)
{
cx.emit_span_lint(
USAGE_OF_TYPE_IR_INHERENT,
path.segments.last().unwrap().ident.span,
TypeIrInherentUsage,
);
}
let (lo, hi, snippet) = match path.segments {
[.., penultimate, segment] if is_mod_inherent(penultimate.res) => {
(segment.ident.span, item.kind.ident().unwrap().span, "*")
}
[.., segment]
if let Some(type_ns) = path.res.type_ns
&& is_mod_inherent(type_ns)
&& let rustc_hir::UseKind::Single(ident) = kind =>
{
let (lo, snippet) =
match cx.tcx.sess.source_map().span_to_snippet(path.span).as_deref() {
Ok("self") => (path.span, "*"),
_ => (segment.ident.span.shrink_to_hi(), "::*"),
};
(lo, if segment.ident == ident { lo } else { ident.span }, snippet)
}
_ => return,
};
cx.emit_span_lint(
NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
path.span,
NonGlobImportTypeIrInherent { suggestion: lo.eq_ctxt(hi).then(|| lo.to(hi)), snippet },
);
}
fn check_path(
&mut self,
cx: &LateContext<'tcx>,
path: &rustc_hir::Path<'tcx>,
_: rustc_hir::HirId,
) {
if let Some(seg) = path.segments.iter().find(|seg| {
seg.res
.opt_def_id()
.is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir, def_id))
}) {
cx.emit_span_lint(DIRECT_USE_OF_RUSTC_TYPE_IR, seg.ident.span, TypeIrDirectUse);
}
}
}
declare_tool_lint! {
/// The `lint_pass_impl_without_macro` detects manual implementations of a lint
/// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`].
pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
Allow,
"`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
}
declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
impl EarlyLintPass for LintPassImpl {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if let ast::ItemKind::Impl(ast::Impl { of_trait: Some(of_trait), .. }) = &item.kind
&& let Some(last) = of_trait.trait_ref.path.segments.last()
&& last.ident.name == sym::LintPass
{
let expn_data = of_trait.trait_ref.path.span.ctxt().outer_expn_data();
let call_site = expn_data.call_site;
if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
&& call_site.ctxt().outer_expn_data().kind
!= ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
{
cx.emit_span_lint(
LINT_PASS_IMPL_WITHOUT_MACRO,
of_trait.trait_ref.path.span,
LintPassByHand,
);
}
}
}
}
declare_tool_lint! {
/// The `bad_opt_access` lint detects accessing options by field instead of
/// the wrapper function.
pub rustc::BAD_OPT_ACCESS,
Deny,
"prevent using options by field access when there is a wrapper function",
report_in_external_macro: true
}
declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
impl LateLintPass<'_> for BadOptAccess {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
let hir::ExprKind::Field(base, target) = expr.kind else { return };
let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
// Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
// avoided.
if !find_attr!(cx.tcx, adt_def.did(), RustcLintOptTy) {
return;
}
for field in adt_def.all_fields() {
if field.name == target.name
&& let Some(lint_message) = find_attr!(cx.tcx, field.did, RustcLintOptDenyFieldAccess { lint_message, } => lint_message)
{
cx.emit_span_lint(
BAD_OPT_ACCESS,
expr.span,
BadOptAccessDiag { msg: lint_message.as_str() },
);
}
}
}
}
declare_tool_lint! {
pub rustc::SPAN_USE_EQ_CTXT,
Allow,
"forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead",
report_in_external_macro: true
}
declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]);
impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
if let hir::ExprKind::Binary(
hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. },
lhs,
rhs,
) = expr.kind
{
if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
}
}
}
}
fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
match &expr.kind {
hir::ExprKind::MethodCall(..) => cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)),
_ => false,
}
}
declare_tool_lint! {
/// The `symbol_intern_string_literal` detects `Symbol::intern` being called on a string literal
pub rustc::SYMBOL_INTERN_STRING_LITERAL,
// rustc_driver crates out of the compiler can't/shouldn't add preinterned symbols;
// bootstrap will deny this manually
Allow,
"Forbid uses of string literals in `Symbol::intern`, suggesting preinterning instead",
report_in_external_macro: true
}
declare_lint_pass!(SymbolInternStringLiteral => [SYMBOL_INTERN_STRING_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for SymbolInternStringLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
if let hir::ExprKind::Call(path, [arg]) = expr.kind
&& let hir::ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::SymbolIntern, def_id)
&& let hir::ExprKind::Lit(kind) = arg.kind
&& let rustc_ast::LitKind::Str(_, _) = kind.node
{
cx.emit_span_lint(
SYMBOL_INTERN_STRING_LITERAL,
kind.span,
SymbolInternStringLiteralDiag,
);
}
}
}
declare_tool_lint! {
/// The `implicit_sysroot_crate_import` detects use of `extern crate` to import non-sysroot crates
/// (e.g. crates.io deps) from the sysroot, which is dangerous because these crates are not guaranteed
/// to exist exactly once, and so may be missing entirely or appear multiple times resulting in ambiguity.
pub rustc::IMPLICIT_SYSROOT_CRATE_IMPORT,
Allow,
"Forbid uses of non-sysroot crates in `extern crate`",
report_in_external_macro: true
}
declare_lint_pass!(ImplicitSysrootCrateImport => [IMPLICIT_SYSROOT_CRATE_IMPORT]);
impl EarlyLintPass for ImplicitSysrootCrateImport {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
fn is_whitelisted(crate_name: &str) -> bool {
// Whitelist of allowed crates.
crate_name.starts_with("rustc_")
|| matches!(
crate_name,
"test" | "self" | "core" | "alloc" | "std" | "proc_macro" | "tikv_jemalloc_sys"
)
}
if let ast::ItemKind::ExternCrate(original_name, imported_name) = &item.kind {
let name = original_name.as_ref().unwrap_or(&imported_name.name).as_str();
let externs = &cx.builder.sess().opts.externs;
if externs.get(name).is_none() && !is_whitelisted(name) {
cx.emit_span_lint(
IMPLICIT_SYSROOT_CRATE_IMPORT,
item.span,
ImplicitSysrootCrateImportDiag { name },
);
}
}
}
}
declare_tool_lint! {
pub rustc::BAD_USE_OF_FIND_ATTR,
Allow,
"Forbid `AttributeKind::` as a prefix in `find_attr!` macros.",
report_in_external_macro: true
}
declare_lint_pass!(BadUseOfFindAttr => [BAD_USE_OF_FIND_ATTR]);
impl EarlyLintPass for BadUseOfFindAttr {
fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &rustc_ast::Arm) {
fn path_contains_attribute_kind(cx: &EarlyContext<'_>, path: &Path) {
for segment in &path.segments {
if segment.ident.as_str() == "AttributeKind" {
cx.emit_span_lint(
BAD_USE_OF_FIND_ATTR,
segment.span(),
AttributeKindInFindAttr,
);
}
}
}
fn find_attr_kind_in_pat(cx: &EarlyContext<'_>, pat: &Pat) {
match &pat.kind {
PatKind::Struct(_, path, fields, _) => {
path_contains_attribute_kind(cx, path);
for field in fields {
find_attr_kind_in_pat(cx, &field.pat);
}
}
PatKind::TupleStruct(_, path, fields) => {
path_contains_attribute_kind(cx, path);
for field in fields {
find_attr_kind_in_pat(cx, &field);
}
}
PatKind::Or(options) => {
for pat in options {
find_attr_kind_in_pat(cx, pat);
}
}
PatKind::Path(_, path) => {
path_contains_attribute_kind(cx, path);
}
PatKind::Tuple(elems) => {
for pat in elems {
find_attr_kind_in_pat(cx, pat);
}
}
PatKind::Box(pat) => {
find_attr_kind_in_pat(cx, pat);
}
PatKind::Deref(pat) => {
find_attr_kind_in_pat(cx, pat);
}
PatKind::Ref(..) => {
find_attr_kind_in_pat(cx, pat);
}
PatKind::Slice(elems) => {
for pat in elems {
find_attr_kind_in_pat(cx, pat);
}
}
PatKind::Guard(pat, ..) => {
find_attr_kind_in_pat(cx, pat);
}
PatKind::Paren(pat) => {
find_attr_kind_in_pat(cx, pat);
}
PatKind::Expr(..)
| PatKind::Range(..)
| PatKind::MacCall(..)
| PatKind::Rest
| PatKind::Missing
| PatKind::Err(..)
| PatKind::Ident(..)
| PatKind::Never
| PatKind::Wild => {}
}
}
if let Some(expn_data) = arm.span.source_callee()
&& let ExpnKind::Macro(_, name) = expn_data.kind
&& name.as_str() == "find_attr"
{
find_attr_kind_in_pat(cx, &arm.pat);
}
}
}