1#![feature(deref_patterns)]
2#![feature(macro_metavar_expr)]
3#![feature(rustc_private)]
4#![feature(unwrap_infallible)]
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::CfgEntry;
86use rustc_attr_ir::lang_items::LangItem;
87use rustc_attr_ir::lang_items::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
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 find_attr,
103};
104use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
105use rustc_lint::{LateContext, Level, Lint, LintContext as _};
106use rustc_middle::hir::nested_filter;
107use rustc_middle::hir::place::PlaceBase;
108use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
109use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, DerefAdjustKind, PointerCoercion};
110use rustc_middle::ty::layout::IntegerExt as _;
111use rustc_middle::ty::{
112 self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
113 TypeFlags, TypeVisitableExt as _, TypeckResults, UintTy, UpvarCapture,
114};
115use rustc_session::config::Input;
116use rustc_span::hygiene::{ExpnKind, MacroKind};
117use rustc_span::source_map::SourceMap;
118use rustc_span::symbol::{Ident, Symbol, kw};
119use rustc_span::{InnerSpan, Span, SyntaxContext};
120use source::{SpanExt as _, walk_span_to_context};
121use visitors::{Visitable, for_each_unconsumed_temporary};
122
123use crate::ast_utils::unordered_over;
124use crate::higher::Range;
125use crate::msrvs::Msrv;
126use crate::res::{MaybeDef as _, MaybeResPath as _};
127use crate::source::HasSourceMap;
128use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
129use crate::visitors::for_each_expr_without_closures;
130
131pub const VEC_METHODS_SHADOWING_SLICE_METHODS: [Symbol; 3] = [sym::as_ptr, sym::is_empty, sym::len];
133
134#[macro_export]
135macro_rules! extract_msrv_attr {
136 () => {
137 fn check_attributes(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) {
138 let sess = rustc_lint::LintContext::sess(cx);
139 self.msrv.check_attributes(attrs);
140 }
141
142 fn check_attributes_post(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) {
143 let sess = rustc_lint::LintContext::sess(cx);
144 self.msrv.check_attributes_post(attrs);
145 }
146 };
147}
148
149pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
172 while let Some(init) = expr
173 .res_local_id()
174 .and_then(|id| find_binding_init(cx, id))
175 .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
176 {
177 expr = init;
178 }
179 expr
180}
181
182pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
191 if let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
192 && matches!(pat.kind, PatKind::Binding(BindingMode::NONE, ..))
193 && let Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id)
194 {
195 return local.init;
196 }
197 None
198}
199
200pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool {
204 for (_, node) in cx.tcx.hir_parent_iter(local) {
205 match node {
206 Node::Pat(..) | Node::PatField(..) => {},
207 Node::LetStmt(let_stmt) => return let_stmt.init.is_some(),
208 _ => return true,
209 }
210 }
211
212 false
213}
214
215pub fn is_in_const_context(cx: &LateContext<'_>) -> bool {
226 debug_assert!(cx.enclosing_body.is_some(), "`LateContext` has no enclosing body");
227 cx.enclosing_body.is_some_and(|id| {
228 cx.tcx
229 .hir_body_const_context(cx.tcx.hir_body_owner_def_id(id))
230 .is_some()
231 })
232}
233
234pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
241 use rustc_hir::ConstContext::{Const, ConstFn, Static};
242 let Some(ctx) = tcx.hir_body_const_context(tcx.hir_enclosing_body_owner(hir_id)) else {
243 return false;
244 };
245 match ctx {
246 ConstFn => false,
247 Static(_)
248 | Const {
249 allow_const_fn_promotion: _,
250 } => true,
251 }
252}
253
254pub fn is_enum_variant_ctor(
256 cx: &LateContext<'_>,
257 enum_item: Symbol,
258 variant_name: Symbol,
259 ctor_call_id: DefId,
260) -> bool {
261 let Some(enum_def_id) = cx.tcx.get_diagnostic_item(enum_item) else {
262 return false;
263 };
264
265 let variants = cx.tcx.adt_def(enum_def_id).variants().iter();
266 variants
267 .filter(|variant| variant.name == variant_name)
268 .filter_map(|variant| variant.ctor.as_ref())
269 .any(|(_, ctor_def_id)| *ctor_def_id == ctor_call_id)
270}
271
272pub fn is_diagnostic_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: Symbol) -> bool {
274 let did = match cx.tcx.def_kind(did) {
275 DefKind::Ctor(..) => cx.tcx.parent(did),
276 DefKind::Variant => match cx.tcx.opt_parent(did) {
278 Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
279 _ => did,
280 },
281 _ => did,
282 };
283
284 cx.tcx.is_diagnostic_item(item, did)
285}
286
287pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> bool {
289 let did = match cx.tcx.def_kind(did) {
290 DefKind::Ctor(..) => cx.tcx.parent(did),
291 DefKind::Variant => match cx.tcx.opt_parent(did) {
293 Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did,
294 _ => did,
295 },
296 _ => did,
297 };
298
299 cx.tcx.lang_items().get(item) == Some(did)
300}
301
302pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
304 expr.basic_res().ctor_parent(cx).is_lang_item(cx, OptionNone)
305}
306
307pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
309 if let ExprKind::Call(e, [arg]) = expr.kind
310 && e.basic_res().ctor_parent(cx).is_lang_item(cx, OptionSome)
311 {
312 Some(arg)
313 } else {
314 None
315 }
316}
317
318pub fn is_empty_block(expr: &Expr<'_>) -> bool {
320 matches!(
321 expr.kind,
322 ExprKind::Block(
323 Block {
324 stmts: [],
325 expr: None,
326 ..
327 },
328 _,
329 )
330 )
331}
332
333pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
335 matches!(
336 expr.kind,
337 ExprKind::Block(
338 Block {
339 stmts: [],
340 expr: None,
341 ..
342 },
343 _
344 ) | ExprKind::Tup([])
345 )
346}
347
348pub fn is_wild(pat: &Pat<'_>) -> bool {
350 matches!(pat.kind, PatKind::Wild)
351}
352
353pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> {
360 if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind
361 && cx
362 .qpath_res(qpath, pat.hir_id)
363 .ctor_parent(cx)
364 .is_lang_item(cx, OptionSome)
365 {
366 Some(inner)
367 } else {
368 None
369 }
370}
371
372pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
374 matches!(pat.kind,
375 PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. })
376 if cx.qpath_res(qpath, pat.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone))
377}
378
379pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
381 is_none_pattern(cx, arm.pat)
382 && matches!(
383 peel_blocks(arm.body).kind,
384 ExprKind::Path(qpath)
385 if cx.qpath_res(&qpath, arm.body.hir_id).ctor_parent(cx).is_lang_item(cx, OptionNone)
386 )
387}
388
389pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
391 match *qpath {
392 QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)),
393 QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath),
394 QPath::TypeRelative(..) => false,
395 }
396}
397
398pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
400 if let Node::Item(item) = cx.tcx.parent_hir_node(cx.tcx.local_def_id_to_hir_id(def_id))
401 && let ItemKind::Impl(imp) = item.kind
402 {
403 imp.of_trait.is_some()
404 } else {
405 false
406 }
407}
408
409pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
410 match *path {
411 QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
412 QPath::TypeRelative(_, seg) => seg,
413 }
414}
415
416pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
417 last_path_segment(qpath)
418 .args
419 .map_or(&[][..], |a| a.args)
420 .iter()
421 .filter_map(|a| match a {
422 GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
423 _ => None,
424 })
425}
426
427pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
432 match expr.kind {
433 ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
434 ExprKind::Path(QPath::Resolved(
435 _,
436 Path {
437 res: Res::Local(local), ..
438 },
439 )) => Some(*local),
440 _ => None,
441 }
442}
443
444pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, owner: OwnerId) -> Option<&'tcx TraitRef<'tcx>> {
460 if let Node::Item(item) = cx.tcx.hir_node(cx.tcx.hir_owner_parent(owner))
461 && let ItemKind::Impl(impl_) = &item.kind
462 && let Some(of_trait) = impl_.of_trait
463 {
464 return Some(&of_trait.trait_ref);
465 }
466 None
467}
468
469fn projection_stack<'a, 'hir>(
477 mut e: &'a Expr<'hir>,
478 ctxt: SyntaxContext,
479) -> Option<(Vec<&'a Expr<'hir>>, &'a Expr<'hir>)> {
480 let mut result = vec![];
481 let root = loop {
482 match e.kind {
483 ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) if e.span.ctxt() == ctxt => {
484 result.push(e);
485 e = ep;
486 },
487 ExprKind::Index(..) | ExprKind::Field(..) => return None,
488 _ => break e,
489 }
490 };
491 result.reverse();
492 Some((result, root))
493}
494
495pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> {
497 cx.typeck_results()
498 .expr_adjustments(e)
499 .iter()
500 .find_map(|a| match a.kind {
501 Adjust::Deref(DerefAdjustKind::Overloaded(d)) => Some(Some(d.mutbl)),
502 Adjust::Deref(DerefAdjustKind::Builtin) => None,
503 _ => Some(None),
504 })
505 .and_then(|x| x)
506}
507
508pub fn can_mut_borrow_both(cx: &LateContext<'_>, ctxt: SyntaxContext, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
511 let Some((s1, r1)) = projection_stack(e1, ctxt) else {
512 return false;
513 };
514 let Some((s2, r2)) = projection_stack(e2, ctxt) else {
515 return false;
516 };
517 if !eq_expr_value(cx, ctxt, r1, r2) {
518 return true;
519 }
520 if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
521 return false;
522 }
523
524 for (x1, x2) in zip(&s1, &s2) {
525 if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
526 return false;
527 }
528
529 match (&x1.kind, &x2.kind) {
530 (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
531 if i1 != i2 {
532 return true;
533 }
534 },
535 _ => return false,
536 }
537 }
538 false
539}
540
541fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
544 let std_types_symbols = &[
545 sym::Vec,
546 sym::VecDeque,
547 sym::LinkedList,
548 sym::HashMap,
549 sym::BTreeMap,
550 sym::HashSet,
551 sym::BTreeSet,
552 sym::BinaryHeap,
553 ];
554
555 if let QPath::TypeRelative(_, method) = path
556 && method.ident.name == sym::new
557 && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id)
558 && let Some(adt) = cx
559 .tcx
560 .type_of(impl_did)
561 .instantiate_identity()
562 .skip_norm_wip()
563 .ty_adt_def()
564 {
565 return Some(adt.did()) == cx.tcx.lang_items().string()
566 || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name));
567 }
568 false
569}
570
571pub fn is_default_equivalent_call(
573 cx: &LateContext<'_>,
574 repl_func: &Expr<'_>,
575 whole_call_expr: Option<&Expr<'_>>,
576) -> bool {
577 if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
578 && let Some(repl_def) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def(cx)
579 && (repl_def.assoc_fn_parent(cx).is_diag_item(cx, sym::Default)
580 || is_default_equivalent_ctor(cx, repl_def.1, repl_func_qpath))
581 {
582 return true;
583 }
584
585 let Some(e) = whole_call_expr else { return false };
588 let Some(default_fn_def_id) = cx.tcx.get_diagnostic_item(sym::default_fn) else {
589 return false;
590 };
591 let Some(ty) = cx.tcx.typeck(e.hir_id.owner.def_id).expr_ty_adjusted_opt(e) else {
592 return false;
593 };
594 let args = rustc_ty::GenericArgs::for_item(cx.tcx, default_fn_def_id, |param, _| {
595 if let rustc_ty::GenericParamDefKind::Lifetime = param.kind {
596 cx.tcx.lifetimes.re_erased.into()
597 } else if param.index == 0 && param.name == kw::SelfUpper {
598 ty.into()
599 } else {
600 param.to_error(cx.tcx)
601 }
602 });
603 let instance = rustc_ty::Instance::try_resolve(cx.tcx, cx.typing_env(), default_fn_def_id, args);
604
605 let Ok(Some(instance)) = instance else { return false };
606 if let rustc_ty::InstanceKind::Item(def) = instance.def
607 && !cx.tcx.is_mir_available(def)
608 {
609 return false;
610 }
611 let ExprKind::Path(ref repl_func_qpath) = repl_func.kind else {
612 return false;
613 };
614 let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() else {
615 return false;
616 };
617
618 let body = cx.tcx.instance_mir(instance.def);
624 for block_data in body.basic_blocks.iter() {
625 if block_data.statements.len() == 1
626 && let StatementKind::Assign(assign) = &block_data.statements[0].kind
627 && assign.0.local == RETURN_PLACE
628 && let Rvalue::Aggregate(kind, _places) = &assign.1
629 && let AggregateKind::Adt(did, variant_index, _, _, _) = **kind
630 && let def = cx.tcx.adt_def(did)
631 && let variant = &def.variant(variant_index)
632 && variant.fields.is_empty()
633 && let Some((_, did)) = variant.ctor
634 && did == repl_def_id
635 {
636 return true;
637 } else if block_data.statements.is_empty()
638 && let Some(term) = &block_data.terminator
639 {
640 match &term.kind {
641 TerminatorKind::Call {
642 func: Operand::Constant(c),
643 ..
644 } if let rustc_ty::FnDef(did, _args) = c.ty().kind()
645 && *did == repl_def_id =>
646 {
647 return true;
648 },
649 TerminatorKind::TailCall {
650 func: Operand::Constant(c),
651 ..
652 } if let rustc_ty::FnDef(did, _args) = c.ty().kind()
653 && *did == repl_def_id =>
654 {
655 return true;
656 },
657 _ => {},
658 }
659 }
660 }
661 false
662}
663
664pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
668 match &e.kind {
669 ExprKind::Lit(lit) => match lit.node {
670 LitKind::Bool(false) | LitKind::Int(Pu128(0), _) => true,
671 LitKind::Str(s, _) => s.is_empty(),
672 _ => false,
673 },
674 ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
675 ExprKind::Repeat(x, len) => {
676 if let ConstArgKind::Anon(anon_const) = len.kind
677 && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind
678 && let LitKind::Int(v, _) = const_lit.node
679 && v <= 32
680 && is_default_equivalent(cx, x)
681 {
682 true
683 } else {
684 false
685 }
686 },
687 ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func, Some(e)),
688 ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg),
689 ExprKind::Path(qpath) => cx
690 .qpath_res(qpath, e.hir_id)
691 .ctor_parent(cx)
692 .is_lang_item(cx, OptionNone),
693 ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
694 ExprKind::Block(Block { stmts: [], expr, .. }, _) => expr.is_some_and(|e| is_default_equivalent(cx, e)),
695 _ => false,
696 }
697}
698
699fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool {
700 if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind
701 && seg.ident.name == sym::from
702 {
703 match arg.kind {
704 ExprKind::Lit(hir::Lit {
705 node: LitKind::Str(sym, _),
706 ..
707 }) => return sym.is_empty() && ty.basic_res().is_lang_item(cx, LangItem::String),
708 ExprKind::Array([]) => return ty.basic_res().is_diag_item(cx, sym::Vec),
709 ExprKind::Repeat(_, len) => {
710 if let ConstArgKind::Anon(anon_const) = len.kind
711 && let ExprKind::Lit(const_lit) = cx.tcx.hir_body(anon_const.body).value.kind
712 && let LitKind::Int(v, _) = const_lit.node
713 {
714 return v == 0 && ty.basic_res().is_diag_item(cx, sym::Vec);
715 }
716 },
717 _ => (),
718 }
719 }
720 false
721}
722
723pub fn can_move_expr_to_closure_no_visit<'tcx>(
755 cx: &LateContext<'tcx>,
756 expr: &'tcx Expr<'_>,
757 loop_ids: &[HirId],
758 ignore_locals: &HirIdSet,
759) -> bool {
760 match expr.kind {
761 ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
762 | ExprKind::Continue(Destination { target_id: Ok(id), .. })
763 if loop_ids.contains(&id) =>
764 {
765 true
766 },
767 ExprKind::Break(..)
768 | ExprKind::Continue(_)
769 | ExprKind::Ret(_)
770 | ExprKind::Yield(..)
771 | ExprKind::InlineAsm(_) => false,
772 ExprKind::Field(
775 &Expr {
776 hir_id,
777 kind:
778 ExprKind::Path(QPath::Resolved(
779 _,
780 Path {
781 res: Res::Local(local_id),
782 ..
783 },
784 )),
785 ..
786 },
787 _,
788 ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
789 false
791 },
792 _ => true,
793 }
794}
795
796#[derive(Debug, Clone, Copy, PartialEq, Eq)]
798pub enum CaptureKind {
799 Value,
800 Use,
801 Ref(Mutability),
802}
803impl CaptureKind {
804 pub fn is_imm_ref(self) -> bool {
805 self == Self::Ref(Mutability::Not)
806 }
807}
808impl std::ops::BitOr for CaptureKind {
809 type Output = Self;
810 fn bitor(self, rhs: Self) -> Self::Output {
811 match (self, rhs) {
812 (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
813 (CaptureKind::Use, _) | (_, CaptureKind::Use) => CaptureKind::Use,
814 (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
815 | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
816 (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
817 }
818 }
819}
820impl std::ops::BitOrAssign for CaptureKind {
821 fn bitor_assign(&mut self, rhs: Self) {
822 *self = *self | rhs;
823 }
824}
825
826pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind {
832 fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
833 let mut capture = CaptureKind::Ref(Mutability::Not);
834 pat.each_binding_or_first(&mut |_, id, span, _| match cx
835 .typeck_results()
836 .extract_binding_mode(cx.sess(), id, span)
837 .0
838 {
839 ByRef::No if !is_copy(cx, cx.typeck_results().node_type(id)) => {
840 capture = CaptureKind::Value;
841 },
842 ByRef::Yes(_, Mutability::Mut) if capture != CaptureKind::Value => {
843 capture = CaptureKind::Ref(Mutability::Mut);
844 },
845 _ => (),
846 });
847 capture
848 }
849
850 debug_assert!(matches!(
851 e.kind,
852 ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
853 ));
854
855 let mut capture = CaptureKind::Value;
856 let mut capture_expr_ty = e;
857
858 for (parent, child_id) in hir_parent_with_src_iter(cx.tcx, e.hir_id) {
859 if let [
860 Adjustment {
861 kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
862 target,
863 },
864 ref adjust @ ..,
865 ] = *cx
866 .typeck_results()
867 .adjustments()
868 .get(child_id)
869 .map_or(&[][..], |x| &**x)
870 && let rustc_ty::RawPtr(_, mutability) | rustc_ty::Ref(_, _, mutability) =
871 *adjust.last().map_or(target, |a| a.target).kind()
872 {
873 return CaptureKind::Ref(mutability);
874 }
875
876 match parent {
877 Node::Expr(e) => match e.kind {
878 ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
879 ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
880 ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
881 return CaptureKind::Ref(Mutability::Mut);
882 },
883 ExprKind::Field(..) => {
884 if capture == CaptureKind::Value {
885 capture_expr_ty = e;
886 }
887 },
888 ExprKind::Let(let_expr) => {
889 let mutability = match pat_capture_kind(cx, let_expr.pat) {
890 CaptureKind::Value | CaptureKind::Use => Mutability::Not,
891 CaptureKind::Ref(m) => m,
892 };
893 return CaptureKind::Ref(mutability);
894 },
895 ExprKind::Match(_, arms, _) => {
896 let mut mutability = Mutability::Not;
897 for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
898 match capture {
899 CaptureKind::Value | CaptureKind::Use => break,
900 CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
901 CaptureKind::Ref(Mutability::Not) => (),
902 }
903 }
904 return CaptureKind::Ref(mutability);
905 },
906 _ => break,
907 },
908 Node::LetStmt(l) => match pat_capture_kind(cx, l.pat) {
909 CaptureKind::Value | CaptureKind::Use => break,
910 capture @ CaptureKind::Ref(_) => return capture,
911 },
912 _ => break,
913 }
914 }
915
916 if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
917 CaptureKind::Ref(Mutability::Not)
919 } else {
920 capture
921 }
922}
923
924pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
927 struct V<'cx, 'tcx> {
928 cx: &'cx LateContext<'tcx>,
929 loops: Vec<HirId>,
931 locals: HirIdSet,
933 allow_closure: bool,
935 captures: HirIdMap<CaptureKind>,
938 }
939 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
940 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
941 if !self.allow_closure {
942 return;
943 }
944
945 match e.kind {
946 ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
947 if !self.locals.contains(&l) {
948 let cap = capture_local_usage(self.cx, e);
949 self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
950 }
951 },
952 ExprKind::Closure(closure) => {
953 for capture in self.cx.typeck_results().closure_min_captures_flattened(closure.def_id) {
954 let local_id = match capture.place.base {
955 PlaceBase::Local(id) => id,
956 PlaceBase::Upvar(var) => var.var_path.hir_id,
957 _ => continue,
958 };
959 if !self.locals.contains(&local_id) {
960 let capture = match capture.info.capture_kind {
961 UpvarCapture::ByValue => CaptureKind::Value,
962 UpvarCapture::ByUse => CaptureKind::Use,
963 UpvarCapture::ByRef(kind) => match kind {
964 BorrowKind::Immutable => CaptureKind::Ref(Mutability::Not),
965 BorrowKind::UniqueImmutable | BorrowKind::Mutable => {
966 CaptureKind::Ref(Mutability::Mut)
967 },
968 },
969 };
970 self.captures
971 .entry(local_id)
972 .and_modify(|e| *e |= capture)
973 .or_insert(capture);
974 }
975 }
976 },
977 ExprKind::Loop(b, ..) => {
978 self.loops.push(e.hir_id);
979 self.visit_block(b);
980 self.loops.pop();
981 },
982 _ => {
983 self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
984 walk_expr(self, e);
985 },
986 }
987 }
988
989 fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
990 p.each_binding_or_first(&mut |_, id, _, _| {
991 self.locals.insert(id);
992 });
993 }
994 }
995
996 let mut v = V {
997 cx,
998 loops: Vec::new(),
999 locals: HirIdSet::default(),
1000 allow_closure: true,
1001 captures: HirIdMap::default(),
1002 };
1003 v.visit_expr(expr);
1004 v.allow_closure.then_some(v.captures)
1005}
1006
1007pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>;
1009
1010pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) {
1013 let mut method_names = Vec::with_capacity(max_depth);
1014 let mut arg_lists = Vec::with_capacity(max_depth);
1015 let mut spans = Vec::with_capacity(max_depth);
1016
1017 let mut current = expr;
1018 for _ in 0..max_depth {
1019 if let ExprKind::MethodCall(path, receiver, args, _) = ¤t.kind {
1020 if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
1021 break;
1022 }
1023 method_names.push(path.ident.name);
1024 arg_lists.push((*receiver, &**args));
1025 spans.push(path.ident.span);
1026 current = receiver;
1027 } else {
1028 break;
1029 }
1030 }
1031
1032 (method_names, arg_lists, spans)
1033}
1034
1035pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[Symbol]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> {
1042 let mut current = expr;
1043 let mut matched = Vec::with_capacity(methods.len());
1044 for method_name in methods.iter().rev() {
1045 if let ExprKind::MethodCall(path, receiver, args, _) = current.kind {
1047 if path.ident.name == *method_name {
1048 if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) {
1049 return None;
1050 }
1051 matched.push((receiver, args)); current = receiver; } else {
1054 return None;
1055 }
1056 } else {
1057 return None;
1058 }
1059 }
1060 matched.reverse();
1062 Some(matched)
1063}
1064
1065pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
1067 cx.tcx
1068 .entry_fn(())
1069 .is_some_and(|(entry_fn_def_id, _)| def_id == entry_fn_def_id)
1070}
1071
1072pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
1074 let parent = cx.tcx.hir_get_parent_item(e.hir_id);
1075 Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl()
1076}
1077
1078pub fn parent_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
1080 let parent_id = cx.tcx.hir_get_parent_item(expr.hir_id).def_id;
1081 match cx.tcx.hir_node_by_def_id(parent_id) {
1082 Node::Item(item) => item.kind.ident().map(|ident| ident.name),
1083 Node::TraitItem(TraitItem { ident, .. }) | Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name),
1084 _ => None,
1085 }
1086}
1087
1088pub struct ContainsName<'a, 'tcx> {
1089 pub cx: &'a LateContext<'tcx>,
1090 pub name: Symbol,
1091}
1092
1093impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> {
1094 type Result = ControlFlow<()>;
1095 type NestedFilter = nested_filter::OnlyBodies;
1096
1097 fn visit_name(&mut self, name: Symbol) -> Self::Result {
1098 if self.name == name {
1099 ControlFlow::Break(())
1100 } else {
1101 ControlFlow::Continue(())
1102 }
1103 }
1104
1105 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
1106 self.cx.tcx
1107 }
1108}
1109
1110pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
1112 let mut cn = ContainsName { cx, name };
1113 cn.visit_expr(expr).is_break()
1114}
1115
1116pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
1118 for_each_expr_without_closures(expr, |e| {
1119 if matches!(e.kind, ExprKind::Ret(..)) {
1120 ControlFlow::Break(())
1121 } else {
1122 ControlFlow::Continue(())
1123 }
1124 })
1125 .is_some()
1126}
1127
1128pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
1130 get_parent_expr_for_hir(cx, e.hir_id)
1131}
1132
1133pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
1136 match cx.tcx.parent_hir_node(hir_id) {
1137 Node::Expr(parent) => Some(parent),
1138 _ => None,
1139 }
1140}
1141
1142pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
1144 let enclosing_node = cx
1145 .tcx
1146 .hir_get_enclosing_scope(hir_id)
1147 .map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
1148 enclosing_node.and_then(|node| match node {
1149 Node::Block(block) => Some(block),
1150 Node::Item(&Item {
1151 kind: ItemKind::Fn { body: eid, .. },
1152 ..
1153 })
1154 | Node::ImplItem(&ImplItem {
1155 kind: ImplItemKind::Fn(_, eid),
1156 ..
1157 })
1158 | Node::TraitItem(&TraitItem {
1159 kind: TraitItemKind::Fn(_, TraitFn::Provided(eid)),
1160 ..
1161 }) => match cx.tcx.hir_body(eid).value.kind {
1162 ExprKind::Block(block, _) => Some(block),
1163 _ => None,
1164 },
1165 _ => None,
1166 })
1167}
1168
1169pub fn get_enclosing_closure<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Closure<'tcx>> {
1171 cx.tcx.hir_parent_iter(hir_id).find_map(|(_, node)| {
1172 if let Node::Expr(expr) = node
1173 && let ExprKind::Closure(closure) = expr.kind
1174 {
1175 Some(closure)
1176 } else {
1177 None
1178 }
1179 })
1180}
1181
1182pub fn is_upvar_in_closure(cx: &LateContext<'_>, closure: &Closure<'_>, local_id: HirId) -> bool {
1184 cx.typeck_results()
1185 .closure_min_captures
1186 .get(&closure.def_id)
1187 .is_some_and(|x| x.contains_key(&local_id))
1188}
1189
1190pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
1192 cx: &LateContext<'tcx>,
1193 expr: &Expr<'_>,
1194) -> Option<&'tcx Expr<'tcx>> {
1195 for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
1196 match node {
1197 Node::Expr(e) => match e.kind {
1198 ExprKind::Closure { .. }
1199 if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
1200 && subs.as_closure().kind() == ClosureKind::FnOnce => {},
1201
1202 ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
1204 _ => (),
1205 },
1206 Node::Stmt(_) | Node::Block(_) | Node::LetStmt(_) | Node::Arm(_) | Node::ExprField(_) => (),
1207 _ => break,
1208 }
1209 }
1210 None
1211}
1212
1213pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
1215 match tcx.hir_parent_iter(id).next() {
1216 Some((
1217 _,
1218 Node::Item(Item {
1219 kind: ItemKind::Impl(imp),
1220 ..
1221 }),
1222 )) => Some(imp),
1223 _ => None,
1224 }
1225}
1226
1227pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
1238 while let ExprKind::Block(
1239 Block {
1240 stmts: [],
1241 expr: Some(inner),
1242 rules: BlockCheckMode::DefaultBlock,
1243 ..
1244 },
1245 _,
1246 ) = expr.kind
1247 {
1248 expr = inner;
1249 }
1250 expr
1251}
1252
1253pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> {
1264 while let ExprKind::Block(
1265 Block {
1266 stmts: [],
1267 expr: Some(inner),
1268 rules: BlockCheckMode::DefaultBlock,
1269 ..
1270 }
1271 | Block {
1272 stmts:
1273 [
1274 Stmt {
1275 kind: StmtKind::Expr(inner) | StmtKind::Semi(inner),
1276 ..
1277 },
1278 ],
1279 expr: None,
1280 rules: BlockCheckMode::DefaultBlock,
1281 ..
1282 },
1283 _,
1284 ) = expr.kind
1285 {
1286 expr = inner;
1287 }
1288 expr
1289}
1290
1291pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1293 let mut iter = tcx.hir_parent_iter(expr.hir_id);
1294 match iter.next() {
1295 Some((
1296 _,
1297 Node::Expr(Expr {
1298 kind: ExprKind::If(_, _, Some(else_expr)),
1299 ..
1300 }),
1301 )) => else_expr.hir_id == expr.hir_id,
1302 _ => false,
1303 }
1304}
1305
1306pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1309 hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
1310 matches!(
1311 node,
1312 Node::LetStmt(LetStmt {
1313 init: Some(init),
1314 els: Some(els),
1315 ..
1316 })
1317 if init.hir_id == child_id || els.hir_id == child_id
1318 )
1319 })
1320}
1321
1322pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1324 hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
1325 matches!(
1326 node,
1327 Node::LetStmt(LetStmt { els: Some(els), .. })
1328 if els.hir_id == child_id
1329 )
1330 })
1331}
1332
1333pub fn is_full_collection_range(cx: &LateContext<'_>, container: Option<HirId>, expr: &Expr<'_>) -> bool {
1335 if let Some(Range { start, end, ty, .. }) = Range::hir(cx, expr) {
1336 start.is_none_or(|start| is_integer_literal(start, 0))
1337 && end.is_none_or(|end| {
1338 if ty.limits() == RangeLimits::HalfOpen
1339 && let Some(container) = container
1340 && let ExprKind::MethodCall(seg, recv, [], _) = end.kind
1341 {
1342 seg.ident.name == sym::len && recv.res_local_id() == Some(container)
1343 } else {
1344 false
1345 }
1346 })
1347 } else {
1348 false
1349 }
1350}
1351
1352pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
1354 if let ExprKind::Lit(spanned) = expr.kind
1355 && let LitKind::Int(v, _) = spanned.node
1356 {
1357 return v == value;
1358 }
1359 false
1360}
1361
1362pub fn is_integer_literal_untyped(expr: &Expr<'_>) -> bool {
1364 if let ExprKind::Lit(spanned) = expr.kind
1365 && let LitKind::Int(_, suffix) = spanned.node
1366 {
1367 return suffix == LitIntType::Unsuffixed;
1368 }
1369
1370 false
1371}
1372
1373pub fn is_float_literal(expr: &Expr<'_>, value: f64) -> bool {
1375 if let ExprKind::Lit(spanned) = expr.kind
1376 && let LitKind::Float(v, _) = spanned.node
1377 {
1378 v.as_str().parse() == Ok(value)
1379 } else {
1380 false
1381 }
1382}
1383
1384pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
1392 cx.typeck_results().adjustments().get(e.hir_id).is_some()
1393}
1394
1395#[must_use]
1399pub fn is_expn_of(mut span: Span, name: Symbol) -> Option<Span> {
1400 loop {
1401 if span.from_expansion() {
1402 let data = span.ctxt().outer_expn_data();
1403 let new_span = data.call_site;
1404
1405 if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind
1406 && mac_name == name
1407 {
1408 return Some(new_span);
1409 }
1410
1411 span = new_span;
1412 } else {
1413 return None;
1414 }
1415 }
1416}
1417
1418#[must_use]
1429pub fn is_direct_expn_of(span: Span, name: Symbol) -> Option<Span> {
1430 if span.from_expansion() {
1431 let data = span.ctxt().outer_expn_data();
1432 let new_span = data.call_site;
1433
1434 if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind
1435 && mac_name == name
1436 {
1437 return Some(new_span);
1438 }
1439 }
1440
1441 None
1442}
1443
1444pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> {
1446 let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip().output();
1447 cx.tcx.instantiate_bound_regions_with_erased(ret_ty)
1448}
1449
1450pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> {
1452 let arg = cx
1453 .tcx
1454 .fn_sig(fn_def_id)
1455 .instantiate_identity()
1456 .skip_norm_wip()
1457 .input(nth);
1458 cx.tcx.instantiate_bound_regions_with_erased(arg)
1459}
1460
1461pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1463 if let ExprKind::Call(fun, _) = expr.kind
1464 && let ExprKind::Path(ref qp) = fun.kind
1465 {
1466 let res = cx.qpath_res(qp, fun.hir_id);
1467 return match res {
1468 Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
1469 Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
1470 _ => false,
1471 };
1472 }
1473 false
1474}
1475
1476pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
1479 fn is_qpath_refutable(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
1480 !matches!(
1481 cx.qpath_res(qpath, id),
1482 Res::Def(DefKind::Struct, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), _)
1483 )
1484 }
1485
1486 fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
1487 i.into_iter().any(|pat| is_refutable(cx, pat))
1488 }
1489
1490 match pat.kind {
1491 PatKind::Missing => unreachable!(),
1492 PatKind::Wild | PatKind::Never => false, PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)),
1494 PatKind::Ref(pat, _, _) => is_refutable(cx, pat),
1495 PatKind::Expr(PatExpr {
1496 kind: PatExprKind::Path(qpath),
1497 hir_id,
1498 ..
1499 }) => is_qpath_refutable(cx, qpath, *hir_id),
1500 PatKind::Or(pats) => {
1501 are_refutable(cx, pats)
1503 },
1504 PatKind::Tuple(pats, _) => are_refutable(cx, pats),
1505 PatKind::Struct(ref qpath, fields, _) => {
1506 is_qpath_refutable(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat))
1507 },
1508 PatKind::TupleStruct(ref qpath, pats, _) => {
1509 is_qpath_refutable(cx, qpath, pat.hir_id) || are_refutable(cx, pats)
1510 },
1511 PatKind::Slice(head, middle, tail) => {
1512 match &cx.typeck_results().node_type(pat.hir_id).kind() {
1513 rustc_ty::Slice(..) => {
1514 !head.is_empty() || middle.is_none() || !tail.is_empty()
1516 },
1517 rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
1518 _ => {
1519 true
1521 },
1522 }
1523 },
1524 PatKind::Expr(..) | PatKind::Range(..) | PatKind::Err(_) | PatKind::Deref(_) | PatKind::Guard(..) => true,
1525 }
1526}
1527
1528pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
1531 if let PatKind::Or(pats) = pat.kind {
1532 pats.iter().for_each(f);
1533 } else {
1534 f(pat);
1535 }
1536}
1537
1538pub fn is_self(slf: &Param<'_>) -> bool {
1539 if let PatKind::Binding(.., name, _) = slf.pat.kind {
1540 name.name == kw::SelfLower
1541 } else {
1542 false
1543 }
1544}
1545
1546pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
1547 if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind
1548 && let Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } = path.res
1549 {
1550 return true;
1551 }
1552 false
1553}
1554
1555pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
1556 (0..decl.inputs.len()).map(move |i| &body.params[i])
1557}
1558
1559pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
1562 fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
1563 if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind
1564 && ddpos.as_opt_usize().is_none()
1565 && cx
1566 .qpath_res(path, arm.pat.hir_id)
1567 .ctor_parent(cx)
1568 .is_lang_item(cx, ResultOk)
1569 && let PatKind::Binding(_, hir_id, _, None) = pat[0].kind
1570 && arm.body.res_local_id() == Some(hir_id)
1571 {
1572 return true;
1573 }
1574 false
1575 }
1576
1577 fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
1578 if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
1579 cx.qpath_res(path, arm.pat.hir_id)
1580 .ctor_parent(cx)
1581 .is_lang_item(cx, ResultErr)
1582 } else {
1583 false
1584 }
1585 }
1586
1587 if let ExprKind::Match(_, arms, ref source) = expr.kind {
1588 if let MatchSource::TryDesugar(_) = *source {
1590 return Some(expr);
1591 }
1592
1593 if arms.len() == 2
1594 && arms[0].guard.is_none()
1595 && arms[1].guard.is_none()
1596 && ((is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])))
1597 {
1598 return Some(expr);
1599 }
1600 }
1601
1602 None
1603}
1604
1605pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool {
1615 let mut suppress_lint = false;
1616
1617 for id in ids {
1618 let level_spec = cx.tcx.lint_level_spec_at_node(lint, id);
1619 if let Some(expectation) = level_spec.lint_id() {
1620 cx.fulfill_expectation(expectation);
1621 }
1622
1623 match level_spec.level() {
1624 Level::Allow | Level::Expect => suppress_lint = true,
1625 Level::Warn | Level::ForceWarn | Level::Deny | Level::Forbid => {},
1626 }
1627 }
1628
1629 suppress_lint
1630}
1631
1632pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
1640 cx.tcx.lint_level_spec_at_node(lint, id).is_allow()
1641}
1642
1643pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
1644 while let PatKind::Ref(subpat, _, _) = pat.kind {
1645 pat = subpat;
1646 }
1647 pat
1648}
1649
1650pub fn int_bits(tcx: TyCtxt<'_>, ity: IntTy) -> u64 {
1651 Integer::from_int_ty(&tcx, ity).size().bits()
1652}
1653
1654#[expect(clippy::cast_possible_wrap)]
1655pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: IntTy) -> i128 {
1657 let amt = 128 - int_bits(tcx, ity);
1658 ((u as i128) << amt) >> amt
1659}
1660
1661#[expect(clippy::cast_sign_loss)]
1662pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: IntTy) -> u128 {
1664 let amt = 128 - int_bits(tcx, ity);
1665 ((u as u128) << amt) >> amt
1666}
1667
1668pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: UintTy) -> u128 {
1670 let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
1671 let amt = 128 - bits;
1672 (u << amt) >> amt
1673}
1674
1675pub fn has_attr(attrs: &[hir::Attribute], symbol: Symbol) -> bool {
1676 attrs.iter().any(|attr| attr.has_name(symbol))
1677}
1678
1679pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
1680 find_attr!(cx.tcx, hir_id, Repr { .. })
1681}
1682
1683pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
1684 let mut prev_enclosing_node = None;
1685 let mut enclosing_node = node;
1686 while Some(enclosing_node) != prev_enclosing_node {
1687 if has_attr(tcx.hir_attrs(enclosing_node), symbol) {
1688 return true;
1689 }
1690 prev_enclosing_node = Some(enclosing_node);
1691 enclosing_node = tcx.hir_get_parent_item(enclosing_node).into();
1692 }
1693
1694 false
1695}
1696
1697pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
1700 tcx.hir_parent_owner_iter(id)
1701 .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
1702 .any(|(id, _)| find_attr!(tcx, id.def_id, AutomaticallyDerived))
1703}
1704
1705pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: Symbol) -> bool {
1707 cx.tcx.crate_name(did.krate) == sym::libc && cx.tcx.def_path_str(did).ends_with(name.as_str())
1711}
1712
1713pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
1718 let mut conds = Vec::new();
1719 let mut blocks: Vec<&Block<'_>> = Vec::new();
1720
1721 while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
1722 conds.push(cond);
1723 if let ExprKind::Block(block, _) = then.kind {
1724 blocks.push(block);
1725 } else {
1726 panic!("ExprKind::If node is not an ExprKind::Block");
1727 }
1728
1729 if let Some(else_expr) = r#else {
1730 expr = else_expr;
1731 } else {
1732 break;
1733 }
1734 }
1735
1736 if !blocks.is_empty()
1738 && let ExprKind::Block(block, _) = expr.kind
1739 {
1740 blocks.push(block);
1741 }
1742
1743 (conds, blocks)
1744}
1745
1746pub fn get_async_closure_expr<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
1748 if let ExprKind::Closure(&Closure {
1749 body,
1750 kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
1751 ..
1752 }) = expr.kind
1753 && let ExprKind::Block(
1754 Block {
1755 expr:
1756 Some(Expr {
1757 kind: ExprKind::DropTemps(inner_expr),
1758 ..
1759 }),
1760 ..
1761 },
1762 _,
1763 ) = tcx.hir_body(body).value.kind
1764 {
1765 Some(inner_expr)
1766 } else {
1767 None
1768 }
1769}
1770
1771pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
1773 get_async_closure_expr(tcx, body.value)
1774}
1775
1776pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1778 let did = match expr.kind {
1779 ExprKind::Call(path, _) => {
1780 if let ExprKind::Path(ref qpath) = path.kind
1781 && let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
1782 {
1783 Some(did)
1784 } else {
1785 None
1786 }
1787 },
1788 ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
1789 _ => None,
1790 };
1791
1792 did.is_some_and(|did| find_attr!(cx.tcx, did, MustUse { .. }))
1793}
1794
1795fn is_body_identity_function<'hir>(cx: &LateContext<'_>, func: &Body<'hir>) -> bool {
1809 let [param] = func.params else {
1810 return false;
1811 };
1812
1813 let mut param_pat = param.pat;
1814
1815 let mut advance_param_pat_over_stmts = |stmts: &[Stmt<'hir>]| {
1822 for stmt in stmts {
1823 if let StmtKind::Let(local) = stmt.kind
1824 && let Some(init) = local.init
1825 && is_expr_identity_of_pat(cx, param_pat, init, true)
1826 {
1827 param_pat = local.pat;
1828 } else {
1829 return false;
1830 }
1831 }
1832
1833 true
1834 };
1835
1836 let mut expr = func.value;
1837 loop {
1838 match expr.kind {
1839 ExprKind::Block(
1840 &Block {
1841 stmts: [],
1842 expr: Some(e),
1843 ..
1844 },
1845 _,
1846 )
1847 | ExprKind::Ret(Some(e)) => expr = e,
1848 ExprKind::Block(
1849 &Block {
1850 stmts: [stmt],
1851 expr: None,
1852 ..
1853 },
1854 _,
1855 ) => {
1856 if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind
1857 && let ExprKind::Ret(Some(ret_val)) = e.kind
1858 {
1859 expr = ret_val;
1860 } else {
1861 return false;
1862 }
1863 },
1864 ExprKind::Block(
1865 &Block {
1866 stmts, expr: Some(e), ..
1867 },
1868 _,
1869 ) => {
1870 if !advance_param_pat_over_stmts(stmts) {
1871 return false;
1872 }
1873
1874 expr = e;
1875 },
1876 ExprKind::Block(&Block { stmts, expr: None, .. }, _) => {
1877 if let Some((last_stmt, stmts)) = stmts.split_last()
1878 && advance_param_pat_over_stmts(stmts)
1879 && let StmtKind::Semi(e) | StmtKind::Expr(e) = last_stmt.kind
1880 && let ExprKind::Ret(Some(ret_val)) = e.kind
1881 {
1882 expr = ret_val;
1883 } else {
1884 return false;
1885 }
1886 },
1887 _ => return is_expr_identity_of_pat(cx, param_pat, expr, true),
1888 }
1889 }
1890}
1891
1892pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>, by_hir: bool) -> bool {
1902 if cx
1903 .typeck_results()
1904 .pat_binding_modes()
1905 .get(pat.hir_id)
1906 .is_some_and(|mode| matches!(mode.0, ByRef::Yes(..)))
1907 {
1908 return false;
1912 }
1913
1914 let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir);
1916
1917 match (pat.kind, expr.kind) {
1918 (PatKind::Binding(_, id, _, _), _) if by_hir => {
1919 expr.res_local_id() == Some(id) && cx.typeck_results().expr_adjustments(expr).is_empty()
1920 },
1921 (PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => {
1922 matches!(path.segments, [ segment] if segment.ident.name == ident.name)
1923 },
1924 (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
1925 if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
1926 {
1927 over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1928 },
1929 (PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
1930 zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1931 },
1932 (PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields))
1933 if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() =>
1934 {
1935 if let ExprKind::Path(ident) = &ident.kind
1937 && qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1938 && over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir))
1940 {
1941 true
1942 } else {
1943 false
1944 }
1945 },
1946 (PatKind::Struct(pat_ident, field_pats, None), ExprKind::Struct(ident, fields, hir::StructTailExpr::None))
1947 if field_pats.len() == fields.len() =>
1948 {
1949 qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1951 && unordered_over(field_pats, fields, |field_pat, field| {
1953 field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
1954 })
1955 },
1956 _ => false,
1957 }
1958}
1959
1960pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1965 match expr.kind {
1966 ExprKind::Closure(&Closure { body, fn_decl, .. })
1967 if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer(()))) =>
1968 {
1969 is_body_identity_function(cx, cx.tcx.hir_body(body))
1970 },
1971 ExprKind::Path(QPath::Resolved(_, path))
1972 if path.segments.iter().all(|seg| seg.infer_args)
1973 && let Some(did) = path.res.opt_def_id() =>
1974 {
1975 cx.tcx.is_diagnostic_item(sym::convert_identity, did)
1976 },
1977 _ => false,
1978 }
1979}
1980
1981pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
1990 match expr.kind {
1991 ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir_body(body)),
1992 _ => expr.basic_res().is_diag_item(cx, sym::convert_identity),
1993 }
1994}
1995
1996pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
1999 for (node, child_id) in hir_parent_with_src_iter(tcx, expr.hir_id) {
2000 match node {
2001 Node::Block(_) => {},
2002 Node::Arm(arm) if arm.body.hir_id == child_id => {},
2003 Node::Expr(expr) => match expr.kind {
2004 ExprKind::Block(..) | ExprKind::DropTemps(_) => {},
2005 ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => {},
2006 ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => return None,
2007 _ => return Some((Node::Expr(expr), child_id)),
2008 },
2009 node => return Some((node, child_id)),
2010 }
2011 }
2012 None
2013}
2014
2015pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
2017 !matches!(
2018 get_expr_use_or_unification_node(tcx, expr),
2019 None | Some((
2020 Node::Stmt(Stmt {
2021 kind: StmtKind::Expr(_)
2022 | StmtKind::Semi(_)
2023 | StmtKind::Let(LetStmt {
2024 pat: Pat {
2025 kind: PatKind::Wild,
2026 ..
2027 },
2028 ..
2029 }),
2030 ..
2031 }),
2032 _
2033 ))
2034 )
2035}
2036
2037pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
2039 matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..))
2040}
2041
2042pub fn is_expr_temporary_value(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2046 !expr.is_place_expr(|base| {
2047 cx.typeck_results()
2048 .adjustments()
2049 .get(base.hir_id)
2050 .is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
2051 })
2052}
2053
2054pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
2055 if is_no_core_crate(cx) {
2056 None
2057 } else if is_no_std_crate(cx) {
2058 Some("core")
2059 } else {
2060 Some("std")
2061 }
2062}
2063
2064pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
2065 find_attr!(cx.tcx, crate, NoStd)
2066}
2067
2068pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
2069 find_attr!(cx.tcx, crate, NoCore)
2070}
2071
2072pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
2082 if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) {
2083 matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }))
2084 } else {
2085 false
2086 }
2087}
2088
2089pub fn fn_has_unsatisfiable_clauses(cx: &LateContext<'_>, did: DefId) -> bool {
2099 use rustc_trait_selection::traits;
2100 let clauses = cx
2101 .tcx
2102 .clauses_of(did)
2103 .clauses
2104 .iter()
2105 .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
2106 traits::impossible_clauses(cx.tcx, traits::elaborate(cx.tcx, clauses).collect::<Vec<_>>())
2107}
2108
2109pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
2111 fn_def_id_with_node_args(cx, expr).map(|(did, _)| did)
2112}
2113
2114pub fn fn_def_id_with_node_args<'tcx>(
2117 cx: &LateContext<'tcx>,
2118 expr: &Expr<'_>,
2119) -> Option<(DefId, GenericArgsRef<'tcx>)> {
2120 let typeck = cx.typeck_results();
2121 match &expr.kind {
2122 ExprKind::MethodCall(..) => Some((
2123 typeck.type_dependent_def_id(expr.hir_id)?,
2124 typeck.node_args(expr.hir_id),
2125 )),
2126 ExprKind::Call(
2127 Expr {
2128 kind: ExprKind::Path(qpath),
2129 hir_id: path_hir_id,
2130 ..
2131 },
2132 ..,
2133 ) => {
2134 if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
2137 typeck.qpath_res(qpath, *path_hir_id)
2138 {
2139 Some((id, typeck.node_args(*path_hir_id)))
2140 } else {
2141 None
2142 }
2143 },
2144 _ => None,
2145 }
2146}
2147
2148pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
2153 let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
2154 let expr_kind = expr_type.kind();
2155 let is_primitive = match expr_kind {
2156 rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type),
2157 rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
2158 if let rustc_ty::Slice(element_type) = inner_ty.kind() {
2159 is_recursively_primitive_type(*element_type)
2160 } else {
2161 unreachable!()
2162 }
2163 },
2164 _ => false,
2165 };
2166
2167 if is_primitive {
2168 match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
2171 rustc_ty::Slice(..) => return Some("slice".into()),
2172 rustc_ty::Array(..) => return Some("array".into()),
2173 rustc_ty::Tuple(..) => return Some("tuple".into()),
2174 _ => {
2175 let refs_peeled = expr_type.peel_refs();
2178 return Some(refs_peeled.walk().last().unwrap().to_string());
2179 },
2180 }
2181 }
2182 None
2183}
2184
2185pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<Vec<&T>>
2193where
2194 Hash: FnMut(&T) -> u64,
2195 Eq: FnMut(&T, &T) -> bool,
2196{
2197 match exprs {
2198 [a, b] if eq(a, b) => return vec![vec![a, b]],
2199 _ if exprs.len() <= 2 => return vec![],
2200 _ => {},
2201 }
2202
2203 let mut buckets: UnindexMap<u64, Vec<Vec<&T>>> = UnindexMap::default();
2204
2205 for expr in exprs {
2206 match buckets.entry(hash(expr)) {
2207 indexmap::map::Entry::Occupied(mut o) => {
2208 let bucket = o.get_mut();
2209 match bucket.iter_mut().find(|group| eq(expr, group[0])) {
2210 Some(group) => group.push(expr),
2211 None => bucket.push(vec![expr]),
2212 }
2213 },
2214 indexmap::map::Entry::Vacant(v) => {
2215 v.insert(vec![vec![expr]]);
2216 },
2217 }
2218 }
2219
2220 buckets
2221 .into_values()
2222 .flatten()
2223 .filter(|group| group.len() > 1)
2224 .collect()
2225}
2226
2227pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
2230 fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
2231 if let PatKind::Ref(pat, _, _) = pat.kind {
2232 peel(pat, count + 1)
2233 } else {
2234 (pat, count)
2235 }
2236 }
2237 peel(pat, 0)
2238}
2239
2240pub fn peel_hir_expr_while<'tcx>(
2242 mut expr: &'tcx Expr<'tcx>,
2243 mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
2244) -> &'tcx Expr<'tcx> {
2245 while let Some(e) = f(expr) {
2246 expr = e;
2247 }
2248 expr
2249}
2250
2251pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
2254 let mut remaining = count;
2255 let e = peel_hir_expr_while(expr, |e| match e.kind {
2256 ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
2257 remaining -= 1;
2258 Some(e)
2259 },
2260 _ => None,
2261 });
2262 (e, count - remaining)
2263}
2264
2265pub fn peel_hir_expr_unary<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
2268 let mut count: usize = 0;
2269 let mut curr_expr = expr;
2270 while let ExprKind::Unary(_, local_expr) = curr_expr.kind {
2271 count = count.wrapping_add(1);
2272 curr_expr = local_expr;
2273 }
2274 (curr_expr, count)
2275}
2276
2277pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
2280 let mut count = 0;
2281 let e = peel_hir_expr_while(expr, |e| match e.kind {
2282 ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
2283 count += 1;
2284 Some(e)
2285 },
2286 _ => None,
2287 });
2288 (e, count)
2289}
2290
2291pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) {
2294 let mut count = 0;
2295 loop {
2296 match &ty.kind {
2297 TyKind::Ref(_, ref_ty) => {
2298 ty = ref_ty.ty;
2299 count += 1;
2300 },
2301 _ => break (ty, count),
2302 }
2303 }
2304}
2305
2306pub fn peel_hir_ty_refs_and_ptrs<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
2308 match &ty.kind {
2309 TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty) => peel_hir_ty_refs_and_ptrs(mut_ty.ty),
2310 _ => ty,
2311 }
2312}
2313
2314pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
2317 loop {
2318 match expr.kind {
2319 ExprKind::AddrOf(_, _, e) => expr = e,
2320 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
2321 _ => break,
2322 }
2323 }
2324 expr
2325}
2326
2327pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> {
2330 let mut operators = Vec::new();
2331 peel_hir_expr_while(expr, |expr| match expr.kind {
2332 ExprKind::AddrOf(_, _, e) => {
2333 operators.push(expr);
2334 Some(e)
2335 },
2336 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => {
2337 operators.push(expr);
2338 Some(e)
2339 },
2340 _ => None,
2341 });
2342 operators
2343}
2344
2345pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
2346 if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
2347 && let Res::Def(_, def_id) = path.res
2348 {
2349 return find_attr!(cx.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..));
2350 }
2351 false
2352}
2353
2354static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalModId, Vec<Symbol>>>> = OnceLock::new();
2355
2356fn test_item_names(tcx: TyCtxt<'_>, module: LocalModId) -> Vec<Symbol> {
2359 let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
2360 let mut map = cache.lock().unwrap();
2361 match map.entry(module) {
2362 Entry::Occupied(entry) => entry.get().clone(),
2363 Entry::Vacant(entry) => {
2364 let mut names = Vec::new();
2365 for id in tcx.hir_module_free_items(module) {
2366 if matches!(tcx.def_kind(id.owner_id), DefKind::Static { .. })
2367 && let item = tcx.hir_item(id)
2368 && let ItemKind::Static(_mut, ident, ty, _body) = item.kind
2369 && let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
2370 && let Res::Def(DefKind::Struct, _) = path.res
2372 && find_attr!(tcx, item.hir_id(), RustcTestMarker(..))
2373 {
2374 names.push(ident.name);
2375 }
2376 }
2377 names.sort_unstable();
2378 entry.insert(names).clone()
2379 },
2380 }
2381}
2382
2383pub fn is_in_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool {
2387 let names = test_item_names(tcx, tcx.parent_module(id));
2388 if names.is_empty() {
2390 return false;
2391 }
2392 once((id, tcx.hir_node(id)))
2393 .chain(tcx.hir_parent_iter(id))
2394 .any(|(_id, node)| {
2397 if let Node::Item(item) = node
2398 && let ItemKind::Fn { ident, .. } = item.kind
2399 {
2400 return names.binary_search(&ident.name).is_ok();
2403 }
2404 false
2405 })
2406}
2407
2408pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool {
2415 let id = tcx.local_def_id_to_hir_id(fn_def_id);
2416 if let Node::Item(item) = tcx.hir_node(id)
2417 && let ItemKind::Fn { ident, .. } = item.kind
2418 {
2419 test_item_names(tcx, tcx.parent_module(id))
2420 .binary_search(&ident.name)
2421 .is_ok()
2422 } else {
2423 false
2424 }
2425}
2426
2427pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
2432 if let Some(cfgs) = find_attr!(tcx, id, CfgTrace(cfgs) => cfgs)
2433 && cfgs
2434 .iter()
2435 .any(|(cfg, _)| matches!(cfg, CfgEntry::NameValue { name: sym::test, .. }))
2436 {
2437 true
2438 } else {
2439 false
2440 }
2441}
2442
2443pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
2445 tcx.hir_parent_id_iter(id).any(|parent_id| is_cfg_test(tcx, parent_id))
2446}
2447
2448pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
2450 is_in_test_function(tcx, hir_id) || is_in_cfg_test(tcx, hir_id) || is_in_integration_test_file(tcx)
2451}
2452
2453fn is_in_integration_test_file(tcx: TyCtxt<'_>) -> bool {
2455 if let Input::File(ref path) = tcx.sess.io.input
2456 && !tcx.sess.opts.unstable_opts.ui_testing
2457 {
2458 path.starts_with("tests")
2459 } else {
2460 false
2461 }
2462}
2463
2464pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
2466 find_attr!(tcx, def_id, CfgTrace(..))
2467 || find_attr!(
2468 tcx.hir_parent_id_iter(tcx.local_def_id_to_hir_id(def_id))
2469 .flat_map(|parent_id| tcx.hir_attrs(parent_id)),
2470 CfgTrace(..)
2471 )
2472}
2473
2474#[derive(Clone, Copy)]
2476pub enum DefinedTy<'tcx> {
2477 Hir(&'tcx hir::Ty<'tcx>),
2479 Mir {
2487 def_site_def_id: Option<DefId>,
2488 ty: Binder<'tcx, Ty<'tcx>>,
2489 },
2490}
2491
2492pub struct ExprUseSite<'tcx> {
2494 pub node: Node<'tcx>,
2496 pub child_id: HirId,
2498 pub adjustments: &'tcx [Adjustment<'tcx>],
2500 pub is_ty_unified: bool,
2502 pub moved_before_use: bool,
2504 pub same_ctxt: bool,
2506}
2507impl<'tcx> ExprUseSite<'tcx> {
2508 pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> {
2509 match self.node {
2510 Node::LetStmt(l) => ExprUseNode::LetStmt(l),
2511 Node::ExprField(field) => ExprUseNode::Field(field),
2512
2513 Node::Item(&Item {
2514 kind: ItemKind::Static(..) | ItemKind::Const(..),
2515 owner_id,
2516 ..
2517 })
2518 | Node::TraitItem(&TraitItem {
2519 kind: TraitItemKind::Const(..),
2520 owner_id,
2521 ..
2522 })
2523 | Node::ImplItem(&ImplItem {
2524 kind: ImplItemKind::Const(..),
2525 owner_id,
2526 ..
2527 }) => ExprUseNode::ConstStatic(owner_id),
2528
2529 Node::Item(&Item {
2530 kind: ItemKind::Fn { .. },
2531 owner_id,
2532 ..
2533 })
2534 | Node::TraitItem(&TraitItem {
2535 kind: TraitItemKind::Fn(..),
2536 owner_id,
2537 ..
2538 })
2539 | Node::ImplItem(&ImplItem {
2540 kind: ImplItemKind::Fn(..),
2541 owner_id,
2542 ..
2543 }) => ExprUseNode::Return(owner_id),
2544
2545 Node::Expr(use_expr) => match use_expr.kind {
2546 ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
2547 def_id: cx.tcx.hir_body_owner_def_id(cx.enclosing_body.unwrap()),
2548 }),
2549
2550 ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
2551 ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) {
2552 Some(i) => ExprUseNode::FnArg(func, i),
2553 None => ExprUseNode::Callee,
2554 },
2555 ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
2556 use_expr.hir_id,
2557 name.args,
2558 args.iter()
2559 .position(|arg| arg.hir_id == self.child_id)
2560 .map_or(0, |i| i + 1),
2561 ),
2562 ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name),
2563 ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl),
2564 _ => ExprUseNode::Other,
2565 },
2566 _ => ExprUseNode::Other,
2567 }
2568 }
2569}
2570
2571pub enum ExprUseNode<'tcx> {
2573 LetStmt(&'tcx LetStmt<'tcx>),
2575 ConstStatic(OwnerId),
2577 Return(OwnerId),
2579 Field(&'tcx ExprField<'tcx>),
2581 FnArg(&'tcx Expr<'tcx>, usize),
2583 MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize),
2585 Callee,
2587 FieldAccess(Ident),
2589 AddrOf(ast::BorrowKind, Mutability),
2591 Other,
2592}
2593impl<'tcx> ExprUseNode<'tcx> {
2594 pub fn is_return(&self) -> bool {
2596 matches!(self, Self::Return(_))
2597 }
2598
2599 pub fn is_recv(&self) -> bool {
2601 matches!(self, Self::MethodArg(_, _, 0))
2602 }
2603
2604 pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
2606 match *self {
2607 Self::LetStmt(LetStmt { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)),
2608 Self::ConstStatic(id) => Some(DefinedTy::Mir {
2609 def_site_def_id: Some(id.def_id.to_def_id()),
2610 ty: Binder::dummy(cx.tcx.type_of(id).instantiate_identity().skip_norm_wip()),
2611 }),
2612 Self::Return(id) => {
2613 if let Node::Expr(Expr {
2614 kind: ExprKind::Closure(c),
2615 ..
2616 }) = cx.tcx.hir_node_by_def_id(id.def_id)
2617 {
2618 match c.fn_decl.output {
2619 FnRetTy::DefaultReturn(_) => None,
2620 FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)),
2621 }
2622 } else {
2623 let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().output();
2624 Some(DefinedTy::Mir {
2625 def_site_def_id: Some(id.def_id.to_def_id()),
2626 ty,
2627 })
2628 }
2629 },
2630 Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) {
2631 Some(Expr {
2632 hir_id,
2633 kind: ExprKind::Struct(path, ..),
2634 ..
2635 }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id))
2636 .and_then(|(adt, variant)| {
2637 variant
2638 .fields
2639 .iter()
2640 .find(|f| f.name == field.ident.name)
2641 .map(|f| (adt, f))
2642 })
2643 .map(|(adt, field_def)| DefinedTy::Mir {
2644 def_site_def_id: Some(adt.did()),
2645 ty: Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip()),
2646 }),
2647 _ => None,
2648 },
2649 Self::FnArg(callee, i) => {
2650 let sig = expr_sig(cx, callee)?;
2651 let (hir_ty, ty) = sig.input_with_hir(i)?;
2652 Some(match hir_ty {
2653 Some(hir_ty) => DefinedTy::Hir(hir_ty),
2654 None => DefinedTy::Mir {
2655 def_site_def_id: sig.predicates_id(),
2656 ty,
2657 },
2658 })
2659 },
2660 Self::MethodArg(id, _, i) => {
2661 let id = cx.typeck_results().type_dependent_def_id(id)?;
2662 let sig = cx.tcx.fn_sig(id).skip_binder();
2663 Some(DefinedTy::Mir {
2664 def_site_def_id: Some(id),
2665 ty: sig.input(i),
2666 })
2667 },
2668 Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None,
2669 }
2670 }
2671}
2672
2673struct ReplacingFilterMap<I, F>(I, F);
2674impl<I, F, U> Iterator for ReplacingFilterMap<I, F>
2675where
2676 I: Iterator,
2677 F: FnMut(&mut I, I::Item) -> Option<U>,
2678{
2679 type Item = U;
2680 fn next(&mut self) -> Option<U> {
2681 while let Some(x) = self.0.next() {
2682 if let Some(x) = (self.1)(&mut self.0, x) {
2683 return Some(x);
2684 }
2685 }
2686 None
2687 }
2688}
2689
2690#[expect(clippy::too_many_lines)]
2693pub fn expr_use_sites<'tcx>(
2694 tcx: TyCtxt<'tcx>,
2695 typeck: &'tcx TypeckResults<'tcx>,
2696 mut ctxt: SyntaxContext,
2697 e: &'tcx Expr<'tcx>,
2698) -> impl Iterator<Item = ExprUseSite<'tcx>> {
2699 let mut adjustments: &[_] = typeck.expr_adjustments(e);
2700 let mut is_ty_unified = false;
2701 let mut moved_before_use = false;
2702 let mut same_ctxt = true;
2703 ReplacingFilterMap(
2704 hir_parent_with_src_iter(tcx, e.hir_id),
2705 move |iter: &mut _, (parent, child_id)| {
2706 let parent_ctxt;
2707 let mut parent_adjustments: &[_] = &[];
2708 match parent {
2709 Node::Expr(parent_expr) => {
2710 parent_ctxt = parent_expr.span.ctxt();
2711 same_ctxt &= parent_ctxt == ctxt;
2712 parent_adjustments = typeck.expr_adjustments(parent_expr);
2713 match parent_expr.kind {
2714 ExprKind::Match(scrutinee, arms, _) if scrutinee.hir_id != child_id => {
2715 is_ty_unified |= arms.len() != 1;
2716 moved_before_use = true;
2717 if adjustments.is_empty() {
2718 adjustments = parent_adjustments;
2719 }
2720 return None;
2721 },
2722 ExprKind::If(cond, _, else_) if cond.hir_id != child_id => {
2723 is_ty_unified |= else_.is_some();
2724 moved_before_use = true;
2725 if adjustments.is_empty() {
2726 adjustments = parent_adjustments;
2727 }
2728 return None;
2729 },
2730 ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
2731 is_ty_unified = true;
2732 moved_before_use = true;
2733 *iter = hir_parent_with_src_iter(tcx, id);
2734 if adjustments.is_empty() {
2735 adjustments = parent_adjustments;
2736 }
2737 return None;
2738 },
2739 ExprKind::Block(b, _) => {
2740 is_ty_unified |= b.targeted_by_break;
2741 moved_before_use = true;
2742 if adjustments.is_empty() {
2743 adjustments = parent_adjustments;
2744 }
2745 return None;
2746 },
2747 ExprKind::DropTemps(_) | ExprKind::Type(..) => {
2748 if adjustments.is_empty() {
2749 adjustments = parent_adjustments;
2750 }
2751 return None;
2752 },
2753 _ => {},
2754 }
2755 },
2756 Node::Arm(arm) => {
2757 parent_ctxt = arm.span.ctxt();
2758 same_ctxt &= parent_ctxt == ctxt;
2759 if arm.body.hir_id == child_id {
2760 return None;
2761 }
2762 },
2763 Node::Block(b) => {
2764 same_ctxt &= b.span.ctxt() == ctxt;
2765 return None;
2766 },
2767 Node::ConstBlock(_) => parent_ctxt = ctxt,
2768 Node::ExprField(&ExprField { span, .. }) => {
2769 parent_ctxt = span.ctxt();
2770 same_ctxt &= parent_ctxt == ctxt;
2771 },
2772 Node::AnonConst(&AnonConst { span, .. })
2773 | Node::ConstArg(&ConstArg { span, .. })
2774 | Node::Field(&FieldDef { span, .. })
2775 | Node::ImplItem(&ImplItem { span, .. })
2776 | Node::Item(&Item { span, .. })
2777 | Node::LetStmt(&LetStmt { span, .. })
2778 | Node::Stmt(&Stmt { span, .. })
2779 | Node::TraitItem(&TraitItem { span, .. })
2780 | Node::Variant(&Variant { span, .. }) => {
2781 parent_ctxt = span.ctxt();
2782 same_ctxt &= parent_ctxt == ctxt;
2783 *iter = hir_parent_with_src_iter(tcx, CRATE_HIR_ID);
2784 },
2785 Node::AssocItemConstraint(_)
2786 | Node::ConstArgExprField(_)
2787 | Node::Crate(_)
2788 | Node::Ctor(_)
2789 | Node::Err(_)
2790 | Node::ForeignItem(_)
2791 | Node::GenericParam(_)
2792 | Node::Infer(_)
2793 | Node::Lifetime(_)
2794 | Node::OpaqueTy(_)
2795 | Node::Param(_)
2796 | Node::Pat(_)
2797 | Node::PatExpr(_)
2798 | Node::PatField(_)
2799 | Node::PathSegment(_)
2800 | Node::PreciseCapturingNonLifetimeArg(_)
2801 | Node::Synthetic
2802 | Node::TraitRef(_)
2803 | Node::Ty(_)
2804 | Node::TyPat(_)
2805 | Node::NestedUseTree(_)
2806 | Node::WherePredicate(_)
2807 | Node::TestBinderForall(_)
2808 | Node::TestBinderExists(_)
2809 | Node::TestBinderBoundTypeConstraint(_) => {
2810 debug_assert!(false, "found {parent:?} which is after the final use node");
2813 return None;
2814 },
2815 }
2816
2817 ctxt = parent_ctxt;
2818 Some(ExprUseSite {
2819 node: parent,
2820 child_id,
2821 adjustments: mem::replace(&mut adjustments, parent_adjustments),
2822 is_ty_unified: mem::replace(&mut is_ty_unified, false),
2823 moved_before_use: mem::replace(&mut moved_before_use, false),
2824 same_ctxt: mem::replace(&mut same_ctxt, true),
2825 })
2826 },
2827 )
2828}
2829
2830pub fn get_expr_use_site<'tcx>(
2831 tcx: TyCtxt<'tcx>,
2832 typeck: &'tcx TypeckResults<'tcx>,
2833 ctxt: SyntaxContext,
2834 e: &'tcx Expr<'tcx>,
2835) -> ExprUseSite<'tcx> {
2836 expr_use_sites(tcx, typeck, ctxt, e).next().unwrap_or_else(|| {
2839 debug_assert!(false, "failed to find a use site for expr {e:?}");
2840 ExprUseSite {
2841 node: Node::Synthetic, child_id: CRATE_HIR_ID,
2843 adjustments: &[],
2844 is_ty_unified: false,
2845 moved_before_use: false,
2846 same_ctxt: false,
2847 }
2848 })
2849}
2850
2851pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str, InnerSpan)> {
2853 let mut pos = 0;
2854 tokenize(s, FrontmatterAllowed::No).map(move |t| {
2855 let end = pos + t.len;
2856 let range = pos as usize..end as usize;
2857 let inner = InnerSpan::new(range.start, range.end);
2858 pos = end;
2859 (t.kind, s.get(range).unwrap_or_default(), inner)
2860 })
2861}
2862
2863pub fn span_contains_comment<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> bool {
2866 span.check_text(sm, |snippet| {
2867 tokenize(snippet, FrontmatterAllowed::No).any(|token| {
2868 matches!(
2869 token.kind,
2870 TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
2871 )
2872 })
2873 })
2874}
2875
2876pub fn span_contains_non_whitespace<'sm>(sm: impl HasSourceMap<'sm>, span: Span, skip_comments: bool) -> bool {
2881 span.check_text(sm, |snippet| {
2882 tokenize_with_text(snippet).any(|(token, _, _)| match token {
2883 TokenKind::Whitespace => false,
2884 TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments,
2885 _ => true,
2886 })
2887 })
2888}
2889
2890pub fn span_extract_comment<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> String {
2894 span_extract_comments(sm, span).join("\n")
2895}
2896
2897pub fn span_extract_comments<'sm>(sm: impl HasSourceMap<'sm>, span: Span) -> Vec<String> {
2901 span.with_source_text(sm, |snippet| {
2902 tokenize_with_text(snippet)
2903 .filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
2904 .map(|(_, s, _)| s.to_string())
2905 .collect::<Vec<_>>()
2906 })
2907 .unwrap_or_default()
2908}
2909
2910pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
2911 sm.span_take_while(span, |&ch| ch == ' ' || ch == ';')
2912}
2913
2914pub fn pat_and_expr_can_be_question_mark<'a, 'hir>(
2939 cx: &LateContext<'_>,
2940 pat: &'a Pat<'hir>,
2941 else_body: &Expr<'_>,
2942) -> Option<&'a Pat<'hir>> {
2943 if let Some([inner_pat]) = as_some_pattern(cx, pat)
2944 && !is_refutable(cx, inner_pat)
2945 && let else_body = peel_blocks(else_body)
2946 && let ExprKind::Ret(Some(ret_val)) = else_body.kind
2947 && let ExprKind::Path(ret_path) = ret_val.kind
2948 && cx
2949 .qpath_res(&ret_path, ret_val.hir_id)
2950 .ctor_parent(cx)
2951 .is_lang_item(cx, OptionNone)
2952 {
2953 Some(inner_pat)
2954 } else {
2955 None
2956 }
2957}
2958
2959macro_rules! op_utils {
2960 ($($name:ident $assign:ident)*) => {
2961 pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
2963
2964 pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
2966
2967 pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
2969 match kind {
2970 $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
2971 _ => None,
2972 }
2973 }
2974 };
2975}
2976
2977op_utils! {
2978 Add AddAssign
2979 Sub SubAssign
2980 Mul MulAssign
2981 Div DivAssign
2982 Rem RemAssign
2983 BitXor BitXorAssign
2984 BitAnd BitAndAssign
2985 BitOr BitOrAssign
2986 Shl ShlAssign
2987 Shr ShrAssign
2988}
2989
2990pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: impl Visitable<'tcx>) -> bool {
2993 match *pat {
2994 PatKind::Wild => true,
2995 PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
2996 !visitors::is_local_used(cx, body, id)
2997 },
2998 _ => false,
2999 }
3000}
3001
3002#[derive(Clone, Copy)]
3003pub enum RequiresSemi {
3004 Yes,
3005 No,
3006}
3007impl RequiresSemi {
3008 pub fn requires_semi(self) -> bool {
3009 matches!(self, Self::Yes)
3010 }
3011}
3012
3013#[expect(clippy::too_many_lines)]
3016pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
3017 struct BreakTarget {
3018 id: HirId,
3019 unused: bool,
3020 }
3021
3022 struct V<'cx, 'tcx> {
3023 cx: &'cx LateContext<'tcx>,
3024 break_targets: Vec<BreakTarget>,
3025 break_targets_for_result_ty: u32,
3026 in_final_expr: bool,
3027 requires_semi: bool,
3028 is_never: bool,
3029 }
3030
3031 impl V<'_, '_> {
3032 fn push_break_target(&mut self, id: HirId) {
3033 self.break_targets.push(BreakTarget { id, unused: true });
3034 self.break_targets_for_result_ty += u32::from(self.in_final_expr);
3035 }
3036 }
3037
3038 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
3039 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
3040 if self.is_never && self.break_targets.is_empty() {
3057 if self.in_final_expr && !self.requires_semi {
3058 match e.kind {
3061 ExprKind::DropTemps(e) => self.visit_expr(e),
3062 ExprKind::If(_, then, Some(else_)) => {
3063 self.visit_expr(then);
3064 self.visit_expr(else_);
3065 },
3066 ExprKind::Match(_, arms, _) => {
3067 for arm in arms {
3068 self.visit_expr(arm.body);
3069 }
3070 },
3071 ExprKind::Loop(b, ..) => {
3072 self.push_break_target(e.hir_id);
3073 self.in_final_expr = false;
3074 self.visit_block(b);
3075 self.break_targets.pop();
3076 },
3077 ExprKind::Block(b, _) => {
3078 if b.targeted_by_break {
3079 self.push_break_target(b.hir_id);
3080 self.visit_block(b);
3081 self.break_targets.pop();
3082 } else {
3083 self.visit_block(b);
3084 }
3085 },
3086 _ => {
3087 self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
3088 },
3089 }
3090 }
3091 return;
3092 }
3093 match e.kind {
3094 ExprKind::DropTemps(e) => self.visit_expr(e),
3095 ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true,
3096 ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
3097 self.in_final_expr = false;
3098 self.visit_expr(e);
3099 self.is_never = true;
3100 },
3101 ExprKind::Break(dest, e) => {
3102 if let Some(e) = e {
3103 self.in_final_expr = false;
3104 self.visit_expr(e);
3105 }
3106 if let Ok(id) = dest.target_id
3107 && let Some((i, target)) = self
3108 .break_targets
3109 .iter_mut()
3110 .enumerate()
3111 .find(|(_, target)| target.id == id)
3112 {
3113 target.unused &= self.is_never;
3114 if i < self.break_targets_for_result_ty as usize {
3115 self.requires_semi = true;
3116 }
3117 }
3118 self.is_never = true;
3119 },
3120 ExprKind::If(cond, then, else_) => {
3121 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3122 self.visit_expr(cond);
3123 self.in_final_expr = in_final_expr;
3124
3125 if self.is_never {
3126 self.visit_expr(then);
3127 if let Some(else_) = else_ {
3128 self.visit_expr(else_);
3129 }
3130 } else {
3131 self.visit_expr(then);
3132 let is_never = mem::replace(&mut self.is_never, false);
3133 if let Some(else_) = else_ {
3134 self.visit_expr(else_);
3135 self.is_never &= is_never;
3136 }
3137 }
3138 },
3139 ExprKind::Match(scrutinee, arms, _) => {
3140 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3141 self.visit_expr(scrutinee);
3142 self.in_final_expr = in_final_expr;
3143
3144 if self.is_never {
3145 for arm in arms {
3146 self.visit_arm(arm);
3147 }
3148 } else {
3149 let mut is_never = true;
3150 for arm in arms {
3151 self.is_never = false;
3152 if let Some(guard) = arm.guard {
3153 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3154 self.visit_expr(guard);
3155 self.in_final_expr = in_final_expr;
3156 self.is_never = false;
3159 }
3160 self.visit_expr(arm.body);
3161 is_never &= self.is_never;
3162 }
3163 self.is_never = is_never;
3164 }
3165 },
3166 ExprKind::Loop(b, _, _, _) => {
3167 self.push_break_target(e.hir_id);
3168 self.in_final_expr = false;
3169 self.visit_block(b);
3170 self.is_never = self.break_targets.pop().unwrap().unused;
3171 },
3172 ExprKind::Block(b, _) => {
3173 if b.targeted_by_break {
3174 self.push_break_target(b.hir_id);
3175 self.visit_block(b);
3176 self.is_never &= self.break_targets.pop().unwrap().unused;
3177 } else {
3178 self.visit_block(b);
3179 }
3180 },
3181 _ => {
3182 self.in_final_expr = false;
3183 walk_expr(self, e);
3184 self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
3185 },
3186 }
3187 }
3188
3189 fn visit_block(&mut self, b: &'tcx Block<'_>) {
3190 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3191 for s in b.stmts {
3192 self.visit_stmt(s);
3193 }
3194 self.in_final_expr = in_final_expr;
3195 if let Some(e) = b.expr {
3196 self.visit_expr(e);
3197 }
3198 }
3199
3200 fn visit_local(&mut self, l: &'tcx LetStmt<'_>) {
3201 if let Some(e) = l.init {
3202 self.visit_expr(e);
3203 }
3204 if let Some(else_) = l.els {
3205 let is_never = self.is_never;
3206 self.visit_block(else_);
3207 self.is_never = is_never;
3208 }
3209 }
3210
3211 fn visit_arm(&mut self, arm: &Arm<'tcx>) {
3212 if let Some(guard) = arm.guard {
3213 let in_final_expr = mem::replace(&mut self.in_final_expr, false);
3214 self.visit_expr(guard);
3215 self.in_final_expr = in_final_expr;
3216 }
3217 self.visit_expr(arm.body);
3218 }
3219 }
3220
3221 if cx.typeck_results().expr_ty(e).is_never() {
3222 Some(RequiresSemi::No)
3223 } else if let ExprKind::Block(b, _) = e.kind
3224 && !b.targeted_by_break
3225 && b.expr.is_none()
3226 {
3227 None
3229 } else {
3230 let mut v = V {
3231 cx,
3232 break_targets: Vec::new(),
3233 break_targets_for_result_ty: 0,
3234 in_final_expr: true,
3235 requires_semi: false,
3236 is_never: false,
3237 };
3238 v.visit_expr(e);
3239 v.is_never
3240 .then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
3241 RequiresSemi::Yes
3242 } else {
3243 RequiresSemi::No
3244 })
3245 }
3246}
3247
3248pub fn get_path_from_caller_to_method_type<'tcx>(
3254 tcx: TyCtxt<'tcx>,
3255 from: LocalDefId,
3256 method: DefId,
3257 args: GenericArgsRef<'tcx>,
3258) -> String {
3259 let assoc_item = tcx.associated_item(method);
3260 let def_id = assoc_item.container_id(tcx);
3261 match assoc_item.container {
3262 rustc_ty::AssocContainer::Trait => get_path_to_callee(tcx, from, def_id),
3263 rustc_ty::AssocContainer::InherentImpl | rustc_ty::AssocContainer::TraitImpl(_) => {
3264 let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
3265 get_path_to_ty(tcx, from, ty, args)
3266 },
3267 }
3268}
3269
3270fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
3271 match ty.kind() {
3272 rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
3273 rustc_ty::Array(..)
3275 | rustc_ty::Dynamic(..)
3276 | rustc_ty::Never
3277 | rustc_ty::RawPtr(_, _)
3278 | rustc_ty::Ref(..)
3279 | rustc_ty::Slice(_)
3280 | rustc_ty::Tuple(_) => format!(
3281 "<{}>",
3282 EarlyBinder::bind(tcx, ty).instantiate(tcx, args).skip_norm_wip()
3283 ),
3284 _ => ty.to_string(),
3285 }
3286}
3287
3288fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
3290 if callee.is_local() {
3292 let callee_path = tcx.def_path(callee);
3293 let caller_path = tcx.def_path(from.to_def_id());
3294 maybe_get_relative_path(&caller_path, &callee_path, 2)
3295 } else {
3296 tcx.def_path_str(callee)
3297 }
3298}
3299
3300fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
3313 use itertools::EitherOrBoth::{Both, Left, Right};
3314
3315 let unique_parts = to
3317 .data
3318 .iter()
3319 .zip_longest(from.data.iter())
3320 .skip_while(|el| matches!(el, Both(l, r) if l == r))
3321 .map(|el| match el {
3322 Both(l, r) => Both(l.data, r.data),
3323 Left(l) => Left(l.data),
3324 Right(r) => Right(r.data),
3325 });
3326
3327 let mut go_up_by = 0;
3329 let mut path = Vec::new();
3330 for el in unique_parts {
3331 match el {
3332 Both(l, r) => {
3333 if let DefPathData::TypeNs(sym) = l {
3343 path.push(sym);
3344 }
3345 if let DefPathData::TypeNs(_) = r {
3346 go_up_by += 1;
3347 }
3348 },
3349 Left(DefPathData::TypeNs(sym)) => path.push(sym),
3354 Right(DefPathData::TypeNs(_)) => go_up_by += 1,
3359 _ => {},
3360 }
3361 }
3362
3363 if go_up_by > max_super {
3364 join_path_syms(once(kw::Crate).chain(to.data.iter().filter_map(|el| {
3366 if let DefPathData::TypeNs(sym) = el.data {
3367 Some(sym)
3368 } else {
3369 None
3370 }
3371 })))
3372 } else if go_up_by == 0 && path.is_empty() {
3373 String::from("Self")
3374 } else {
3375 join_path_syms(repeat_n(kw::Super, go_up_by).chain(path))
3376 }
3377}
3378
3379pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
3382 matches!(
3383 cx.tcx.parent_hir_node(id),
3384 Node::Stmt(..) | Node::Block(Block { stmts: [], .. })
3385 )
3386}
3387
3388pub fn is_block_like(expr: &Expr<'_>) -> bool {
3391 matches!(
3392 expr.kind,
3393 ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
3394 )
3395}
3396
3397pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
3399 fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
3400 match expr.kind {
3401 ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => contains_block(lhs, true),
3402 _ if is_block_like(expr) => is_operand,
3403 _ => false,
3404 }
3405 }
3406
3407 contains_block(expr, false)
3408}
3409
3410pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3412 if let Some(parent_expr) = get_parent_expr(cx, expr)
3413 && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
3414 && receiver.hir_id == expr.hir_id
3415 {
3416 return true;
3417 }
3418 false
3419}
3420
3421pub fn leaks_droppable_temporary_with_limited_lifetime<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3424 for_each_unconsumed_temporary(cx, expr, |temporary_ty| {
3425 if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env())
3426 && temporary_ty
3427 .walk()
3428 .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static()))
3429 {
3430 ControlFlow::Break(())
3431 } else {
3432 ControlFlow::Continue(())
3433 }
3434 })
3435 .is_break()
3436}
3437
3438pub fn leaks_droppable_temporary<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3441 for_each_unconsumed_temporary(cx, expr, |temporary_ty| {
3442 if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env()) {
3443 ControlFlow::Break(())
3444 } else {
3445 ControlFlow::Continue(())
3446 }
3447 })
3448 .is_break()
3449}
3450
3451pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
3462 let expr_ty_is_adjusted = cx
3463 .typeck_results()
3464 .expr_adjustments(expr)
3465 .iter()
3466 .any(|adj| !matches!(adj.kind, Adjust::NeverToAny));
3468 if expr_ty_is_adjusted {
3469 return true;
3470 }
3471
3472 match expr.kind {
3475 ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => {
3476 let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip();
3477
3478 if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) {
3479 return false;
3480 }
3481
3482 let self_arg_count = usize::from(matches!(expr.kind, ExprKind::MethodCall(..)));
3483 let mut args_with_ty_param = {
3484 fn_sig
3485 .inputs()
3486 .skip_binder()
3487 .iter()
3488 .skip(self_arg_count)
3489 .zip(args)
3490 .filter_map(|(arg_ty, arg)| {
3491 if arg_ty.has_type_flags(TypeFlags::HAS_TY_PARAM) {
3492 Some(arg)
3493 } else {
3494 None
3495 }
3496 })
3497 };
3498 args_with_ty_param.any(|arg| expr_requires_coercion(cx, arg))
3499 },
3500 ExprKind::Struct(qpath, _, _) => {
3502 let res = cx.typeck_results().qpath_res(qpath, expr.hir_id);
3503 if let Some((_, v_def)) = adt_and_variant_of_res(cx, res) {
3504 let rustc_ty::Adt(_, generic_args) = cx.typeck_results().expr_ty_adjusted(expr).kind() else {
3505 return true;
3507 };
3508 v_def
3509 .fields
3510 .iter()
3511 .any(|field| field.ty(cx.tcx, generic_args).has_type_flags(TypeFlags::HAS_TY_PARAM))
3512 } else {
3513 false
3514 }
3515 },
3516 ExprKind::Block(
3518 &Block {
3519 expr: Some(ret_expr), ..
3520 },
3521 _,
3522 )
3523 | ExprKind::Ret(Some(ret_expr)) => expr_requires_coercion(cx, ret_expr),
3524
3525 ExprKind::Array(elems) | ExprKind::Tup(elems) => elems.iter().any(|elem| expr_requires_coercion(cx, elem)),
3527 ExprKind::Repeat(rep_elem, _) => expr_requires_coercion(cx, rep_elem),
3529 ExprKind::If(_, then, maybe_else) => {
3531 expr_requires_coercion(cx, then) || maybe_else.is_some_and(|e| expr_requires_coercion(cx, e))
3532 },
3533 ExprKind::Match(_, arms, _) => arms
3534 .iter()
3535 .map(|arm| arm.body)
3536 .any(|body| expr_requires_coercion(cx, body)),
3537 _ => false,
3538 }
3539}
3540
3541pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3544 if let Some(hir_id) = expr.res_local_id()
3545 && let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
3546 {
3547 matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
3548 } else if let ExprKind::Path(p) = &expr.kind
3549 && let Some(mutability) = cx
3550 .qpath_res(p, expr.hir_id)
3551 .opt_def_id()
3552 .and_then(|id| cx.tcx.static_mutability(id))
3553 {
3554 mutability == Mutability::Mut
3555 } else if let ExprKind::Field(parent, _) = expr.kind {
3556 is_mutable(cx, parent)
3557 } else {
3558 true
3559 }
3560}
3561
3562pub fn peel_hir_ty_options<'tcx>(cx: &LateContext<'tcx>, mut hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
3565 let Some(option_def_id) = cx.tcx.get_diagnostic_item(sym::Option) else {
3566 return hir_ty;
3567 };
3568 while let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind
3569 && let Some(segment) = path.segments.last()
3570 && segment.ident.name == sym::Option
3571 && let Res::Def(DefKind::Enum, def_id) = segment.res
3572 && def_id == option_def_id
3573 && let [GenericArg::Type(arg_ty)] = segment.args().args
3574 {
3575 hir_ty = arg_ty.as_unambig_ty();
3576 }
3577 hir_ty
3578}
3579
3580pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
3583 if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind
3584 && let ExprKind::Call(_, [into_future_arg]) = match_value.kind
3585 && let ctxt = expr.span.ctxt()
3586 && for_each_expr_without_closures(into_future_arg, |e| {
3587 walk_span_to_context(e.span, ctxt).map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))
3588 })
3589 .is_none()
3590 {
3591 Some(into_future_arg)
3592 } else {
3593 None
3594 }
3595}
3596
3597pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
3599 if let ExprKind::Call(fn_expr, []) = &expr.kind
3600 && let ExprKind::Path(qpath) = &fn_expr.kind
3601 && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
3602 {
3603 cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
3604 } else {
3605 false
3606 }
3607}
3608
3609pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3625 let enclosing_body_owner = cx
3626 .tcx
3627 .local_def_id_to_hir_id(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
3628 let mut prev_id = expr.hir_id;
3629 let mut skip_until_id = None;
3630 for (hir_id, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
3631 if hir_id == enclosing_body_owner {
3632 return true;
3633 }
3634 if let Some(id) = skip_until_id {
3635 prev_id = hir_id;
3636 if id == hir_id {
3637 skip_until_id = None;
3638 }
3639 continue;
3640 }
3641 match node {
3642 Node::Block(Block { expr, .. }) if expr.is_some_and(|expr| expr.hir_id == prev_id) => {},
3643 Node::Arm(arm) if arm.body.hir_id == prev_id => {},
3644 Node::Expr(expr) => match expr.kind {
3645 ExprKind::Ret(_) => return true,
3646 ExprKind::If(_, then, opt_else)
3647 if then.hir_id == prev_id || opt_else.is_some_and(|els| els.hir_id == prev_id) => {},
3648 ExprKind::Match(_, arms, _) if arms.iter().any(|arm| arm.hir_id == prev_id) => {},
3649 ExprKind::Block(block, _) if block.hir_id == prev_id => {},
3650 ExprKind::Break(
3651 Destination {
3652 target_id: Ok(target_id),
3653 ..
3654 },
3655 _,
3656 ) => skip_until_id = Some(target_id),
3657 _ => break,
3658 },
3659 _ => break,
3660 }
3661 prev_id = hir_id;
3662 }
3663
3664 false
3667}
3668
3669pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
3672 cx.typeck_results().expr_adjustments(expr).iter().any(|adj| {
3673 matches!(
3674 adj.kind,
3675 Adjust::Deref(DerefAdjustKind::Overloaded(_))
3676 | Adjust::Pointer(PointerCoercion::Unsize)
3677 | Adjust::NeverToAny
3678 )
3679 })
3680}
3681
3682pub fn is_expr_async_block(expr: &Expr<'_>) -> bool {
3684 matches!(
3685 expr.kind,
3686 ExprKind::Closure(Closure {
3687 kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(
3688 CoroutineDesugaring::Async,
3689 CoroutineSource::Block
3690 )),
3691 ..
3692 })
3693 )
3694}
3695
3696pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool {
3698 cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS)
3699}
3700
3701#[inline]
3704pub fn hir_parent_with_src_iter(tcx: TyCtxt<'_>, mut id: HirId) -> impl Iterator<Item = (Node<'_>, HirId)> {
3705 tcx.hir_parent_id_iter(id)
3706 .map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent)))
3707}