use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
+use clippy_utils::ty::needs_ordered_drop;
+use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
- eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed,
- search_same, ContainsName, HirEqInterExpr, SpanlessEq,
+ capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause,
+ is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
};
use core::iter;
+use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::intravisit;
-use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind};
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene::walk_chain;
fn lint_branches_sharing_code<'tcx>(
cx: &LateContext<'tcx>,
conds: &[&'tcx Expr<'_>],
- blocks: &[&Block<'tcx>],
+ blocks: &[&'tcx Block<'_>],
expr: &'tcx Expr<'_>,
) {
// We only lint ifs with multiple blocks
}
}
+/// Checks if the statement modifies or moves any of the given locals.
+fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool {
+ for_each_expr(s, |e| {
+ if let Some(id) = path_to_local(e)
+ && locals.contains(&id)
+ && !capture_local_usage(cx, e).is_imm_ref()
+ {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(())
+ }
+ })
+ .is_some()
+}
+
/// Checks if the given statement should be considered equal to the statement in the same position
/// for each block.
fn eq_stmts(
.all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt)))
}
-fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq {
+#[expect(clippy::too_many_lines)]
+fn scan_block_for_eq<'tcx>(
+ cx: &LateContext<'tcx>,
+ conds: &[&'tcx Expr<'_>],
+ block: &'tcx Block<'_>,
+ blocks: &[&'tcx Block<'_>],
+) -> BlockEq {
let mut eq = SpanlessEq::new(cx);
let mut eq = eq.inter_expr();
let mut moved_locals = Vec::new();
+ let mut cond_locals = HirIdSet::default();
+ for &cond in conds {
+ let _: Option<!> = for_each_expr(cond, |e| {
+ if let Some(id) = path_to_local(e) {
+ cond_locals.insert(id);
+ }
+ ControlFlow::Continue(())
+ });
+ }
+
+ let mut local_needs_ordered_drop = false;
let start_end_eq = block
.stmts
.iter()
.enumerate()
- .find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals))
+ .find(|&(i, stmt)| {
+ if let StmtKind::Local(l) = stmt.kind
+ && needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id))
+ {
+ local_needs_ordered_drop = true;
+ return true;
+ }
+ modifies_any_local(cx, stmt, &cond_locals)
+ || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)
+ })
.map_or(block.stmts.len(), |(i, _)| i);
+ if local_needs_ordered_drop {
+ return BlockEq {
+ start_end_eq,
+ end_begin_eq: None,
+ moved_locals,
+ };
+ }
+
// Walk backwards through the final expression/statements so long as their hashes are equal. Note
// `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block
// to match those in other blocks. e.g. If each block ends with the following the hash value will be