From 8322ae2cfbeabe5520452435640cf409c3cb9807 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 29 Mar 2026 04:56:28 -0400 Subject: [PATCH] Rework `expr_use_ctxt` into an iterator over successive use sites. --- clippy_lints/src/casts/ref_as_ptr.rs | 5 +- clippy_lints/src/dereference.rs | 30 +- clippy_lints/src/float_literal.rs | 7 +- clippy_lints/src/methods/manual_inspect.rs | 12 +- clippy_lints/src/methods/manual_repeat_n.rs | 4 +- clippy_lints/src/methods/unnecessary_fold.rs | 12 +- .../src/needless_borrows_for_generic_args.rs | 11 +- clippy_lints/src/operators/identity_op.rs | 4 +- clippy_lints/src/ranges.rs | 17 +- clippy_utils/src/lib.rs | 361 +++++++++++------- 10 files changed, 268 insertions(+), 195 deletions(-) diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index b3805c678174..aef04dc9f7f9 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::{ExprUseNode, expr_use_ctxt, is_expr_temporary_value, std_or_core}; +use clippy_utils::{ExprUseNode, get_expr_use_site, is_expr_temporary_value, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -22,13 +22,12 @@ pub(super) fn check<'tcx>( if matches!(cast_from.kind(), ty::Ref(..)) && let ty::RawPtr(_, to_mutbl) = cast_to.kind() - && let use_cx = expr_use_ctxt(cx, expr) && let Some(std_or_core) = std_or_core(cx) { if let ExprKind::AddrOf(_, _, addr_inner) = cast_expr.kind && is_expr_temporary_value(cx, addr_inner) && matches!( - use_cx.use_node(cx), + get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_) ) { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 26dfa7593f22..f41d9aa02d53 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -4,7 +4,7 @@ use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; use clippy_utils::{ - DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, + DefinedTy, ExprUseNode, get_expr_use_site, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; @@ -19,7 +19,7 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypeckResults}; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol}; +use rustc_span::{Span, Symbol, SyntaxContext}; use std::borrow::Cow; declare_clippy_lint! { @@ -276,15 +276,15 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let use_cx = expr_use_ctxt(cx, expr); - let adjusted_ty = use_cx.adjustments.last().map_or(expr_ty, |a| a.target); + let use_site = get_expr_use_site(cx.tcx, typeck, SyntaxContext::root(), expr); + let adjusted_ty = use_site.adjustments.last().map_or(expr_ty, |a| a.target); match kind { - RefOp::Deref if use_cx.same_ctxt => { - let use_node = use_cx.use_node(cx); + RefOp::Deref if use_site.same_ctxt => { + let use_node = use_site.use_node(cx); let sub_ty = typeck.expr_ty(sub_expr); if let ExprUseNode::FieldAccess(name) = use_node - && !use_cx.moved_before_use + && !use_site.moved_before_use && !ty_contains_field(sub_ty, name.name) { self.state = Some(( @@ -331,9 +331,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { }, )); }, - RefOp::AddrOf(mutability) if use_cx.same_ctxt => { + RefOp::AddrOf(mutability) if use_site.same_ctxt => { // Find the number of times the borrow is auto-derefed. - let mut iter = use_cx.adjustments.iter(); + let mut iter = use_site.adjustments.iter(); let mut deref_count = 0usize; let next_adjust = loop { match iter.next() { @@ -350,13 +350,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } }; - let use_node = use_cx.use_node(cx); + let use_node = use_site.use_node(cx); let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| { TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()) }); let can_auto_borrow = match use_node { ExprUseNode::FieldAccess(_) - if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) => + if !use_site.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) => { // `DerefMut` will not be automatically applied to `ManuallyDrop<_>` // field expressions when the base type is a union and the parent @@ -364,10 +364,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // // e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a // deref through `ManuallyDrop<_>` will not compile. - !adjust_derefs_manually_drop(use_cx.adjustments, expr_ty) + !adjust_derefs_manually_drop(use_site.adjustments, expr_ty) }, - ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true, - ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => { + ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_site.moved_before_use => true, + ExprUseNode::MethodArg(hir_id, _, 0) if !use_site.moved_before_use => { // Check for calls to trait methods where the trait is implemented // on a reference. // Two cases need to be handled: @@ -455,7 +455,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { msg, stability, for_field_access: if let ExprUseNode::FieldAccess(name) = use_node - && !use_cx.moved_before_use + && !use_site.moved_before_use { Some(name.name) } else { diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index c6ae2cfc2545..ab1f5b88bace 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -1,6 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal}; +use clippy_utils::{ExprUseNode, get_expr_use_site, numeric_literal}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -143,7 +143,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { } } else if digits > max as usize && count_digits(&float_str) < digits { if digits >= self.const_literal_digits_threshold - && matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_)) + && matches!( + get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx), + ExprUseNode::ConstStatic(_) + ) { // If a big enough number of digits is specified and it's a constant // we assume the user is definining a constant, and excessive precision is ok diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs index 1a5b180b0c86..a89a656a6bc7 100644 --- a/clippy_lints/src/methods/manual_inspect.rs +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -4,13 +4,13 @@ use clippy_utils::source::{IntoSpan, SpanRangeExt}; use clippy_utils::ty::get_field_by_name; use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures}; -use clippy_utils::{ExprUseNode, expr_use_ctxt, sym}; +use clippy_utils::{ExprUseNode, get_expr_use_site, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_span::{DUMMY_SP, Span, Symbol}; +use rustc_span::{DUMMY_SP, Span, Symbol, SyntaxContext}; use super::MANUAL_INSPECT; @@ -49,7 +49,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: // Nested closures don't need to treat returns specially. let _: Option = for_each_expr(cx, cx.tcx.hir_body(c.body).value, |e| { if e.res_local_id() == Some(arg_id) { - let (kind, same_ctxt) = check_use(cx, e); + let (kind, same_ctxt) = check_use(cx, ctxt, e); match (kind, same_ctxt && e.span.ctxt() == ctxt) { (_, false) | (UseKind::Deref | UseKind::Return(..), true) => { requires_copy = true; @@ -67,7 +67,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: } else if matches!(e.kind, ExprKind::Ret(_)) { ret_count += 1; } else if e.res_local_id() == Some(arg_id) { - let (kind, same_ctxt) = check_use(cx, e); + let (kind, same_ctxt) = check_use(cx, ctxt, e); match (kind, same_ctxt && e.span.ctxt() == ctxt) { (UseKind::Return(..), false) => { return ControlFlow::Break(()); @@ -209,8 +209,8 @@ enum UseKind<'tcx> { } /// Checks how the value is used, and whether it was used in the same `SyntaxContext`. -fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { - let use_cx = expr_use_ctxt(cx, e); +fn check_use<'tcx>(cx: &LateContext<'tcx>, ctxt: SyntaxContext, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { + let use_cx = get_expr_use_site(cx.tcx, cx.typeck_results(), ctxt, e); if use_cx .adjustments .first() diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints/src/methods/manual_repeat_n.rs index 1bb112732fa5..6f65fc48b38a 100644 --- a/clippy_lints/src/methods/manual_repeat_n.rs +++ b/clippy_lints/src/methods/manual_repeat_n.rs @@ -2,7 +2,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_context}; -use clippy_utils::{expr_use_ctxt, fn_def_id, std_or_core, sym}; +use clippy_utils::{fn_def_id, get_expr_use_site, std_or_core, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; @@ -21,7 +21,7 @@ pub(super) fn check<'tcx>( && let ExprKind::Call(_, [repeat_arg]) = repeat_expr.kind && let Some(def_id) = fn_def_id(cx, repeat_expr) && cx.tcx.is_diagnostic_item(sym::iter_repeat, def_id) - && !expr_use_ctxt(cx, expr).is_ty_unified + && !get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).is_ty_unified && let Some(std_or_core) = std_or_core(cx) && msrv.meets(cx, msrvs::REPEAT_N) { diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 367d98ece195..4f25548ef69a 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::snippet_with_context; -use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs}; +use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_blocks, strip_pat_refs}; use rustc_ast::ast; use rustc_data_structures::packed::Pu128; use rustc_errors::{Applicability, Diag}; @@ -17,10 +17,10 @@ /// Do we need to suggest turbofish when suggesting a replacement method? /// Changing `fold` to `sum` needs it sometimes when the return type can't be /// inferred. This checks for some common cases where it can be safely omitted -fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool { - let use_cx = expr_use_ctxt(cx, expr); - if use_cx.same_ctxt - && let use_node = use_cx.use_node(cx) +fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) -> bool { + let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr); + if use_site.same_ctxt + && let use_node = use_site.use_node(cx) && let Some(ty) = use_node.defined_ty(cx) { // some common cases where turbofish isn't needed: @@ -209,7 +209,7 @@ fn check_fold_with_method( pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, init: &hir::Expr<'_>, acc: &hir::Expr<'_>, fold_span: Span, diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index c77398cbc836..75302c45666e 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -4,7 +4,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; -use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_n_hir_expr_refs, sym}; +use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_n_hir_expr_refs, sym}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -17,6 +17,7 @@ self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty, }; use rustc_session::impl_lint_pass; +use rustc_span::SyntaxContext; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::collections::VecDeque; @@ -82,10 +83,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if matches!(expr.kind, ExprKind::AddrOf(..)) && !expr.span.from_expansion() - && let use_cx = expr_use_ctxt(cx, expr) - && use_cx.same_ctxt - && !use_cx.is_ty_unified - && let use_node = use_cx.use_node(cx) + && let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), SyntaxContext::root(), expr) + && use_site.same_ctxt + && !use_site.is_ty_unified + && let use_node = use_site.use_node(cx) && let Some(DefinedTy::Mir { def_site_def_id: _, ty }) = use_node.defined_ty(cx) && let ty::Param(param_ty) = *ty.skip_binder().kind() && let Some((hir_id, fn_id, i)) = match use_node { diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index ce50e6e35dcc..b18957aaff44 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt, integer_const, is_zero_integer_const}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext}; +use clippy_utils::{ExprUseNode, clip, get_expr_use_site, peel_hir_expr_refs, unsext}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath}; @@ -250,7 +250,7 @@ fn span_ineffective_operation( } fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - match expr_use_ctxt(cx, expr).use_node(cx) { + match get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx) { ExprUseNode::LetStmt(letstmt) => letstmt.ty.is_some(), ExprUseNode::Return(_) => true, _ => false, diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 39019c646bd5..aa95cac1b0da 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -6,7 +6,7 @@ use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; -use clippy_utils::{expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, sym}; +use clippy_utils::{fn_def_id, get_expr_use_site, get_parent_expr, higher, is_in_const_context, is_integer_const, sym}; use rustc_ast::Mutability; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; @@ -14,7 +14,7 @@ use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty}; use rustc_session::impl_lint_pass; -use rustc_span::{DesugaringKind, Span, Spanned}; +use rustc_span::{DesugaringKind, Span, Spanned, SyntaxContext}; use std::cmp::Ordering; declare_clippy_lint! { @@ -355,18 +355,19 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option( cx: &LateContext<'tcx>, + ctxt: SyntaxContext, expr: &'tcx Expr<'_>, original: RangeLimits, inner_ty: Ty<'tcx>, ) -> bool { - let use_ctxt = expr_use_ctxt(cx, expr); - let (Node::Expr(parent_expr), false) = (use_ctxt.node, use_ctxt.is_ty_unified) else { + let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), ctxt, expr); + let (Node::Expr(parent_expr), false) = (use_site.node, use_site.is_ty_unified) else { return false; }; // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)` if let ExprKind::Call(func, [arg]) = parent_expr.kind - && arg.hir_id == use_ctxt.child_id + && arg.hir_id == use_site.child_id && let ExprKind::Path(qpath) = func.kind && cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter) && parent_expr.span.is_desugaring(DesugaringKind::ForLoop) @@ -377,7 +378,7 @@ fn can_switch_ranges<'tcx>( // Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`, // or `RangeBounds` traits. if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind - && receiver.hir_id == use_ctxt.child_id + && receiver.hir_id == use_site.child_id && let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) && let Some(trait_did) = cx.tcx.trait_of_assoc(method_did) && matches!( @@ -392,7 +393,7 @@ fn can_switch_ranges<'tcx>( // or `RangeBounds` trait. if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = parent_expr.kind && let Some(id) = fn_def_id(cx, parent_expr) - && let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_ctxt.child_id) + && let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_site.child_id) { let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) { arg_idx + 1 @@ -512,7 +513,7 @@ fn check_range_switch<'tcx>( && span.can_be_used_for_suggestions() && limits == kind && let Some(y) = predicate(cx, end) - && can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y)) + && can_switch_ranges(cx, span.ctxt(), expr, kind, cx.typeck_results().expr_ty(y)) { span_lint_and_then(cx, lint, span, msg, |diag| { let mut app = Applicability::MachineApplicable; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index b37558c1a7fc..71da6d8e5d46 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,5 @@ #![feature(box_patterns)] #![feature(macro_metavar_expr)] -#![feature(never_type)] #![feature(rustc_private)] #![feature(unwrap_infallible)] #![recursion_limit = "512"] @@ -94,11 +93,12 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{ - self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring, - CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, - HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, - OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, - TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr, + self as hir, AnonConst, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, CRATE_HIR_ID, Closure, ConstArg, + ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, + FieldDef, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, + LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, + PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, Variant, def, + find_attr, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use rustc_lint::{LateContext, Level, Lint, LintContext}; @@ -110,12 +110,12 @@ use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt, - TypeFlags, TypeVisitableExt, UintTy, UpvarCapture, + TypeFlags, TypeVisitableExt, TypeckResults, UintTy, UpvarCapture, }; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; -use rustc_span::{InnerSpan, Span}; +use rustc_span::{InnerSpan, Span, SyntaxContext}; use source::{SpanRangeExt, walk_span_to_context}; use visitors::{Visitable, for_each_unconsumed_temporary}; @@ -825,11 +825,10 @@ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. })) )); - let mut child_id = e.hir_id; let mut capture = CaptureKind::Value; let mut capture_expr_ty = e; - for (parent_id, parent) in cx.tcx.hir_parent_iter(e.hir_id) { + for (parent, child_id) in hir_parent_with_src_iter(cx.tcx, e.hir_id) { if let [ Adjustment { kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), @@ -885,8 +884,6 @@ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { }, _ => break, } - - child_id = parent_id; } if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) { @@ -1282,38 +1279,28 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { /// Checks if the given expression is a part of `let else` /// returns `true` for both the `init` and the `else` part pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { - let mut child_id = expr.hir_id; - for (parent_id, node) in tcx.hir_parent_iter(child_id) { - if let Node::LetStmt(LetStmt { - init: Some(init), - els: Some(els), - .. - }) = node - && (init.hir_id == child_id || els.hir_id == child_id) - { - return true; - } - - child_id = parent_id; - } - - false + hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| { + matches!( + node, + Node::LetStmt(LetStmt { + init: Some(init), + els: Some(els), + .. + }) + if init.hir_id == child_id || els.hir_id == child_id + ) + }) } /// Checks if the given expression is the else clause of a `let else` expression pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { - let mut child_id = expr.hir_id; - for (parent_id, node) in tcx.hir_parent_iter(child_id) { - if let Node::LetStmt(LetStmt { els: Some(els), .. }) = node - && els.hir_id == child_id - { - return true; - } - - child_id = parent_id; - } - - false + hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| { + matches!( + node, + Node::LetStmt(LetStmt { els: Some(els), .. }) + if els.hir_id == child_id + ) + }) } /// Checks whether the given `Expr` is a range equivalent to a `RangeFull`. @@ -2033,22 +2020,20 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool /// Gets the node where an expression is either used, or it's type is unified with another branch. /// Returns both the node and the `HirId` of the closest child node. pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> { - let mut child_id = expr.hir_id; - let mut iter = tcx.hir_parent_iter(child_id); - loop { - match iter.next() { - None => break None, - Some((id, Node::Block(_))) => child_id = id, - Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id, - Some((_, Node::Expr(expr))) => match expr.kind { - ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id, - ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id, - ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None, - _ => break Some((Node::Expr(expr), child_id)), + for (node, child_id) in hir_parent_with_src_iter(tcx, expr.hir_id) { + match node { + Node::Block(_) => {}, + Node::Arm(arm) if arm.body.hir_id == child_id => {}, + Node::Expr(expr) => match expr.kind { + ExprKind::Block(..) | ExprKind::DropTemps(_) => {}, + ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => {}, + ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => return None, + _ => return Some((Node::Expr(expr), child_id)), }, - Some((_, node)) => break Some((node, child_id)), + node => return Some((node, child_id)), } } + None } /// Checks if the result of an expression is used, or it's type is unified with another branch. @@ -2493,60 +2478,12 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { find_attr!(tcx, def_id, CfgTrace(..)) || find_attr!( - tcx.hir_parent_iter(tcx.local_def_id_to_hir_id(def_id)) - .flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)), + tcx.hir_parent_id_iter(tcx.local_def_id_to_hir_id(def_id)) + .flat_map(|parent_id| tcx.hir_attrs(parent_id)), CfgTrace(..) ) } -/// Walks up the HIR tree from the given expression in an attempt to find where the value is -/// consumed. -/// -/// Termination has three conditions: -/// - The given function returns `Break`. This function will return the value. -/// - The consuming node is found. This function will return `Continue(use_node, child_id)`. -/// - No further parent nodes are found. This will trigger a debug assert or return `None`. -/// -/// This allows walking through `if`, `match`, `break`, and block expressions to find where the -/// value produced by the expression is consumed. -pub fn walk_to_expr_usage<'tcx, T>( - cx: &LateContext<'tcx>, - e: &Expr<'tcx>, - mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow, -) -> Option, HirId)>> { - let mut iter = cx.tcx.hir_parent_iter(e.hir_id); - let mut child_id = e.hir_id; - - while let Some((parent_id, parent)) = iter.next() { - if let ControlFlow::Break(x) = f(parent_id, parent, child_id) { - return Some(ControlFlow::Break(x)); - } - let parent_expr = match parent { - Node::Expr(e) => e, - Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { - child_id = parent_id; - continue; - }, - Node::Arm(a) if a.body.hir_id == child_id => { - child_id = parent_id; - continue; - }, - _ => return Some(ControlFlow::Continue((parent, child_id))), - }; - match parent_expr.kind { - ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, - ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { - child_id = id; - iter = cx.tcx.hir_parent_iter(id); - }, - ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id, - _ => return Some(ControlFlow::Continue((parent, child_id))), - } - } - debug_assert!(false, "no parent node found for `{child_id:?}`"); - None -} - /// A type definition as it would be viewed from within a function. #[derive(Clone, Copy)] pub enum DefinedTy<'tcx> { @@ -2565,11 +2502,11 @@ pub enum DefinedTy<'tcx> { }, } -/// The context an expressions value is used in. -pub struct ExprUseCtxt<'tcx> { +/// The location that recives the value of an expression. +pub struct ExprUseSite<'tcx> { /// The parent node which consumes the value. pub node: Node<'tcx>, - /// The child id of the node the value came from. + /// The ID of the immediate child of the use node. pub child_id: HirId, /// Any adjustments applied to the type. pub adjustments: &'tcx [Adjustment<'tcx>], @@ -2580,7 +2517,7 @@ pub struct ExprUseCtxt<'tcx> { /// Whether the use site has the same `SyntaxContext` as the value. pub same_ctxt: bool, } -impl<'tcx> ExprUseCtxt<'tcx> { +impl<'tcx> ExprUseSite<'tcx> { pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> { match self.node { Node::LetStmt(l) => ExprUseNode::LetStmt(l), @@ -2746,54 +2683,178 @@ pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option> { } } -/// Gets the context an expression's value is used in. -pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtxt<'tcx> { - let mut adjustments = [].as_slice(); +struct ReplacingFilterMap(I, F); +impl Iterator for ReplacingFilterMap +where + I: Iterator, + F: FnMut(&mut I, I::Item) -> Option, +{ + type Item = U; + fn next(&mut self) -> Option { + while let Some(x) = self.0.next() { + if let Some(x) = (self.1)(&mut self.0, x) { + return Some(x); + } + } + None + } +} + +/// Returns an iterator which walks successive value using parent nodes skipping any node +/// which simply moves a value. +#[expect(clippy::too_many_lines)] +pub fn expr_use_sites<'tcx>( + tcx: TyCtxt<'tcx>, + typeck: &'tcx TypeckResults<'tcx>, + mut ctxt: SyntaxContext, + e: &'tcx Expr<'tcx>, +) -> impl Iterator> { + let mut adjustments: &[_] = typeck.expr_adjustments(e); let mut is_ty_unified = false; let mut moved_before_use = false; let mut same_ctxt = true; - let ctxt = e.span.ctxt(); - let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow { - if adjustments.is_empty() - && let Node::Expr(e) = cx.tcx.hir_node(child_id) - { - adjustments = cx.typeck_results().expr_adjustments(e); - } - same_ctxt &= cx.tcx.hir_span(parent_id).ctxt() == ctxt; - if let Node::Expr(e) = parent { - match e.kind { - ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { - is_ty_unified = true; - moved_before_use = true; + ReplacingFilterMap( + hir_parent_with_src_iter(tcx, e.hir_id), + move |iter: &mut _, (parent, child_id)| { + let parent_ctxt; + let mut parent_adjustments: &[_] = &[]; + match parent { + Node::Expr(parent_expr) => { + parent_ctxt = parent_expr.span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + parent_adjustments = typeck.expr_adjustments(parent_expr); + match parent_expr.kind { + ExprKind::Match(scrutinee, arms, _) if scrutinee.hir_id != child_id => { + is_ty_unified |= arms.len() != 1; + moved_before_use = true; + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::If(cond, _, else_) if cond.hir_id != child_id => { + is_ty_unified |= else_.is_some(); + moved_before_use = true; + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { + is_ty_unified = true; + moved_before_use = true; + *iter = hir_parent_with_src_iter(tcx, id); + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::Block(b, _) => { + is_ty_unified |= b.targeted_by_break; + moved_before_use = true; + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::DropTemps(_) | ExprKind::Type(..) => { + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + _ => {}, + } }, - ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { - is_ty_unified = true; - moved_before_use = true; + Node::Arm(arm) => { + parent_ctxt = arm.span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + if arm.body.hir_id == child_id { + return None; + } + }, + Node::Block(b) => { + same_ctxt &= b.span.ctxt() == ctxt; + return None; + }, + Node::ConstBlock(_) => parent_ctxt = ctxt, + Node::ExprField(&ExprField { span, .. }) => { + parent_ctxt = span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + }, + Node::AnonConst(&AnonConst { span, .. }) + | Node::ConstArg(&ConstArg { span, .. }) + | Node::Field(&FieldDef { span, .. }) + | Node::ImplItem(&ImplItem { span, .. }) + | Node::Item(&Item { span, .. }) + | Node::LetStmt(&LetStmt { span, .. }) + | Node::Stmt(&Stmt { span, .. }) + | Node::TraitItem(&TraitItem { span, .. }) + | Node::Variant(&Variant { span, .. }) => { + parent_ctxt = span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + *iter = hir_parent_with_src_iter(tcx, CRATE_HIR_ID); + }, + Node::AssocItemConstraint(_) + | Node::ConstArgExprField(_) + | Node::Crate(_) + | Node::Ctor(_) + | Node::Err(_) + | Node::ForeignItem(_) + | Node::GenericParam(_) + | Node::Infer(_) + | Node::Lifetime(_) + | Node::OpaqueTy(_) + | Node::Param(_) + | Node::Pat(_) + | Node::PatExpr(_) + | Node::PatField(_) + | Node::PathSegment(_) + | Node::PreciseCapturingNonLifetimeArg(_) + | Node::Synthetic + | Node::TraitRef(_) + | Node::Ty(_) + | Node::TyPat(_) + | Node::WherePredicate(_) => { + // This shouldn't be possible to hit; the inner iterator should have + // been moved to the end before we hit any of these nodes. + debug_assert!(false, "found {parent:?} which is after the final use node"); + return None; }, - ExprKind::Block(..) => moved_before_use = true, - _ => {}, } - } - ControlFlow::Continue(()) - }); - match node { - Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt { - node, - child_id, - adjustments, - is_ty_unified, - moved_before_use, - same_ctxt, + + ctxt = parent_ctxt; + Some(ExprUseSite { + node: parent, + child_id, + adjustments: mem::replace(&mut adjustments, parent_adjustments), + is_ty_unified: mem::replace(&mut is_ty_unified, false), + moved_before_use: mem::replace(&mut moved_before_use, false), + same_ctxt: mem::replace(&mut same_ctxt, true), + }) }, - None => ExprUseCtxt { - node: Node::Crate(cx.tcx.hir_root_module()), - child_id: HirId::INVALID, + ) +} + +pub fn get_expr_use_site<'tcx>( + tcx: TyCtxt<'tcx>, + typeck: &'tcx TypeckResults<'tcx>, + ctxt: SyntaxContext, + e: &'tcx Expr<'tcx>, +) -> ExprUseSite<'tcx> { + // The value in `unwrap_or` doesn't actually matter; an expression always + // has a use site. + expr_use_sites(tcx, typeck, ctxt, e).next().unwrap_or_else(|| { + debug_assert!(false, "failed to find a use site for expr {e:?}"); + ExprUseSite { + node: Node::Synthetic, // The crate root would also work. + child_id: CRATE_HIR_ID, adjustments: &[], - is_ty_unified: true, - moved_before_use: true, + is_ty_unified: false, + moved_before_use: false, same_ctxt: false, - }, - } + } + }) } /// Tokenizes the input while keeping the text associated with each token. @@ -3628,3 +3689,11 @@ pub fn is_expr_async_block(expr: &Expr<'_>) -> bool { pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool { cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS) } + +/// Returns an iterator over successive parent nodes paired with the ID of the node which +/// immediatly preceeded them. +#[inline] +pub fn hir_parent_with_src_iter(tcx: TyCtxt<'_>, mut id: HirId) -> impl Iterator, HirId)> { + tcx.hir_parent_id_iter(id) + .map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent))) +}