Rework expr_use_ctxt into an iterator over successive use sites.

This commit is contained in:
Jason Newcomb
2026-03-29 04:56:28 -04:00
parent fee9100776
commit 8322ae2cfb
10 changed files with 268 additions and 195 deletions
+2 -3
View File
@@ -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(_)
)
{
+15 -15
View File
@@ -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 {
+5 -2
View File
@@ -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
+6 -6
View File
@@ -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()
+2 -2
View File
@@ -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)
{
+6 -6
View File
@@ -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,
@@ -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 {
+2 -2
View File
@@ -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,
+9 -8
View File
@@ -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<R
/// check that the obligations are still satisfied after switching the range type.
fn can_switch_ranges<'tcx>(
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;
+215 -146
View File
@@ -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<T>,
) -> Option<ControlFlow<T, (Node<'tcx>, 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<DefinedTy<'tcx>> {
}
}
/// 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>(I, F);
impl<I, F, U> Iterator for ReplacingFilterMap<I, F>
where
I: Iterator,
F: FnMut(&mut I, I::Item) -> Option<U>,
{
type Item = U;
fn next(&mut self) -> Option<U> {
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<Item = ExprUseSite<'tcx>> {
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<Item = (Node<'_>, HirId)> {
tcx.hir_parent_id_iter(id)
.map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent)))
}