1#![cfg_attr(bootstrap, feature(unwrap_infallible))]
2#![feature(deref_patterns)]
3#![feature(macro_metavar_expr)]
4#![feature(rustc_private)]
5#![recursion_limit = "512"]
6#![expect(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
7#![warn(
8 rust_2018_idioms,
9 trivial_casts,
10 trivial_numeric_casts,
11 unused_lifetimes,
12 unused_qualifications,
13 rustc::internal
14)]
15
16extern crate rustc_abi;
19extern crate rustc_ast;
20extern crate rustc_attr_ir;
21extern crate rustc_attr_parsing;
22extern crate rustc_const_eval;
23extern crate rustc_data_structures;
24#[expect(
25 unused_extern_crates,
26 reason = "The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate."
27)]
28extern crate rustc_driver;
29extern crate rustc_errors;
30extern crate rustc_hir;
31extern crate rustc_hir_analysis;
32extern crate rustc_hir_typeck;
33extern crate rustc_index;
34extern crate rustc_infer;
35extern crate rustc_lexer;
36extern crate rustc_lint;
37extern crate rustc_middle;
38extern crate rustc_mir_dataflow;
39extern crate rustc_session;
40extern crate rustc_span;
41extern crate rustc_trait_selection;
42
43pub mod ast_utils;
44#[deny(missing_docs)]
45pub mod attrs;
46mod check_proc_macro;
47pub mod comparisons;
48pub mod consts;
49pub mod diagnostics;
50pub mod eager_or_lazy;
51pub mod higher;
52mod hir_utils;
53pub mod macros;
54pub mod mir;
55pub mod msrvs;
56pub mod numeric_literal;
57pub mod paths;
58pub mod qualify_min_const_fn;
59pub mod res;
60pub mod source;
61pub mod str_utils;
62pub mod sugg;
63pub mod sym;
64pub mod ty;
65pub mod usage;
66pub mod visitors;
67
68pub use self::attrs::*;
69pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
70pub use self::hir_utils::{
71 HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, has_ambiguous_literal_in_expr, hash_expr,
72 hash_stmt, is_bool, over,
73};
74
75use core::mem;
76use core::ops::ControlFlow;
77use std::collections::hash_map::Entry;
78use std::iter::{once, repeat_n, zip};
79use std::sync::{Mutex, OnceLock};
80
81use itertools::Itertools as _;
82use rustc_abi::Integer;
83use rustc_ast::ast::{self, LitKind, RangeLimits};
84use rustc_ast::{LitIntType, join_path_syms};
85use rustc_attr_ir::lang_items::LangItem;
86use rustc_attr_ir::lang_items::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
87use rustc_attr_ir::{CfgEntry, find_attr};
88use rustc_data_structures::fx::FxHashMap;
89use rustc_data_structures::indexmap;
90use rustc_data_structures::packed::Pu128;
91use rustc_data_structures::unhash::UnindexMap;
92use rustc_hir::def::{DefKind, Res};
93use rustc_hir::def_id::{DefId, LocalDefId, LocalModId};
94use rustc_hir::definitions::{DefPath, DefPathData};
95use rustc_hir::intravisit::{Visitor, walk_expr};
96use rustc_hir::{
97 self as hir, AnonConst, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, CRATE_HIR_ID, Closure, ConstArg,
98 ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind,
99 FieldDef, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, HirIdSet, Impl, ImplItem, ImplItemKind, Item,
100 ItemKind, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind,
101 Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, Variant, def,
102};
103use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
104use rustc_lint::{LateContext, Level, Lint, LintContext as _};
105use rustc_middle::hir::nested_filter;
106use rustc_middle::hir::place::PlaceBase;
107use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
108use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, DerefAdjustKind, PointerCoercion};
109use rustc_middle::ty::layout::IntegerExt as _;
110use rustc_middle::ty::{
111 self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
112 TypeFlags, TypeVisitableExt as _, TypeckResults, UintTy, UpvarCapture,
113};
114use rustc_session::config::Input;
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::{SpanExt as _, walk_span_to_context};
120use visitors::{Visitable, for_each_unconsumed_temporary};
121
122use crate::ast_utils::unordered_over;
123use crate::higher::Range;
124use crate::msrvs::Msrv;
125use crate::res::{MaybeDef as _, MaybeResPath as _};
126use crate::source::HasSourceMap;
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(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(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(_)
247 | Const {
248 allow_const_fn_promotion: _,
249 } => true,
250 }
251}
252
253pub fn is_enum_variant_ctor(
255 cx: &LateContext<'_>,
256 enum_item: Symbol,
257 variant_name: Symbol,
258 ctor_call_id: DefId,
259) -> bool {
260 let Some(enum_def_id) = cx.tcx.get_diagnostic_item(enum_item) else {
261 return false;
262 };
263
264 let variants = cx.tcx.adt_def(enum_def_id).variants().iter();
265 variants
266 .filter(|variant| variant.name == variant_name)
267 .filter_map(|variant| variant.ctor.as_ref())
268 .any(|(_, ctor_def_id)| *ctor_def_id == ctor_call_id)
269}
270
271pub fn is_diagnostic_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: Symbol) -> bool {
273 let did = match cx.tcx.def_kind(did) {
274 DefKind::Ctor(..) => cx.tcx.parent(did),
275 DefKind::Variant => match cx.tcx.opt_parent(did) {
277 Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
278 _ => did,
279 },
280 _ => did,
281 };
282
283 cx.tcx.is_diagnostic_item(item, did)
284}
285
286pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> bool {
288 let did = match cx.tcx.def_kind(did) {
289 DefKind::Ctor(..) => cx.tcx.parent(did),
290 DefKind::Variant => match cx.tcx.opt_parent(did) {
292 Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
293 _ => did,
294 },
295 _ => did,
296 };
297
298 cx.tcx.lang_items().get(item) == Some(did)
299}
300
301pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
303 expr.basic_res().ctor_parent(cx).is_lang_item(cx, OptionNone)
304}
305
306pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
308 if let ExprKind::Call(e, [arg]) = expr.kind
309 && e.basic_res().ctor_parent(cx).is_lang_item(cx, OptionSome)
310 {
311 Some(arg)
312 } else {
313 None
314 }
315}
316
317pub fn is_empty_block(expr: &Expr<'_>) -> bool {
319 matches!(
320 expr.kind,
321 ExprKind::Block(
322 Block {
323 stmts: [],
324 expr: None,
325 ..
326 },
327 _,
328 )
329 )
330}
331
332pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
334 matches!(
335 expr.kind,
336 ExprKind::Block(
337 Block {
338 stmts: [],
339 expr: None,
340 ..
341 },
342 _
343 ) | ExprKind::Tup([])
344 )
345}
346
347pub fn is_wild(pat: &Pat<'_>) -> bool {
349 matches!(pat.kind, PatKind::Wild)
350}
351
352pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> {
359 if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind
360 && cx
361 .qpath_res(qpath, pat.hir_id)
362 .ctor_parent(cx)
363 .is_lang_item(cx, OptionSome)
364 {
365 Some(inner)
366 } else {
367 None
368 }
369}
370
371pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
373 matches!(pat.kind,
374 PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. })
375 if cx.qpath_res(qpath, pat.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone))
376}
377
378pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
380 is_none_pattern(cx, arm.pat)
381 && matches!(
382 peel_blocks(arm.body).kind,
383 ExprKind::Path(qpath)
384 if cx.qpath_res(&qpath, arm.body.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone)
385 )
386}
387
388pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
390 match *qpath {
391 QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)),
392 QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath),
393 QPath::TypeRelative(..) => false,
394 }
395}
396
397pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
399 if let Node::Item(item) = cx.tcx.parent_hir_node(cx.tcx.local_def_id_to_hir_id(def_id))
400 && let ItemKind::Impl(imp) = item.kind
401 {
402 imp.of_trait.is_some()
403 } else {
404 false
405 }
406}
407
408pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
409 match *path {
410 QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
411 QPath::TypeRelative(_, seg) => seg,
412 }
413}
414
415pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
416 last_path_segment(qpath)
417 .args
418 .map_or(&[][..], |a| a.args)
419 .iter()
420 .filter_map(|a| match a {
421 GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
422 _ => None,
423 })
424}
425
426pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
431 match expr.kind {
432 ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
433 ExprKind::Path(QPath::Resolved(
434 _,
435 Path {
436 res: Res::Local(local), ..
437 },
438 )) => Some(*local),
439 _ => None,
440 }
441}
442
443pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, owner: OwnerId) -> Option<&'tcx TraitRef<'tcx>> {
459 if let Node::Item(item) = cx.tcx.hir_node(cx.tcx.hir_owner_parent(owner))
460 && let ItemKind::Impl(impl_) = &item.kind
461 && let Some(of_trait) = impl_.of_trait
462 {
463 return Some(&of_trait.trait_ref);
464 }
465 None
466}
467
468fn projection_stack<'a, 'hir>(
476 mut e: &'a Expr<'hir>,
477 ctxt: SyntaxContext,
478) -> Option<(Vec<&'a Expr<'hir>>, &'a Expr<'hir>)> {
479 let mut result = vec![];
480 let root = loop {
481 match e.kind {
482 ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) if e.span.ctxt() == ctxt => {
483 result.push(e);
484 e = ep;
485 },
486 ExprKind::Index(..) | ExprKind::Field(..) => return None,
487 _ => break e,
488 }
489 };
490 result.reverse();
491 Some((result, root))
492}
493
494pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> {
496 cx.typeck_results()
497 .expr_adjustments(e)
498 .iter()
499 .find_map(|a| match a.kind {
500 Adjust::Deref(DerefAdjustKind::Overloaded(d)) => Some(Some(d.mutbl)),
501 Adjust::Deref(DerefAdjustKind::Builtin) => None,
502 _ => Some(None),
503 })
504 .and_then(|x| x)
505}
506
507pub fn can_mut_borrow_both(cx: &LateContext<'_>, ctxt: SyntaxContext, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
510 let Some((s1, r1)) = projection_stack(e1, ctxt) else {
511 return false;
512 };
513 let Some((s2, r2)) = projection_stack(e2, ctxt) else {
514 return false;
515 };
516 if !eq_expr_value(cx, ctxt, r1, r2) {
517 return true;
518 }
519 if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
520 return false;
521 }
522
523 for (x1, x2) in zip(&s1, &s2) {
524 if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
525 return false;
526 }
527
528 match (&x1.kind, &x2.kind) {
529 (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
530 if i1 != i2 {
531 return true;
532 }
533 },
534 _ => return false,
535 }
536 }
537 false
538}
539
540fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
543 let std_types_symbols = &[
544 sym::Vec,
545 sym::VecDeque,
546 sym::LinkedList,
547 sym::HashMap,
548 sym::BTreeMap,
549 sym::HashSet,
550 sym::BTreeSet,
551 sym::BinaryHeap,
552 ];
553
554 if let QPath::TypeRelative(_, method) = path
555 && method.ident.name == sym::new
556 && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id)
557 && let Some(adt) = cx
558 .tcx
559 .type_of(impl_did)
560 .instantiate_identity()
561 .skip_norm_wip()
562 .ty_adt_def()
563 {
564 return Some(adt.did()) == cx.tcx.lang_items().string()
565 || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name));
566 }
567 false
568}
569
570pub fn is_default_equivalent_call(
572 cx: &LateContext<'_>,
573 repl_func: &Expr<'_>,
574 whole_call_expr: Option<&Expr<'_>>,
575) -> bool {
576 if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
577 && let Some(repl_def) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def(cx)
578 && (repl_def.assoc_fn_parent(cx).is_diag_item(cx, sym::Default)
579 || is_default_equivalent_ctor(cx, repl_def.1, repl_func_qpath))
580 {
581 return true;
582 }
583
584 let Some(e) = whole_call_expr else { return false };
587 let Some(default_fn_def_id) = cx.tcx.get_diagnostic_item(sym::default_fn) else {
588 return false;
589 };
590 let Some(ty) = cx.tcx.typeck(e.hir_id.owner.def_id).expr_ty_adjusted_opt(e) else {
591 return false;
592 };
593 let args = rustc_ty::GenericArgs::for_item(cx.tcx, default_fn_def_id, |param, _| {
594 if let rustc_ty::GenericParamDefKind::Lifetime = param.kind {
595 cx.tcx.lifetimes.re_erased.into()
596 } else if param.index == 0 && param.name == kw::SelfUpper {
597 ty.into()
598 } else {
599 param.to_error(cx.tcx)
600 }
601 });
602 let instance = rustc_ty::Instance::try_resolve(cx.tcx, cx.typing_env(), default_fn_def_id, args);
603
604 let Ok(Some(instance)) = instance else { return false };
605 if let rustc_ty::InstanceKind::Item(def) = instance.def
606 && !cx.tcx.is_mir_available(def)
607 {
608 return false;
609 }
610 let ExprKind::Path(ref repl_func_qpath) = repl_func.kind else {
611 return false;
612 };
613 let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() else {
614 return false;
615 };
616
617 let body = cx.tcx.instance_mir(instance.def);
623 for block_data in body.basic_blocks.iter() {
624 if block_data.statements.len() == 1
625 && let StatementKind::Assign(assign) = &block_data.statements[0].kind
626 && assign.0.local == RETURN_PLACE
627 && let Rvalue::Aggregate(kind, _places) = &assign.1
628 && let AggregateKind::Adt(did, variant_index, _, _, _) = **kind
629 && let def = cx.tcx.adt_def(did)
630 && let variant = &def.variant(variant_index)
631 && variant.fields.is_empty()
632 && let Some((_, did)) = variant.ctor
633 && did == repl_def_id
634 {
635 return true;
636 } else if block_data.statements.is_empty()
637 && let Some(term) = &block_data.terminator
638 {
639 match &term.kind {
640 TerminatorKind::Call {
641 func: Operand::Constant(c),
642 ..
643 } if let rustc_ty::FnDef(did, _args) = c.ty().kind()
644 && *did == repl_def_id =>
645 {
646 return true;
647 },
648 TerminatorKind::TailCall {
649 func: Operand::Constant(c),
650 ..
651 } if let rustc_ty::FnDef(did, _args) = c.ty().kind()
652 && *did == repl_def_id =>
653 {
654 return true;
655 },
656 _ => {},
657 }
658 }
659 }
660 false
661}
662
663pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
667 match &e.kind {
668 ExprKind::Lit(lit) => match lit.node {
669 LitKind::Bool(false) | LitKind::Int(Pu128(0), _) => true,
670 LitKind::Str(s, _) => s.is_empty(),
671 _ => false,
672 },
673 ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
674 ExprKind::Repeat(x, len) => {
675 if let ConstArgKind::Anon(anon_const) = len.kind
676 && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind
677 && let LitKind::Int(v, _) = const_lit.node
678 && v <= 32
679 && is_default_equivalent(cx, x)
680 {
681 true
682 } else {
683 false
684 }
685 },
686 ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func, Some(e)),
687 ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg),
688 ExprKind::Path(qpath) => cx
689 .qpath_res(qpath, e.hir_id)
690 .ctor_parent(cx)
691 .is_lang_item(cx, OptionNone),
692 ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
693 ExprKind::Block(Block { stmts: [], expr, .. }, _) => expr.is_some_and(|e| is_default_equivalent(cx, e)),
694 _ => false,
695 }
696}
697
698fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool {
699 if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind
700 && seg.ident.name == sym::from
701 {
702 match arg.kind {
703 ExprKind::Lit(hir::Lit {
704 node: LitKind::Str(sym, _),
705 ..
706 }) => return sym.is_empty() && ty.basic_res().is_lang_item(cx, LangItem::String),
707 ExprKind::Array([]) => return ty.basic_res().is_diag_item(cx, sym::Vec),
708 ExprKind::Repeat(_, len) => {
709 if let ConstArgKind::Anon(anon_const) = len.kind
710 && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind
711 && let LitKind::Int(v, _) = const_lit.node
712 {
713 return v == 0 && ty.basic_res().is_diag_item(cx, sym::Vec);
714 }
715 },
716 _ => (),
717 }
718 }
719 false
720}
721
722pub fn can_move_expr_to_closure_no_visit<'tcx>(
754 cx: &LateContext<'tcx>,
755 expr: &'tcx Expr<'_>,
756 loop_ids: &[HirId],
757 ignore_locals: &HirIdSet,
758) -> bool {
759 match expr.kind {
760 ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
761 | ExprKind::Continue(Destination { target_id: Ok(id), .. })
762 if loop_ids.contains(&id) =>
763 {
764 true
765 },
766 ExprKind::Break(..)
767 | ExprKind::Continue(_)
768 | ExprKind::Ret(_)
769 | ExprKind::Yield(..)
770 | ExprKind::InlineAsm(_) => false,
771 ExprKind::Field(
774 &Expr {
775 hir_id,
776 kind:
777 ExprKind::Path(QPath::Resolved(
778 _,
779 Path {
780 res: Res::Local(local_id),
781 ..
782 },
783 )),
784 ..
785 },
786 _,
787 ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
788 false
790 },
791 _ => true,
792 }
793}
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq)]
797pub enum CaptureKind {
798 Value,
799 Use,
800 Ref(Mutability),
801}
802impl CaptureKind {
803 pub fn is_imm_ref(self) -> bool {
804 self == Self::Ref(Mutability::Not)
805 }
806}
807impl std::ops::BitOr for CaptureKind {
808 type Output = Self;
809 fn bitor(self, rhs: Self) -> Self::Output {
810 match (self, rhs) {
811 (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
812 (CaptureKind::Use, _) | (_, CaptureKind::Use) => CaptureKind::Use,
813 (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
814 | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
815 (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
816 }
817 }
818}
819impl std::ops::BitOrAssign for CaptureKind {
820 fn bitor_assign(&mut self, rhs: Self) {
821 *self = *self | rhs;
822 }
823}
824
825pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind {
831 fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
832 let mut capture = CaptureKind::Ref(Mutability::Not);
833 pat.each_binding_or_first(&mut |_, id, span, _| match cx
834 .typeck_results()
835 .extract_binding_mode(cx.sess(), id, span)
836 .0
837 {
838 ByRef::No if !is_copy(cx, cx.typeck_results().node_type(id)) => {
839 capture = CaptureKind::Value;
840 },
841 ByRef::Yes(_, Mutability::Mut) if capture != CaptureKind::Value => {
842 capture = CaptureKind::Ref(Mutability::Mut);
843 },
844 _ => (),
845 });
846 capture
847 }
848
849 debug_assert!(matches!(
850 e.kind,
851 ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
852 ));
853
854 let mut capture = CaptureKind::Value;
855 let mut capture_expr_ty = e;
856
857 for (parent, child_id) in hir_parent_with_src_iter(cx.tcx, e.hir_id) {
858 if let [
859 Adjustment {
860 kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
861 target,
862 },
863 ref adjust @ ..,
864 ] = *cx
865 .typeck_results()
866 .adjustments()
867 .get(child_id)
868 .map_or(&[][..], |x| &**x)
869 && let rustc_ty::RawPtr(_, mutability) | rustc_ty::Ref(_, _, mutability) =
870 *adjust.last().map_or(target, |a| a.target).kind()
871 {
872 return CaptureKind::Ref(mutability);
873 }
874
875 match parent {
876 Node::Expr(e) => match e.kind {
877 ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
878 ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
879 ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
880 return CaptureKind::Ref(Mutability::Mut);
881 },
882 ExprKind::Field(..) => {
883 if capture == CaptureKind::Value {
884 capture_expr_ty = e;
885 }
886 },
887 ExprKind::Let(let_expr) => {
888 let mutability = match pat_capture_kind(cx, let_expr.pat) {
889 CaptureKind::Value | CaptureKind::Use => Mutability::Not,
890 CaptureKind::Ref(m) => m,
891 };
892 return CaptureKind::Ref(mutability);
893 },
894 ExprKind::Match(_, arms, _) => {
895 let mut mutability = Mutability::Not;
896 for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
897 match capture {
898 CaptureKind::Value | CaptureKind::Use => break,
899 CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
900 CaptureKind::Ref(Mutability::Not) => (),
901 }
902 }
903 return CaptureKind::Ref(mutability);
904 },
905 _ => break,
906 },
907 Node::LetStmt(l) => match pat_capture_kind(cx, l.pat) {
908 CaptureKind::Value | CaptureKind::Use => break,
909 capture @ CaptureKind::Ref(_) => return capture,
910 },
911 _ => break,
912 }
913 }
914
915 if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
916 CaptureKind::Ref(Mutability::Not)
918 } else {
919 capture
920 }
921}
922
923pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
926 struct V<'cx, 'tcx> {
927 cx: &'cx LateContext<'tcx>,
928 loops: Vec<HirId>,
930 locals: HirIdSet,
932 allow_closure: bool,
934 captures: HirIdMap<CaptureKind>,
937 }
938 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
939 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
940 if !self.allow_closure {
941 return;
942 }
943
944 match e.kind {
945 ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
946 if !self.locals.contains(&l) {
947 let cap = capture_local_usage(self.cx, e);
948 self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
949 }
950 },
951 ExprKind::Closure(closure) => {
952 for capture in self.cx.typeck_results().closure_min_captures_flattened(closure.def_id) {
953 let local_id = match capture.place.base {
954 PlaceBase::Local(id) => id,
955 PlaceBase::Upvar(var) => var.var_path.hir_id,
956 _ => continue,
957 };
958 if !self.locals.contains(&local_id) {
959 let capture = match capture.info.capture_kind {
960 UpvarCapture::ByValue => CaptureKind::Value,
961 UpvarCapture::ByUse => CaptureKind::Use,
962 UpvarCapture::ByRef(kind) => match kind {
963 BorrowKind::Immutable => CaptureKind::Ref(Mutability::Not),
964 BorrowKind::UniqueImmutable | BorrowKind::Mutable => {
965 CaptureKind::Ref(Mutability::Mut)
966 },
967 },
968 };
969 self.captures
970 .entry(local_id)
971 .and_modify(|e| *e |= capture)
972 .or_insert(capture);
973 }
974 }
975 },
976 ExprKind::Loop(b, ..) => {
977 self.loops.push(e.hir_id);
978 self.visit_block(b);
979 self.loops.pop();
980 },
981 _ => {
982 self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
983 walk_expr(self, e);
984 },
985 }
986 }
987
988 fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
989 p.each_binding_or_first(&mut |_, id, _, _| {
990 self.locals.insert(id);
991 });
992 }
993 }
994
995 let mut v = V {
996 cx,
997 loops: Vec::new(),
998 locals: HirIdSet::default(),
999 allow_closure: true,
1000 captures: HirIdMap::default(),
1001 };
1002 v.visit_expr(expr);
1003 v.allow_closure.then_some(v.captures)
1004}
1005
1006pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>;
1008
1009pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) {
1012 let mut method_names = Vec::with_capacity(max_depth);
1013 let mut arg_lists = Vec::with_capacity(max_depth);
1014 let mut spans = Vec::with_capacity(max_depth);
1015
1016 let mut current = expr;
1017 for _ in 0..max_depth {
1018 if let ExprKind::MethodCall(path, receiver, args, _) = ¤t.kind {
1019 if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
1020 break;
1021 }
1022 method_names.push(path.ident.name);
1023 arg_lists.push((*receiver, &**args));
1024 spans.push(path.ident.span);
1025 current = receiver;
1026 } else {
1027 break;
1028 }
1029 }
1030
1031 (method_names, arg_lists, spans)
1032}
1033
1034pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[Symbol]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> {
1041 let mut current = expr;
1042 let mut matched = Vec::with_capacity(methods.len());
1043 for method_name in methods.iter().rev() {
1044 if let ExprKind::MethodCall(path, receiver, args, _) = current.kind {
1046 if path.ident.name == *method_name {
1047 if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
1048 return None;
1049 }
1050 matched.push((receiver, args)); current = receiver; } else {
1053 return None;
1054 }
1055 } else {
1056 return None;
1057 }
1058 }
1059 matched.reverse();
1061 Some(matched)
1062}
1063
1064pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
1066 cx.tcx
1067 .entry_fn(())
1068 .is_some_and(|(entry_fn_def_id, _)| def_id == entry_fn_def_id)
1069}
1070
1071pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
1073 let parent = cx.tcx.hir_get_parent_item(e.hir_id);
1074 Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl()
1075}
1076
1077pub fn parent_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
1079 let parent_id = cx.tcx.hir_get_parent_item(expr.hir_id).def_id;
1080 match cx.tcx.hir_node_by_def_id(parent_id) {
1081 Node::Item(item) => item.kind.ident().map(|ident| ident.name),
1082 Node::TraitItem(TraitItem { ident, .. }) | Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name),
1083 _ => None,
1084 }
1085}
1086
1087pub struct ContainsName<'a, 'tcx> {
1088 pub cx: &'a LateContext<'tcx>,
1089 pub name: Symbol,
1090}
1091
1092impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> {
1093 type Result = ControlFlow<()>;
1094 type NestedFilter = nested_filter::OnlyBodies;
1095
1096 fn visit_name(&mut self, name: Symbol) -> Self::Result {
1097 if self.name == name {
1098 ControlFlow::Break(())
1099 } else {
1100 ControlFlow::Continue(())
1101 }
1102 }
1103
1104 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
1105 self.cx.tcx
1106 }
1107}
1108
1109pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
1111 let mut cn = ContainsName { cx, name };
1112 cn.visit_expr(expr).is_break()
1113}
1114
1115pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
1117 for_each_expr_without_closures(expr, |e| {
1118 if matches!(e.kind, ExprKind::Ret(..)) {
1119 ControlFlow::Break(())
1120 } else {
1121 ControlFlow::Continue(())
1122 }
1123 })
1124 .is_some()
1125}
1126
1127pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
1129 get_parent_expr_for_hir(cx, e.hir_id)
1130}
1131
1132pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
1135 match cx.tcx.parent_hir_node(hir_id) {
1136 Node::Expr(parent) => Some(parent),
1137 _ => None,
1138 }
1139}
1140
1141pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
1143 let enclosing_node = cx
1144 .tcx
1145 .hir_get_enclosing_scope(hir_id)
1146 .map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
1147 enclosing_node.and_then(|node| match node {
1148 Node::Block(block) => Some(block),
1149 Node::Item(&Item {
1150 kind: ItemKind::Fn { body: eid, .. },
1151 ..
1152 })
1153 | Node::ImplItem(&ImplItem {
1154 kind: ImplItemKind::Fn(_, eid),
1155 ..
1156 })
1157 | Node::TraitItem(&TraitItem {
1158 kind: TraitItemKind::Fn(_, TraitFn::Provided(eid)),
1159 ..
1160 }) => match cx.tcx.hir_body(eid).value.kind {
1161 ExprKind::Block(block, _) => Some(block),
1162 _ => None,
1163 },
1164 _ => None,
1165 })
1166}
1167
1168pub fn get_enclosing_closure<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Closure<'tcx>> {
1170 cx.tcx.hir_parent_iter(hir_id).find_map(|(_, node)| {
1171 if let Node::Expr(expr) = node
1172 && let ExprKind::Closure(closure) = expr.kind
1173 {
1174 Some(closure)
1175 } else {
1176 None
1177 }
1178 })
1179}
1180
1181pub fn is_upvar_in_closure(cx: &LateContext<'_>, closure: &Closure<'_>, local_id: HirId) -> bool {
1183 cx.typeck_results()
1184 .closure_min_captures
1185 .get(&closure.def_id)
1186 .is_some_and(|x| x.contains_key(&local_id))
1187}
1188
1189pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
1191 cx: &LateContext<'tcx>,
1192 expr: &Expr<'_>,
1193) -> Option<&'tcx Expr<'tcx>> {
1194 for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
1195 match node {
1196 Node::Expr(e) => match e.kind {
1197 ExprKind::Closure { .. }
1198 if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
1199 && subs.as_closure().kind() == ClosureKind::FnOnce => {},
1200
1201 ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
1203 _ => (),
1204 },
1205 Node::Stmt(_) | Node::Block(_) | Node::LetStmt(_) | Node::Arm(_) | Node::ExprField(_) => (),
1206 _ => break,
1207 }
1208 }
1209 None
1210}
1211
1212pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
1214 match tcx.hir_parent_iter(id).next() {
1215 Some((
1216 _,
1217 Node::Item(Item {
1218 kind: ItemKind::Impl(imp),
1219 ..
1220 }),
1221 )) => Some(imp),
1222 _ => None,
1223 }
1224}
1225
1226pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
1237 while let ExprKind::Block(
1238 Block {
1239 stmts: [],
1240 expr: Some(inner),
1241 rules: BlockCheckMode::DefaultBlock,
1242 ..
1243 },
1244 _,
1245 ) = expr.kind
1246 {
1247 expr = inner;
1248 }
1249 expr
1250}
1251
1252pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
1263 while let ExprKind::Block(
1264 Block {
1265 stmts: [],
1266 expr: Some(inner),
1267 rules: BlockCheckMode::DefaultBlock,
1268 ..
1269 }
1270 | Block {
1271 stmts:
1272 [
1273 Stmt {
1274 kind: StmtKind::Expr(inner) | StmtKind::Semi(inner),
1275 ..
1276 },
1277 ],
1278 expr: None,
1279 rules: BlockCheckMode::DefaultBlock,
1280 ..
1281 },
1282 _,
1283 ) = expr.kind
1284 {
1285 expr = inner;
1286 }
1287 expr
1288}
1289
1290pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1292 let mut iter = tcx.hir_parent_iter(expr.hir_id);
1293 match iter.next() {
1294 Some((
1295 _,
1296 Node::Expr(Expr {
1297 kind: ExprKind::If(_, _, Some(else_expr)),
1298 ..
1299 }),
1300 )) => else_expr.hir_id == expr.hir_id,
1301 _ => false,
1302 }
1303}
1304
1305pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1308 hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
1309 matches!(
1310 node,
1311 Node::LetStmt(LetStmt {
1312 init: Some(init),
1313 els: Some(els),
1314 ..
1315 })
1316 if init.hir_id == child_id || els.hir_id == child_id
1317 )
1318 })
1319}
1320
1321pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1323 hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
1324 matches!(
1325 node,
1326 Node::LetStmt(LetStmt { els: Some(els), .. })
1327 if els.hir_id == child_id
1328 )
1329 })
1330}
1331
1332pub fn is_full_collection_range(cx: &LateContext<'_>, container: Option<HirId>, expr: &Expr<'_>) -> bool {
1334 if let Some(Range { start, end, ty, .. }) = Range::hir(cx, expr) {
1335 start.is_none_or(|start| is_integer_literal(start, 0))
1336 && end.is_none_or(|end| {
1337 if ty.limits() == RangeLimits::HalfOpen
1338 && let Some(container) = container
1339 && let ExprKind::MethodCall(seg, recv, [], _) = end.kind
1340 {
1341 seg.ident.name == sym::len && recv.res_local_id() == Some(container)
1342 } else {
1343 false
1344 }
1345 })
1346 } else {
1347 false
1348 }
1349}
1350
1351pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
1353 if let ExprKind::Lit(spanned) = expr.kind
1354 && let LitKind::Int(v, _) = spanned.node
1355 {
1356 return v == value;
1357 }
1358 false
1359}
1360
1361pub fn is_integer_literal_untyped(expr: &Expr<'_>) -> bool {
1363 if let ExprKind::Lit(spanned) = expr.kind
1364 && let LitKind::Int(_, suffix) = spanned.node
1365 {
1366 return suffix == LitIntType::Unsuffixed;
1367 }
1368
1369 false
1370}
1371
1372pub fn is_float_literal(expr: &Expr<'_>, value: f64) -> bool {
1374 if let ExprKind::Lit(spanned) = expr.kind
1375 && let LitKind::Float(v, _) = spanned.node
1376 {
1377 v.as_str().parse() == Ok(value)
1378 } else {
1379 false
1380 }
1381}
1382
1383pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
1391 cx.typeck_results().adjustments().get(e.hir_id).is_some()
1392}
1393
1394#[must_use]
1398pub fn is_expn_of(mut span: Span, name: Symbol) -> Option<Span> {
1399 loop {
1400 if span.from_expansion() {
1401 let data = span.ctxt().outer_expn_data();
1402 let new_span = data.call_site;
1403
1404 if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind
1405 && mac_name == name
1406 {
1407 return Some(new_span);
1408 }
1409
1410 span = new_span;
1411 } else {
1412 return None;
1413 }
1414 }
1415}
1416
1417#[must_use]
1428pub fn is_direct_expn_of(span: Span, name: Symbol) -> Option<Span> {
1429 if span.from_expansion() {
1430 let data = span.ctxt().outer_expn_data();
1431 let new_span = data.call_site;
1432
1433 if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind
1434 && mac_name == name
1435 {
1436 return Some(new_span);
1437 }
1438 }
1439
1440 None
1441}
1442
1443pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> {
1445 let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip().output();
1446 cx.tcx.instantiate_bound_regions_with_erased(ret_ty)
1447}
1448
1449pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> {
1451 let arg = cx
1452 .tcx
1453 .fn_sig(fn_def_id)
1454 .instantiate_identity()
1455 .skip_norm_wip()
1456 .input(nth);
1457 cx.tcx.instantiate_bound_regions_with_erased(arg)
1458}
1459
1460pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1462 if let ExprKind::Call(fun, _) = expr.kind
1463 && let ExprKind::Path(ref qp) = fun.kind
1464 {
1465 let res = cx.qpath_res(qp, fun.hir_id);
1466 return match res {
1467 Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
1468 Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
1469 _ => false,
1470 };
1471 }
1472 false
1473}
1474
1475pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
1478 fn is_qpath_refutable(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
1479 !matches!(
1480 cx.qpath_res(qpath, id),
1481 Res::Def(DefKind::Struct, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), _)
1482 )
1483 }
1484
1485 fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
1486 i.into_iter().any(|pat| is_refutable(cx, pat))
1487 }
1488
1489 match pat.kind {
1490 PatKind::Missing => unreachable!(),
1491 PatKind::Wild | PatKind::Never => false, PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)),
1493 PatKind::Ref(pat, _, _) => is_refutable(cx, pat),
1494 PatKind::Expr(PatExpr {
1495 kind: PatExprKind::Path(qpath),
1496 hir_id,
1497 ..
1498 }) => is_qpath_refutable(cx, qpath, *hir_id),
1499 PatKind::Or(pats) => {
1500 are_refutable(cx, pats)
1502 },
1503 PatKind::Tuple(pats, _) => are_refutable(cx, pats),
1504 PatKind::Struct(ref qpath, fields, _) => {
1505 is_qpath_refutable(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat))
1506 },
1507 PatKind::TupleStruct(ref qpath, pats, _) => {
1508 is_qpath_refutable(cx, qpath, pat.hir_id) || are_refutable(cx, pats)
1509 },
1510 PatKind::Slice(head, middle, tail) => {
1511 match &cx.typeck_results().node_type(pat.hir_id).kind() {
1512 rustc_ty::Slice(..) => {
1513 !head.is_empty() || middle.is_none() || !tail.is_empty()
1515 },
1516 rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
1517 _ => {
1518 true
1520 },
1521 }
1522 },
1523 PatKind::Expr(..) | PatKind::Range(..) | PatKind::Err(_) | PatKind::Deref(_) | PatKind::Guard(..) => true,
1524 }
1525}
1526
1527pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
1530 if let PatKind::Or(pats) = pat.kind {
1531 pats.iter().for_each(f);
1532 } else {
1533 f(pat);
1534 }
1535}
1536
1537pub fn is_self(slf: &Param<'_>) -> bool {
1538 if let PatKind::Binding(.., name, _) = slf.pat.kind {
1539 name.name == kw::SelfLower
1540 } else {
1541 false
1542 }
1543}
1544
1545pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
1546 if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind
1547 && let Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } = path.res
1548 {
1549 return true;
1550 }
1551 false
1552}
1553
1554pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
1555 (0..decl.inputs.len()).map(move |i| &body.params[i])
1556}
1557
1558pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
1561 fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
1562 if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind
1563 && ddpos.as_opt_usize().is_none()
1564 && cx
1565 .qpath_res(path, arm.pat.hir_id)
1566 .ctor_parent(cx)
1567 .is_lang_item(cx, ResultOk)
1568 && let PatKind::Binding(_, hir_id, _, None) = pat[0].kind
1569 && arm.body.res_local_id() == Some(hir_id)
1570 {
1571 return true;
1572 }
1573 false
1574 }
1575
1576 fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
1577 if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
1578 cx.qpath_res(path, arm.pat.hir_id)
1579 .ctor_parent(cx)
1580 .is_lang_item(cx, ResultErr)
1581 } else {
1582 false
1583 }
1584 }
1585
1586 if let ExprKind::Match(_, arms, ref source) = expr.kind {
1587 if let MatchSource::TryDesugar(_) = *source {
1589 return Some(expr);
1590 }
1591
1592 if arms.len() == 2
1593 && arms[0].guard.is_none()
1594 && arms[1].guard.is_none()
1595 && ((is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])))
1596 {
1597 return Some(expr);
1598 }
1599 }
1600
1601 None
1602}
1603
1604pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool {
1614 let mut suppress_lint = false;
1615
1616 for id in ids {
1617 let level_spec = cx.tcx.lint_level_spec_at_node(lint, id);
1618 if let Some(expectation) = level_spec.lint_id() {
1619 cx.fulfill_expectation(expectation);
1620 }
1621
1622 match level_spec.level() {
1623 Level::Allow | Level::Expect => suppress_lint = true,
1624 Level::Warn | Level::ForceWarn | Level::Deny | Level::Forbid => {},
1625 }
1626 }
1627
1628 suppress_lint
1629}
1630
1631pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
1639 cx.tcx.lint_level_spec_at_node(lint, id).is_allow()
1640}
1641
1642pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
1643 while let PatKind::Ref(subpat, _, _) = pat.kind {
1644 pat = subpat;
1645 }
1646 pat
1647}
1648
1649pub fn int_bits(tcx: TyCtxt<'_>, ity: IntTy) -> u64 {
1650 Integer::from_int_ty(&tcx, ity).size().bits()
1651}
1652
1653#[expect(clippy::cast_possible_wrap)]
1654pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: IntTy) -> i128 {
1656 let amt = 128 - int_bits(tcx, ity);
1657 ((u as i128) << amt) >> amt
1658}
1659
1660#[expect(clippy::cast_sign_loss)]
1661pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: IntTy) -> u128 {
1663 let amt = 128 - int_bits(tcx, ity);
1664 ((u as u128) << amt) >> amt
1665}
1666
1667pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: UintTy) -> u128 {
1669 let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
1670 let amt = 128 - bits;
1671 (u << amt) >> amt
1672}
1673
1674pub fn has_attr(attrs: &[rustc_attr_ir::Attribute], symbol: Symbol) -> bool {
1685 attrs.iter().any(|attr| attr.has_name(symbol))
1686}
1687
1688pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
1689 find_attr!(cx.tcx, hir_id, Repr { .. })
1690}
1691
1692pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
1693 let mut prev_enclosing_node = None;
1694 let mut enclosing_node = node;
1695 while Some(enclosing_node) != prev_enclosing_node {
1696 if has_attr(tcx.hir_attrs(enclosing_node), symbol) {
1697 return true;
1698 }
1699 prev_enclosing_node = Some(enclosing_node);
1700 enclosing_node = tcx.hir_get_parent_item(enclosing_node).into();
1701 }
1702
1703 false
1704}
1705
1706pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
1709 tcx.hir_parent_owner_iter(id)
1710 .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
1711 .any(|(id, _)| find_attr!(tcx, id.def_id, AutomaticallyDerived))
1712}
1713
1714pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: Symbol) -> bool {
1716 cx.tcx.crate_name(did.krate) == sym::libc && cx.tcx.def_path_str(did).ends_with(name.as_str())
1720}
1721
1722pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
1727 let mut conds = Vec::new();
1728 let mut blocks: Vec<&Block<'_>> = Vec::new();
1729
1730 while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
1731 conds.push(cond);
1732 if let ExprKind::Block(block, _) = then.kind {
1733 blocks.push(block);
1734 } else {
1735 panic!("ExprKind::If node is not an ExprKind::Block");
1736 }
1737
1738 if let Some(else_expr) = r#else {
1739 expr = else_expr;
1740 } else {
1741 break;
1742 }
1743 }
1744
1745 if !blocks.is_empty()
1747 && let ExprKind::Block(block, _) = expr.kind
1748 {
1749 blocks.push(block);
1750 }
1751
1752 (conds, blocks)
1753}
1754
1755pub fn get_async_closure_expr<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
1757 if let ExprKind::Closure(&Closure {
1758 body,
1759 kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
1760 ..
1761 }) = expr.kind
1762 && let ExprKind::Block(
1763 Block {
1764 expr:
1765 Some(Expr {
1766 kind: ExprKind::DropTemps(inner_expr),
1767 ..
1768 }),
1769 ..
1770 },
1771 _,
1772 ) = tcx.hir_body(body).value.kind
1773 {
1774 Some(inner_expr)
1775 } else {
1776 None
1777 }
1778}
1779
1780pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
1782 get_async_closure_expr(tcx, body.value)
1783}
1784
1785pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1787 let did = match expr.kind {
1788 ExprKind::Call(path, _) => {
1789 if let ExprKind::Path(ref qpath) = path.kind
1790 && let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
1791 {
1792 Some(did)
1793 } else {
1794 None
1795 }
1796 },
1797 ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
1798 _ => None,
1799 };
1800
1801 did.is_some_and(|did| find_attr!(cx.tcx, did, MustUse { .. }))
1802}
1803
1804fn is_body_identity_function<'hir>(cx: &LateContext<'_>, func: &Body<'hir>) -> bool {
1818 let [param] = func.params else {
1819 return false;
1820 };
1821
1822 let mut param_pat = param.pat;
1823
1824 let mut advance_param_pat_over_stmts = |stmts: &[Stmt<'hir>]| {
1831 for stmt in stmts {
1832 if let StmtKind::Let(local) = stmt.kind
1833 && let Some(init) = local.init
1834 && is_expr_identity_of_pat(cx, param_pat, init, true)
1835 {
1836 param_pat = local.pat;
1837 } else {
1838 return false;
1839 }
1840 }
1841
1842 true
1843 };
1844
1845 let mut expr = func.value;
1846 loop {
1847 match expr.kind {
1848 ExprKind::Block(
1849 &Block {
1850 stmts: [],
1851 expr: Some(e),
1852 ..
1853 },
1854 _,
1855 )
1856 | ExprKind::Ret(Some(e)) => expr = e,
1857 ExprKind::Block(
1858 &Block {
1859 stmts: [stmt],
1860 expr: None,
1861 ..
1862 },
1863 _,
1864 ) => {
1865 if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind
1866 && let ExprKind::Ret(Some(ret_val)) = e.kind
1867 {
1868 expr = ret_val;
1869 } else {
1870 return false;
1871 }
1872 },
1873 ExprKind::Block(
1874 &Block {
1875 stmts, expr: Some(e), ..
1876 },
1877 _,
1878 ) => {
1879 if !advance_param_pat_over_stmts(stmts) {
1880 return false;
1881 }
1882
1883 expr = e;
1884 },
1885 ExprKind::Block(&Block { stmts, expr: None, .. }, _) => {
1886 if let Some((last_stmt, stmts)) = stmts.split_last()
1887 && advance_param_pat_over_stmts(stmts)
1888 && let StmtKind::Semi(e) | StmtKind::Expr(e) = last_stmt.kind
1889 && let ExprKind::Ret(Some(ret_val)) = e.kind
1890 {
1891 expr = ret_val;
1892 } else {
1893 return false;
1894 }
1895 },
1896 _ => return is_expr_identity_of_pat(cx, param_pat, expr, true),
1897 }
1898 }
1899}
1900
1901pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>, by_hir: bool) -> bool {
1911 if cx
1912 .typeck_results()
1913 .pat_binding_modes()
1914 .get(pat.hir_id)
1915 .is_some_and(|mode| matches!(mode.0, ByRef::Yes(..)))
1916 {
1917 return false;
1921 }
1922
1923 let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir);
1925
1926 match (pat.kind, expr.kind) {
1927 (PatKind::Binding(_, id, _, _), _) if by_hir => {
1928 expr.res_local_id() == Some(id) && cx.typeck_results().expr_adjustments(expr).is_empty()
1929 },
1930 (PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => {
1931 matches!(path.segments, [ segment] if segment.ident.name == ident.name)
1932 },
1933 (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
1934 if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
1935 {
1936 over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1937 },
1938 (PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
1939 zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1940 },
1941 (PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields))
1942 if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() =>
1943 {
1944 if let ExprKind::Path(ident) = &ident.kind
1946 && qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1947 && over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir))
1949 {
1950 true
1951 } else {
1952 false
1953 }
1954 },
1955 (PatKind::Struct(pat_ident, field_pats, None), ExprKind::Struct(ident, fields, hir::StructTailExpr::None))
1956 if field_pats.len() == fields.len() =>
1957 {
1958 qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1960 && unordered_over(field_pats, fields, |field_pat, field| {
1962 field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
1963 })
1964 },
1965 _ => false,
1966 }
1967}
1968
1969pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1974 match expr.kind {
1975 ExprKind::Closure(&Closure { body, fn_decl, .. })
1976 if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer(()))) =>
1977 {
1978 is_body_identity_function(cx, cx.tcx.hir_body(body))
1979 },
1980 ExprKind::Path(QPath::Resolved(_, path))
1981 if path.segments.iter().all(|seg| seg.infer_args)
1982 && let Some(did) = path.res.opt_def_id() =>
1983 {
1984 cx.tcx.is_diagnostic_item(sym::convert_identity, did)
1985 },
1986 _ => false,
1987 }
1988}
1989
1990pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1999 match expr.kind {
2000 ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir_body(body)),
2001 _ => expr.basic_res().is_diag_item(cx, sym::convert_identity),
2002 }
2003}
2004
2005pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
2008 for (node, child_id) in hir_parent_with_src_iter(tcx, expr.hir_id) {
2009 match node {
2010 Node::Block(_) => {},
2011 Node::Arm(arm) if arm.body.hir_id == child_id => {},
2012 Node::Expr(expr) => match expr.kind {
2013 ExprKind::Block(..) | ExprKind::DropTemps(_) => {},
2014 ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => {},
2015 ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => return None,
2016 _ => return Some((Node::Expr(expr), child_id)),
2017 },
2018 node => return Some((node, child_id)),
2019 }
2020 }
2021 None
2022}
2023
2024pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
2026 !matches!(
2027 get_expr_use_or_unification_node(tcx, expr),
2028 None | Some((
2029 Node::Stmt(Stmt {
2030 kind: StmtKind::Expr(_)
2031 | StmtKind::Semi(_)
2032 | StmtKind::Let(LetStmt {
2033 pat: Pat {
2034 kind: PatKind::Wild,
2035 ..
2036 },
2037 ..
2038 }),
2039 ..
2040 }),
2041 _
2042 ))
2043 )
2044}
2045
2046pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
2048 matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..))
2049}
2050
2051pub fn is_expr_temporary_value(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2055 !expr.is_place_expr(|base| {
2056 cx.typeck_results()
2057 .adjustments()
2058 .get(base.hir_id)
2059 .is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
2060 })
2061}
2062
2063pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
2064 if is_no_core_crate(cx) {
2065 None
2066 } else if is_no_std_crate(cx) {
2067 Some("core")
2068 } else {
2069 Some("std")
2070 }
2071}
2072
2073pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
2074 find_attr!(cx.tcx, crate, NoStd)
2075}
2076
2077pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
2078 find_attr!(cx.tcx, crate, NoCore)
2079}
2080
2081pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
2091 if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) {
2092 matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }))
2093 } else {
2094 false
2095 }
2096}
2097
2098pub fn fn_has_unsatisfiable_clauses(cx: &LateContext<'_>, did: DefId) -> bool {
2108 use rustc_trait_selection::traits;
2109 let clauses = cx
2110 .tcx
2111 .clauses_of(did)
2112 .clauses
2113 .iter()
2114 .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
2115 traits::impossible_clauses(cx.tcx, traits::elaborate(cx.tcx, clauses).collect::<Vec<_>>())
2116}
2117
2118pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
2120 fn_def_id_with_node_args(cx, expr).map(|(did, _)| did)
2121}
2122
2123pub fn fn_def_id_with_node_args<'tcx>(
2126 cx: &LateContext<'tcx>,
2127 expr: &Expr<'_>,
2128) -> Option<(DefId, GenericArgsRef<'tcx>)> {
2129 let typeck = cx.typeck_results();
2130 match &expr.kind {
2131 ExprKind::MethodCall(..) => Some((
2132 typeck.type_dependent_def_id(expr.hir_id)?,
2133 typeck.node_args(expr.hir_id),
2134 )),
2135 ExprKind::Call(
2136 Expr {
2137 kind: ExprKind::Path(qpath),
2138 hir_id: path_hir_id,
2139 ..
2140 },
2141 ..,
2142 ) => {
2143 if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
2146 typeck.qpath_res(qpath, *path_hir_id)
2147 {
2148 Some((id, typeck.node_args(*path_hir_id)))
2149 } else {
2150 None
2151 }
2152 },
2153 _ => None,
2154 }
2155}
2156
2157pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
2162 let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
2163 let expr_kind = expr_type.kind();
2164 let is_primitive = match expr_kind {
2165 rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type),
2166 rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
2167 if let rustc_ty::Slice(element_type) = inner_ty.kind() {
2168 is_recursively_primitive_type(*element_type)
2169 } else {
2170 unreachable!()
2171 }
2172 },
2173 _ => false,
2174 };
2175
2176 if is_primitive {
2177 match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
2180 rustc_ty::Slice(..) => return Some("slice".into()),
2181 rustc_ty::Array(..) => return Some("array".into()),
2182 rustc_ty::Tuple(..) => return Some("tuple".into()),
2183 _ => {
2184 let refs_peeled = expr_type.peel_refs();
2187 return Some(refs_peeled.walk().last().unwrap().to_string());
2188 },
2189 }
2190 }
2191 None
2192}
2193
2194pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<Vec<&T>>
2202where
2203 Hash: FnMut(&T) -> u64,
2204 Eq: FnMut(&T, &T) -> bool,
2205{
2206 match exprs {
2207 [a, b] if eq(a, b) => return vec![vec![a, b]],
2208 _ if exprs.len() <= 2 => return vec![],
2209 _ => {},
2210 }
2211
2212 let mut buckets: UnindexMap<u64, Vec<Vec<&T>>> = UnindexMap::default();
2213
2214 for expr in exprs {
2215 match buckets.entry(hash(expr)) {
2216 indexmap::map::Entry::Occupied(mut o) => {
2217 let bucket = o.get_mut();
2218 match bucket.iter_mut().find(|group| eq(expr, group[0])) {
2219 Some(group) => group.push(expr),
2220 None => bucket.push(vec![expr]),
2221 }
2222 },
2223 indexmap::map::Entry::Vacant(v) => {
2224 v.insert(vec![vec![expr]]);
2225 },
2226 }
2227 }
2228
2229 buckets
2230 .into_values()
2231 .flatten()
2232 .filter(|group| group.len() > 1)
2233 .collect()
2234}
2235
2236pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
2239 fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
2240 if let PatKind::Ref(pat, _, _) = pat.kind {
2241 peel(pat, count + 1)
2242 } else {
2243 (pat, count)
2244 }
2245 }
2246 peel(pat, 0)
2247}
2248
2249pub fn peel_hir_expr_while<'tcx>(
2251 mut expr: &'tcx Expr<'tcx>,
2252 mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
2253) -> &'tcx Expr<'tcx> {
2254 while let Some(e) = f(expr) {
2255 expr = e;
2256 }
2257 expr
2258}
2259
2260pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
2263 let mut remaining = count;
2264 let e = peel_hir_expr_while(expr, |e| match e.kind {
2265 ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
2266 remaining -= 1;
2267 Some(e)
2268 },
2269 _ => None,
2270 });
2271 (e, count - remaining)
2272}
2273
2274pub fn peel_hir_expr_unary<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
2277 let mut count: usize = 0;
2278 let mut curr_expr = expr;
2279 while let ExprKind::Unary(_, local_expr) = curr_expr.kind {
2280 count = count.wrapping_add(1);
2281 curr_expr = local_expr;
2282 }
2283 (curr_expr, count)
2284}
2285
2286pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
2289 let mut count = 0;
2290 let e = peel_hir_expr_while(expr, |e| match e.kind {
2291 ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
2292 count += 1;
2293 Some(e)
2294 },
2295 _ => None,
2296 });
2297 (e, count)
2298}
2299
2300pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) {
2303 let mut count = 0;
2304 loop {
2305 match &ty.kind {
2306 TyKind::Ref(_, inner_ty, _) => {
2307 ty = inner_ty;
2308 count += 1;
2309 },
2310 _ => break (ty, count),
2311 }
2312 }
2313}
2314
2315pub fn peel_hir_ty_refs_and_ptrs<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
2317 match &ty.kind {
2318 TyKind::Ptr(inner_ty, _) | TyKind::Ref(_, inner_ty, _) => peel_hir_ty_refs_and_ptrs(inner_ty),
2319 _ => ty,
2320 }
2321}
2322
2323pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
2326 loop {
2327 match expr.kind {
2328 ExprKind::AddrOf(_, _, e) => expr = e,
2329 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
2330 _ => break,
2331 }
2332 }
2333 expr
2334}
2335
2336pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> {
2339 let mut operators = Vec::new();
2340 peel_hir_expr_while(expr, |expr| match expr.kind {
2341 ExprKind::AddrOf(_, _, e) => {
2342 operators.push(expr);
2343 Some(e)
2344 },
2345 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => {
2346 operators.push(expr);
2347 Some(e)
2348 },
2349 _ => None,
2350 });
2351 operators
2352}
2353
2354pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
2355 if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
2356 && let Res::Def(_, def_id) = path.res
2357 {
2358 return find_attr!(cx.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..));
2359 }
2360 false
2361}
2362
2363static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalModId, Vec<Symbol>>>> = OnceLock::new();
2364
2365fn test_item_names(tcx: TyCtxt<'_>, module: LocalModId) -> Vec<Symbol> {
2368 let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
2369 let mut map = cache.lock().unwrap();
2370 match map.entry(module) {
2371 Entry::Occupied(entry) => entry.get().clone(),
2372 Entry::Vacant(entry) => {
2373 let mut names = Vec::new();
2374 for id in tcx.hir_module_free_items(module) {
2375 if matches!(tcx.def_kind(id.owner_id), DefKind::Static { .. })
2376 && let item = tcx.hir_item(id)
2377 && let ItemKind::Static(_mut, ident, ty, _body) = item.kind
2378 && let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
2379 && let Res::Def(DefKind::Struct, _) = path.res
2381 && find_attr!(tcx, item.hir_id(), RustcTestMarker(..))
2382 {
2383 names.push(ident.name);
2384 }
2385 }
2386 names.sort_unstable();
2387 entry.insert(names).clone()
2388 },
2389 }
2390}
2391
2392pub fn is_in_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool {
2396 let names = test_item_names(tcx, tcx.parent_module(id));
2397 if names.is_empty() {
2399 return false;
2400 }
2401 once((id, tcx.hir_node(id)))
2402 .chain(tcx.hir_parent_iter(id))
2403 .any(|(_id, node)| {
2406 if let Node::Item(item) = node
2407 && let ItemKind::Fn { ident, .. } = item.kind
2408 {
2409 return names.binary_search(&ident.name).is_ok();
2412 }
2413 false
2414 })
2415}
2416
2417pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool {
2424 let id = tcx.local_def_id_to_hir_id(fn_def_id);
2425 if let Node::Item(item) = tcx.hir_node(id)
2426 && let ItemKind::Fn { ident, .. } = item.kind
2427 {
2428 test_item_names(tcx, tcx.parent_module(id))
2429 .binary_search(&ident.name)
2430 .is_ok()
2431 } else {
2432 false
2433 }
2434}
2435
2436pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
2441 if let Some(cfgs) = find_attr!(tcx, id, CfgTrace(cfgs) => cfgs)
2442 && cfgs
2443 .iter()
2444 .any(|(cfg, _)| matches!(cfg, CfgEntry::NameValue { name: sym::test, .. }))
2445 {
2446 true
2447 } else {
2448 false
2449 }
2450}
2451
2452pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
2454 tcx.hir_parent_id_iter(id).any(|parent_id| is_cfg_test(tcx, parent_id))
2455}
2456
2457pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
2459 is_in_test_function(tcx, hir_id) || is_in_cfg_test(tcx, hir_id) || is_in_integration_test_file(tcx)
2460}
2461
2462fn is_in_integration_test_file(tcx: TyCtxt<'_>) -> bool {
2464 if let Input::File(ref path) = tcx.sess.io.input
2465 && !tcx.sess.opts.unstable_opts.ui_testing
2466 {
2467 path.starts_with("tests")
2468 } else {
2469 false
2470 }
2471}
2472
2473pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
2475 find_attr!(tcx, def_id, CfgTrace(..))
2476 || find_attr!(
2477 tcx.hir_parent_id_iter(tcx.local_def_id_to_hir_id(def_id))
2478 .flat_map(|parent_id| tcx.hir_attrs(parent_id)),
2479 CfgTrace(..)
2480 )
2481}
2482
2483#[derive(Clone, Copy)]
2485pub enum DefinedTy<'tcx> {
2486 Hir(&'tcx hir::Ty<'tcx>),
2488 Mir {
2496 def_site_def_id: Option<DefId>,
2497 ty: Binder<'tcx, Ty<'tcx>>,
2498 },
2499}
2500
2501pub struct ExprUseSite<'tcx> {
2503 pub node: Node<'tcx>,
2505 pub child_id: HirId,
2507 pub adjustments: &'tcx [Adjustment<'tcx>],
2509 pub is_ty_unified: bool,
2511 pub moved_before_use: bool,
2513 pub same_ctxt: bool,
2515}
2516impl<'tcx> ExprUseSite<'tcx> {
2517 pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> {
2518 match self.node {
2519 Node::LetStmt(l) => ExprUseNode::LetStmt(l),
2520 Node::ExprField(field) => ExprUseNode::Field(field),
2521
2522 Node::Item(&Item {
2523 kind: ItemKind::Static(..) | ItemKind::Const(..),
2524 owner_id,
2525 ..
2526 })
2527 | Node::TraitItem(&TraitItem {
2528 kind: TraitItemKind::Const(..),
2529 owner_id,
2530 ..
2531 })
2532 | Node::ImplItem(&ImplItem {
2533 kind: ImplItemKind::Const(..),
2534 owner_id,
2535 ..
2536 }) => ExprUseNode::ConstStatic(owner_id),
2537
2538 Node::Item(&Item {
2539 kind: ItemKind::Fn { .. },
2540 owner_id,
2541 ..
2542 })
2543 | Node::TraitItem(&TraitItem {
2544 kind: TraitItemKind::Fn(..),
2545 owner_id,
2546 ..
2547 })
2548 | Node::ImplItem(&ImplItem {
2549 kind: ImplItemKind::Fn(..),
2550 owner_id,
2551 ..
2552 }) => ExprUseNode::Return(owner_id),
2553
2554 Node::Expr(use_expr) => match use_expr.kind {
2555 ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
2556 def_id: cx.tcx.hir_body_owner_def_id(cx.enclosing_body.unwrap()),
2557 }),
2558
2559 ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
2560 ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) {
2561 Some(i) => ExprUseNode::FnArg(func, i),
2562 None => ExprUseNode::Callee,
2563 },
2564 ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
2565 use_expr.hir_id,
2566 name.args,
2567 args.iter()
2568 .position(|arg| arg.hir_id == self.child_id)
2569 .map_or(0, |i| i + 1),
2570 ),
2571 ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name),
2572 ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl),
2573 _ => ExprUseNode::Other,
2574 },
2575 _ => ExprUseNode::Other,
2576 }
2577 }
2578}
2579
2580pub enum ExprUseNode<'tcx> {
2582 LetStmt(&'tcx LetStmt<'tcx>),
2584 ConstStatic(OwnerId),
2586 Return(OwnerId),
2588 Field(&'tcx ExprField<'tcx>),
2590 FnArg(&'tcx Expr<'tcx>, usize),
2592 MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize),
2594 Callee,
2596 FieldAccess(Ident),
2598 AddrOf(ast::BorrowKind, Mutability),
2600 Other,
2601}
2602impl<'tcx> ExprUseNode<'tcx> {
2603 pub fn is_return(&self) -> bool {
2605 matches!(self, Self::Return(_))
2606 }
2607
2608 pub fn is_recv(&self) -> bool {
2610 matches!(self, Self::MethodArg(_, _, 0))
2611 }
2612
2613 pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
2615 match *self {
2616 Self::LetStmt(LetStmt { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)),
2617 Self::ConstStatic(id) => Some(DefinedTy::Mir {
2618 def_site_def_id: Some(id.def_id.to_def_id()),
2619 ty: Binder::dummy(cx.tcx.type_of(id).instantiate_identity().skip_norm_wip()),
2620 }),
2621 Self::Return(id) => {
2622 if let Node::Expr(Expr {
2623 kind: ExprKind::Closure(c),
2624 ..
2625 }) = cx.tcx.hir_node_by_def_id(id.def_id)
2626 {
2627 match c.fn_decl.output {
2628 FnRetTy::DefaultReturn(_) => None,
2629 FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)),
2630 }
2631 } else {
2632 let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().output();
2633 Some(DefinedTy::Mir {
2634 def_site_def_id: Some(id.def_id.to_def_id()),
2635 ty,
2636 })
2637 }
2638 },
2639 Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) {
2640 Some(Expr {
2641 hir_id,
2642 kind: ExprKind::Struct(path, ..),
2643 ..
2644 }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id))
2645 .and_then(|(adt, variant)| {
2646 variant
2647 .fields
2648 .iter()
2649 .find(|f| f.name == field.ident.name)
2650 .map(|f| (adt, f))
2651 })
2652 .map(|(adt, field_def)| DefinedTy::Mir {
2653 def_site_def_id: Some(adt.did()),
2654 ty: Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip()),
2655 }),
2656 _ => None,
2657 },
2658 Self::FnArg(callee, i) => {
2659 let sig = expr_sig(cx, callee)?;
2660 let (hir_ty, ty) = sig.input_with_hir(i)?;
2661 Some(match hir_ty {
2662 Some(hir_ty) => DefinedTy::Hir(hir_ty),
2663 None => DefinedTy::Mir {
2664 def_site_def_id: sig.predicates_id(),
2665 ty,
2666 },
2667 })
2668 },
2669 Self::MethodArg(id, _, i) => {
2670 let id = cx.typeck_results().type_dependent_def_id(id)?;
2671 let sig = cx.tcx.fn_sig(id).skip_binder();
2672 Some(DefinedTy::Mir {
2673 def_site_def_id: Some(id),
2674 ty: sig.input(i),
2675 })
2676 },
2677 Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None,
2678 }
2679 }
2680}
2681
2682struct ReplacingFilterMap<I, F>(I, F);
2683impl<I, F, U> Iterator for ReplacingFilterMap<I, F>
2684where
2685 I: Iterator,
2686 F: FnMut(&mut I, I::Item) -> Option<U>,
2687{
2688 type Item = U;
2689 fn next(&mut self) -> Option<U> {
2690 while let Some(x) = self.0.next() {
2691 if let Some(x) = (self.1)(&mut self.0, x) {
2692 return Some(x);
2693 }
2694 }
2695 None
2696 }
2697}
2698
2699#[expect(clippy::too_many_lines)]
2702pub fn expr_use_sites<'tcx>(
2703 tcx: TyCtxt<'tcx>,
2704 typeck: &'tcx TypeckResults<'tcx>,
2705 mut ctxt: SyntaxContext,
2706 e: &'tcx Expr<'tcx>,
2707) -> impl Iterator<Item = ExprUseSite<'tcx>> {
2708 let mut adjustments: &[_] = typeck.expr_adjustments(e);
2709 let mut is_ty_unified = false;
2710 let mut moved_before_use = false;
2711 let mut same_ctxt = true;
2712 ReplacingFilterMap(
2713 hir_parent_with_src_iter(tcx, e.hir_id),
2714 move |iter: &mut _, (parent, child_id)| {
2715 let parent_ctxt;
2716 let mut parent_adjustments: &[_] = &[];
2717 match parent {
2718 Node::Expr(parent_expr) => {
2719 parent_ctxt = parent_expr.span.ctxt();
2720 same_ctxt &= parent_ctxt == ctxt;
2721 parent_adjustments = typeck.expr_adjustments(parent_expr);
2722 match parent_expr.kind {
2723 ExprKind::Match(scrutinee, arms, _) if scrutinee.hir_id != child_id => {
2724 is_ty_unified |= arms.len() != 1;
2725 moved_before_use = true;
2726 if adjustments.is_empty() {
2727 adjustments = parent_adjustments;
2728 }
2729 return None;
2730 },
2731 ExprKind::If(cond, _, else_) if cond.hir_id != child_id => {
2732 is_ty_unified |= else_.is_some();
2733 moved_before_use = true;
2734 if adjustments.is_empty() {
2735 adjustments = parent_adjustments;
2736 }
2737 return None;
2738 },
2739 ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
2740 is_ty_unified = true;
2741 moved_before_use = true;
2742 *iter = hir_parent_with_src_iter(tcx, id);
2743 if adjustments.is_empty() {
2744 adjustments = parent_adjustments;
2745 }
2746 return None;
2747 },
2748 ExprKind::Block(b, _) => {
2749 is_ty_unified |= b.targeted_by_break;
2750 moved_before_use = true;
2751 if adjustments.is_empty() {
2752 adjustments = parent_adjustments;
2753 }
2754 return None;
2755 },
2756 ExprKind::DropTemps(_) | ExprKind::Type(..) => {
2757 if adjustments.is_empty() {
2758 adjustments = parent_adjustments;
2759 }
2760 return None;
2761 },
2762 _ => {},
2763 }
2764 },
2765 Node::Arm(arm) => {
2766 parent_ctxt = arm.span.ctxt();
2767 same_ctxt &= parent_ctxt == ctxt;
2768 if arm.body.hir_id == child_id {
2769 return None;
2770 }
2771 },
2772 Node::Block(b) => {
2773 same_ctxt &= b.span.ctxt() == ctxt;
2774 return None;
2775 },
2776 Node::ConstBlock(_) => parent_ctxt = ctxt,
2777 Node::ExprField(&ExprField { span, .. }) => {
2778 parent_ctxt = span.ctxt();
2779 same_ctxt &= parent_ctxt == ctxt;
2780 },
2781 Node::AnonConst(&AnonConst { span, .. })
2782 | Node::ConstArg(&ConstArg { span, .. })
2783 | Node::Field(&FieldDef { span, .. })
2784 | Node::ImplItem(&ImplItem { span, .. })
2785 | Node::Item(&Item { span, .. })
2786 | Node::LetStmt(&LetStmt { span, .. })
2787 | Node::Stmt(&Stmt { span, .. })
2788 | Node::TraitItem(&TraitItem { span, .. })
2789 | Node::Variant(&Variant { span, .. }) => {
2790 parent_ctxt = span.ctxt();
2791 same_ctxt &= parent_ctxt == ctxt;
2792 *iter = hir_parent_with_src_iter(tcx, CRATE_HIR_ID);
2793 },
2794 Node::AssocItemConstraint(_)
2795 | Node::ConstArgExprField(_)
2796 | Node::Crate(_)
2797 | Node::Ctor(_)
2798 | Node::Err(_)
2799 | Node::ForeignItem(_)
2800 | Node::GenericParam(_)
2801 | Node::Infer(_)
2802 | Node::Lifetime(_)
2803 | Node::OpaqueTy(_)
2804 | Node::Param(_)
2805 | Node::Pat(_)
2806 | Node::PatExpr(_)
2807 | Node::PatField(_)
2808 | Node::PathSegment(_)
2809 | Node::PreciseCapturingNonLifetimeArg(_)
2810 | Node::Synthetic
2811 | Node::TraitRef(_)
2812 | Node::Ty(_)
2813 | Node::TyPat(_)
2814 | Node::NestedUseTree(_)
2815 | Node::WherePredicate(_)
2816 | Node::TestBinderForall(_)
2817 | Node::TestBinderExists(_)
2818 | Node::TestBinderBoundTypeConstraint(_) => {
2819 debug_assert!(false, "found {parent:?} which is after the final use node");
2822 return None;
2823 },
2824 }
2825
2826 ctxt = parent_ctxt;
2827 Some(ExprUseSite {
2828 node: parent,
2829 child_id,
2830 adjustments: mem::replace(&mut adjustments, parent_adjustments),
2831 is_ty_unified: mem::replace(&mut is_ty_unified, false),
2832 moved_before_use: mem::replace(&mut moved_before_use, false),
2833 same_ctxt: mem::replace(&mut same_ctxt, true),
2834 })
2835 },
2836 )
2837}
2838
2839pub fn get_expr_use_site<'tcx>(
2840 tcx: TyCtxt<'tcx>,
2841 typeck: &'tcx TypeckResults<'tcx>,
2842 ctxt: SyntaxContext,
2843 e: &'tcx Expr<'tcx>,
2844) -> ExprUseSite<'tcx> {
2845 expr_use_sites(tcx, typeck, ctxt, e).next().unwrap_or_else(|| {
2848 debug_assert!(false, "failed to find a use site for expr {e:?}");
2849 ExprUseSite {
2850 node: Node::Synthetic, child_id: CRATE_HIR_ID,
2852 adjustments: &[],
2853 is_ty_unified: false,
2854 moved_before_use: false,
2855 same_ctxt: false,
2856 }
2857 })
2858}
2859
2860pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str, InnerSpan)> {
2862 let mut pos = 0;
2863 tokenize(s, FrontmatterAllowed::No).map(move |t| {
2864 let end = pos + t.len;
2865 let range = pos as usize..end as usize;
2866 let inner = InnerSpan::new(range.start, range.end);
2867 pos = end;
2868 (t.kind, s.get(range).unwrap_or_default(), inner)
2869 })
2870}
2871
2872pub fn span_contains_comment<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> bool {
2875 span.check_text(sm, |snippet| {
2876 tokenize(snippet, FrontmatterAllowed::No).any(|token| {
2877 matches!(
2878 token.kind,
2879 TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
2880 )
2881 })
2882 })
2883}
2884
2885pub fn span_contains_non_whitespace<'sm>(sm: impl HasSourceMap<'sm>, span: Span, skip_comments: bool) -> bool {
2890 span.check_text(sm, |snippet| {
2891 tokenize_with_text(snippet).any(|(token, _, _)| match token {
2892 TokenKind::Whitespace => false,
2893 TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments,
2894 _ => true,
2895 })
2896 })
2897}
2898
2899pub fn span_extract_comment<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> String {
2903 span_extract_comments(sm, span).join("\n")
2904}
2905
2906pub fn span_extract_comments<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Vec<String> {
2910 span.with_source_text(sm, |snippet| {
2911 tokenize_with_text(snippet)
2912 .filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
2913 .map(|(_, s, _)| s.to_string())
2914 .collect::<Vec<_>>()
2915 })
2916 .unwrap_or_default()
2917}
2918
2919pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
2920 sm.span_take_while(span, |&ch| ch == ' ' || ch == ';')
2921}
2922
2923pub fn pat_and_expr_can_be_question_mark<'a, 'hir>(
2948 cx: &LateContext<'_>,
2949 pat: &'a Pat<'hir>,
2950 else_body: &Expr<'_>,
2951) -> Option<&'a Pat<'hir>> {
2952 if let Some([inner_pat]) = as_some_pattern(cx, pat)
2953 && !is_refutable(cx, inner_pat)
2954 && let else_body = peel_blocks(else_body)
2955 && let ExprKind::Ret(Some(ret_val)) = else_body.kind
2956 && let ExprKind::Path(ret_path) = ret_val.kind
2957 && cx
2958 .qpath_res(&ret_path, ret_val.hir_id)
2959 .ctor_parent(cx)
2960 .is_lang_item(cx, OptionNone)
2961 {
2962 Some(inner_pat)
2963 } else {
2964 None
2965 }
2966}
2967
2968macro_rules! op_utils {
2969 ($($name:ident $assign:ident)*) => {
2970 pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
2972
2973 pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
2975
2976 pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
2978 match kind {
2979 $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
2980 _ => None,
2981 }
2982 }
2983 };
2984}
2985
2986op_utils! {
2987 Add AddAssign
2988 Sub SubAssign
2989 Mul MulAssign
2990 Div DivAssign
2991 Rem RemAssign
2992 BitXor BitXorAssign
2993 BitAnd BitAndAssign
2994 BitOr BitOrAssign
2995 Shl ShlAssign
2996 Shr ShrAssign
2997}
2998
2999pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: impl Visitable<'tcx>) -> bool {
3002 match *pat {
3003 PatKind::Wild => true,
3004 PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
3005 !visitors::is_local_used(cx, body, id)
3006 },
3007 _ => false,
3008 }
3009}
3010
3011#[derive(Clone, Copy)]
3012pub enum RequiresSemi {
3013 Yes,
3014 No,
3015}
3016impl RequiresSemi {
3017 pub fn requires_semi(self) -> bool {
3018 matches!(self, Self::Yes)
3019 }
3020}
3021
3022#[expect(clippy::too_many_lines)]
3025pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
3026 struct BreakTarget {
3027 id: HirId,
3028 unused: bool,
3029 }
3030
3031 struct V<'cx, 'tcx> {
3032 cx: &'cx LateContext<'tcx>,
3033 break_targets: Vec<BreakTarget>,
3034 break_targets_for_result_ty: u32,
3035 in_final_expr: bool,
3036 requires_semi: bool,
3037 is_never: bool,
3038 }
3039
3040 impl V<'_, '_> {
3041 fn push_break_target(&mut self, id: HirId) {
3042 self.break_targets.push(BreakTarget { id, unused: true });
3043 self.break_targets_for_result_ty += u32::from(self.in_final_expr);
3044 }
3045 }
3046
3047 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
3048 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
3049 if self.is_never && self.break_targets.is_empty() {
3066 if self.in_final_expr && !self.requires_semi {
3067 match e.kind {
3070 ExprKind::DropTemps(e) => self.visit_expr(e),
3071 ExprKind::If(_, then, Some(else_)) => {
3072 self.visit_expr(then);
3073 self.visit_expr(else_);
3074 },
3075 ExprKind::Match(_, arms, _) => {
3076 for arm in arms {
3077 self.visit_expr(arm.body);
3078 }
3079 },
3080 ExprKind::Loop(b, ..) => {
3081 self.push_break_target(e.hir_id);
3082 self.in_final_expr = false;
3083 self.visit_block(b);
3084 self.break_targets.pop();
3085 },
3086 ExprKind::Block(b, _) => {
3087 if b.targeted_by_break {
3088 self.push_break_target(b.hir_id);
3089 self.visit_block(b);
3090 self.break_targets.pop();
3091 } else {
3092 self.visit_block(b);
3093 }
3094 },
3095 _ => {
3096 self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
3097 },
3098 }
3099 }
3100 return;
3101 }
3102 match e.kind {
3103 ExprKind::DropTemps(e) => self.visit_expr(e),
3104 ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true,
3105 ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
3106 self.in_final_expr = false;
3107 self.visit_expr(e);
3108 self.is_never = true;
3109 },
3110 ExprKind::Break(dest, e) => {
3111 if let Some(e) = e {
3112 self.in_final_expr = false;
3113 self.visit_expr(e);
3114 }
3115 if let Ok(id) = dest.target_id
3116 && let Some((i, target)) = self
3117 .break_targets
3118 .iter_mut()
3119 .enumerate()
3120 .find(|(_, target)| target.id == id)
3121 {
3122 target.unused &= self.is_never;
3123 if i < self.break_targets_for_result_ty as usize {
3124 self.requires_semi = true;
3125 }
3126 }
3127 self.is_never = true;
3128 },
3129 ExprKind::If(cond, then, else_) => {
3130 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3131 self.visit_expr(cond);
3132 self.in_final_expr = in_final_expr;
3133
3134 if self.is_never {
3135 self.visit_expr(then);
3136 if let Some(else_) = else_ {
3137 self.visit_expr(else_);
3138 }
3139 } else {
3140 self.visit_expr(then);
3141 let is_never = mem::replace(&mut self.is_never, false);
3142 if let Some(else_) = else_ {
3143 self.visit_expr(else_);
3144 self.is_never &= is_never;
3145 }
3146 }
3147 },
3148 ExprKind::Match(scrutinee, arms, _) => {
3149 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3150 self.visit_expr(scrutinee);
3151 self.in_final_expr = in_final_expr;
3152
3153 if self.is_never {
3154 for arm in arms {
3155 self.visit_arm(arm);
3156 }
3157 } else {
3158 let mut is_never = true;
3159 for arm in arms {
3160 self.is_never = false;
3161 if let Some(guard) = arm.guard {
3162 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3163 self.visit_expr(guard);
3164 self.in_final_expr = in_final_expr;
3165 self.is_never = false;
3168 }
3169 self.visit_expr(arm.body);
3170 is_never &= self.is_never;
3171 }
3172 self.is_never = is_never;
3173 }
3174 },
3175 ExprKind::Loop(b, _, _, _) => {
3176 self.push_break_target(e.hir_id);
3177 self.in_final_expr = false;
3178 self.visit_block(b);
3179 self.is_never = self.break_targets.pop().unwrap().unused;
3180 },
3181 ExprKind::Block(b, _) => {
3182 if b.targeted_by_break {
3183 self.push_break_target(b.hir_id);
3184 self.visit_block(b);
3185 self.is_never &= self.break_targets.pop().unwrap().unused;
3186 } else {
3187 self.visit_block(b);
3188 }
3189 },
3190 _ => {
3191 self.in_final_expr = false;
3192 walk_expr(self, e);
3193 self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
3194 },
3195 }
3196 }
3197
3198 fn visit_block(&mut self, b: &'tcx Block<'_>) {
3199 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3200 for s in b.stmts {
3201 self.visit_stmt(s);
3202 }
3203 self.in_final_expr = in_final_expr;
3204 if let Some(e) = b.expr {
3205 self.visit_expr(e);
3206 }
3207 }
3208
3209 fn visit_local(&mut self, l: &'tcx LetStmt<'_>) {
3210 if let Some(e) = l.init {
3211 self.visit_expr(e);
3212 }
3213 if let Some(else_) = l.els {
3214 let is_never = self.is_never;
3215 self.visit_block(else_);
3216 self.is_never = is_never;
3217 }
3218 }
3219
3220 fn visit_arm(&mut self, arm: &Arm<'tcx>) {
3221 if let Some(guard) = arm.guard {
3222 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3223 self.visit_expr(guard);
3224 self.in_final_expr = in_final_expr;
3225 }
3226 self.visit_expr(arm.body);
3227 }
3228 }
3229
3230 if cx.typeck_results().expr_ty(e).is_never() {
3231 Some(RequiresSemi::No)
3232 } else if let ExprKind::Block(b, _) = e.kind
3233 && !b.targeted_by_break
3234 && b.expr.is_none()
3235 {
3236 None
3238 } else {
3239 let mut v = V {
3240 cx,
3241 break_targets: Vec::new(),
3242 break_targets_for_result_ty: 0,
3243 in_final_expr: true,
3244 requires_semi: false,
3245 is_never: false,
3246 };
3247 v.visit_expr(e);
3248 v.is_never
3249 .then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
3250 RequiresSemi::Yes
3251 } else {
3252 RequiresSemi::No
3253 })
3254 }
3255}
3256
3257pub fn get_path_from_caller_to_method_type<'tcx>(
3263 tcx: TyCtxt<'tcx>,
3264 from: LocalDefId,
3265 method: DefId,
3266 args: GenericArgsRef<'tcx>,
3267) -> String {
3268 let assoc_item = tcx.associated_item(method);
3269 let def_id = assoc_item.container_id(tcx);
3270 match assoc_item.container {
3271 rustc_ty::AssocContainer::Trait => get_path_to_callee(tcx, from, def_id),
3272 rustc_ty::AssocContainer::InherentImpl | rustc_ty::AssocContainer::TraitImpl(_) => {
3273 let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
3274 get_path_to_ty(tcx, from, ty, args)
3275 },
3276 }
3277}
3278
3279fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
3280 match ty.kind() {
3281 rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
3282 rustc_ty::Array(..)
3284 | rustc_ty::Dynamic(..)
3285 | rustc_ty::Never
3286 | rustc_ty::RawPtr(_, _)
3287 | rustc_ty::Ref(..)
3288 | rustc_ty::Slice(_)
3289 | rustc_ty::Tuple(_) => format!(
3290 "<{}>",
3291 EarlyBinder::bind(tcx, ty).instantiate(tcx, args).skip_norm_wip()
3292 ),
3293 _ => ty.to_string(),
3294 }
3295}
3296
3297fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
3299 if callee.is_local() {
3301 let callee_path = tcx.def_path(callee);
3302 let caller_path = tcx.def_path(from.to_def_id());
3303 maybe_get_relative_path(&caller_path, &callee_path, 2)
3304 } else {
3305 tcx.def_path_str(callee)
3306 }
3307}
3308
3309fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
3322 use itertools::EitherOrBoth::{Both, Left, Right};
3323
3324 let unique_parts = to
3326 .data
3327 .iter()
3328 .zip_longest(from.data.iter())
3329 .skip_while(|el| matches!(el, Both(l, r) if l == r))
3330 .map(|el| match el {
3331 Both(l, r) => Both(l.data, r.data),
3332 Left(l) => Left(l.data),
3333 Right(r) => Right(r.data),
3334 });
3335
3336 let mut go_up_by = 0;
3338 let mut path = Vec::new();
3339 for el in unique_parts {
3340 match el {
3341 Both(l, r) => {
3342 if let DefPathData::TypeNs(sym) = l {
3352 path.push(sym);
3353 }
3354 if let DefPathData::TypeNs(_) = r {
3355 go_up_by += 1;
3356 }
3357 },
3358 Left(DefPathData::TypeNs(sym)) => path.push(sym),
3363 Right(DefPathData::TypeNs(_)) => go_up_by += 1,
3368 _ => {},
3369 }
3370 }
3371
3372 if go_up_by > max_super {
3373 join_path_syms(once(kw::Crate).chain(to.data.iter().filter_map(|el| {
3375 if let DefPathData::TypeNs(sym) = el.data {
3376 Some(sym)
3377 } else {
3378 None
3379 }
3380 })))
3381 } else if go_up_by == 0 && path.is_empty() {
3382 String::from("Self")
3383 } else {
3384 join_path_syms(repeat_n(kw::Super, go_up_by).chain(path))
3385 }
3386}
3387
3388pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
3391 matches!(
3392 cx.tcx.parent_hir_node(id),
3393 Node::Stmt(..) | Node::Block(Block { stmts: [], .. })
3394 )
3395}
3396
3397pub fn is_block_like(expr: &Expr<'_>) -> bool {
3400 matches!(
3401 expr.kind,
3402 ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
3403 )
3404}
3405
3406pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
3408 fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
3409 match expr.kind {
3410 ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => contains_block(lhs, true),
3411 _ if is_block_like(expr) => is_operand,
3412 _ => false,
3413 }
3414 }
3415
3416 contains_block(expr, false)
3417}
3418
3419pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3421 if let Some(parent_expr) = get_parent_expr(cx, expr)
3422 && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
3423 && receiver.hir_id == expr.hir_id
3424 {
3425 return true;
3426 }
3427 false
3428}
3429
3430pub fn leaks_droppable_temporary_with_limited_lifetime<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3433 for_each_unconsumed_temporary(cx, expr, |temporary_ty| {
3434 if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env())
3435 && temporary_ty
3436 .walk()
3437 .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static()))
3438 {
3439 ControlFlow::Break(())
3440 } else {
3441 ControlFlow::Continue(())
3442 }
3443 })
3444 .is_break()
3445}
3446
3447pub fn leaks_droppable_temporary<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3450 for_each_unconsumed_temporary(cx, expr, |temporary_ty| {
3451 if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env()) {
3452 ControlFlow::Break(())
3453 } else {
3454 ControlFlow::Continue(())
3455 }
3456 })
3457 .is_break()
3458}
3459
3460pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
3471 let expr_ty_is_adjusted = cx
3472 .typeck_results()
3473 .expr_adjustments(expr)
3474 .iter()
3475 .any(|adj| !matches!(adj.kind, Adjust::NeverToAny));
3477 if expr_ty_is_adjusted {
3478 return true;
3479 }
3480
3481 match expr.kind {
3484 ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => {
3485 let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip();
3486
3487 if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) {
3488 return false;
3489 }
3490
3491 let self_arg_count = usize::from(matches!(expr.kind, ExprKind::MethodCall(..)));
3492 let mut args_with_ty_param = {
3493 fn_sig
3494 .inputs()
3495 .skip_binder()
3496 .iter()
3497 .skip(self_arg_count)
3498 .zip(args)
3499 .filter_map(|(arg_ty, arg)| {
3500 if arg_ty.has_type_flags(TypeFlags::HAS_TY_PARAM) {
3501 Some(arg)
3502 } else {
3503 None
3504 }
3505 })
3506 };
3507 args_with_ty_param.any(|arg| expr_requires_coercion(cx, arg))
3508 },
3509 ExprKind::Struct(qpath, _, _) => {
3511 let res = cx.typeck_results().qpath_res(qpath, expr.hir_id);
3512 if let Some((_, v_def)) = adt_and_variant_of_res(cx, res) {
3513 let rustc_ty::Adt(_, generic_args) = cx.typeck_results().expr_ty_adjusted(expr).kind() else {
3514 return true;
3516 };
3517 v_def
3518 .fields
3519 .iter()
3520 .any(|field| field.ty(cx.tcx, generic_args).has_type_flags(TypeFlags::HAS_TY_PARAM))
3521 } else {
3522 false
3523 }
3524 },
3525 ExprKind::Block(
3527 &Block {
3528 expr: Some(ret_expr), ..
3529 },
3530 _,
3531 )
3532 | ExprKind::Ret(Some(ret_expr)) => expr_requires_coercion(cx, ret_expr),
3533
3534 ExprKind::Array(elems) | ExprKind::Tup(elems) => elems.iter().any(|elem| expr_requires_coercion(cx, elem)),
3536 ExprKind::Repeat(rep_elem, _) => expr_requires_coercion(cx, rep_elem),
3538 ExprKind::If(_, then, maybe_else) => {
3540 expr_requires_coercion(cx, then) || maybe_else.is_some_and(|e| expr_requires_coercion(cx, e))
3541 },
3542 ExprKind::Match(_, arms, _) => arms
3543 .iter()
3544 .map(|arm| arm.body)
3545 .any(|body| expr_requires_coercion(cx, body)),
3546 _ => false,
3547 }
3548}
3549
3550pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3553 if let Some(hir_id) = expr.res_local_id()
3554 && let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
3555 {
3556 matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
3557 } else if let ExprKind::Path(p) = &expr.kind
3558 && let Some(mutability) = cx
3559 .qpath_res(p, expr.hir_id)
3560 .opt_def_id()
3561 .and_then(|id| cx.tcx.static_mutability(id))
3562 {
3563 mutability == Mutability::Mut
3564 } else if let ExprKind::Field(parent, _) = expr.kind {
3565 is_mutable(cx, parent)
3566 } else {
3567 true
3568 }
3569}
3570
3571pub fn peel_hir_ty_options<'tcx>(cx: &LateContext<'tcx>, mut hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
3574 let Some(option_def_id) = cx.tcx.get_diagnostic_item(sym::Option) else {
3575 return hir_ty;
3576 };
3577 while let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind
3578 && let Some(segment) = path.segments.last()
3579 && segment.ident.name == sym::Option
3580 && let Res::Def(DefKind::Enum, def_id) = segment.res
3581 && def_id == option_def_id
3582 && let [GenericArg::Type(arg_ty)] = segment.args().args
3583 {
3584 hir_ty = arg_ty.as_unambig_ty();
3585 }
3586 hir_ty
3587}
3588
3589pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
3592 if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind
3593 && let ExprKind::Call(_, [into_future_arg]) = match_value.kind
3594 && let ctxt = expr.span.ctxt()
3595 && for_each_expr_without_closures(into_future_arg, |e| {
3596 walk_span_to_context(e.span, ctxt).map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))
3597 })
3598 .is_none()
3599 {
3600 Some(into_future_arg)
3601 } else {
3602 None
3603 }
3604}
3605
3606pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3608 if let ExprKind::Call(fn_expr, []) = &expr.kind
3609 && let ExprKind::Path(qpath) = &fn_expr.kind
3610 && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
3611 {
3612 cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
3613 } else {
3614 false
3615 }
3616}
3617
3618pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3634 let enclosing_body_owner = cx
3635 .tcx
3636 .local_def_id_to_hir_id(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
3637 let mut prev_id = expr.hir_id;
3638 let mut skip_until_id = None;
3639 for (hir_id, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
3640 if hir_id == enclosing_body_owner {
3641 return true;
3642 }
3643 if let Some(id) = skip_until_id {
3644 prev_id = hir_id;
3645 if id == hir_id {
3646 skip_until_id = None;
3647 }
3648 continue;
3649 }
3650 match node {
3651 Node::Block(Block { expr, .. }) if expr.is_some_and(|expr| expr.hir_id == prev_id) => {},
3652 Node::Arm(arm) if arm.body.hir_id == prev_id => {},
3653 Node::Expr(expr) => match expr.kind {
3654 ExprKind::Ret(_) => return true,
3655 ExprKind::If(_, then, opt_else)
3656 if then.hir_id == prev_id || opt_else.is_some_and(|els| els.hir_id == prev_id) => {},
3657 ExprKind::Match(_, arms, _) if arms.iter().any(|arm| arm.hir_id == prev_id) => {},
3658 ExprKind::Block(block, _) if block.hir_id == prev_id => {},
3659 ExprKind::Break(
3660 Destination {
3661 target_id: Ok(target_id),
3662 ..
3663 },
3664 _,
3665 ) => skip_until_id = Some(target_id),
3666 _ => break,
3667 },
3668 _ => break,
3669 }
3670 prev_id = hir_id;
3671 }
3672
3673 false
3676}
3677
3678pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3681 cx.typeck_results().expr_adjustments(expr).iter().any(|adj| {
3682 matches!(
3683 adj.kind,
3684 Adjust::Deref(DerefAdjustKind::Overloaded(_))
3685 | Adjust::Pointer(PointerCoercion::Unsize)
3686 | Adjust::NeverToAny
3687 )
3688 })
3689}
3690
3691pub fn is_expr_async_block(expr: &Expr<'_>) -> bool {
3693 matches!(
3694 expr.kind,
3695 ExprKind::Closure(Closure {
3696 kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(
3697 CoroutineDesugaring::Async,
3698 CoroutineSource::Block
3699 )),
3700 ..
3701 })
3702 )
3703}
3704
3705pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool {
3707 cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS)
3708}
3709
3710#[inline]
3713pub fn hir_parent_with_src_iter(tcx: TyCtxt<'_>, mut id: HirId) -> impl Iterator<Item = (Node<'_>, HirId)> {
3714 tcx.hir_parent_id_iter(id)
3715 .map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent)))
3716}