#![feature(array_chunks)]
#![feature(box_patterns)]
#![feature(f128)]
#![feature(f16)]
#![feature(if_let_guard)]
#![feature(macro_metavar_expr_concat)]
#![feature(let_chains)]
#![feature(never_type)]
#![feature(rustc_private)]
#![feature(assert_matches)]
#![feature(unwrap_infallible)]
#![feature(array_windows)]
#![recursion_limit = "512"]
#![allow(
clippy::missing_errors_doc,
clippy::missing_panics_doc,
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
)]
extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_attr;
extern crate rustc_const_eval;
extern crate rustc_data_structures;
#[allow(unused_extern_crates)]
extern crate rustc_driver;
extern crate rustc_errors;
extern crate rustc_hir;
extern crate rustc_hir_typeck;
extern crate rustc_index;
extern crate rustc_infer;
extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_mir_dataflow;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
extern crate rustc_trait_selection;
#[macro_use]
pub mod sym_helper;
pub mod ast_utils;
pub mod attrs;
mod check_proc_macro;
pub mod comparisons;
pub mod consts;
pub mod diagnostics;
pub mod eager_or_lazy;
pub mod higher;
mod hir_utils;
pub mod macros;
pub mod mir;
pub mod numeric_literal;
pub mod paths;
pub mod ptr;
pub mod qualify_min_const_fn;
pub mod source;
pub mod str_utils;
pub mod sugg;
pub mod ty;
pub mod usage;
pub mod visitors;
pub use self::attrs::*;
pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
pub use self::hir_utils::{
HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over,
};
use core::mem;
use core::ops::ControlFlow;
use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault;
use std::iter::{once, repeat};
use std::sync::{Mutex, MutexGuard, OnceLock};
use clippy_config::types::DisallowedPath;
use itertools::Itertools;
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::packed::Pu128;
use rustc_data_structures::unhash::UnhashMap;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LOCAL_CRATE, LocalDefId, LocalModDefId};
use rustc_hir::definitions::{DefPath, DefPathData};
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
use rustc_hir::{
self as hir, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext,
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef,
TyKind, UnOp, def,
};
use rustc_lexer::{TokenKind, tokenize};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::mir::Const;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::fast_reject::SimplifiedType;
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy,
Ty, TyCtxt, TypeVisitableExt, UintTy, UpvarCapture,
};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{Ident, Symbol, kw};
use rustc_span::{InnerSpan, Span, sym};
use rustc_target::abi::Integer;
use visitors::Visitable;
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
use crate::higher::Range;
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
use crate::visitors::for_each_expr_without_closures;
use rustc_middle::hir::nested_filter;
#[macro_export]
macro_rules! extract_msrv_attr {
($context:ident) => {
fn check_attributes(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) {
let sess = rustc_lint::LintContext::sess(cx);
self.msrv.check_attributes(sess, attrs);
}
fn check_attributes_post(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) {
let sess = rustc_lint::LintContext::sess(cx);
self.msrv.check_attributes_post(sess, attrs);
}
};
}
pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
while let Some(init) = path_to_local(expr)
.and_then(|id| find_binding_init(cx, id))
.filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
{
expr = init;
}
expr
}
pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
if let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& matches!(pat.kind, PatKind::Binding(BindingMode::NONE, ..))
&& let Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id)
{
return local.init;
}
None
}
pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool {
for (_, node) in cx.tcx.hir().parent_iter(local) {
match node {
Node::Pat(..) | Node::PatField(..) => {},
Node::LetStmt(let_stmt) => return let_stmt.init.is_some(),
_ => return true,
}
}
false
}
pub fn is_in_const_context(cx: &LateContext<'_>) -> bool {
debug_assert!(cx.enclosing_body.is_some(), "`LateContext` has no enclosing body");
cx.enclosing_body.is_some_and(|id| {
cx.tcx
.hir()
.body_const_context(cx.tcx.hir().body_owner_def_id(id))
.is_some()
})
}
pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
use ConstContext::{Const, ConstFn, Static};
let hir = tcx.hir();
let Some(ctx) = hir.body_const_context(hir.enclosing_body_owner(hir_id)) else {
return false;
};
match ctx {
ConstFn => false,
Static(_) | Const { inline: _ } => true,
}
}
pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool {
if let Res::Def(DefKind::Ctor(..), id) = res
&& let Some(lang_id) = cx.tcx.lang_items().get(lang_item)
&& let Some(id) = cx.tcx.opt_parent(id)
{
id == lang_id
} else {
false
}
}
pub fn is_enum_variant_ctor(
cx: &LateContext<'_>,
enum_item: Symbol,
variant_name: Symbol,
ctor_call_id: DefId,
) -> bool {
let Some(enum_def_id) = cx.tcx.get_diagnostic_item(enum_item) else {
return false;
};
let variants = cx.tcx.adt_def(enum_def_id).variants().iter();
variants
.filter(|variant| variant.name == variant_name)
.filter_map(|variant| variant.ctor.as_ref())
.any(|(_, ctor_def_id)| *ctor_def_id == ctor_call_id)
}
pub fn is_diagnostic_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: Symbol) -> bool {
let did = match cx.tcx.def_kind(did) {
DefKind::Ctor(..) => cx.tcx.parent(did),
DefKind::Variant => match cx.tcx.opt_parent(did) {
Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
_ => did,
},
_ => did,
};
cx.tcx.is_diagnostic_item(item, did)
}
pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> bool {
let did = match cx.tcx.def_kind(did) {
DefKind::Ctor(..) => cx.tcx.parent(did),
DefKind::Variant => match cx.tcx.opt_parent(did) {
Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
_ => did,
},
_ => did,
};
cx.tcx.lang_items().get(item) == Some(did)
}
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Block(
Block {
stmts: [],
expr: None,
..
},
_
) | ExprKind::Tup([])
)
}
pub fn is_wild(pat: &Pat<'_>) -> bool {
matches!(pat.kind, PatKind::Wild)
}
pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
match *qpath {
QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)),
QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath),
_ => false,
}
}
pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
let trt_id = cx.tcx.trait_of_item(def_id);
trt_id.is_some_and(|trt_id| match_def_path(cx, trt_id, path))
}
pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
cx.tcx.trait_of_item(method_id).is_none()
} else {
false
}
}
pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() {
return cx.tcx.is_diagnostic_item(diag_item, adt.did());
}
}
false
}
pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
return cx.tcx.is_diagnostic_item(diag_item, trait_did);
}
false
}
pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
cx.typeck_results()
.type_dependent_def_id(expr.hir_id)
.is_some_and(|did| is_diag_trait_item(cx, did, diag_item))
}
pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
if let Node::Item(item) = cx.tcx.parent_hir_node(cx.tcx.local_def_id_to_hir_id(def_id))
&& let ItemKind::Impl(imp) = item.kind
{
imp.of_trait.is_some()
} else {
false
}
}
pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
if let ExprKind::Path(ref qpath) = expr.kind {
cx.qpath_res(qpath, expr.hir_id)
.opt_def_id()
.is_some_and(|def_id| is_diag_trait_item(cx, def_id, diag_item))
} else {
false
}
}
pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
match *path {
QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
QPath::TypeRelative(_, seg) => seg,
QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
}
}
pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
last_path_segment(qpath)
.args
.map_or(&[][..], |a| a.args)
.iter()
.filter_map(|a| match a {
hir::GenericArg::Type(ty) => Some(*ty),
_ => None,
})
}
pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
match *path {
QPath::Resolved(_, path) => match_path(path, segments),
QPath::TypeRelative(ty, segment) => match ty.kind {
TyKind::Path(ref inner_path) => {
if let [prefix @ .., end] = segments {
if match_qpath(inner_path, prefix) {
return segment.ident.name.as_str() == *end;
}
}
false
},
_ => false,
},
QPath::LangItem(..) => false,
}
}
pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
path_def_id(cx, expr).is_some_and(|id| match_def_path(cx, id, segments))
}
pub fn is_path_lang_item<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>, lang_item: LangItem) -> bool {
path_def_id(cx, maybe_path).is_some_and(|id| cx.tcx.lang_items().get(lang_item) == Some(id))
}
pub fn is_path_diagnostic_item<'tcx>(
cx: &LateContext<'_>,
maybe_path: &impl MaybePath<'tcx>,
diag_item: Symbol,
) -> bool {
path_def_id(cx, maybe_path).is_some_and(|id| cx.tcx.is_diagnostic_item(diag_item, id))
}
pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
path.segments
.iter()
.rev()
.zip(segments.iter().rev())
.all(|(a, b)| a.ident.name.as_str() == *b)
}
pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind {
if let Res::Local(id) = path.res {
return Some(id);
}
}
None
}
pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
path_to_local(expr) == Some(id)
}
pub trait MaybePath<'hir> {
fn hir_id(&self) -> HirId;
fn qpath_opt(&self) -> Option<&QPath<'hir>>;
}
macro_rules! maybe_path {
($ty:ident, $kind:ident) => {
impl<'hir> MaybePath<'hir> for hir::$ty<'hir> {
fn hir_id(&self) -> HirId {
self.hir_id
}
fn qpath_opt(&self) -> Option<&QPath<'hir>> {
match &self.kind {
hir::$kind::Path(qpath) => Some(qpath),
_ => None,
}
}
}
};
}
maybe_path!(Expr, ExprKind);
maybe_path!(Pat, PatKind);
maybe_path!(Ty, TyKind);
pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res {
match maybe_path.qpath_opt() {
None => Res::Err,
Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()),
}
}
pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option<DefId> {
path_res(cx, maybe_path).opt_def_id()
}
fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
let ty = match name {
"bool" => SimplifiedType::Bool,
"char" => SimplifiedType::Char,
"str" => SimplifiedType::Str,
"array" => SimplifiedType::Array,
"slice" => SimplifiedType::Slice,
"const_ptr" => SimplifiedType::Ptr(Mutability::Not),
"mut_ptr" => SimplifiedType::Ptr(Mutability::Mut),
"isize" => SimplifiedType::Int(IntTy::Isize),
"i8" => SimplifiedType::Int(IntTy::I8),
"i16" => SimplifiedType::Int(IntTy::I16),
"i32" => SimplifiedType::Int(IntTy::I32),
"i64" => SimplifiedType::Int(IntTy::I64),
"i128" => SimplifiedType::Int(IntTy::I128),
"usize" => SimplifiedType::Uint(UintTy::Usize),
"u8" => SimplifiedType::Uint(UintTy::U8),
"u16" => SimplifiedType::Uint(UintTy::U16),
"u32" => SimplifiedType::Uint(UintTy::U32),
"u64" => SimplifiedType::Uint(UintTy::U64),
"u128" => SimplifiedType::Uint(UintTy::U128),
"f32" => SimplifiedType::Float(FloatTy::F32),
"f64" => SimplifiedType::Float(FloatTy::F64),
_ => {
return [].iter().copied();
},
};
tcx.incoherent_impls(ty).iter().copied()
}
fn non_local_item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
match tcx.def_kind(def_id) {
DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx
.module_children(def_id)
.iter()
.filter(|item| item.ident.name == name)
.map(|child| child.res.expect_non_local())
.collect(),
DefKind::Impl { .. } => tcx
.associated_item_def_ids(def_id)
.iter()
.copied()
.filter(|assoc_def_id| tcx.item_name(*assoc_def_id) == name)
.map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id))
.collect(),
_ => Vec::new(),
}
}
fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symbol) -> Vec<Res> {
let hir = tcx.hir();
let root_mod;
let item_kind = match tcx.hir_node_by_def_id(local_id) {
Node::Crate(r#mod) => {
root_mod = ItemKind::Mod(r#mod);
&root_mod
},
Node::Item(item) => &item.kind,
_ => return Vec::new(),
};
let res = |ident: Ident, owner_id: OwnerId| {
if ident.name == name {
let def_id = owner_id.to_def_id();
Some(Res::Def(tcx.def_kind(def_id), def_id))
} else {
None
}
};
match item_kind {
ItemKind::Mod(r#mod) => r#mod
.item_ids
.iter()
.filter_map(|&item_id| res(hir.item(item_id).ident, item_id.owner_id))
.collect(),
ItemKind::Impl(r#impl) => r#impl
.items
.iter()
.filter_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id))
.collect(),
ItemKind::Trait(.., trait_item_refs) => trait_item_refs
.iter()
.filter_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id))
.collect(),
_ => Vec::new(),
}
}
fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
if let Some(local_id) = def_id.as_local() {
local_item_children_by_name(tcx, local_id, name)
} else {
non_local_item_children_by_name(tcx, def_id, name)
}
}
pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> Vec<Res> {
tcx.crates(())
.iter()
.copied()
.filter(move |&num| tcx.crate_name(num) == name)
.map(CrateNum::as_def_id)
.map(|id| Res::Def(tcx.def_kind(id), id))
.collect()
}
pub fn def_path_res(tcx: TyCtxt<'_>, path: &[&str]) -> Vec<Res> {
let (base, path) = match path {
[primitive] => {
return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)];
},
[base, path @ ..] => (base, path),
_ => return Vec::new(),
};
let base_sym = Symbol::intern(base);
let local_crate = if tcx.crate_name(LOCAL_CRATE) == base_sym {
Some(LOCAL_CRATE.as_def_id())
} else {
None
};
let crates = find_primitive_impls(tcx, base)
.chain(local_crate)
.map(|id| Res::Def(tcx.def_kind(id), id))
.chain(find_crates(tcx, base_sym))
.collect();
def_path_res_with_base(tcx, crates, path)
}
pub fn def_path_res_with_base(tcx: TyCtxt<'_>, mut base: Vec<Res>, mut path: &[&str]) -> Vec<Res> {
while let [segment, rest @ ..] = path {
path = rest;
let segment = Symbol::intern(segment);
base = base
.into_iter()
.filter_map(|res| res.opt_def_id())
.flat_map(|def_id| {
let inherent_impl_children = tcx
.inherent_impls(def_id)
.iter()
.flat_map(|&impl_def_id| item_children_by_name(tcx, impl_def_id, segment));
let direct_children = item_children_by_name(tcx, def_id, segment);
inherent_impl_children.chain(direct_children)
})
.collect();
}
base
}
pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator<Item = DefId> + use<> {
def_path_res(tcx, path).into_iter().filter_map(|res| res.opt_def_id())
}
pub fn create_disallowed_map(
tcx: TyCtxt<'_>,
disallowed: &'static [DisallowedPath],
) -> DefIdMap<(&'static str, Option<&'static str>)> {
disallowed
.iter()
.map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x.reason()))
.flat_map(|(name, path, reason)| def_path_def_ids(tcx, &path).map(move |id| (id, (name, reason))))
.collect()
}
pub fn get_trait_def_id(tcx: TyCtxt<'_>, path: &[&str]) -> Option<DefId> {
def_path_res(tcx, path).into_iter().find_map(|res| match res {
Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
_ => None,
})
}
pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, def_id: LocalDefId) -> Option<&'tcx TraitRef<'tcx>> {
let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
if parent_impl != hir::CRATE_OWNER_ID
&& let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent_impl.def_id)
&& let ItemKind::Impl(impl_) = &item.kind
{
return impl_.of_trait.as_ref();
}
None
}
fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
let mut result = vec![];
let root = loop {
match e.kind {
ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => {
result.push(e);
e = ep;
},
_ => break e,
};
};
result.reverse();
(result, root)
}
pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> {
cx.typeck_results()
.expr_adjustments(e)
.iter()
.find_map(|a| match a.kind {
Adjust::Deref(Some(d)) => Some(Some(d.mutbl)),
Adjust::Deref(None) => None,
_ => Some(None),
})
.and_then(|x| x)
}
pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
let (s1, r1) = projection_stack(e1);
let (s2, r2) = projection_stack(e2);
if !eq_expr_value(cx, r1, r2) {
return true;
}
if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
return false;
}
for (x1, x2) in s1.iter().zip(s2.iter()) {
if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
return false;
}
match (&x1.kind, &x2.kind) {
(ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
if i1 != i2 {
return true;
}
},
(ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => {
if !eq_expr_value(cx, i1, i2) {
return false;
}
},
_ => return false,
}
}
false
}
fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
let std_types_symbols = &[
sym::Vec,
sym::VecDeque,
sym::LinkedList,
sym::HashMap,
sym::BTreeMap,
sym::HashSet,
sym::BTreeSet,
sym::BinaryHeap,
];
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() {
return std_types_symbols.iter().any(|&symbol| {
cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string()
});
}
}
}
}
false
}
pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool {
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
&& let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
&& (is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath))
{
true
} else {
false
}
}
pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match &e.kind {
ExprKind::Lit(lit) => match lit.node {
LitKind::Bool(false) | LitKind::Int(Pu128(0), _) => true,
LitKind::Str(s, _) => s.is_empty(),
_ => false,
},
ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
ExprKind::Repeat(x, ArrayLen::Body(len)) => {
if let ConstArgKind::Anon(anon_const) = len.kind
&& let ExprKind::Lit(const_lit) = cx.tcx.hir().body(anon_const.body).value.kind
&& let LitKind::Int(v, _) = const_lit.node
&& v <= 32
&& is_default_equivalent(cx, x)
{
true
} else {
false
}
},
ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func),
ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg),
ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone),
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
_ => false,
}
}
fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool {
if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind
&& seg.ident.name == sym::from
{
match arg.kind {
ExprKind::Lit(hir::Lit {
node: LitKind::Str(sym, _),
..
}) => return sym.is_empty() && is_path_lang_item(cx, ty, LangItem::String),
ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec),
ExprKind::Repeat(_, ArrayLen::Body(len)) => {
if let ConstArgKind::Anon(anon_const) = len.kind
&& let ExprKind::Lit(const_lit) = cx.tcx.hir().body(anon_const.body).value.kind
&& let LitKind::Int(v, _) = const_lit.node
{
return v == 0 && is_path_diagnostic_item(cx, ty, sym::Vec);
}
},
_ => (),
}
}
false
}
pub fn can_move_expr_to_closure_no_visit<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
loop_ids: &[HirId],
ignore_locals: &HirIdSet,
) -> bool {
match expr.kind {
ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
| ExprKind::Continue(Destination { target_id: Ok(id), .. })
if loop_ids.contains(&id) =>
{
true
},
ExprKind::Break(..)
| ExprKind::Continue(_)
| ExprKind::Ret(_)
| ExprKind::Yield(..)
| ExprKind::InlineAsm(_) => false,
ExprKind::Field(
&Expr {
hir_id,
kind:
ExprKind::Path(QPath::Resolved(
_,
Path {
res: Res::Local(local_id),
..
},
)),
..
},
_,
) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
false
},
_ => true,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptureKind {
Value,
Ref(Mutability),
}
impl CaptureKind {
pub fn is_imm_ref(self) -> bool {
self == Self::Ref(Mutability::Not)
}
}
impl std::ops::BitOr for CaptureKind {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
(CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
| (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
(CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
}
}
}
impl std::ops::BitOrAssign for CaptureKind {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind {
fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
let mut capture = CaptureKind::Ref(Mutability::Not);
pat.each_binding_or_first(&mut |_, id, span, _| match cx
.typeck_results()
.extract_binding_mode(cx.sess(), id, span)
.unwrap()
.0
{
ByRef::No if !is_copy(cx, cx.typeck_results().node_type(id)) => {
capture = CaptureKind::Value;
},
ByRef::Yes(Mutability::Mut) if capture != CaptureKind::Value => {
capture = CaptureKind::Ref(Mutability::Mut);
},
_ => (),
});
capture
}
debug_assert!(matches!(
e.kind,
ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
));
let mut child_id = e.hir_id;
let mut capture = CaptureKind::Value;
let mut capture_expr_ty = e;
for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) {
if let [
Adjustment {
kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
target,
},
ref adjust @ ..,
] = *cx
.typeck_results()
.adjustments()
.get(child_id)
.map_or(&[][..], |x| &**x)
{
if let rustc_ty::RawPtr(_, mutability) | rustc_ty::Ref(_, _, mutability) =
*adjust.last().map_or(target, |a| a.target).kind()
{
return CaptureKind::Ref(mutability);
}
}
match parent {
Node::Expr(e) => match e.kind {
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
return CaptureKind::Ref(Mutability::Mut);
},
ExprKind::Field(..) => {
if capture == CaptureKind::Value {
capture_expr_ty = e;
}
},
ExprKind::Let(let_expr) => {
let mutability = match pat_capture_kind(cx, let_expr.pat) {
CaptureKind::Value => Mutability::Not,
CaptureKind::Ref(m) => m,
};
return CaptureKind::Ref(mutability);
},
ExprKind::Match(_, arms, _) => {
let mut mutability = Mutability::Not;
for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
match capture {
CaptureKind::Value => break,
CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
CaptureKind::Ref(Mutability::Not) => (),
}
}
return CaptureKind::Ref(mutability);
},
_ => break,
},
Node::LetStmt(l) => match pat_capture_kind(cx, l.pat) {
CaptureKind::Value => break,
capture @ CaptureKind::Ref(_) => return capture,
},
_ => break,
}
child_id = parent_id;
}
if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
CaptureKind::Ref(Mutability::Not)
} else {
capture
}
}
pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
loops: Vec<HirId>,
locals: HirIdSet,
allow_closure: bool,
captures: HirIdMap<CaptureKind>,
}
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if !self.allow_closure {
return;
}
match e.kind {
ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
if !self.locals.contains(&l) {
let cap = capture_local_usage(self.cx, e);
self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
}
},
ExprKind::Closure(closure) => {
for capture in self.cx.typeck_results().closure_min_captures_flattened(closure.def_id) {
let local_id = match capture.place.base {
PlaceBase::Local(id) => id,
PlaceBase::Upvar(var) => var.var_path.hir_id,
_ => continue,
};
if !self.locals.contains(&local_id) {
let capture = match capture.info.capture_kind {
UpvarCapture::ByValue => CaptureKind::Value,
UpvarCapture::ByRef(kind) => match kind {
BorrowKind::Immutable => CaptureKind::Ref(Mutability::Not),
BorrowKind::UniqueImmutable | BorrowKind::Mutable => {
CaptureKind::Ref(Mutability::Mut)
},
},
};
self.captures
.entry(local_id)
.and_modify(|e| *e |= capture)
.or_insert(capture);
}
}
},
ExprKind::Loop(b, ..) => {
self.loops.push(e.hir_id);
self.visit_block(b);
self.loops.pop();
},
_ => {
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
walk_expr(self, e);
},
}
}
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
p.each_binding_or_first(&mut |_, id, _, _| {
self.locals.insert(id);
});
}
}
let mut v = V {
cx,
allow_closure: true,
loops: Vec::new(),
locals: HirIdSet::default(),
captures: HirIdMap::default(),
};
v.visit_expr(expr);
v.allow_closure.then_some(v.captures)
}
pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>;
pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) {
let mut method_names = Vec::with_capacity(max_depth);
let mut arg_lists = Vec::with_capacity(max_depth);
let mut spans = Vec::with_capacity(max_depth);
let mut current = expr;
for _ in 0..max_depth {
if let ExprKind::MethodCall(path, receiver, args, _) = ¤t.kind {
if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
break;
}
method_names.push(path.ident.name);
arg_lists.push((*receiver, &**args));
spans.push(path.ident.span);
current = receiver;
} else {
break;
}
}
(method_names, arg_lists, spans)
}
pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> {
let mut current = expr;
let mut matched = Vec::with_capacity(methods.len());
for method_name in methods.iter().rev() {
if let ExprKind::MethodCall(path, receiver, args, _) = current.kind {
if path.ident.name.as_str() == *method_name {
if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
return None;
}
matched.push((receiver, args)); current = receiver; } else {
return None;
}
} else {
return None;
}
}
matched.reverse();
Some(matched)
}
pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
cx.tcx
.entry_fn(())
.is_some_and(|(entry_fn_def_id, _)| def_id == entry_fn_def_id)
}
pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
let parent = cx.tcx.hir().get_parent_item(e.hir_id);
Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl()
}
pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id).def_id;
match cx.tcx.hir_node_by_def_id(parent_id) {
Node::Item(Item { ident, .. })
| Node::TraitItem(TraitItem { ident, .. })
| Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name),
_ => None,
}
}
pub struct ContainsName<'a, 'tcx> {
pub cx: &'a LateContext<'tcx>,
pub name: Symbol,
}
impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> {
type Result = ControlFlow<()>;
type NestedFilter = nested_filter::OnlyBodies;
fn visit_name(&mut self, name: Symbol) -> Self::Result {
if self.name == name {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
}
pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
let mut cn = ContainsName { cx, name };
cn.visit_expr(expr).is_break()
}
pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
for_each_expr_without_closures(expr, |e| {
if matches!(e.kind, ExprKind::Ret(..)) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})
.is_some()
}
pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
get_parent_expr_for_hir(cx, e.hir_id)
}
pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
match cx.tcx.parent_hir_node(hir_id) {
Node::Expr(parent) => Some(parent),
_ => None,
}
}
pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
let map = &cx.tcx.hir();
let enclosing_node = map
.get_enclosing_scope(hir_id)
.map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
enclosing_node.and_then(|node| match node {
Node::Block(block) => Some(block),
Node::Item(&Item {
kind: ItemKind::Fn(_, _, eid),
..
})
| Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(_, eid),
..
}) => match cx.tcx.hir().body(eid).value.kind {
ExprKind::Block(block, _) => Some(block),
_ => None,
},
_ => None,
})
}
pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'_>,
) -> Option<&'tcx Expr<'tcx>> {
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match node {
Node::Expr(e) => match e.kind {
ExprKind::Closure { .. }
if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
&& subs.as_closure().kind() == ClosureKind::FnOnce => {},
ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
_ => (),
},
Node::Stmt(_) | Node::Block(_) | Node::LetStmt(_) | Node::Arm(_) | Node::ExprField(_) => (),
_ => break,
}
}
None
}
pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
match tcx.hir().parent_iter(id).next() {
Some((
_,
Node::Item(Item {
kind: ItemKind::Impl(imp),
..
}),
)) => Some(imp),
_ => None,
}
}
pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
while let ExprKind::Block(
Block {
stmts: [],
expr: Some(inner),
rules: BlockCheckMode::DefaultBlock,
..
},
_,
) = expr.kind
{
expr = inner;
}
expr
}
pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
while let ExprKind::Block(
Block {
stmts: [],
expr: Some(inner),
rules: BlockCheckMode::DefaultBlock,
..
}
| Block {
stmts:
[
Stmt {
kind: StmtKind::Expr(inner) | StmtKind::Semi(inner),
..
},
],
expr: None,
rules: BlockCheckMode::DefaultBlock,
..
},
_,
) = expr.kind
{
expr = inner;
}
expr
}
pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
let mut iter = tcx.hir().parent_iter(expr.hir_id);
match iter.next() {
Some((
_,
Node::Expr(Expr {
kind: ExprKind::If(_, _, Some(else_expr)),
..
}),
)) => else_expr.hir_id == expr.hir_id,
_ => false,
}
}
pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
let mut child_id = expr.hir_id;
for (parent_id, node) in tcx.hir().parent_iter(child_id) {
if let Node::LetStmt(LetStmt {
init: Some(init),
els: Some(els),
..
}) = node
&& (init.hir_id == child_id || els.hir_id == child_id)
{
return true;
}
child_id = parent_id;
}
false
}
pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
let mut child_id = expr.hir_id;
for (parent_id, node) in tcx.hir().parent_iter(child_id) {
if let Node::LetStmt(LetStmt { els: Some(els), .. }) = node
&& els.hir_id == child_id
{
return true;
}
child_id = parent_id;
}
false
}
pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
if let Some(Range { start, end, limits }) = Range::hir(expr) {
let start_is_none_or_min = start.map_or(true, |start| {
if let rustc_ty::Adt(_, subst) = ty.kind()
&& let bnd_ty = subst.type_at(0)
&& let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx)
&& let Some(min_const) = mir_to_const(cx.tcx, Const::from_ty_const(min_val, bnd_ty, cx.tcx))
&& let Some(start_const) = ConstEvalCtxt::new(cx).eval(start)
{
start_const == min_const
} else {
false
}
});
let end_is_none_or_max = end.map_or(true, |end| match limits {
RangeLimits::Closed => {
if let rustc_ty::Adt(_, subst) = ty.kind()
&& let bnd_ty = subst.type_at(0)
&& let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx)
&& let Some(max_const) = mir_to_const(cx.tcx, Const::from_ty_const(max_val, bnd_ty, cx.tcx))
&& let Some(end_const) = ConstEvalCtxt::new(cx).eval(end)
{
end_const == max_const
} else {
false
}
},
RangeLimits::HalfOpen => {
if let Some(container_path) = container_path
&& let ExprKind::MethodCall(name, self_arg, [], _) = end.kind
&& name.ident.name == sym::len
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
{
container_path.res == path.res
} else {
false
}
},
});
return start_is_none_or_min && end_is_none_or_max;
}
false
}
pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
if is_integer_literal(e, value) {
return true;
}
let enclosing_body = cx.tcx.hir().enclosing_body_owner(e.hir_id);
if let Some(Constant::Int(v)) =
ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), cx.tcx.typeck(enclosing_body)).eval(e)
{
return value == v;
}
false
}
pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
if let ExprKind::Lit(spanned) = expr.kind {
if let LitKind::Int(v, _) = spanned.node {
return v == value;
}
}
false
}
pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
cx.typeck_results().adjustments().get(e.hir_id).is_some()
}
#[must_use]
pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
loop {
if span.from_expansion() {
let data = span.ctxt().outer_expn_data();
let new_span = data.call_site;
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
if mac_name.as_str() == name {
return Some(new_span);
}
}
span = new_span;
} else {
return None;
}
}
}
#[must_use]
pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
if span.from_expansion() {
let data = span.ctxt().outer_expn_data();
let new_span = data.call_site;
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
if mac_name.as_str() == name {
return Some(new_span);
}
}
}
None
}
pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> {
let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().output();
cx.tcx.instantiate_bound_regions_with_erased(ret_ty)
}
pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> {
let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().input(nth);
cx.tcx.instantiate_bound_regions_with_erased(arg)
}
pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let ExprKind::Call(fun, _) = expr.kind {
if let ExprKind::Path(ref qp) = fun.kind {
let res = cx.qpath_res(qp, fun.hir_id);
return match res {
Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
_ => false,
};
}
}
false
}
pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
matches!(
cx.qpath_res(qpath, id),
Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
)
}
fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
i.into_iter().any(|pat| is_refutable(cx, pat))
}
match pat.kind {
PatKind::Wild | PatKind::Never => false, PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)),
PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat),
PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
PatKind::Or(pats) => {
are_refutable(cx, pats)
},
PatKind::Tuple(pats, _) => are_refutable(cx, pats),
PatKind::Struct(ref qpath, fields, _) => {
is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat))
},
PatKind::TupleStruct(ref qpath, pats, _) => is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats),
PatKind::Slice(head, middle, tail) => {
match &cx.typeck_results().node_type(pat.hir_id).kind() {
rustc_ty::Slice(..) => {
!head.is_empty() || middle.is_none() || !tail.is_empty()
},
rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
_ => {
true
},
}
},
PatKind::Lit(..) | PatKind::Range(..) | PatKind::Err(_) | PatKind::Deref(_) => true,
}
}
pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
if let PatKind::Or(pats) = pat.kind {
pats.iter().for_each(f);
} else {
f(pat);
}
}
pub fn is_self(slf: &Param<'_>) -> bool {
if let PatKind::Binding(.., name, _) = slf.pat.kind {
name.name == kw::SelfLower
} else {
false
}
}
pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind {
if let Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } = path.res {
return true;
}
}
false
}
pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
(0..decl.inputs.len()).map(move |i| &body.params[i])
}
pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind
&& ddpos.as_opt_usize().is_none()
&& is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultOk)
&& let PatKind::Binding(_, hir_id, _, None) = pat[0].kind
&& path_to_local_id(arm.body, hir_id)
{
return true;
}
false
}
fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultErr)
} else {
false
}
}
if let ExprKind::Match(_, arms, ref source) = expr.kind {
if let MatchSource::TryDesugar(_) = *source {
return Some(expr);
}
if arms.len() == 2
&& arms[0].guard.is_none()
&& arms[1].guard.is_none()
&& ((is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])))
{
return Some(expr);
}
}
None
}
pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool {
let mut suppress_lint = false;
for id in ids {
let (level, _) = cx.tcx.lint_level_at_node(lint, id);
if let Some(expectation) = level.get_expectation_id() {
cx.fulfill_expectation(expectation);
}
match level {
Level::Allow | Level::Expect(_) => suppress_lint = true,
Level::Warn | Level::ForceWarn(_) | Level::Deny | Level::Forbid => {},
}
}
suppress_lint
}
pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
}
pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
while let PatKind::Ref(subpat, _) = pat.kind {
pat = subpat;
}
pat
}
pub fn int_bits(tcx: TyCtxt<'_>, ity: IntTy) -> u64 {
Integer::from_int_ty(&tcx, ity).size().bits()
}
#[expect(clippy::cast_possible_wrap)]
pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: IntTy) -> i128 {
let amt = 128 - int_bits(tcx, ity);
((u as i128) << amt) >> amt
}
#[expect(clippy::cast_sign_loss)]
pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: IntTy) -> u128 {
let amt = 128 - int_bits(tcx, ity);
((u as u128) << amt) >> amt
}
pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: UintTy) -> u128 {
let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
let amt = 128 - bits;
(u << amt) >> amt
}
pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool {
attrs.iter().any(|attr| attr.has_name(symbol))
}
pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
has_attr(cx.tcx.hir().attrs(hir_id), sym::repr)
}
pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
let map = &tcx.hir();
let mut prev_enclosing_node = None;
let mut enclosing_node = node;
while Some(enclosing_node) != prev_enclosing_node {
if has_attr(map.attrs(enclosing_node), symbol) {
return true;
}
prev_enclosing_node = Some(enclosing_node);
enclosing_node = map.get_parent_item(enclosing_node).into();
}
false
}
pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir()
.parent_owner_iter(id)
.filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
.any(|(id, _)| {
has_attr(
tcx.hir().attrs(tcx.local_def_id_to_hir_id(id.def_id)),
sym::automatically_derived,
)
})
}
pub fn match_function_call<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
path: &[&str],
) -> Option<&'tcx [Expr<'tcx>]> {
if let ExprKind::Call(fun, args) = expr.kind
&& let ExprKind::Path(ref qpath) = fun.kind
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
&& match_def_path(cx, fun_def_id, path)
{
return Some(args);
};
None
}
pub fn match_function_call_with_def_id<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
fun_def_id: DefId,
) -> Option<&'tcx [Expr<'tcx>]> {
if let ExprKind::Call(fun, args) = expr.kind
&& let ExprKind::Path(ref qpath) = fun.kind
&& cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id)
{
return Some(args);
};
None
}
pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
let search_path = cx.get_def_path(did);
paths
.iter()
.position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
}
pub fn match_def_path(cx: &LateContext<'_>, did: DefId, syms: &[&str]) -> bool {
let path = cx.get_def_path(did);
syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
}
pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
let path = cx.get_def_path(did);
path.first().is_some_and(|s| s.as_str() == "libc") && path.last().is_some_and(|s| s.as_str() == name)
}
pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
let mut conds = Vec::new();
let mut blocks: Vec<&Block<'_>> = Vec::new();
while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
conds.push(cond);
if let ExprKind::Block(block, _) = then.kind {
blocks.push(block);
} else {
panic!("ExprKind::If node is not an ExprKind::Block");
}
if let Some(else_expr) = r#else {
expr = else_expr;
} else {
break;
}
}
if !blocks.is_empty() {
if let ExprKind::Block(block, _) = expr.kind {
blocks.push(block);
}
}
(conds, blocks)
}
pub fn is_async_fn(kind: FnKind<'_>) -> bool {
match kind {
FnKind::ItemFn(_, _, header) => header.asyncness.is_async(),
FnKind::Method(_, sig) => sig.header.asyncness.is_async(),
FnKind::Closure => false,
}
}
pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Closure(&Closure { body, .. }) = body.value.kind {
if let ExprKind::Block(
Block {
stmts: [],
expr:
Some(Expr {
kind: ExprKind::DropTemps(expr),
..
}),
..
},
_,
) = tcx.hir().body(body).value.kind
{
return Some(expr);
}
};
None
}
pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let did = match expr.kind {
ExprKind::Call(path, _) => {
if let ExprKind::Path(ref qpath) = path.kind
&& let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
{
Some(did)
} else {
None
}
},
ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
_ => None,
};
did.is_some_and(|did| cx.tcx.has_attr(did, sym::must_use))
}
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
fn check_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
if cx
.typeck_results()
.pat_binding_modes()
.get(pat.hir_id)
.is_some_and(|mode| matches!(mode.0, ByRef::Yes(_)))
{
return false;
}
match (pat.kind, expr.kind) {
(PatKind::Binding(_, id, _, _), _) => {
path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty()
},
(PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
{
pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr))
},
_ => false,
}
}
let [param] = func.params else {
return false;
};
let mut expr = func.value;
loop {
match expr.kind {
ExprKind::Block(
&Block {
stmts: [],
expr: Some(e),
..
},
_,
)
| ExprKind::Ret(Some(e)) => expr = e,
ExprKind::Block(
&Block {
stmts: [stmt],
expr: None,
..
},
_,
) => {
if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind
&& let ExprKind::Ret(Some(ret_val)) = e.kind
{
expr = ret_val;
} else {
return false;
}
},
_ => return check_pat(cx, param.pat, expr),
}
}
}
pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Closure(&Closure { body, fn_decl, .. })
if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) =>
{
is_body_identity_function(cx, cx.tcx.hir().body(body))
},
ExprKind::Path(QPath::Resolved(_, path))
if path.segments.iter().all(|seg| seg.infer_args)
&& let Some(did) = path.res.opt_def_id() =>
{
cx.tcx.is_diagnostic_item(sym::convert_identity, did)
},
_ => false,
}
}
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)),
_ => path_def_id(cx, expr).is_some_and(|id| cx.tcx.is_diagnostic_item(sym::convert_identity, id)),
}
}
pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
let mut child_id = expr.hir_id;
let mut iter = tcx.hir().parent_iter(child_id);
loop {
match iter.next() {
None => break None,
Some((id, Node::Block(_))) => child_id = id,
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
Some((_, Node::Expr(expr))) => match expr.kind {
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
_ => break Some((Node::Expr(expr), child_id)),
},
Some((_, node)) => break Some((node, child_id)),
}
}
}
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
!matches!(
get_expr_use_or_unification_node(tcx, expr),
None | Some((
Node::Stmt(Stmt {
kind: StmtKind::Expr(_)
| StmtKind::Semi(_)
| StmtKind::Let(LetStmt {
pat: Pat {
kind: PatKind::Wild,
..
},
..
}),
..
}),
_
))
)
}
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..))
}
pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
if !is_no_std_crate(cx) {
Some("std")
} else if !is_no_core_crate(cx) {
Some("core")
} else {
None
}
}
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
if let ast::AttrKind::Normal(ref normal) = attr.kind {
normal.item.path == sym::no_std
} else {
false
}
})
}
pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
if let ast::AttrKind::Normal(ref normal) = attr.kind {
normal.item.path == sym::no_core
} else {
false
}
})
}
pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) {
matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }))
} else {
false
}
}
pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
use rustc_trait_selection::traits;
let predicates = cx
.tcx
.predicates_of(did)
.predicates
.iter()
.filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
traits::impossible_predicates(cx.tcx, traits::elaborate(cx.tcx, predicates).collect::<Vec<_>>())
}
pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
fn_def_id_with_node_args(cx, expr).map(|(did, _)| did)
}
pub fn fn_def_id_with_node_args<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'_>,
) -> Option<(DefId, GenericArgsRef<'tcx>)> {
let typeck = cx.typeck_results();
match &expr.kind {
ExprKind::MethodCall(..) => Some((
typeck.type_dependent_def_id(expr.hir_id)?,
typeck.node_args(expr.hir_id),
)),
ExprKind::Call(
Expr {
kind: ExprKind::Path(qpath),
hir_id: path_hir_id,
..
},
..,
) => {
if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
typeck.qpath_res(qpath, *path_hir_id)
{
Some((id, typeck.node_args(*path_hir_id)))
} else {
None
}
},
_ => None,
}
}
pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
let expr_kind = expr_type.kind();
let is_primitive = match expr_kind {
rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type),
rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
if let rustc_ty::Slice(element_type) = inner_ty.kind() {
is_recursively_primitive_type(*element_type)
} else {
unreachable!()
}
},
_ => false,
};
if is_primitive {
match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
rustc_ty::Slice(..) => return Some("slice".into()),
rustc_ty::Array(..) => return Some("array".into()),
rustc_ty::Tuple(..) => return Some("tuple".into()),
_ => {
let refs_peeled = expr_type.peel_refs();
return Some(refs_peeled.walk().last().unwrap().to_string());
},
}
}
None
}
pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<(&T, &T)>
where
Hash: FnMut(&T) -> u64,
Eq: FnMut(&T, &T) -> bool,
{
match exprs {
[a, b] if eq(a, b) => return vec![(a, b)],
_ if exprs.len() <= 2 => return vec![],
_ => {},
}
let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
let mut map: UnhashMap<u64, Vec<&_>> =
UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
for expr in exprs {
match map.entry(hash(expr)) {
Entry::Occupied(mut o) => {
for o in o.get() {
if eq(o, expr) {
match_expr_list.push((o, expr));
}
}
o.get_mut().push(expr);
},
Entry::Vacant(v) => {
v.insert(vec![expr]);
},
}
}
match_expr_list
}
pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
if let PatKind::Ref(pat, _) = pat.kind {
peel(pat, count + 1)
} else {
(pat, count)
}
}
peel(pat, 0)
}
pub fn peel_hir_expr_while<'tcx>(
mut expr: &'tcx Expr<'tcx>,
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
) -> &'tcx Expr<'tcx> {
while let Some(e) = f(expr) {
expr = e;
}
expr
}
pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
let mut remaining = count;
let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
remaining -= 1;
Some(e)
},
_ => None,
});
(e, count - remaining)
}
pub fn peel_hir_expr_unary<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
let mut count: usize = 0;
let mut curr_expr = expr;
while let ExprKind::Unary(_, local_expr) = curr_expr.kind {
count = count.wrapping_add(1);
curr_expr = local_expr;
}
(curr_expr, count)
}
pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
let mut count = 0;
let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
count += 1;
Some(e)
},
_ => None,
});
(e, count)
}
pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) {
let mut count = 0;
loop {
match &ty.kind {
TyKind::Ref(_, ref_ty) => {
ty = ref_ty.ty;
count += 1;
},
_ => break (ty, count),
}
}
}
pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) {
let mut count = 0;
while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() {
ty = *dest_ty;
count += 1;
}
(ty, count)
}
pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
loop {
match expr.kind {
ExprKind::AddrOf(_, _, e) => expr = e,
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
_ => break,
}
}
expr
}
pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
if let Res::Def(_, def_id) = path.res {
return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
}
}
false
}
static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalModDefId, Vec<Symbol>>>> = OnceLock::new();
fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalModDefId, f: impl Fn(&[Symbol]) -> bool) -> bool {
let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
let mut map: MutexGuard<'_, FxHashMap<LocalModDefId, Vec<Symbol>>> = cache.lock().unwrap();
let value = map.entry(module);
match value {
Entry::Occupied(entry) => f(entry.get()),
Entry::Vacant(entry) => {
let mut names = Vec::new();
for id in tcx.hir().module_items(module) {
if matches!(tcx.def_kind(id.owner_id), DefKind::Const)
&& let item = tcx.hir().item(id)
&& let ItemKind::Const(ty, _generics, _body) = item.kind
{
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
if let Res::Def(DefKind::Struct, _) = path.res {
let has_test_marker = tcx
.hir()
.attrs(item.hir_id())
.iter()
.any(|a| a.has_name(sym::rustc_test_marker));
if has_test_marker {
names.push(item.ident.name);
}
}
}
}
}
names.sort_unstable();
f(entry.insert(names))
},
}
}
pub fn is_in_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool {
with_test_item_names(tcx, tcx.parent_module(id), |names| {
let node = tcx.hir_node(id);
once((id, node))
.chain(tcx.hir().parent_iter(id))
.any(|(_id, node)| {
if let Node::Item(item) = node {
if let ItemKind::Fn(_, _, _) = item.kind {
return names.binary_search(&item.ident.name).is_ok();
}
}
false
})
})
}
pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir().attrs(id).iter().any(|attr| {
if attr.has_name(sym::cfg)
&& let Some(items) = attr.meta_item_list()
&& let [item] = &*items
&& item.has_name(sym::test)
{
true
} else {
false
}
})
}
pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir()
.parent_id_iter(id)
.any(|parent_id| is_cfg_test(tcx, parent_id))
}
pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
is_in_test_function(tcx, hir_id) || is_in_cfg_test(tcx, hir_id)
}
pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
let hir = tcx.hir();
tcx.has_attr(def_id, sym::cfg)
|| hir
.parent_iter(tcx.local_def_id_to_hir_id(def_id))
.flat_map(|(parent_id, _)| hir.attrs(parent_id))
.any(|attr| attr.has_name(sym::cfg))
}
pub fn walk_to_expr_usage<'tcx, T>(
cx: &LateContext<'tcx>,
e: &Expr<'tcx>,
mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>,
) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> {
let map = cx.tcx.hir();
let mut iter = map.parent_iter(e.hir_id);
let mut child_id = e.hir_id;
while let Some((parent_id, parent)) = iter.next() {
if let ControlFlow::Break(x) = f(parent_id, parent, child_id) {
return Some(ControlFlow::Break(x));
}
let parent_expr = match parent {
Node::Expr(e) => e,
Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
child_id = parent_id;
continue;
},
Node::Arm(a) if a.body.hir_id == child_id => {
child_id = parent_id;
continue;
},
_ => return Some(ControlFlow::Continue((parent, child_id))),
};
match parent_expr.kind {
ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
child_id = id;
iter = map.parent_iter(id);
},
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id,
_ => return Some(ControlFlow::Continue((parent, child_id))),
}
}
debug_assert!(false, "no parent node found for `{child_id:?}`");
None
}
#[derive(Clone, Copy)]
pub enum DefinedTy<'tcx> {
Hir(&'tcx hir::Ty<'tcx>),
Mir {
def_site_def_id: Option<DefId>,
ty: Binder<'tcx, Ty<'tcx>>,
},
}
pub struct ExprUseCtxt<'tcx> {
pub node: Node<'tcx>,
pub child_id: HirId,
pub adjustments: &'tcx [Adjustment<'tcx>],
pub is_ty_unified: bool,
pub moved_before_use: bool,
pub same_ctxt: bool,
}
impl<'tcx> ExprUseCtxt<'tcx> {
pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> {
match self.node {
Node::LetStmt(l) => ExprUseNode::LetStmt(l),
Node::ExprField(field) => ExprUseNode::Field(field),
Node::Item(&Item {
kind: ItemKind::Static(..) | ItemKind::Const(..),
owner_id,
..
})
| Node::TraitItem(&TraitItem {
kind: TraitItemKind::Const(..),
owner_id,
..
})
| Node::ImplItem(&ImplItem {
kind: ImplItemKind::Const(..),
owner_id,
..
}) => ExprUseNode::ConstStatic(owner_id),
Node::Item(&Item {
kind: ItemKind::Fn(..),
owner_id,
..
})
| Node::TraitItem(&TraitItem {
kind: TraitItemKind::Fn(..),
owner_id,
..
})
| Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(..),
owner_id,
..
}) => ExprUseNode::Return(owner_id),
Node::Expr(use_expr) => match use_expr.kind {
ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
}),
ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) {
Some(i) => ExprUseNode::FnArg(func, i),
None => ExprUseNode::Callee,
},
ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
use_expr.hir_id,
name.args,
args.iter()
.position(|arg| arg.hir_id == self.child_id)
.map_or(0, |i| i + 1),
),
ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name),
ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl),
_ => ExprUseNode::Other,
},
_ => ExprUseNode::Other,
}
}
}
pub enum ExprUseNode<'tcx> {
LetStmt(&'tcx LetStmt<'tcx>),
ConstStatic(OwnerId),
Return(OwnerId),
Field(&'tcx ExprField<'tcx>),
FnArg(&'tcx Expr<'tcx>, usize),
MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize),
Callee,
FieldAccess(Ident),
AddrOf(ast::BorrowKind, Mutability),
Other,
}
impl<'tcx> ExprUseNode<'tcx> {
pub fn is_return(&self) -> bool {
matches!(self, Self::Return(_))
}
pub fn is_recv(&self) -> bool {
matches!(self, Self::MethodArg(_, _, 0))
}
pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
match *self {
Self::LetStmt(LetStmt { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)),
Self::ConstStatic(id) => Some(DefinedTy::Mir {
def_site_def_id: Some(id.def_id.to_def_id()),
ty: Binder::dummy(cx.tcx.type_of(id).instantiate_identity()),
}),
Self::Return(id) => {
if let Node::Expr(Expr {
kind: ExprKind::Closure(c),
..
}) = cx.tcx.hir_node_by_def_id(id.def_id)
{
match c.fn_decl.output {
FnRetTy::DefaultReturn(_) => None,
FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)),
}
} else {
let ty = cx.tcx.fn_sig(id).instantiate_identity().output();
Some(DefinedTy::Mir { def_site_def_id: Some(id.def_id.to_def_id()), ty })
}
},
Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) {
Some(Expr {
hir_id,
kind: ExprKind::Struct(path, ..),
..
}) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id))
.and_then(|(adt, variant)| {
variant
.fields
.iter()
.find(|f| f.name == field.ident.name)
.map(|f| (adt, f))
})
.map(|(adt, field_def)| DefinedTy::Mir {
def_site_def_id: Some(adt.did()),
ty: Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity()),
}),
_ => None,
},
Self::FnArg(callee, i) => {
let sig = expr_sig(cx, callee)?;
let (hir_ty, ty) = sig.input_with_hir(i)?;
Some(match hir_ty {
Some(hir_ty) => DefinedTy::Hir(hir_ty),
None => DefinedTy::Mir {
def_site_def_id: sig.predicates_id(),
ty,
}
})
},
Self::MethodArg(id, _, i) => {
let id = cx.typeck_results().type_dependent_def_id(id)?;
let sig = cx.tcx.fn_sig(id).skip_binder();
Some(DefinedTy::Mir {
def_site_def_id: Some(id),
ty: sig.input(i),
})
},
Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None,
}
}
}
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprUseCtxt<'tcx> {
let mut adjustments = [].as_slice();
let mut is_ty_unified = false;
let mut moved_before_use = false;
let mut same_ctxt = true;
let ctxt = e.span.ctxt();
let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow<!> {
if adjustments.is_empty()
&& let Node::Expr(e) = cx.tcx.hir_node(child_id)
{
adjustments = cx.typeck_results().expr_adjustments(e);
}
same_ctxt &= cx.tcx.hir().span(parent_id).ctxt() == ctxt;
if let Node::Expr(e) = parent {
match e.kind {
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
is_ty_unified = true;
moved_before_use = true;
},
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
is_ty_unified = true;
moved_before_use = true;
},
ExprKind::Block(..) => moved_before_use = true,
_ => {},
}
}
ControlFlow::Continue(())
});
match node {
Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt {
node,
child_id,
adjustments,
is_ty_unified,
moved_before_use,
same_ctxt,
},
#[allow(unreachable_patterns)]
Some(ControlFlow::Break(_)) => unreachable!("type of node is ControlFlow<!>"),
None => ExprUseCtxt {
node: Node::Crate(cx.tcx.hir().root_module()),
child_id: HirId::INVALID,
adjustments: &[],
is_ty_unified: true,
moved_before_use: true,
same_ctxt: false,
},
}
}
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str, InnerSpan)> {
let mut pos = 0;
tokenize(s).map(move |t| {
let end = pos + t.len;
let range = pos as usize..end as usize;
let inner = InnerSpan::new(range.start, range.end);
pos = end;
(t.kind, s.get(range).unwrap_or_default(), inner)
})
}
pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
let Ok(snippet) = sm.span_to_snippet(span) else {
return false;
};
return tokenize(&snippet).any(|token| {
matches!(
token.kind,
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
)
});
}
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
let snippet = sm.span_to_snippet(span).unwrap_or_default();
let res = tokenize_with_text(&snippet)
.filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
.map(|(_, s, _)| s)
.join("\n");
res
}
pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
sm.span_take_while(span, |&ch| ch == ' ' || ch == ';')
}
pub fn pat_and_expr_can_be_question_mark<'a, 'hir>(
cx: &LateContext<'_>,
pat: &'a Pat<'hir>,
else_body: &Expr<'_>,
) -> Option<&'a Pat<'hir>> {
if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind
&& is_res_lang_ctor(cx, cx.qpath_res(&pat_path, pat.hir_id), OptionSome)
&& !is_refutable(cx, inner_pat)
&& let else_body = peel_blocks(else_body)
&& let ExprKind::Ret(Some(ret_val)) = else_body.kind
&& let ExprKind::Path(ret_path) = ret_val.kind
&& is_res_lang_ctor(cx, cx.qpath_res(&ret_path, ret_val.hir_id), OptionNone)
{
Some(inner_pat)
} else {
None
}
}
macro_rules! op_utils {
($($name:ident $assign:ident)*) => {
pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
match kind {
$(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
_ => None,
}
}
};
}
op_utils! {
Add AddAssign
Sub SubAssign
Mul MulAssign
Div DivAssign
Rem RemAssign
BitXor BitXorAssign
BitAnd BitAndAssign
BitOr BitOrAssign
Shl ShlAssign
Shr ShrAssign
}
pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: impl Visitable<'tcx>) -> bool {
match *pat {
PatKind::Wild => true,
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
!visitors::is_local_used(cx, body, id)
},
_ => false,
}
}
#[derive(Clone, Copy)]
pub enum RequiresSemi {
Yes,
No,
}
impl RequiresSemi {
pub fn requires_semi(self) -> bool {
matches!(self, Self::Yes)
}
}
#[expect(clippy::too_many_lines)]
pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
struct BreakTarget {
id: HirId,
unused: bool,
}
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
break_targets: Vec<BreakTarget>,
break_targets_for_result_ty: u32,
in_final_expr: bool,
requires_semi: bool,
is_never: bool,
}
impl V<'_, '_> {
fn push_break_target(&mut self, id: HirId) {
self.break_targets.push(BreakTarget { id, unused: true });
self.break_targets_for_result_ty += u32::from(self.in_final_expr);
}
}
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.is_never && self.break_targets.is_empty() {
if self.in_final_expr && !self.requires_semi {
match e.kind {
ExprKind::DropTemps(e) => self.visit_expr(e),
ExprKind::If(_, then, Some(else_)) => {
self.visit_expr(then);
self.visit_expr(else_);
},
ExprKind::Match(_, arms, _) => {
for arm in arms {
self.visit_expr(arm.body);
}
},
ExprKind::Loop(b, ..) => {
self.push_break_target(e.hir_id);
self.in_final_expr = false;
self.visit_block(b);
self.break_targets.pop();
},
ExprKind::Block(b, _) => {
if b.targeted_by_break {
self.push_break_target(b.hir_id);
self.visit_block(b);
self.break_targets.pop();
} else {
self.visit_block(b);
}
},
_ => {
self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
},
}
}
return;
}
match e.kind {
ExprKind::DropTemps(e) => self.visit_expr(e),
ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true,
ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
self.in_final_expr = false;
self.visit_expr(e);
self.is_never = true;
},
ExprKind::Break(dest, e) => {
if let Some(e) = e {
self.in_final_expr = false;
self.visit_expr(e);
}
if let Ok(id) = dest.target_id
&& let Some((i, target)) = self
.break_targets
.iter_mut()
.enumerate()
.find(|(_, target)| target.id == id)
{
target.unused &= self.is_never;
if i < self.break_targets_for_result_ty as usize {
self.requires_semi = true;
}
}
self.is_never = true;
},
ExprKind::If(cond, then, else_) => {
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
self.visit_expr(cond);
self.in_final_expr = in_final_expr;
if self.is_never {
self.visit_expr(then);
if let Some(else_) = else_ {
self.visit_expr(else_);
}
} else {
self.visit_expr(then);
let is_never = mem::replace(&mut self.is_never, false);
if let Some(else_) = else_ {
self.visit_expr(else_);
self.is_never &= is_never;
}
}
},
ExprKind::Match(scrutinee, arms, _) => {
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
self.visit_expr(scrutinee);
self.in_final_expr = in_final_expr;
if self.is_never {
for arm in arms {
self.visit_arm(arm);
}
} else {
let mut is_never = true;
for arm in arms {
self.is_never = false;
if let Some(guard) = arm.guard {
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
self.visit_expr(guard);
self.in_final_expr = in_final_expr;
self.is_never = false;
}
self.visit_expr(arm.body);
is_never &= self.is_never;
}
self.is_never = is_never;
}
},
ExprKind::Loop(b, _, _, _) => {
self.push_break_target(e.hir_id);
self.in_final_expr = false;
self.visit_block(b);
self.is_never = self.break_targets.pop().unwrap().unused;
},
ExprKind::Block(b, _) => {
if b.targeted_by_break {
self.push_break_target(b.hir_id);
self.visit_block(b);
self.is_never &= self.break_targets.pop().unwrap().unused;
} else {
self.visit_block(b);
}
},
_ => {
self.in_final_expr = false;
walk_expr(self, e);
self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
},
}
}
fn visit_block(&mut self, b: &'tcx Block<'_>) {
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
for s in b.stmts {
self.visit_stmt(s);
}
self.in_final_expr = in_final_expr;
if let Some(e) = b.expr {
self.visit_expr(e);
}
}
fn visit_local(&mut self, l: &'tcx LetStmt<'_>) {
if let Some(e) = l.init {
self.visit_expr(e);
}
if let Some(else_) = l.els {
let is_never = self.is_never;
self.visit_block(else_);
self.is_never = is_never;
}
}
fn visit_arm(&mut self, arm: &Arm<'tcx>) {
if let Some(guard) = arm.guard {
let in_final_expr = mem::replace(&mut self.in_final_expr, false);
self.visit_expr(guard);
self.in_final_expr = in_final_expr;
}
self.visit_expr(arm.body);
}
}
if cx.typeck_results().expr_ty(e).is_never() {
Some(RequiresSemi::No)
} else if let ExprKind::Block(b, _) = e.kind
&& !b.targeted_by_break
&& b.expr.is_none()
{
None
} else {
let mut v = V {
cx,
break_targets: Vec::new(),
break_targets_for_result_ty: 0,
in_final_expr: true,
requires_semi: false,
is_never: false,
};
v.visit_expr(e);
v.is_never
.then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
RequiresSemi::Yes
} else {
RequiresSemi::No
})
}
}
pub fn get_path_from_caller_to_method_type<'tcx>(
tcx: TyCtxt<'tcx>,
from: LocalDefId,
method: DefId,
args: GenericArgsRef<'tcx>,
) -> String {
let assoc_item = tcx.associated_item(method);
let def_id = assoc_item.container_id(tcx);
match assoc_item.container {
rustc_ty::AssocItemContainer::Trait => get_path_to_callee(tcx, from, def_id),
rustc_ty::AssocItemContainer::Impl => {
let ty = tcx.type_of(def_id).instantiate_identity();
get_path_to_ty(tcx, from, ty, args)
},
}
}
fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
match ty.kind() {
rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
rustc_ty::Array(..)
| rustc_ty::Dynamic(..)
| rustc_ty::Never
| rustc_ty::RawPtr(_, _)
| rustc_ty::Ref(..)
| rustc_ty::Slice(_)
| rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)),
_ => ty.to_string(),
}
}
fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
if callee.is_local() {
let callee_path = tcx.def_path(callee);
let caller_path = tcx.def_path(from.to_def_id());
maybe_get_relative_path(&caller_path, &callee_path, 2)
} else {
tcx.def_path_str(callee)
}
}
fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
use itertools::EitherOrBoth::{Both, Left, Right};
let unique_parts = to
.data
.iter()
.zip_longest(from.data.iter())
.skip_while(|el| matches!(el, Both(l, r) if l == r))
.map(|el| match el {
Both(l, r) => Both(l.data, r.data),
Left(l) => Left(l.data),
Right(r) => Right(r.data),
});
let mut go_up_by = 0;
let mut path = Vec::new();
for el in unique_parts {
match el {
Both(l, r) => {
if let DefPathData::TypeNs(s) = l {
path.push(s.to_string());
}
if let DefPathData::TypeNs(_) = r {
go_up_by += 1;
}
},
Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()),
Right(DefPathData::TypeNs(_)) => go_up_by += 1,
_ => {},
}
}
if go_up_by > max_super {
once(String::from("crate"))
.chain(to.data.iter().filter_map(|el| {
if let DefPathData::TypeNs(sym) = el.data {
Some(sym.to_string())
} else {
None
}
}))
.join("::")
} else {
repeat(String::from("super")).take(go_up_by).chain(path).join("::")
}
}
pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
matches!(
cx.tcx.parent_hir_node(id),
Node::Stmt(..) | Node::Block(Block { stmts: [], .. })
)
}
pub fn is_block_like(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
)
}
pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
match expr.kind {
ExprKind::Binary(_, lhs, _) => contains_block(lhs, true),
_ if is_block_like(expr) => is_operand,
_ => false,
}
}
contains_block(expr, false)
}
pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
&& receiver.hir_id == expr.hir_id
{
return true;
}
false
}