1#![feature(box_patterns)]
2#![feature(macro_metavar_expr)]
3#![feature(rustc_private)]
4#![feature(unwrap_infallible)]
5#![recursion_limit = "512"]
6#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
7#![warn(
8 trivial_casts,
9 trivial_numeric_casts,
10 rust_2018_idioms,
11 unused_lifetimes,
12 unused_qualifications,
13 rustc::internal
14)]
15
16extern crate rustc_abi;
19extern crate rustc_ast;
20extern crate rustc_attr_parsing;
21extern crate rustc_const_eval;
22extern crate rustc_data_structures;
23#[expect(
24 unused_extern_crates,
25 reason = "The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate."
26)]
27extern crate rustc_driver;
28extern crate rustc_errors;
29extern crate rustc_hir;
30extern crate rustc_hir_analysis;
31extern crate rustc_hir_typeck;
32extern crate rustc_index;
33extern crate rustc_infer;
34extern crate rustc_lexer;
35extern crate rustc_lint;
36extern crate rustc_middle;
37extern crate rustc_mir_dataflow;
38extern crate rustc_session;
39extern crate rustc_span;
40extern crate rustc_trait_selection;
41
42pub mod ast_utils;
43#[deny(missing_docs)]
44pub mod attrs;
45mod check_proc_macro;
46pub mod comparisons;
47pub mod consts;
48pub mod diagnostics;
49pub mod eager_or_lazy;
50pub mod higher;
51mod hir_utils;
52pub mod macros;
53pub mod mir;
54pub mod msrvs;
55pub mod numeric_literal;
56pub mod paths;
57pub mod qualify_min_const_fn;
58pub mod res;
59pub mod source;
60pub mod str_utils;
61pub mod sugg;
62pub mod sym;
63pub mod ty;
64pub mod usage;
65pub mod visitors;
66
67pub use self::attrs::*;
68pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
69pub use self::hir_utils::{
70 HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, has_ambiguous_literal_in_expr, hash_expr,
71 hash_stmt, is_bool, over,
72};
73
74use core::mem;
75use core::ops::ControlFlow;
76use std::collections::hash_map::Entry;
77use std::iter::{once, repeat_n, zip};
78use std::sync::{Mutex, MutexGuard, OnceLock};
79
80use itertools::Itertools;
81use rustc_abi::Integer;
82use rustc_ast::ast::{self, LitKind, RangeLimits};
83use rustc_ast::{LitIntType, join_path_syms};
84use rustc_data_structures::fx::FxHashMap;
85use rustc_data_structures::indexmap;
86use rustc_data_structures::packed::Pu128;
87use rustc_data_structures::unhash::UnindexMap;
88use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
89use rustc_hir::attrs::CfgEntry;
90use rustc_hir::def::{DefKind, Res};
91use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
92use rustc_hir::definitions::{DefPath, DefPathData};
93use rustc_hir::hir_id::{HirIdMap, HirIdSet};
94use rustc_hir::intravisit::{Visitor, walk_expr};
95use rustc_hir::{
96 self as hir, AnonConst, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, CRATE_HIR_ID, Closure, ConstArg,
97 ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind,
98 FieldDef, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem,
99 LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path,
100 PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, Variant, def,
101 find_attr,
102};
103use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
104use rustc_lint::{LateContext, Level, Lint, LintContext};
105use rustc_middle::hir::nested_filter;
106use rustc_middle::hir::place::PlaceBase;
107use rustc_middle::lint::LevelAndSource;
108use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
109use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, DerefAdjustKind, PointerCoercion};
110use rustc_middle::ty::layout::IntegerExt;
111use rustc_middle::ty::{
112 self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
113 TypeFlags, TypeVisitableExt, TypeckResults, UintTy, UpvarCapture,
114};
115use rustc_span::hygiene::{ExpnKind, MacroKind};
116use rustc_span::source_map::SourceMap;
117use rustc_span::symbol::{Ident, Symbol, kw};
118use rustc_span::{InnerSpan, Span, SyntaxContext};
119use source::{SpanRangeExt, walk_span_to_context};
120use visitors::{Visitable, for_each_unconsumed_temporary};
121
122use crate::ast_utils::unordered_over;
123use crate::consts::{ConstEvalCtxt, Constant};
124use crate::higher::Range;
125use crate::msrvs::Msrv;
126use crate::res::{MaybeDef, MaybeQPath, MaybeResPath};
127use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
128use crate::visitors::for_each_expr_without_closures;
129
130pub const VEC_METHODS_SHADOWING_SLICE_METHODS: [Symbol; 3] = [sym::as_ptr, sym::is_empty, sym::len];
132
133#[macro_export]
134macro_rules! extract_msrv_attr {
135 () => {
136 fn check_attributes(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) {
137 let sess = rustc_lint::LintContext::sess(cx);
138 self.msrv.check_attributes(sess, attrs);
139 }
140
141 fn check_attributes_post(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) {
142 let sess = rustc_lint::LintContext::sess(cx);
143 self.msrv.check_attributes_post(sess, attrs);
144 }
145 };
146}
147
148pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
171 while let Some(init) = expr
172 .res_local_id()
173 .and_then(|id| find_binding_init(cx, id))
174 .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
175 {
176 expr = init;
177 }
178 expr
179}
180
181pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
190 if let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
191 && matches!(pat.kind, PatKind::Binding(BindingMode::NONE, ..))
192 && let Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id)
193 {
194 return local.init;
195 }
196 None
197}
198
199pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool {
203 for (_, node) in cx.tcx.hir_parent_iter(local) {
204 match node {
205 Node::Pat(..) | Node::PatField(..) => {},
206 Node::LetStmt(let_stmt) => return let_stmt.init.is_some(),
207 _ => return true,
208 }
209 }
210
211 false
212}
213
214pub fn is_in_const_context(cx: &LateContext<'_>) -> bool {
225 debug_assert!(cx.enclosing_body.is_some(), "`LateContext` has no enclosing body");
226 cx.enclosing_body.is_some_and(|id| {
227 cx.tcx
228 .hir_body_const_context(cx.tcx.hir_body_owner_def_id(id))
229 .is_some()
230 })
231}
232
233pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
240 use rustc_hir::ConstContext::{Const, ConstFn, Static};
241 let Some(ctx) = tcx.hir_body_const_context(tcx.hir_enclosing_body_owner(hir_id)) else {
242 return false;
243 };
244 match ctx {
245 ConstFn => false,
246 Static(_) | Const { inline: _ } => true,
247 }
248}
249
250pub fn is_enum_variant_ctor(
252 cx: &LateContext<'_>,
253 enum_item: Symbol,
254 variant_name: Symbol,
255 ctor_call_id: DefId,
256) -> bool {
257 let Some(enum_def_id) = cx.tcx.get_diagnostic_item(enum_item) else {
258 return false;
259 };
260
261 let variants = cx.tcx.adt_def(enum_def_id).variants().iter();
262 variants
263 .filter(|variant| variant.name == variant_name)
264 .filter_map(|variant| variant.ctor.as_ref())
265 .any(|(_, ctor_def_id)| *ctor_def_id == ctor_call_id)
266}
267
268pub fn is_diagnostic_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: Symbol) -> bool {
270 let did = match cx.tcx.def_kind(did) {
271 DefKind::Ctor(..) => cx.tcx.parent(did),
272 DefKind::Variant => match cx.tcx.opt_parent(did) {
274 Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
275 _ => did,
276 },
277 _ => did,
278 };
279
280 cx.tcx.is_diagnostic_item(item, did)
281}
282
283pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> bool {
285 let did = match cx.tcx.def_kind(did) {
286 DefKind::Ctor(..) => cx.tcx.parent(did),
287 DefKind::Variant => match cx.tcx.opt_parent(did) {
289 Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
290 _ => did,
291 },
292 _ => did,
293 };
294
295 cx.tcx.lang_items().get(item) == Some(did)
296}
297
298pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
300 expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone)
301}
302
303pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
305 if let ExprKind::Call(e, [arg]) = expr.kind
306 && e.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome)
307 {
308 Some(arg)
309 } else {
310 None
311 }
312}
313
314pub fn is_empty_block(expr: &Expr<'_>) -> bool {
316 matches!(
317 expr.kind,
318 ExprKind::Block(
319 Block {
320 stmts: [],
321 expr: None,
322 ..
323 },
324 _,
325 )
326 )
327}
328
329pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
331 matches!(
332 expr.kind,
333 ExprKind::Block(
334 Block {
335 stmts: [],
336 expr: None,
337 ..
338 },
339 _
340 ) | ExprKind::Tup([])
341 )
342}
343
344pub fn is_wild(pat: &Pat<'_>) -> bool {
346 matches!(pat.kind, PatKind::Wild)
347}
348
349pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> {
356 if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind
357 && cx
358 .qpath_res(qpath, pat.hir_id)
359 .ctor_parent(cx)
360 .is_lang_item(cx, OptionSome)
361 {
362 Some(inner)
363 } else {
364 None
365 }
366}
367
368pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
370 matches!(pat.kind,
371 PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. })
372 if cx.qpath_res(qpath, pat.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone))
373}
374
375pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
377 is_none_pattern(cx, arm.pat)
378 && matches!(
379 peel_blocks(arm.body).kind,
380 ExprKind::Path(qpath)
381 if cx.qpath_res(&qpath, arm.body.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone)
382 )
383}
384
385pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
387 match *qpath {
388 QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)),
389 QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath),
390 QPath::TypeRelative(..) => false,
391 }
392}
393
394pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
396 if let Node::Item(item) = cx.tcx.parent_hir_node(cx.tcx.local_def_id_to_hir_id(def_id))
397 && let ItemKind::Impl(imp) = item.kind
398 {
399 imp.of_trait.is_some()
400 } else {
401 false
402 }
403}
404
405pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
406 match *path {
407 QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
408 QPath::TypeRelative(_, seg) => seg,
409 }
410}
411
412pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
413 last_path_segment(qpath)
414 .args
415 .map_or(&[][..], |a| a.args)
416 .iter()
417 .filter_map(|a| match a {
418 GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
419 _ => None,
420 })
421}
422
423pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
428 match expr.kind {
429 ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
430 ExprKind::Path(QPath::Resolved(
431 _,
432 Path {
433 res: Res::Local(local), ..
434 },
435 )) => Some(*local),
436 _ => None,
437 }
438}
439
440pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, owner: OwnerId) -> Option<&'tcx TraitRef<'tcx>> {
456 if let Node::Item(item) = cx.tcx.hir_node(cx.tcx.hir_owner_parent(owner))
457 && let ItemKind::Impl(impl_) = &item.kind
458 && let Some(of_trait) = impl_.of_trait
459 {
460 return Some(&of_trait.trait_ref);
461 }
462 None
463}
464
465fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
473 let mut result = vec![];
474 let root = loop {
475 match e.kind {
476 ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => {
477 result.push(e);
478 e = ep;
479 },
480 _ => break e,
481 }
482 };
483 result.reverse();
484 (result, root)
485}
486
487pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> {
489 cx.typeck_results()
490 .expr_adjustments(e)
491 .iter()
492 .find_map(|a| match a.kind {
493 Adjust::Deref(DerefAdjustKind::Overloaded(d)) => Some(Some(d.mutbl)),
494 Adjust::Deref(DerefAdjustKind::Builtin) => None,
495 _ => Some(None),
496 })
497 .and_then(|x| x)
498}
499
500pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
503 let (s1, r1) = projection_stack(e1);
504 let (s2, r2) = projection_stack(e2);
505 if !eq_expr_value(cx, r1, r2) {
506 return true;
507 }
508 if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
509 return false;
510 }
511
512 for (x1, x2) in zip(&s1, &s2) {
513 if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
514 return false;
515 }
516
517 match (&x1.kind, &x2.kind) {
518 (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
519 if i1 != i2 {
520 return true;
521 }
522 },
523 (ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => {
524 if !eq_expr_value(cx, i1, i2) {
525 return false;
526 }
527 },
528 _ => return false,
529 }
530 }
531 false
532}
533
534fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
537 let std_types_symbols = &[
538 sym::Vec,
539 sym::VecDeque,
540 sym::LinkedList,
541 sym::HashMap,
542 sym::BTreeMap,
543 sym::HashSet,
544 sym::BTreeSet,
545 sym::BinaryHeap,
546 ];
547
548 if let QPath::TypeRelative(_, method) = path
549 && method.ident.name == sym::new
550 && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id)
551 && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def()
552 {
553 return Some(adt.did()) == cx.tcx.lang_items().string()
554 || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name));
555 }
556 false
557}
558
559pub fn is_default_equivalent_call(
561 cx: &LateContext<'_>,
562 repl_func: &Expr<'_>,
563 whole_call_expr: Option<&Expr<'_>>,
564) -> bool {
565 if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
566 && let Some(repl_def) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def(cx)
567 && (repl_def.assoc_fn_parent(cx).is_diag_item(cx, sym::Default)
568 || is_default_equivalent_ctor(cx, repl_def.1, repl_func_qpath))
569 {
570 return true;
571 }
572
573 let Some(e) = whole_call_expr else { return false };
576 let Some(default_fn_def_id) = cx.tcx.get_diagnostic_item(sym::default_fn) else {
577 return false;
578 };
579 let Some(ty) = cx.tcx.typeck(e.hir_id.owner.def_id).expr_ty_adjusted_opt(e) else {
580 return false;
581 };
582 let args = rustc_ty::GenericArgs::for_item(cx.tcx, default_fn_def_id, |param, _| {
583 if let rustc_ty::GenericParamDefKind::Lifetime = param.kind {
584 cx.tcx.lifetimes.re_erased.into()
585 } else if param.index == 0 && param.name == kw::SelfUpper {
586 ty.into()
587 } else {
588 param.to_error(cx.tcx)
589 }
590 });
591 let instance = rustc_ty::Instance::try_resolve(cx.tcx, cx.typing_env(), default_fn_def_id, args);
592
593 let Ok(Some(instance)) = instance else { return false };
594 if let rustc_ty::InstanceKind::Item(def) = instance.def
595 && !cx.tcx.is_mir_available(def)
596 {
597 return false;
598 }
599 let ExprKind::Path(ref repl_func_qpath) = repl_func.kind else {
600 return false;
601 };
602 let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() else {
603 return false;
604 };
605
606 let body = cx.tcx.instance_mir(instance.def);
612 for block_data in body.basic_blocks.iter() {
613 if block_data.statements.len() == 1
614 && let StatementKind::Assign(assign) = &block_data.statements[0].kind
615 && assign.0.local == RETURN_PLACE
616 && let Rvalue::Aggregate(kind, _places) = &assign.1
617 && let AggregateKind::Adt(did, variant_index, _, _, _) = **kind
618 && let def = cx.tcx.adt_def(did)
619 && let variant = &def.variant(variant_index)
620 && variant.fields.is_empty()
621 && let Some((_, did)) = variant.ctor
622 && did == repl_def_id
623 {
624 return true;
625 } else if block_data.statements.is_empty()
626 && let Some(term) = &block_data.terminator
627 {
628 match &term.kind {
629 TerminatorKind::Call {
630 func: Operand::Constant(c),
631 ..
632 } if let rustc_ty::FnDef(did, _args) = c.ty().kind()
633 && *did == repl_def_id =>
634 {
635 return true;
636 },
637 TerminatorKind::TailCall {
638 func: Operand::Constant(c),
639 ..
640 } if let rustc_ty::FnDef(did, _args) = c.ty().kind()
641 && *did == repl_def_id =>
642 {
643 return true;
644 },
645 _ => {},
646 }
647 }
648 }
649 false
650}
651
652pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
656 match &e.kind {
657 ExprKind::Lit(lit) => match lit.node {
658 LitKind::Bool(false) | LitKind::Int(Pu128(0), _) => true,
659 LitKind::Str(s, _) => s.is_empty(),
660 _ => false,
661 },
662 ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
663 ExprKind::Repeat(x, len) => {
664 if let ConstArgKind::Anon(anon_const) = len.kind
665 && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind
666 && let LitKind::Int(v, _) = const_lit.node
667 && v <= 32
668 && is_default_equivalent(cx, x)
669 {
670 true
671 } else {
672 false
673 }
674 },
675 ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func, Some(e)),
676 ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg),
677 ExprKind::Path(qpath) => cx
678 .qpath_res(qpath, e.hir_id)
679 .ctor_parent(cx)
680 .is_lang_item(cx, OptionNone),
681 ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
682 ExprKind::Block(Block { stmts: [], expr, .. }, _) => expr.is_some_and(|e| is_default_equivalent(cx, e)),
683 _ => false,
684 }
685}
686
687fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool {
688 if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind
689 && seg.ident.name == sym::from
690 {
691 match arg.kind {
692 ExprKind::Lit(hir::Lit {
693 node: LitKind::Str(sym, _),
694 ..
695 }) => return sym.is_empty() && ty.basic_res().is_lang_item(cx, LangItem::String),
696 ExprKind::Array([]) => return ty.basic_res().is_diag_item(cx, sym::Vec),
697 ExprKind::Repeat(_, len) => {
698 if let ConstArgKind::Anon(anon_const) = len.kind
699 && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind
700 && let LitKind::Int(v, _) = const_lit.node
701 {
702 return v == 0 && ty.basic_res().is_diag_item(cx, sym::Vec);
703 }
704 },
705 _ => (),
706 }
707 }
708 false
709}
710
711pub fn can_move_expr_to_closure_no_visit<'tcx>(
743 cx: &LateContext<'tcx>,
744 expr: &'tcx Expr<'_>,
745 loop_ids: &[HirId],
746 ignore_locals: &HirIdSet,
747) -> bool {
748 match expr.kind {
749 ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
750 | ExprKind::Continue(Destination { target_id: Ok(id), .. })
751 if loop_ids.contains(&id) =>
752 {
753 true
754 },
755 ExprKind::Break(..)
756 | ExprKind::Continue(_)
757 | ExprKind::Ret(_)
758 | ExprKind::Yield(..)
759 | ExprKind::InlineAsm(_) => false,
760 ExprKind::Field(
763 &Expr {
764 hir_id,
765 kind:
766 ExprKind::Path(QPath::Resolved(
767 _,
768 Path {
769 res: Res::Local(local_id),
770 ..
771 },
772 )),
773 ..
774 },
775 _,
776 ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
777 false
779 },
780 _ => true,
781 }
782}
783
784#[derive(Debug, Clone, Copy, PartialEq, Eq)]
786pub enum CaptureKind {
787 Value,
788 Use,
789 Ref(Mutability),
790}
791impl CaptureKind {
792 pub fn is_imm_ref(self) -> bool {
793 self == Self::Ref(Mutability::Not)
794 }
795}
796impl std::ops::BitOr for CaptureKind {
797 type Output = Self;
798 fn bitor(self, rhs: Self) -> Self::Output {
799 match (self, rhs) {
800 (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
801 (CaptureKind::Use, _) | (_, CaptureKind::Use) => CaptureKind::Use,
802 (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
803 | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
804 (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
805 }
806 }
807}
808impl std::ops::BitOrAssign for CaptureKind {
809 fn bitor_assign(&mut self, rhs: Self) {
810 *self = *self | rhs;
811 }
812}
813
814pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind {
820 fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
821 let mut capture = CaptureKind::Ref(Mutability::Not);
822 pat.each_binding_or_first(&mut |_, id, span, _| match cx
823 .typeck_results()
824 .extract_binding_mode(cx.sess(), id, span)
825 .0
826 {
827 ByRef::No if !is_copy(cx, cx.typeck_results().node_type(id)) => {
828 capture = CaptureKind::Value;
829 },
830 ByRef::Yes(_, Mutability::Mut) if capture != CaptureKind::Value => {
831 capture = CaptureKind::Ref(Mutability::Mut);
832 },
833 _ => (),
834 });
835 capture
836 }
837
838 debug_assert!(matches!(
839 e.kind,
840 ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
841 ));
842
843 let mut capture = CaptureKind::Value;
844 let mut capture_expr_ty = e;
845
846 for (parent, child_id) in hir_parent_with_src_iter(cx.tcx, e.hir_id) {
847 if let [
848 Adjustment {
849 kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
850 target,
851 },
852 ref adjust @ ..,
853 ] = *cx
854 .typeck_results()
855 .adjustments()
856 .get(child_id)
857 .map_or(&[][..], |x| &**x)
858 && let rustc_ty::RawPtr(_, mutability) | rustc_ty::Ref(_, _, mutability) =
859 *adjust.last().map_or(target, |a| a.target).kind()
860 {
861 return CaptureKind::Ref(mutability);
862 }
863
864 match parent {
865 Node::Expr(e) => match e.kind {
866 ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
867 ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
868 ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
869 return CaptureKind::Ref(Mutability::Mut);
870 },
871 ExprKind::Field(..) => {
872 if capture == CaptureKind::Value {
873 capture_expr_ty = e;
874 }
875 },
876 ExprKind::Let(let_expr) => {
877 let mutability = match pat_capture_kind(cx, let_expr.pat) {
878 CaptureKind::Value | CaptureKind::Use => Mutability::Not,
879 CaptureKind::Ref(m) => m,
880 };
881 return CaptureKind::Ref(mutability);
882 },
883 ExprKind::Match(_, arms, _) => {
884 let mut mutability = Mutability::Not;
885 for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
886 match capture {
887 CaptureKind::Value | CaptureKind::Use => break,
888 CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
889 CaptureKind::Ref(Mutability::Not) => (),
890 }
891 }
892 return CaptureKind::Ref(mutability);
893 },
894 _ => break,
895 },
896 Node::LetStmt(l) => match pat_capture_kind(cx, l.pat) {
897 CaptureKind::Value | CaptureKind::Use => break,
898 capture @ CaptureKind::Ref(_) => return capture,
899 },
900 _ => break,
901 }
902 }
903
904 if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
905 CaptureKind::Ref(Mutability::Not)
907 } else {
908 capture
909 }
910}
911
912pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
915 struct V<'cx, 'tcx> {
916 cx: &'cx LateContext<'tcx>,
917 loops: Vec<HirId>,
919 locals: HirIdSet,
921 allow_closure: bool,
923 captures: HirIdMap<CaptureKind>,
926 }
927 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
928 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
929 if !self.allow_closure {
930 return;
931 }
932
933 match e.kind {
934 ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
935 if !self.locals.contains(&l) {
936 let cap = capture_local_usage(self.cx, e);
937 self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
938 }
939 },
940 ExprKind::Closure(closure) => {
941 for capture in self.cx.typeck_results().closure_min_captures_flattened(closure.def_id) {
942 let local_id = match capture.place.base {
943 PlaceBase::Local(id) => id,
944 PlaceBase::Upvar(var) => var.var_path.hir_id,
945 _ => continue,
946 };
947 if !self.locals.contains(&local_id) {
948 let capture = match capture.info.capture_kind {
949 UpvarCapture::ByValue => CaptureKind::Value,
950 UpvarCapture::ByUse => CaptureKind::Use,
951 UpvarCapture::ByRef(kind) => match kind {
952 BorrowKind::Immutable => CaptureKind::Ref(Mutability::Not),
953 BorrowKind::UniqueImmutable | BorrowKind::Mutable => {
954 CaptureKind::Ref(Mutability::Mut)
955 },
956 },
957 };
958 self.captures
959 .entry(local_id)
960 .and_modify(|e| *e |= capture)
961 .or_insert(capture);
962 }
963 }
964 },
965 ExprKind::Loop(b, ..) => {
966 self.loops.push(e.hir_id);
967 self.visit_block(b);
968 self.loops.pop();
969 },
970 _ => {
971 self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
972 walk_expr(self, e);
973 },
974 }
975 }
976
977 fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
978 p.each_binding_or_first(&mut |_, id, _, _| {
979 self.locals.insert(id);
980 });
981 }
982 }
983
984 let mut v = V {
985 cx,
986 loops: Vec::new(),
987 locals: HirIdSet::default(),
988 allow_closure: true,
989 captures: HirIdMap::default(),
990 };
991 v.visit_expr(expr);
992 v.allow_closure.then_some(v.captures)
993}
994
995pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>;
997
998pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) {
1001 let mut method_names = Vec::with_capacity(max_depth);
1002 let mut arg_lists = Vec::with_capacity(max_depth);
1003 let mut spans = Vec::with_capacity(max_depth);
1004
1005 let mut current = expr;
1006 for _ in 0..max_depth {
1007 if let ExprKind::MethodCall(path, receiver, args, _) = ¤t.kind {
1008 if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
1009 break;
1010 }
1011 method_names.push(path.ident.name);
1012 arg_lists.push((*receiver, &**args));
1013 spans.push(path.ident.span);
1014 current = receiver;
1015 } else {
1016 break;
1017 }
1018 }
1019
1020 (method_names, arg_lists, spans)
1021}
1022
1023pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[Symbol]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> {
1030 let mut current = expr;
1031 let mut matched = Vec::with_capacity(methods.len());
1032 for method_name in methods.iter().rev() {
1033 if let ExprKind::MethodCall(path, receiver, args, _) = current.kind {
1035 if path.ident.name == *method_name {
1036 if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
1037 return None;
1038 }
1039 matched.push((receiver, args)); current = receiver; } else {
1042 return None;
1043 }
1044 } else {
1045 return None;
1046 }
1047 }
1048 matched.reverse();
1050 Some(matched)
1051}
1052
1053pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
1055 cx.tcx
1056 .entry_fn(())
1057 .is_some_and(|(entry_fn_def_id, _)| def_id == entry_fn_def_id)
1058}
1059
1060pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
1062 let parent = cx.tcx.hir_get_parent_item(e.hir_id);
1063 Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl()
1064}
1065
1066pub fn parent_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
1068 let parent_id = cx.tcx.hir_get_parent_item(expr.hir_id).def_id;
1069 match cx.tcx.hir_node_by_def_id(parent_id) {
1070 Node::Item(item) => item.kind.ident().map(|ident| ident.name),
1071 Node::TraitItem(TraitItem { ident, .. }) | Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name),
1072 _ => None,
1073 }
1074}
1075
1076pub struct ContainsName<'a, 'tcx> {
1077 pub cx: &'a LateContext<'tcx>,
1078 pub name: Symbol,
1079}
1080
1081impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> {
1082 type Result = ControlFlow<()>;
1083 type NestedFilter = nested_filter::OnlyBodies;
1084
1085 fn visit_name(&mut self, name: Symbol) -> Self::Result {
1086 if self.name == name {
1087 ControlFlow::Break(())
1088 } else {
1089 ControlFlow::Continue(())
1090 }
1091 }
1092
1093 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
1094 self.cx.tcx
1095 }
1096}
1097
1098pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
1100 let mut cn = ContainsName { cx, name };
1101 cn.visit_expr(expr).is_break()
1102}
1103
1104pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
1106 for_each_expr_without_closures(expr, |e| {
1107 if matches!(e.kind, ExprKind::Ret(..)) {
1108 ControlFlow::Break(())
1109 } else {
1110 ControlFlow::Continue(())
1111 }
1112 })
1113 .is_some()
1114}
1115
1116pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
1118 get_parent_expr_for_hir(cx, e.hir_id)
1119}
1120
1121pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
1124 match cx.tcx.parent_hir_node(hir_id) {
1125 Node::Expr(parent) => Some(parent),
1126 _ => None,
1127 }
1128}
1129
1130pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
1132 let enclosing_node = cx
1133 .tcx
1134 .hir_get_enclosing_scope(hir_id)
1135 .map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
1136 enclosing_node.and_then(|node| match node {
1137 Node::Block(block) => Some(block),
1138 Node::Item(&Item {
1139 kind: ItemKind::Fn { body: eid, .. },
1140 ..
1141 })
1142 | Node::ImplItem(&ImplItem {
1143 kind: ImplItemKind::Fn(_, eid),
1144 ..
1145 })
1146 | Node::TraitItem(&TraitItem {
1147 kind: TraitItemKind::Fn(_, TraitFn::Provided(eid)),
1148 ..
1149 }) => match cx.tcx.hir_body(eid).value.kind {
1150 ExprKind::Block(block, _) => Some(block),
1151 _ => None,
1152 },
1153 _ => None,
1154 })
1155}
1156
1157pub fn get_enclosing_closure<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Closure<'tcx>> {
1159 cx.tcx.hir_parent_iter(hir_id).find_map(|(_, node)| {
1160 if let Node::Expr(expr) = node
1161 && let ExprKind::Closure(closure) = expr.kind
1162 {
1163 Some(closure)
1164 } else {
1165 None
1166 }
1167 })
1168}
1169
1170pub fn is_upvar_in_closure(cx: &LateContext<'_>, closure: &Closure<'_>, local_id: HirId) -> bool {
1172 cx.typeck_results()
1173 .closure_min_captures
1174 .get(&closure.def_id)
1175 .is_some_and(|x| x.contains_key(&local_id))
1176}
1177
1178pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
1180 cx: &LateContext<'tcx>,
1181 expr: &Expr<'_>,
1182) -> Option<&'tcx Expr<'tcx>> {
1183 for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
1184 match node {
1185 Node::Expr(e) => match e.kind {
1186 ExprKind::Closure { .. }
1187 if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
1188 && subs.as_closure().kind() == ClosureKind::FnOnce => {},
1189
1190 ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
1192 _ => (),
1193 },
1194 Node::Stmt(_) | Node::Block(_) | Node::LetStmt(_) | Node::Arm(_) | Node::ExprField(_) => (),
1195 _ => break,
1196 }
1197 }
1198 None
1199}
1200
1201pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
1203 match tcx.hir_parent_iter(id).next() {
1204 Some((
1205 _,
1206 Node::Item(Item {
1207 kind: ItemKind::Impl(imp),
1208 ..
1209 }),
1210 )) => Some(imp),
1211 _ => None,
1212 }
1213}
1214
1215pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
1226 while let ExprKind::Block(
1227 Block {
1228 stmts: [],
1229 expr: Some(inner),
1230 rules: BlockCheckMode::DefaultBlock,
1231 ..
1232 },
1233 _,
1234 ) = expr.kind
1235 {
1236 expr = inner;
1237 }
1238 expr
1239}
1240
1241pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
1252 while let ExprKind::Block(
1253 Block {
1254 stmts: [],
1255 expr: Some(inner),
1256 rules: BlockCheckMode::DefaultBlock,
1257 ..
1258 }
1259 | Block {
1260 stmts:
1261 [
1262 Stmt {
1263 kind: StmtKind::Expr(inner) | StmtKind::Semi(inner),
1264 ..
1265 },
1266 ],
1267 expr: None,
1268 rules: BlockCheckMode::DefaultBlock,
1269 ..
1270 },
1271 _,
1272 ) = expr.kind
1273 {
1274 expr = inner;
1275 }
1276 expr
1277}
1278
1279pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1281 let mut iter = tcx.hir_parent_iter(expr.hir_id);
1282 match iter.next() {
1283 Some((
1284 _,
1285 Node::Expr(Expr {
1286 kind: ExprKind::If(_, _, Some(else_expr)),
1287 ..
1288 }),
1289 )) => else_expr.hir_id == expr.hir_id,
1290 _ => false,
1291 }
1292}
1293
1294pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1297 hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
1298 matches!(
1299 node,
1300 Node::LetStmt(LetStmt {
1301 init: Some(init),
1302 els: Some(els),
1303 ..
1304 })
1305 if init.hir_id == child_id || els.hir_id == child_id
1306 )
1307 })
1308}
1309
1310pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1312 hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
1313 matches!(
1314 node,
1315 Node::LetStmt(LetStmt { els: Some(els), .. })
1316 if els.hir_id == child_id
1317 )
1318 })
1319}
1320
1321pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
1336 let ty = cx.typeck_results().expr_ty(expr);
1337 if let Some(Range { start, end, limits, .. }) = Range::hir(cx, expr) {
1338 let start_is_none_or_min = start.is_none_or(|start| {
1339 if let rustc_ty::Adt(_, subst) = ty.kind()
1340 && let bnd_ty = subst.type_at(0)
1341 && let Some(start_const) = ConstEvalCtxt::new(cx).eval(start)
1342 {
1343 start_const.is_numeric_min(cx.tcx, bnd_ty)
1344 } else {
1345 false
1346 }
1347 });
1348 let end_is_none_or_max = end.is_none_or(|end| match limits {
1349 RangeLimits::Closed => {
1350 if let rustc_ty::Adt(_, subst) = ty.kind()
1351 && let bnd_ty = subst.type_at(0)
1352 && let Some(end_const) = ConstEvalCtxt::new(cx).eval(end)
1353 {
1354 end_const.is_numeric_max(cx.tcx, bnd_ty)
1355 } else {
1356 false
1357 }
1358 },
1359 RangeLimits::HalfOpen => {
1360 if let Some(container_path) = container_path
1361 && let ExprKind::MethodCall(name, self_arg, [], _) = end.kind
1362 && name.ident.name == sym::len
1363 && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
1364 {
1365 container_path.res == path.res
1366 } else {
1367 false
1368 }
1369 },
1370 });
1371 return start_is_none_or_min && end_is_none_or_max;
1372 }
1373 false
1374}
1375
1376pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
1379 if is_integer_literal(e, value) {
1380 return true;
1381 }
1382 let enclosing_body = cx.tcx.hir_enclosing_body_owner(e.hir_id);
1383 if let Some(Constant::Int(v)) =
1384 ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), cx.tcx.typeck(enclosing_body)).eval(e)
1385 {
1386 return value == v;
1387 }
1388 false
1389}
1390
1391pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
1393 if let ExprKind::Lit(spanned) = expr.kind
1395 && let LitKind::Int(v, _) = spanned.node
1396 {
1397 return v == value;
1398 }
1399 false
1400}
1401
1402pub fn is_integer_literal_untyped(expr: &Expr<'_>) -> bool {
1404 if let ExprKind::Lit(spanned) = expr.kind
1405 && let LitKind::Int(_, suffix) = spanned.node
1406 {
1407 return suffix == LitIntType::Unsuffixed;
1408 }
1409
1410 false
1411}
1412
1413pub fn is_float_literal(expr: &Expr<'_>, value: f64) -> bool {
1415 if let ExprKind::Lit(spanned) = expr.kind
1416 && let LitKind::Float(v, _) = spanned.node
1417 {
1418 v.as_str().parse() == Ok(value)
1419 } else {
1420 false
1421 }
1422}
1423
1424pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
1432 cx.typeck_results().adjustments().get(e.hir_id).is_some()
1433}
1434
1435#[must_use]
1439pub fn is_expn_of(mut span: Span, name: Symbol) -> Option<Span> {
1440 loop {
1441 if span.from_expansion() {
1442 let data = span.ctxt().outer_expn_data();
1443 let new_span = data.call_site;
1444
1445 if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind
1446 && mac_name == name
1447 {
1448 return Some(new_span);
1449 }
1450
1451 span = new_span;
1452 } else {
1453 return None;
1454 }
1455 }
1456}
1457
1458#[must_use]
1469pub fn is_direct_expn_of(span: Span, name: Symbol) -> Option<Span> {
1470 if span.from_expansion() {
1471 let data = span.ctxt().outer_expn_data();
1472 let new_span = data.call_site;
1473
1474 if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind
1475 && mac_name == name
1476 {
1477 return Some(new_span);
1478 }
1479 }
1480
1481 None
1482}
1483
1484pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> {
1486 let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().output();
1487 cx.tcx.instantiate_bound_regions_with_erased(ret_ty)
1488}
1489
1490pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> {
1492 let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().input(nth);
1493 cx.tcx.instantiate_bound_regions_with_erased(arg)
1494}
1495
1496pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1498 if let ExprKind::Call(fun, _) = expr.kind
1499 && let ExprKind::Path(ref qp) = fun.kind
1500 {
1501 let res = cx.qpath_res(qp, fun.hir_id);
1502 return match res {
1503 Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
1504 Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
1505 _ => false,
1506 };
1507 }
1508 false
1509}
1510
1511pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
1514 fn is_qpath_refutable(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
1515 !matches!(
1516 cx.qpath_res(qpath, id),
1517 Res::Def(DefKind::Struct, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), _)
1518 )
1519 }
1520
1521 fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
1522 i.into_iter().any(|pat| is_refutable(cx, pat))
1523 }
1524
1525 match pat.kind {
1526 PatKind::Missing => unreachable!(),
1527 PatKind::Wild | PatKind::Never => false, PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)),
1529 PatKind::Box(pat) | PatKind::Ref(pat, _, _) => is_refutable(cx, pat),
1530 PatKind::Expr(PatExpr {
1531 kind: PatExprKind::Path(qpath),
1532 hir_id,
1533 ..
1534 }) => is_qpath_refutable(cx, qpath, *hir_id),
1535 PatKind::Or(pats) => {
1536 are_refutable(cx, pats)
1538 },
1539 PatKind::Tuple(pats, _) => are_refutable(cx, pats),
1540 PatKind::Struct(ref qpath, fields, _) => {
1541 is_qpath_refutable(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat))
1542 },
1543 PatKind::TupleStruct(ref qpath, pats, _) => {
1544 is_qpath_refutable(cx, qpath, pat.hir_id) || are_refutable(cx, pats)
1545 },
1546 PatKind::Slice(head, middle, tail) => {
1547 match &cx.typeck_results().node_type(pat.hir_id).kind() {
1548 rustc_ty::Slice(..) => {
1549 !head.is_empty() || middle.is_none() || !tail.is_empty()
1551 },
1552 rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
1553 _ => {
1554 true
1556 },
1557 }
1558 },
1559 PatKind::Expr(..) | PatKind::Range(..) | PatKind::Err(_) | PatKind::Deref(_) | PatKind::Guard(..) => true,
1560 }
1561}
1562
1563pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
1566 if let PatKind::Or(pats) = pat.kind {
1567 pats.iter().for_each(f);
1568 } else {
1569 f(pat);
1570 }
1571}
1572
1573pub fn is_self(slf: &Param<'_>) -> bool {
1574 if let PatKind::Binding(.., name, _) = slf.pat.kind {
1575 name.name == kw::SelfLower
1576 } else {
1577 false
1578 }
1579}
1580
1581pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
1582 if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind
1583 && let Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } = path.res
1584 {
1585 return true;
1586 }
1587 false
1588}
1589
1590pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
1591 (0..decl.inputs.len()).map(move |i| &body.params[i])
1592}
1593
1594pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
1597 fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
1598 if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind
1599 && ddpos.as_opt_usize().is_none()
1600 && cx
1601 .qpath_res(path, arm.pat.hir_id)
1602 .ctor_parent(cx)
1603 .is_lang_item(cx, ResultOk)
1604 && let PatKind::Binding(_, hir_id, _, None) = pat[0].kind
1605 && arm.body.res_local_id() == Some(hir_id)
1606 {
1607 return true;
1608 }
1609 false
1610 }
1611
1612 fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
1613 if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
1614 cx.qpath_res(path, arm.pat.hir_id)
1615 .ctor_parent(cx)
1616 .is_lang_item(cx, ResultErr)
1617 } else {
1618 false
1619 }
1620 }
1621
1622 if let ExprKind::Match(_, arms, ref source) = expr.kind {
1623 if let MatchSource::TryDesugar(_) = *source {
1625 return Some(expr);
1626 }
1627
1628 if arms.len() == 2
1629 && arms[0].guard.is_none()
1630 && arms[1].guard.is_none()
1631 && ((is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])))
1632 {
1633 return Some(expr);
1634 }
1635 }
1636
1637 None
1638}
1639
1640pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool {
1650 let mut suppress_lint = false;
1651
1652 for id in ids {
1653 let LevelAndSource { level, lint_id, .. } = cx.tcx.lint_level_at_node(lint, id);
1654 if let Some(expectation) = lint_id {
1655 cx.fulfill_expectation(expectation);
1656 }
1657
1658 match level {
1659 Level::Allow | Level::Expect => suppress_lint = true,
1660 Level::Warn | Level::ForceWarn | Level::Deny | Level::Forbid => {},
1661 }
1662 }
1663
1664 suppress_lint
1665}
1666
1667pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
1675 cx.tcx.lint_level_at_node(lint, id).level == Level::Allow
1676}
1677
1678pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
1679 while let PatKind::Ref(subpat, _, _) = pat.kind {
1680 pat = subpat;
1681 }
1682 pat
1683}
1684
1685pub fn int_bits(tcx: TyCtxt<'_>, ity: IntTy) -> u64 {
1686 Integer::from_int_ty(&tcx, ity).size().bits()
1687}
1688
1689#[expect(clippy::cast_possible_wrap)]
1690pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: IntTy) -> i128 {
1692 let amt = 128 - int_bits(tcx, ity);
1693 ((u as i128) << amt) >> amt
1694}
1695
1696#[expect(clippy::cast_sign_loss)]
1697pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: IntTy) -> u128 {
1699 let amt = 128 - int_bits(tcx, ity);
1700 ((u as u128) << amt) >> amt
1701}
1702
1703pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: UintTy) -> u128 {
1705 let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
1706 let amt = 128 - bits;
1707 (u << amt) >> amt
1708}
1709
1710pub fn has_attr(attrs: &[hir::Attribute], symbol: Symbol) -> bool {
1711 attrs.iter().any(|attr| attr.has_name(symbol))
1712}
1713
1714pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
1715 find_attr!(cx.tcx, hir_id, Repr { .. })
1716}
1717
1718pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
1719 let mut prev_enclosing_node = None;
1720 let mut enclosing_node = node;
1721 while Some(enclosing_node) != prev_enclosing_node {
1722 if has_attr(tcx.hir_attrs(enclosing_node), symbol) {
1723 return true;
1724 }
1725 prev_enclosing_node = Some(enclosing_node);
1726 enclosing_node = tcx.hir_get_parent_item(enclosing_node).into();
1727 }
1728
1729 false
1730}
1731
1732pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
1735 tcx.hir_parent_owner_iter(id)
1736 .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
1737 .any(|(id, _)| {
1738 find_attr!(
1739 tcx.hir_attrs(tcx.local_def_id_to_hir_id(id.def_id)),
1740 AutomaticallyDerived(..)
1741 )
1742 })
1743}
1744
1745pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: Symbol) -> bool {
1747 cx.tcx.crate_name(did.krate) == sym::libc && cx.tcx.def_path_str(did).ends_with(name.as_str())
1750}
1751
1752pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
1757 let mut conds = Vec::new();
1758 let mut blocks: Vec<&Block<'_>> = Vec::new();
1759
1760 while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
1761 conds.push(cond);
1762 if let ExprKind::Block(block, _) = then.kind {
1763 blocks.push(block);
1764 } else {
1765 panic!("ExprKind::If node is not an ExprKind::Block");
1766 }
1767
1768 if let Some(else_expr) = r#else {
1769 expr = else_expr;
1770 } else {
1771 break;
1772 }
1773 }
1774
1775 if !blocks.is_empty()
1777 && let ExprKind::Block(block, _) = expr.kind
1778 {
1779 blocks.push(block);
1780 }
1781
1782 (conds, blocks)
1783}
1784
1785pub fn get_async_closure_expr<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
1787 if let ExprKind::Closure(&Closure {
1788 body,
1789 kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
1790 ..
1791 }) = expr.kind
1792 && let ExprKind::Block(
1793 Block {
1794 expr:
1795 Some(Expr {
1796 kind: ExprKind::DropTemps(inner_expr),
1797 ..
1798 }),
1799 ..
1800 },
1801 _,
1802 ) = tcx.hir_body(body).value.kind
1803 {
1804 Some(inner_expr)
1805 } else {
1806 None
1807 }
1808}
1809
1810pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
1812 get_async_closure_expr(tcx, body.value)
1813}
1814
1815pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1817 let did = match expr.kind {
1818 ExprKind::Call(path, _) => {
1819 if let ExprKind::Path(ref qpath) = path.kind
1820 && let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
1821 {
1822 Some(did)
1823 } else {
1824 None
1825 }
1826 },
1827 ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
1828 _ => None,
1829 };
1830
1831 did.is_some_and(|did| find_attr!(cx.tcx, did, MustUse { .. }))
1832}
1833
1834fn is_body_identity_function<'hir>(cx: &LateContext<'_>, func: &Body<'hir>) -> bool {
1848 let [param] = func.params else {
1849 return false;
1850 };
1851
1852 let mut param_pat = param.pat;
1853
1854 let mut advance_param_pat_over_stmts = |stmts: &[Stmt<'hir>]| {
1861 for stmt in stmts {
1862 if let StmtKind::Let(local) = stmt.kind
1863 && let Some(init) = local.init
1864 && is_expr_identity_of_pat(cx, param_pat, init, true)
1865 {
1866 param_pat = local.pat;
1867 } else {
1868 return false;
1869 }
1870 }
1871
1872 true
1873 };
1874
1875 let mut expr = func.value;
1876 loop {
1877 match expr.kind {
1878 ExprKind::Block(
1879 &Block {
1880 stmts: [],
1881 expr: Some(e),
1882 ..
1883 },
1884 _,
1885 )
1886 | ExprKind::Ret(Some(e)) => expr = e,
1887 ExprKind::Block(
1888 &Block {
1889 stmts: [stmt],
1890 expr: None,
1891 ..
1892 },
1893 _,
1894 ) => {
1895 if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind
1896 && let ExprKind::Ret(Some(ret_val)) = e.kind
1897 {
1898 expr = ret_val;
1899 } else {
1900 return false;
1901 }
1902 },
1903 ExprKind::Block(
1904 &Block {
1905 stmts, expr: Some(e), ..
1906 },
1907 _,
1908 ) => {
1909 if !advance_param_pat_over_stmts(stmts) {
1910 return false;
1911 }
1912
1913 expr = e;
1914 },
1915 ExprKind::Block(&Block { stmts, expr: None, .. }, _) => {
1916 if let Some((last_stmt, stmts)) = stmts.split_last()
1917 && advance_param_pat_over_stmts(stmts)
1918 && let StmtKind::Semi(e) | StmtKind::Expr(e) = last_stmt.kind
1919 && let ExprKind::Ret(Some(ret_val)) = e.kind
1920 {
1921 expr = ret_val;
1922 } else {
1923 return false;
1924 }
1925 },
1926 _ => return is_expr_identity_of_pat(cx, param_pat, expr, true),
1927 }
1928 }
1929}
1930
1931pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>, by_hir: bool) -> bool {
1941 if cx
1942 .typeck_results()
1943 .pat_binding_modes()
1944 .get(pat.hir_id)
1945 .is_some_and(|mode| matches!(mode.0, ByRef::Yes(..)))
1946 {
1947 return false;
1951 }
1952
1953 let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir);
1955
1956 match (pat.kind, expr.kind) {
1957 (PatKind::Binding(_, id, _, _), _) if by_hir => {
1958 expr.res_local_id() == Some(id) && cx.typeck_results().expr_adjustments(expr).is_empty()
1959 },
1960 (PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => {
1961 matches!(path.segments, [ segment] if segment.ident.name == ident.name)
1962 },
1963 (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
1964 if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
1965 {
1966 over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1967 },
1968 (PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
1969 zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1970 },
1971 (PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields))
1972 if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() =>
1973 {
1974 if let ExprKind::Path(ident) = &ident.kind
1976 && qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1977 && over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir))
1979 {
1980 true
1981 } else {
1982 false
1983 }
1984 },
1985 (PatKind::Struct(pat_ident, field_pats, None), ExprKind::Struct(ident, fields, hir::StructTailExpr::None))
1986 if field_pats.len() == fields.len() =>
1987 {
1988 qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1990 && unordered_over(field_pats, fields, |field_pat, field| {
1992 field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
1993 })
1994 },
1995 _ => false,
1996 }
1997}
1998
1999pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2004 match expr.kind {
2005 ExprKind::Closure(&Closure { body, fn_decl, .. })
2006 if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer(()))) =>
2007 {
2008 is_body_identity_function(cx, cx.tcx.hir_body(body))
2009 },
2010 ExprKind::Path(QPath::Resolved(_, path))
2011 if path.segments.iter().all(|seg| seg.infer_args)
2012 && let Some(did) = path.res.opt_def_id() =>
2013 {
2014 cx.tcx.is_diagnostic_item(sym::convert_identity, did)
2015 },
2016 _ => false,
2017 }
2018}
2019
2020pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2029 match expr.kind {
2030 ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir_body(body)),
2031 _ => expr.basic_res().is_diag_item(cx, sym::convert_identity),
2032 }
2033}
2034
2035pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
2038 for (node, child_id) in hir_parent_with_src_iter(tcx, expr.hir_id) {
2039 match node {
2040 Node::Block(_) => {},
2041 Node::Arm(arm) if arm.body.hir_id == child_id => {},
2042 Node::Expr(expr) => match expr.kind {
2043 ExprKind::Block(..) | ExprKind::DropTemps(_) => {},
2044 ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => {},
2045 ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => return None,
2046 _ => return Some((Node::Expr(expr), child_id)),
2047 },
2048 node => return Some((node, child_id)),
2049 }
2050 }
2051 None
2052}
2053
2054pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
2056 !matches!(
2057 get_expr_use_or_unification_node(tcx, expr),
2058 None | Some((
2059 Node::Stmt(Stmt {
2060 kind: StmtKind::Expr(_)
2061 | StmtKind::Semi(_)
2062 | StmtKind::Let(LetStmt {
2063 pat: Pat {
2064 kind: PatKind::Wild,
2065 ..
2066 },
2067 ..
2068 }),
2069 ..
2070 }),
2071 _
2072 ))
2073 )
2074}
2075
2076pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
2078 matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..))
2079}
2080
2081pub fn is_expr_temporary_value(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2085 !expr.is_place_expr(|base| {
2086 cx.typeck_results()
2087 .adjustments()
2088 .get(base.hir_id)
2089 .is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
2090 })
2091}
2092
2093pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
2094 if is_no_core_crate(cx) {
2095 None
2096 } else if is_no_std_crate(cx) {
2097 Some("core")
2098 } else {
2099 Some("std")
2100 }
2101}
2102
2103pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
2104 find_attr!(cx.tcx, crate, NoStd(..))
2105}
2106
2107pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
2108 find_attr!(cx.tcx, crate, NoCore(..))
2109}
2110
2111pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
2121 if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) {
2122 matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }))
2123 } else {
2124 false
2125 }
2126}
2127
2128pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
2138 use rustc_trait_selection::traits;
2139 let predicates = cx
2140 .tcx
2141 .predicates_of(did)
2142 .predicates
2143 .iter()
2144 .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
2145 traits::impossible_predicates(cx.tcx, traits::elaborate(cx.tcx, predicates).collect::<Vec<_>>())
2146}
2147
2148pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
2150 fn_def_id_with_node_args(cx, expr).map(|(did, _)| did)
2151}
2152
2153pub fn fn_def_id_with_node_args<'tcx>(
2156 cx: &LateContext<'tcx>,
2157 expr: &Expr<'_>,
2158) -> Option<(DefId, GenericArgsRef<'tcx>)> {
2159 let typeck = cx.typeck_results();
2160 match &expr.kind {
2161 ExprKind::MethodCall(..) => Some((
2162 typeck.type_dependent_def_id(expr.hir_id)?,
2163 typeck.node_args(expr.hir_id),
2164 )),
2165 ExprKind::Call(
2166 Expr {
2167 kind: ExprKind::Path(qpath),
2168 hir_id: path_hir_id,
2169 ..
2170 },
2171 ..,
2172 ) => {
2173 if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
2176 typeck.qpath_res(qpath, *path_hir_id)
2177 {
2178 Some((id, typeck.node_args(*path_hir_id)))
2179 } else {
2180 None
2181 }
2182 },
2183 _ => None,
2184 }
2185}
2186
2187pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
2192 let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
2193 let expr_kind = expr_type.kind();
2194 let is_primitive = match expr_kind {
2195 rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type),
2196 rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
2197 if let rustc_ty::Slice(element_type) = inner_ty.kind() {
2198 is_recursively_primitive_type(*element_type)
2199 } else {
2200 unreachable!()
2201 }
2202 },
2203 _ => false,
2204 };
2205
2206 if is_primitive {
2207 match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
2210 rustc_ty::Slice(..) => return Some("slice".into()),
2211 rustc_ty::Array(..) => return Some("array".into()),
2212 rustc_ty::Tuple(..) => return Some("tuple".into()),
2213 _ => {
2214 let refs_peeled = expr_type.peel_refs();
2217 return Some(refs_peeled.walk().last().unwrap().to_string());
2218 },
2219 }
2220 }
2221 None
2222}
2223
2224pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<Vec<&T>>
2232where
2233 Hash: FnMut(&T) -> u64,
2234 Eq: FnMut(&T, &T) -> bool,
2235{
2236 match exprs {
2237 [a, b] if eq(a, b) => return vec![vec![a, b]],
2238 _ if exprs.len() <= 2 => return vec![],
2239 _ => {},
2240 }
2241
2242 let mut buckets: UnindexMap<u64, Vec<Vec<&T>>> = UnindexMap::default();
2243
2244 for expr in exprs {
2245 match buckets.entry(hash(expr)) {
2246 indexmap::map::Entry::Occupied(mut o) => {
2247 let bucket = o.get_mut();
2248 match bucket.iter_mut().find(|group| eq(expr, group[0])) {
2249 Some(group) => group.push(expr),
2250 None => bucket.push(vec![expr]),
2251 }
2252 },
2253 indexmap::map::Entry::Vacant(v) => {
2254 v.insert(vec![vec![expr]]);
2255 },
2256 }
2257 }
2258
2259 buckets
2260 .into_values()
2261 .flatten()
2262 .filter(|group| group.len() > 1)
2263 .collect()
2264}
2265
2266pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
2269 fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
2270 if let PatKind::Ref(pat, _, _) = pat.kind {
2271 peel(pat, count + 1)
2272 } else {
2273 (pat, count)
2274 }
2275 }
2276 peel(pat, 0)
2277}
2278
2279pub fn peel_hir_expr_while<'tcx>(
2281 mut expr: &'tcx Expr<'tcx>,
2282 mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
2283) -> &'tcx Expr<'tcx> {
2284 while let Some(e) = f(expr) {
2285 expr = e;
2286 }
2287 expr
2288}
2289
2290pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
2293 let mut remaining = count;
2294 let e = peel_hir_expr_while(expr, |e| match e.kind {
2295 ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
2296 remaining -= 1;
2297 Some(e)
2298 },
2299 _ => None,
2300 });
2301 (e, count - remaining)
2302}
2303
2304pub fn peel_hir_expr_unary<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
2307 let mut count: usize = 0;
2308 let mut curr_expr = expr;
2309 while let ExprKind::Unary(_, local_expr) = curr_expr.kind {
2310 count = count.wrapping_add(1);
2311 curr_expr = local_expr;
2312 }
2313 (curr_expr, count)
2314}
2315
2316pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
2319 let mut count = 0;
2320 let e = peel_hir_expr_while(expr, |e| match e.kind {
2321 ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
2322 count += 1;
2323 Some(e)
2324 },
2325 _ => None,
2326 });
2327 (e, count)
2328}
2329
2330pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) {
2333 let mut count = 0;
2334 loop {
2335 match &ty.kind {
2336 TyKind::Ref(_, ref_ty) => {
2337 ty = ref_ty.ty;
2338 count += 1;
2339 },
2340 _ => break (ty, count),
2341 }
2342 }
2343}
2344
2345pub fn peel_hir_ty_refs_and_ptrs<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
2347 match &ty.kind {
2348 TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty) => peel_hir_ty_refs_and_ptrs(mut_ty.ty),
2349 _ => ty,
2350 }
2351}
2352
2353pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
2356 loop {
2357 match expr.kind {
2358 ExprKind::AddrOf(_, _, e) => expr = e,
2359 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
2360 _ => break,
2361 }
2362 }
2363 expr
2364}
2365
2366pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> {
2369 let mut operators = Vec::new();
2370 peel_hir_expr_while(expr, |expr| match expr.kind {
2371 ExprKind::AddrOf(_, _, e) => {
2372 operators.push(expr);
2373 Some(e)
2374 },
2375 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => {
2376 operators.push(expr);
2377 Some(e)
2378 },
2379 _ => None,
2380 });
2381 operators
2382}
2383
2384pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
2385 if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
2386 && let Res::Def(_, def_id) = path.res
2387 {
2388 return find_attr!(cx.tcx, def_id, CfgTrace(..) | CfgAttrTrace);
2389 }
2390 false
2391}
2392
2393static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalModDefId, Vec<Symbol>>>> = OnceLock::new();
2394
2395fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalModDefId, f: impl FnOnce(&[Symbol]) -> bool) -> bool {
2398 let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
2399 let mut map: MutexGuard<'_, FxHashMap<LocalModDefId, Vec<Symbol>>> = cache.lock().unwrap();
2400 let value = map.entry(module);
2401 match value {
2402 Entry::Occupied(entry) => f(entry.get()),
2403 Entry::Vacant(entry) => {
2404 let mut names = Vec::new();
2405 for id in tcx.hir_module_free_items(module) {
2406 if matches!(tcx.def_kind(id.owner_id), DefKind::Const { .. })
2407 && let item = tcx.hir_item(id)
2408 && let ItemKind::Const(ident, _generics, ty, _body) = item.kind
2409 && let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
2410 && let Res::Def(DefKind::Struct, _) = path.res
2412 && find_attr!(tcx, item.hir_id(), RustcTestMarker(..))
2413 {
2414 names.push(ident.name);
2415 }
2416 }
2417 names.sort_unstable();
2418 f(entry.insert(names))
2419 },
2420 }
2421}
2422
2423pub fn is_in_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool {
2427 with_test_item_names(tcx, tcx.parent_module(id), |names| {
2428 let node = tcx.hir_node(id);
2429 once((id, node))
2430 .chain(tcx.hir_parent_iter(id))
2431 .any(|(_id, node)| {
2434 if let Node::Item(item) = node
2435 && let ItemKind::Fn { ident, .. } = item.kind
2436 {
2437 return names.binary_search(&ident.name).is_ok();
2440 }
2441 false
2442 })
2443 })
2444}
2445
2446pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool {
2453 let id = tcx.local_def_id_to_hir_id(fn_def_id);
2454 if let Node::Item(item) = tcx.hir_node(id)
2455 && let ItemKind::Fn { ident, .. } = item.kind
2456 {
2457 with_test_item_names(tcx, tcx.parent_module(id), |names| {
2458 names.binary_search(&ident.name).is_ok()
2459 })
2460 } else {
2461 false
2462 }
2463}
2464
2465pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
2470 if let Some(cfgs) = find_attr!(tcx, id, CfgTrace(cfgs) => cfgs)
2471 && cfgs
2472 .iter()
2473 .any(|(cfg, _)| matches!(cfg, CfgEntry::NameValue { name: sym::test, .. }))
2474 {
2475 true
2476 } else {
2477 false
2478 }
2479}
2480
2481pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
2483 tcx.hir_parent_id_iter(id).any(|parent_id| is_cfg_test(tcx, parent_id))
2484}
2485
2486pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
2488 is_in_test_function(tcx, hir_id) || is_in_cfg_test(tcx, hir_id)
2489}
2490
2491pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
2493 find_attr!(tcx, def_id, CfgTrace(..))
2494 || find_attr!(
2495 tcx.hir_parent_id_iter(tcx.local_def_id_to_hir_id(def_id))
2496 .flat_map(|parent_id| tcx.hir_attrs(parent_id)),
2497 CfgTrace(..)
2498 )
2499}
2500
2501#[derive(Clone, Copy)]
2503pub enum DefinedTy<'tcx> {
2504 Hir(&'tcx hir::Ty<'tcx>),
2506 Mir {
2514 def_site_def_id: Option<DefId>,
2515 ty: Binder<'tcx, Ty<'tcx>>,
2516 },
2517}
2518
2519pub struct ExprUseSite<'tcx> {
2521 pub node: Node<'tcx>,
2523 pub child_id: HirId,
2525 pub adjustments: &'tcx [Adjustment<'tcx>],
2527 pub is_ty_unified: bool,
2529 pub moved_before_use: bool,
2531 pub same_ctxt: bool,
2533}
2534impl<'tcx> ExprUseSite<'tcx> {
2535 pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> {
2536 match self.node {
2537 Node::LetStmt(l) => ExprUseNode::LetStmt(l),
2538 Node::ExprField(field) => ExprUseNode::Field(field),
2539
2540 Node::Item(&Item {
2541 kind: ItemKind::Static(..) | ItemKind::Const(..),
2542 owner_id,
2543 ..
2544 })
2545 | Node::TraitItem(&TraitItem {
2546 kind: TraitItemKind::Const(..),
2547 owner_id,
2548 ..
2549 })
2550 | Node::ImplItem(&ImplItem {
2551 kind: ImplItemKind::Const(..),
2552 owner_id,
2553 ..
2554 }) => ExprUseNode::ConstStatic(owner_id),
2555
2556 Node::Item(&Item {
2557 kind: ItemKind::Fn { .. },
2558 owner_id,
2559 ..
2560 })
2561 | Node::TraitItem(&TraitItem {
2562 kind: TraitItemKind::Fn(..),
2563 owner_id,
2564 ..
2565 })
2566 | Node::ImplItem(&ImplItem {
2567 kind: ImplItemKind::Fn(..),
2568 owner_id,
2569 ..
2570 }) => ExprUseNode::Return(owner_id),
2571
2572 Node::Expr(use_expr) => match use_expr.kind {
2573 ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
2574 def_id: cx.tcx.hir_body_owner_def_id(cx.enclosing_body.unwrap()),
2575 }),
2576
2577 ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
2578 ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) {
2579 Some(i) => ExprUseNode::FnArg(func, i),
2580 None => ExprUseNode::Callee,
2581 },
2582 ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
2583 use_expr.hir_id,
2584 name.args,
2585 args.iter()
2586 .position(|arg| arg.hir_id == self.child_id)
2587 .map_or(0, |i| i + 1),
2588 ),
2589 ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name),
2590 ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl),
2591 _ => ExprUseNode::Other,
2592 },
2593 _ => ExprUseNode::Other,
2594 }
2595 }
2596}
2597
2598pub enum ExprUseNode<'tcx> {
2600 LetStmt(&'tcx LetStmt<'tcx>),
2602 ConstStatic(OwnerId),
2604 Return(OwnerId),
2606 Field(&'tcx ExprField<'tcx>),
2608 FnArg(&'tcx Expr<'tcx>, usize),
2610 MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize),
2612 Callee,
2614 FieldAccess(Ident),
2616 AddrOf(ast::BorrowKind, Mutability),
2618 Other,
2619}
2620impl<'tcx> ExprUseNode<'tcx> {
2621 pub fn is_return(&self) -> bool {
2623 matches!(self, Self::Return(_))
2624 }
2625
2626 pub fn is_recv(&self) -> bool {
2628 matches!(self, Self::MethodArg(_, _, 0))
2629 }
2630
2631 pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
2633 match *self {
2634 Self::LetStmt(LetStmt { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)),
2635 Self::ConstStatic(id) => Some(DefinedTy::Mir {
2636 def_site_def_id: Some(id.def_id.to_def_id()),
2637 ty: Binder::dummy(cx.tcx.type_of(id).instantiate_identity()),
2638 }),
2639 Self::Return(id) => {
2640 if let Node::Expr(Expr {
2641 kind: ExprKind::Closure(c),
2642 ..
2643 }) = cx.tcx.hir_node_by_def_id(id.def_id)
2644 {
2645 match c.fn_decl.output {
2646 FnRetTy::DefaultReturn(_) => None,
2647 FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)),
2648 }
2649 } else {
2650 let ty = cx.tcx.fn_sig(id).instantiate_identity().output();
2651 Some(DefinedTy::Mir {
2652 def_site_def_id: Some(id.def_id.to_def_id()),
2653 ty,
2654 })
2655 }
2656 },
2657 Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) {
2658 Some(Expr {
2659 hir_id,
2660 kind: ExprKind::Struct(path, ..),
2661 ..
2662 }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id))
2663 .and_then(|(adt, variant)| {
2664 variant
2665 .fields
2666 .iter()
2667 .find(|f| f.name == field.ident.name)
2668 .map(|f| (adt, f))
2669 })
2670 .map(|(adt, field_def)| DefinedTy::Mir {
2671 def_site_def_id: Some(adt.did()),
2672 ty: Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity()),
2673 }),
2674 _ => None,
2675 },
2676 Self::FnArg(callee, i) => {
2677 let sig = expr_sig(cx, callee)?;
2678 let (hir_ty, ty) = sig.input_with_hir(i)?;
2679 Some(match hir_ty {
2680 Some(hir_ty) => DefinedTy::Hir(hir_ty),
2681 None => DefinedTy::Mir {
2682 def_site_def_id: sig.predicates_id(),
2683 ty,
2684 },
2685 })
2686 },
2687 Self::MethodArg(id, _, i) => {
2688 let id = cx.typeck_results().type_dependent_def_id(id)?;
2689 let sig = cx.tcx.fn_sig(id).skip_binder();
2690 Some(DefinedTy::Mir {
2691 def_site_def_id: Some(id),
2692 ty: sig.input(i),
2693 })
2694 },
2695 Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None,
2696 }
2697 }
2698}
2699
2700struct ReplacingFilterMap<I, F>(I, F);
2701impl<I, F, U> Iterator for ReplacingFilterMap<I, F>
2702where
2703 I: Iterator,
2704 F: FnMut(&mut I, I::Item) -> Option<U>,
2705{
2706 type Item = U;
2707 fn next(&mut self) -> Option<U> {
2708 while let Some(x) = self.0.next() {
2709 if let Some(x) = (self.1)(&mut self.0, x) {
2710 return Some(x);
2711 }
2712 }
2713 None
2714 }
2715}
2716
2717#[expect(clippy::too_many_lines)]
2720pub fn expr_use_sites<'tcx>(
2721 tcx: TyCtxt<'tcx>,
2722 typeck: &'tcx TypeckResults<'tcx>,
2723 mut ctxt: SyntaxContext,
2724 e: &'tcx Expr<'tcx>,
2725) -> impl Iterator<Item = ExprUseSite<'tcx>> {
2726 let mut adjustments: &[_] = typeck.expr_adjustments(e);
2727 let mut is_ty_unified = false;
2728 let mut moved_before_use = false;
2729 let mut same_ctxt = true;
2730 ReplacingFilterMap(
2731 hir_parent_with_src_iter(tcx, e.hir_id),
2732 move |iter: &mut _, (parent, child_id)| {
2733 let parent_ctxt;
2734 let mut parent_adjustments: &[_] = &[];
2735 match parent {
2736 Node::Expr(parent_expr) => {
2737 parent_ctxt = parent_expr.span.ctxt();
2738 same_ctxt &= parent_ctxt == ctxt;
2739 parent_adjustments = typeck.expr_adjustments(parent_expr);
2740 match parent_expr.kind {
2741 ExprKind::Match(scrutinee, arms, _) if scrutinee.hir_id != child_id => {
2742 is_ty_unified |= arms.len() != 1;
2743 moved_before_use = true;
2744 if adjustments.is_empty() {
2745 adjustments = parent_adjustments;
2746 }
2747 return None;
2748 },
2749 ExprKind::If(cond, _, else_) if cond.hir_id != child_id => {
2750 is_ty_unified |= else_.is_some();
2751 moved_before_use = true;
2752 if adjustments.is_empty() {
2753 adjustments = parent_adjustments;
2754 }
2755 return None;
2756 },
2757 ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
2758 is_ty_unified = true;
2759 moved_before_use = true;
2760 *iter = hir_parent_with_src_iter(tcx, id);
2761 if adjustments.is_empty() {
2762 adjustments = parent_adjustments;
2763 }
2764 return None;
2765 },
2766 ExprKind::Block(b, _) => {
2767 is_ty_unified |= b.targeted_by_break;
2768 moved_before_use = true;
2769 if adjustments.is_empty() {
2770 adjustments = parent_adjustments;
2771 }
2772 return None;
2773 },
2774 ExprKind::DropTemps(_) | ExprKind::Type(..) => {
2775 if adjustments.is_empty() {
2776 adjustments = parent_adjustments;
2777 }
2778 return None;
2779 },
2780 _ => {},
2781 }
2782 },
2783 Node::Arm(arm) => {
2784 parent_ctxt = arm.span.ctxt();
2785 same_ctxt &= parent_ctxt == ctxt;
2786 if arm.body.hir_id == child_id {
2787 return None;
2788 }
2789 },
2790 Node::Block(b) => {
2791 same_ctxt &= b.span.ctxt() == ctxt;
2792 return None;
2793 },
2794 Node::ConstBlock(_) => parent_ctxt = ctxt,
2795 Node::ExprField(&ExprField { span, .. }) => {
2796 parent_ctxt = span.ctxt();
2797 same_ctxt &= parent_ctxt == ctxt;
2798 },
2799 Node::AnonConst(&AnonConst { span, .. })
2800 | Node::ConstArg(&ConstArg { span, .. })
2801 | Node::Field(&FieldDef { span, .. })
2802 | Node::ImplItem(&ImplItem { span, .. })
2803 | Node::Item(&Item { span, .. })
2804 | Node::LetStmt(&LetStmt { span, .. })
2805 | Node::Stmt(&Stmt { span, .. })
2806 | Node::TraitItem(&TraitItem { span, .. })
2807 | Node::Variant(&Variant { span, .. }) => {
2808 parent_ctxt = span.ctxt();
2809 same_ctxt &= parent_ctxt == ctxt;
2810 *iter = hir_parent_with_src_iter(tcx, CRATE_HIR_ID);
2811 },
2812 Node::AssocItemConstraint(_)
2813 | Node::ConstArgExprField(_)
2814 | Node::Crate(_)
2815 | Node::Ctor(_)
2816 | Node::Err(_)
2817 | Node::ForeignItem(_)
2818 | Node::GenericParam(_)
2819 | Node::Infer(_)
2820 | Node::Lifetime(_)
2821 | Node::OpaqueTy(_)
2822 | Node::Param(_)
2823 | Node::Pat(_)
2824 | Node::PatExpr(_)
2825 | Node::PatField(_)
2826 | Node::PathSegment(_)
2827 | Node::PreciseCapturingNonLifetimeArg(_)
2828 | Node::Synthetic
2829 | Node::TraitRef(_)
2830 | Node::Ty(_)
2831 | Node::TyPat(_)
2832 | Node::WherePredicate(_) => {
2833 debug_assert!(false, "found {parent:?} which is after the final use node");
2836 return None;
2837 },
2838 }
2839
2840 ctxt = parent_ctxt;
2841 Some(ExprUseSite {
2842 node: parent,
2843 child_id,
2844 adjustments: mem::replace(&mut adjustments, parent_adjustments),
2845 is_ty_unified: mem::replace(&mut is_ty_unified, false),
2846 moved_before_use: mem::replace(&mut moved_before_use, false),
2847 same_ctxt: mem::replace(&mut same_ctxt, true),
2848 })
2849 },
2850 )
2851}
2852
2853pub fn get_expr_use_site<'tcx>(
2854 tcx: TyCtxt<'tcx>,
2855 typeck: &'tcx TypeckResults<'tcx>,
2856 ctxt: SyntaxContext,
2857 e: &'tcx Expr<'tcx>,
2858) -> ExprUseSite<'tcx> {
2859 expr_use_sites(tcx, typeck, ctxt, e).next().unwrap_or_else(|| {
2862 debug_assert!(false, "failed to find a use site for expr {e:?}");
2863 ExprUseSite {
2864 node: Node::Synthetic, child_id: CRATE_HIR_ID,
2866 adjustments: &[],
2867 is_ty_unified: false,
2868 moved_before_use: false,
2869 same_ctxt: false,
2870 }
2871 })
2872}
2873
2874pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str, InnerSpan)> {
2876 let mut pos = 0;
2877 tokenize(s, FrontmatterAllowed::No).map(move |t| {
2878 let end = pos + t.len;
2879 let range = pos as usize..end as usize;
2880 let inner = InnerSpan::new(range.start, range.end);
2881 pos = end;
2882 (t.kind, s.get(range).unwrap_or_default(), inner)
2883 })
2884}
2885
2886pub fn span_contains_comment(cx: &impl source::HasSession, span: Span) -> bool {
2889 span.check_source_text(cx, |snippet| {
2890 tokenize(snippet, FrontmatterAllowed::No).any(|token| {
2891 matches!(
2892 token.kind,
2893 TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
2894 )
2895 })
2896 })
2897}
2898
2899pub fn span_contains_non_whitespace(cx: &impl source::HasSession, span: Span, skip_comments: bool) -> bool {
2904 span.check_source_text(cx, |snippet| {
2905 tokenize_with_text(snippet).any(|(token, _, _)| match token {
2906 TokenKind::Whitespace => false,
2907 TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments,
2908 _ => true,
2909 })
2910 })
2911}
2912pub fn span_extract_comment(cx: &impl source::HasSession, span: Span) -> String {
2916 span_extract_comments(cx, span).join("\n")
2917}
2918
2919pub fn span_extract_comments(cx: &impl source::HasSession, span: Span) -> Vec<String> {
2923 span.with_source_text(cx, |snippet| {
2924 tokenize_with_text(snippet)
2925 .filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
2926 .map(|(_, s, _)| s.to_string())
2927 .collect::<Vec<_>>()
2928 })
2929 .unwrap_or_default()
2930}
2931
2932pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
2933 sm.span_take_while(span, |&ch| ch == ' ' || ch == ';')
2934}
2935
2936pub fn pat_and_expr_can_be_question_mark<'a, 'hir>(
2961 cx: &LateContext<'_>,
2962 pat: &'a Pat<'hir>,
2963 else_body: &Expr<'_>,
2964) -> Option<&'a Pat<'hir>> {
2965 if let Some([inner_pat]) = as_some_pattern(cx, pat)
2966 && !is_refutable(cx, inner_pat)
2967 && let else_body = peel_blocks(else_body)
2968 && let ExprKind::Ret(Some(ret_val)) = else_body.kind
2969 && let ExprKind::Path(ret_path) = ret_val.kind
2970 && cx
2971 .qpath_res(&ret_path, ret_val.hir_id)
2972 .ctor_parent(cx)
2973 .is_lang_item(cx, OptionNone)
2974 {
2975 Some(inner_pat)
2976 } else {
2977 None
2978 }
2979}
2980
2981macro_rules! op_utils {
2982 ($($name:ident $assign:ident)*) => {
2983 pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
2985
2986 pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
2988
2989 pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
2991 match kind {
2992 $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
2993 _ => None,
2994 }
2995 }
2996 };
2997}
2998
2999op_utils! {
3000 Add AddAssign
3001 Sub SubAssign
3002 Mul MulAssign
3003 Div DivAssign
3004 Rem RemAssign
3005 BitXor BitXorAssign
3006 BitAnd BitAndAssign
3007 BitOr BitOrAssign
3008 Shl ShlAssign
3009 Shr ShrAssign
3010}
3011
3012pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: impl Visitable<'tcx>) -> bool {
3015 match *pat {
3016 PatKind::Wild => true,
3017 PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
3018 !visitors::is_local_used(cx, body, id)
3019 },
3020 _ => false,
3021 }
3022}
3023
3024#[derive(Clone, Copy)]
3025pub enum RequiresSemi {
3026 Yes,
3027 No,
3028}
3029impl RequiresSemi {
3030 pub fn requires_semi(self) -> bool {
3031 matches!(self, Self::Yes)
3032 }
3033}
3034
3035#[expect(clippy::too_many_lines)]
3038pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
3039 struct BreakTarget {
3040 id: HirId,
3041 unused: bool,
3042 }
3043
3044 struct V<'cx, 'tcx> {
3045 cx: &'cx LateContext<'tcx>,
3046 break_targets: Vec<BreakTarget>,
3047 break_targets_for_result_ty: u32,
3048 in_final_expr: bool,
3049 requires_semi: bool,
3050 is_never: bool,
3051 }
3052
3053 impl V<'_, '_> {
3054 fn push_break_target(&mut self, id: HirId) {
3055 self.break_targets.push(BreakTarget { id, unused: true });
3056 self.break_targets_for_result_ty += u32::from(self.in_final_expr);
3057 }
3058 }
3059
3060 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
3061 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
3062 if self.is_never && self.break_targets.is_empty() {
3079 if self.in_final_expr && !self.requires_semi {
3080 match e.kind {
3083 ExprKind::DropTemps(e) => self.visit_expr(e),
3084 ExprKind::If(_, then, Some(else_)) => {
3085 self.visit_expr(then);
3086 self.visit_expr(else_);
3087 },
3088 ExprKind::Match(_, arms, _) => {
3089 for arm in arms {
3090 self.visit_expr(arm.body);
3091 }
3092 },
3093 ExprKind::Loop(b, ..) => {
3094 self.push_break_target(e.hir_id);
3095 self.in_final_expr = false;
3096 self.visit_block(b);
3097 self.break_targets.pop();
3098 },
3099 ExprKind::Block(b, _) => {
3100 if b.targeted_by_break {
3101 self.push_break_target(b.hir_id);
3102 self.visit_block(b);
3103 self.break_targets.pop();
3104 } else {
3105 self.visit_block(b);
3106 }
3107 },
3108 _ => {
3109 self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
3110 },
3111 }
3112 }
3113 return;
3114 }
3115 match e.kind {
3116 ExprKind::DropTemps(e) => self.visit_expr(e),
3117 ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true,
3118 ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
3119 self.in_final_expr = false;
3120 self.visit_expr(e);
3121 self.is_never = true;
3122 },
3123 ExprKind::Break(dest, e) => {
3124 if let Some(e) = e {
3125 self.in_final_expr = false;
3126 self.visit_expr(e);
3127 }
3128 if let Ok(id) = dest.target_id
3129 && let Some((i, target)) = self
3130 .break_targets
3131 .iter_mut()
3132 .enumerate()
3133 .find(|(_, target)| target.id == id)
3134 {
3135 target.unused &= self.is_never;
3136 if i < self.break_targets_for_result_ty as usize {
3137 self.requires_semi = true;
3138 }
3139 }
3140 self.is_never = true;
3141 },
3142 ExprKind::If(cond, then, else_) => {
3143 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3144 self.visit_expr(cond);
3145 self.in_final_expr = in_final_expr;
3146
3147 if self.is_never {
3148 self.visit_expr(then);
3149 if let Some(else_) = else_ {
3150 self.visit_expr(else_);
3151 }
3152 } else {
3153 self.visit_expr(then);
3154 let is_never = mem::replace(&mut self.is_never, false);
3155 if let Some(else_) = else_ {
3156 self.visit_expr(else_);
3157 self.is_never &= is_never;
3158 }
3159 }
3160 },
3161 ExprKind::Match(scrutinee, arms, _) => {
3162 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3163 self.visit_expr(scrutinee);
3164 self.in_final_expr = in_final_expr;
3165
3166 if self.is_never {
3167 for arm in arms {
3168 self.visit_arm(arm);
3169 }
3170 } else {
3171 let mut is_never = true;
3172 for arm in arms {
3173 self.is_never = false;
3174 if let Some(guard) = arm.guard {
3175 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3176 self.visit_expr(guard);
3177 self.in_final_expr = in_final_expr;
3178 self.is_never = false;
3180 }
3181 self.visit_expr(arm.body);
3182 is_never &= self.is_never;
3183 }
3184 self.is_never = is_never;
3185 }
3186 },
3187 ExprKind::Loop(b, _, _, _) => {
3188 self.push_break_target(e.hir_id);
3189 self.in_final_expr = false;
3190 self.visit_block(b);
3191 self.is_never = self.break_targets.pop().unwrap().unused;
3192 },
3193 ExprKind::Block(b, _) => {
3194 if b.targeted_by_break {
3195 self.push_break_target(b.hir_id);
3196 self.visit_block(b);
3197 self.is_never &= self.break_targets.pop().unwrap().unused;
3198 } else {
3199 self.visit_block(b);
3200 }
3201 },
3202 _ => {
3203 self.in_final_expr = false;
3204 walk_expr(self, e);
3205 self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
3206 },
3207 }
3208 }
3209
3210 fn visit_block(&mut self, b: &'tcx Block<'_>) {
3211 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3212 for s in b.stmts {
3213 self.visit_stmt(s);
3214 }
3215 self.in_final_expr = in_final_expr;
3216 if let Some(e) = b.expr {
3217 self.visit_expr(e);
3218 }
3219 }
3220
3221 fn visit_local(&mut self, l: &'tcx LetStmt<'_>) {
3222 if let Some(e) = l.init {
3223 self.visit_expr(e);
3224 }
3225 if let Some(else_) = l.els {
3226 let is_never = self.is_never;
3227 self.visit_block(else_);
3228 self.is_never = is_never;
3229 }
3230 }
3231
3232 fn visit_arm(&mut self, arm: &Arm<'tcx>) {
3233 if let Some(guard) = arm.guard {
3234 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3235 self.visit_expr(guard);
3236 self.in_final_expr = in_final_expr;
3237 }
3238 self.visit_expr(arm.body);
3239 }
3240 }
3241
3242 if cx.typeck_results().expr_ty(e).is_never() {
3243 Some(RequiresSemi::No)
3244 } else if let ExprKind::Block(b, _) = e.kind
3245 && !b.targeted_by_break
3246 && b.expr.is_none()
3247 {
3248 None
3250 } else {
3251 let mut v = V {
3252 cx,
3253 break_targets: Vec::new(),
3254 break_targets_for_result_ty: 0,
3255 in_final_expr: true,
3256 requires_semi: false,
3257 is_never: false,
3258 };
3259 v.visit_expr(e);
3260 v.is_never
3261 .then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
3262 RequiresSemi::Yes
3263 } else {
3264 RequiresSemi::No
3265 })
3266 }
3267}
3268
3269pub fn get_path_from_caller_to_method_type<'tcx>(
3275 tcx: TyCtxt<'tcx>,
3276 from: LocalDefId,
3277 method: DefId,
3278 args: GenericArgsRef<'tcx>,
3279) -> String {
3280 let assoc_item = tcx.associated_item(method);
3281 let def_id = assoc_item.container_id(tcx);
3282 match assoc_item.container {
3283 rustc_ty::AssocContainer::Trait => get_path_to_callee(tcx, from, def_id),
3284 rustc_ty::AssocContainer::InherentImpl | rustc_ty::AssocContainer::TraitImpl(_) => {
3285 let ty = tcx.type_of(def_id).instantiate_identity();
3286 get_path_to_ty(tcx, from, ty, args)
3287 },
3288 }
3289}
3290
3291fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
3292 match ty.kind() {
3293 rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
3294 rustc_ty::Array(..)
3296 | rustc_ty::Dynamic(..)
3297 | rustc_ty::Never
3298 | rustc_ty::RawPtr(_, _)
3299 | rustc_ty::Ref(..)
3300 | rustc_ty::Slice(_)
3301 | rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)),
3302 _ => ty.to_string(),
3303 }
3304}
3305
3306fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
3308 if callee.is_local() {
3310 let callee_path = tcx.def_path(callee);
3311 let caller_path = tcx.def_path(from.to_def_id());
3312 maybe_get_relative_path(&caller_path, &callee_path, 2)
3313 } else {
3314 tcx.def_path_str(callee)
3315 }
3316}
3317
3318fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
3331 use itertools::EitherOrBoth::{Both, Left, Right};
3332
3333 let unique_parts = to
3335 .data
3336 .iter()
3337 .zip_longest(from.data.iter())
3338 .skip_while(|el| matches!(el, Both(l, r) if l == r))
3339 .map(|el| match el {
3340 Both(l, r) => Both(l.data, r.data),
3341 Left(l) => Left(l.data),
3342 Right(r) => Right(r.data),
3343 });
3344
3345 let mut go_up_by = 0;
3347 let mut path = Vec::new();
3348 for el in unique_parts {
3349 match el {
3350 Both(l, r) => {
3351 if let DefPathData::TypeNs(sym) = l {
3361 path.push(sym);
3362 }
3363 if let DefPathData::TypeNs(_) = r {
3364 go_up_by += 1;
3365 }
3366 },
3367 Left(DefPathData::TypeNs(sym)) => path.push(sym),
3372 Right(DefPathData::TypeNs(_)) => go_up_by += 1,
3377 _ => {},
3378 }
3379 }
3380
3381 if go_up_by > max_super {
3382 join_path_syms(once(kw::Crate).chain(to.data.iter().filter_map(|el| {
3384 if let DefPathData::TypeNs(sym) = el.data {
3385 Some(sym)
3386 } else {
3387 None
3388 }
3389 })))
3390 } else if go_up_by == 0 && path.is_empty() {
3391 String::from("Self")
3392 } else {
3393 join_path_syms(repeat_n(kw::Super, go_up_by).chain(path))
3394 }
3395}
3396
3397pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
3400 matches!(
3401 cx.tcx.parent_hir_node(id),
3402 Node::Stmt(..) | Node::Block(Block { stmts: [], .. })
3403 )
3404}
3405
3406pub fn is_block_like(expr: &Expr<'_>) -> bool {
3409 matches!(
3410 expr.kind,
3411 ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
3412 )
3413}
3414
3415pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
3417 fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
3418 match expr.kind {
3419 ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => contains_block(lhs, true),
3420 _ if is_block_like(expr) => is_operand,
3421 _ => false,
3422 }
3423 }
3424
3425 contains_block(expr, false)
3426}
3427
3428pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3430 if let Some(parent_expr) = get_parent_expr(cx, expr)
3431 && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
3432 && receiver.hir_id == expr.hir_id
3433 {
3434 return true;
3435 }
3436 false
3437}
3438
3439pub fn leaks_droppable_temporary_with_limited_lifetime<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3442 for_each_unconsumed_temporary(cx, expr, |temporary_ty| {
3443 if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env())
3444 && temporary_ty
3445 .walk()
3446 .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static()))
3447 {
3448 ControlFlow::Break(())
3449 } else {
3450 ControlFlow::Continue(())
3451 }
3452 })
3453 .is_break()
3454}
3455
3456pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
3467 let expr_ty_is_adjusted = cx
3468 .typeck_results()
3469 .expr_adjustments(expr)
3470 .iter()
3471 .any(|adj| !matches!(adj.kind, Adjust::NeverToAny));
3473 if expr_ty_is_adjusted {
3474 return true;
3475 }
3476
3477 match expr.kind {
3480 ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => {
3481 let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity();
3482
3483 if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) {
3484 return false;
3485 }
3486
3487 let self_arg_count = usize::from(matches!(expr.kind, ExprKind::MethodCall(..)));
3488 let mut args_with_ty_param = {
3489 fn_sig
3490 .inputs()
3491 .skip_binder()
3492 .iter()
3493 .skip(self_arg_count)
3494 .zip(args)
3495 .filter_map(|(arg_ty, arg)| {
3496 if arg_ty.has_type_flags(TypeFlags::HAS_TY_PARAM) {
3497 Some(arg)
3498 } else {
3499 None
3500 }
3501 })
3502 };
3503 args_with_ty_param.any(|arg| expr_requires_coercion(cx, arg))
3504 },
3505 ExprKind::Struct(qpath, _, _) => {
3507 let res = cx.typeck_results().qpath_res(qpath, expr.hir_id);
3508 if let Some((_, v_def)) = adt_and_variant_of_res(cx, res) {
3509 let rustc_ty::Adt(_, generic_args) = cx.typeck_results().expr_ty_adjusted(expr).kind() else {
3510 return true;
3512 };
3513 v_def
3514 .fields
3515 .iter()
3516 .any(|field| field.ty(cx.tcx, generic_args).has_type_flags(TypeFlags::HAS_TY_PARAM))
3517 } else {
3518 false
3519 }
3520 },
3521 ExprKind::Block(
3523 &Block {
3524 expr: Some(ret_expr), ..
3525 },
3526 _,
3527 )
3528 | ExprKind::Ret(Some(ret_expr)) => expr_requires_coercion(cx, ret_expr),
3529
3530 ExprKind::Array(elems) | ExprKind::Tup(elems) => elems.iter().any(|elem| expr_requires_coercion(cx, elem)),
3532 ExprKind::Repeat(rep_elem, _) => expr_requires_coercion(cx, rep_elem),
3534 ExprKind::If(_, then, maybe_else) => {
3536 expr_requires_coercion(cx, then) || maybe_else.is_some_and(|e| expr_requires_coercion(cx, e))
3537 },
3538 ExprKind::Match(_, arms, _) => arms
3539 .iter()
3540 .map(|arm| arm.body)
3541 .any(|body| expr_requires_coercion(cx, body)),
3542 _ => false,
3543 }
3544}
3545
3546pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3549 if let Some(hir_id) = expr.res_local_id()
3550 && let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
3551 {
3552 matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
3553 } else if let ExprKind::Path(p) = &expr.kind
3554 && let Some(mutability) = cx
3555 .qpath_res(p, expr.hir_id)
3556 .opt_def_id()
3557 .and_then(|id| cx.tcx.static_mutability(id))
3558 {
3559 mutability == Mutability::Mut
3560 } else if let ExprKind::Field(parent, _) = expr.kind {
3561 is_mutable(cx, parent)
3562 } else {
3563 true
3564 }
3565}
3566
3567pub fn peel_hir_ty_options<'tcx>(cx: &LateContext<'tcx>, mut hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
3570 let Some(option_def_id) = cx.tcx.get_diagnostic_item(sym::Option) else {
3571 return hir_ty;
3572 };
3573 while let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind
3574 && let Some(segment) = path.segments.last()
3575 && segment.ident.name == sym::Option
3576 && let Res::Def(DefKind::Enum, def_id) = segment.res
3577 && def_id == option_def_id
3578 && let [GenericArg::Type(arg_ty)] = segment.args().args
3579 {
3580 hir_ty = arg_ty.as_unambig_ty();
3581 }
3582 hir_ty
3583}
3584
3585pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
3588 if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind
3589 && let ExprKind::Call(_, [into_future_arg]) = match_value.kind
3590 && let ctxt = expr.span.ctxt()
3591 && for_each_expr_without_closures(into_future_arg, |e| {
3592 walk_span_to_context(e.span, ctxt).map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))
3593 })
3594 .is_none()
3595 {
3596 Some(into_future_arg)
3597 } else {
3598 None
3599 }
3600}
3601
3602pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3604 if let ExprKind::Call(fn_expr, []) = &expr.kind
3605 && let ExprKind::Path(qpath) = &fn_expr.kind
3606 && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
3607 {
3608 cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
3609 } else {
3610 false
3611 }
3612}
3613
3614pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3631 let enclosing_body_owner = cx
3632 .tcx
3633 .local_def_id_to_hir_id(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
3634 let mut prev_id = expr.hir_id;
3635 let mut skip_until_id = None;
3636 for (hir_id, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
3637 if hir_id == enclosing_body_owner {
3638 return true;
3639 }
3640 if let Some(id) = skip_until_id {
3641 prev_id = hir_id;
3642 if id == hir_id {
3643 skip_until_id = None;
3644 }
3645 continue;
3646 }
3647 match node {
3648 Node::Block(Block { expr, .. }) if expr.is_some_and(|expr| expr.hir_id == prev_id) => {},
3649 Node::Arm(arm) if arm.body.hir_id == prev_id => {},
3650 Node::Expr(expr) => match expr.kind {
3651 ExprKind::Ret(_) => return true,
3652 ExprKind::If(_, then, opt_else)
3653 if then.hir_id == prev_id || opt_else.is_some_and(|els| els.hir_id == prev_id) => {},
3654 ExprKind::Match(_, arms, _) if arms.iter().any(|arm| arm.hir_id == prev_id) => {},
3655 ExprKind::Block(block, _) if block.hir_id == prev_id => {},
3656 ExprKind::Break(
3657 Destination {
3658 target_id: Ok(target_id),
3659 ..
3660 },
3661 _,
3662 ) => skip_until_id = Some(target_id),
3663 _ => break,
3664 },
3665 _ => break,
3666 }
3667 prev_id = hir_id;
3668 }
3669
3670 false
3673}
3674
3675pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3678 cx.typeck_results().expr_adjustments(expr).iter().any(|adj| {
3679 matches!(
3680 adj.kind,
3681 Adjust::Deref(DerefAdjustKind::Overloaded(_))
3682 | Adjust::Pointer(PointerCoercion::Unsize)
3683 | Adjust::NeverToAny
3684 )
3685 })
3686}
3687
3688pub fn is_expr_async_block(expr: &Expr<'_>) -> bool {
3690 matches!(
3691 expr.kind,
3692 ExprKind::Closure(Closure {
3693 kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(
3694 CoroutineDesugaring::Async,
3695 CoroutineSource::Block
3696 )),
3697 ..
3698 })
3699 )
3700}
3701
3702pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool {
3704 cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS)
3705}
3706
3707#[inline]
3710pub fn hir_parent_with_src_iter(tcx: TyCtxt<'_>, mut id: HirId) -> impl Iterator<Item = (Node<'_>, HirId)> {
3711 tcx.hir_parent_id_iter(id)
3712 .map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent)))
3713}