Skip to content

Commit 3206fb4

Browse files
committed
Auto merge of #9138 - Jarcho:branches_sharing_code_2, r=giraffate
Fixes for `branches_sharing_code` fixes #7198 fixes #7452 fixes #7555 fixes #7589 changelog: Don't suggest moving modifications to locals used in any of the condition expressions in `branches_sharing_code` changelog: Don't suggest moving anything after a local with a significant drop in `branches_sharing_code`
2 parents 8a801c7 + 55563f9 commit 3206fb4

File tree

4 files changed

+182
-8
lines changed

4 files changed

+182
-8
lines changed

clippy_lints/src/copies.rs

+58-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
22
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
3+
use clippy_utils::ty::needs_ordered_drop;
4+
use clippy_utils::visitors::for_each_expr;
35
use clippy_utils::{
4-
eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed,
5-
search_same, ContainsName, HirEqInterExpr, SpanlessEq,
6+
capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause,
7+
is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
68
};
79
use core::iter;
10+
use core::ops::ControlFlow;
811
use rustc_errors::Applicability;
912
use rustc_hir::intravisit;
10-
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind};
13+
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind};
1114
use rustc_lint::{LateContext, LateLintPass};
1215
use rustc_session::{declare_lint_pass, declare_tool_lint};
1316
use rustc_span::hygiene::walk_chain;
@@ -214,7 +217,7 @@ fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&
214217
fn lint_branches_sharing_code<'tcx>(
215218
cx: &LateContext<'tcx>,
216219
conds: &[&'tcx Expr<'_>],
217-
blocks: &[&Block<'tcx>],
220+
blocks: &[&'tcx Block<'_>],
218221
expr: &'tcx Expr<'_>,
219222
) {
220223
// We only lint ifs with multiple blocks
@@ -340,6 +343,21 @@ fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool {
340343
}
341344
}
342345

346+
/// Checks if the statement modifies or moves any of the given locals.
347+
fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool {
348+
for_each_expr(s, |e| {
349+
if let Some(id) = path_to_local(e)
350+
&& locals.contains(&id)
351+
&& !capture_local_usage(cx, e).is_imm_ref()
352+
{
353+
ControlFlow::Break(())
354+
} else {
355+
ControlFlow::Continue(())
356+
}
357+
})
358+
.is_some()
359+
}
360+
343361
/// Checks if the given statement should be considered equal to the statement in the same position
344362
/// for each block.
345363
fn eq_stmts(
@@ -365,18 +383,52 @@ fn eq_stmts(
365383
.all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt)))
366384
}
367385

368-
fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq {
386+
#[expect(clippy::too_many_lines)]
387+
fn scan_block_for_eq<'tcx>(
388+
cx: &LateContext<'tcx>,
389+
conds: &[&'tcx Expr<'_>],
390+
block: &'tcx Block<'_>,
391+
blocks: &[&'tcx Block<'_>],
392+
) -> BlockEq {
369393
let mut eq = SpanlessEq::new(cx);
370394
let mut eq = eq.inter_expr();
371395
let mut moved_locals = Vec::new();
372396

397+
let mut cond_locals = HirIdSet::default();
398+
for &cond in conds {
399+
let _: Option<!> = for_each_expr(cond, |e| {
400+
if let Some(id) = path_to_local(e) {
401+
cond_locals.insert(id);
402+
}
403+
ControlFlow::Continue(())
404+
});
405+
}
406+
407+
let mut local_needs_ordered_drop = false;
373408
let start_end_eq = block
374409
.stmts
375410
.iter()
376411
.enumerate()
377-
.find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals))
412+
.find(|&(i, stmt)| {
413+
if let StmtKind::Local(l) = stmt.kind
414+
&& needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id))
415+
{
416+
local_needs_ordered_drop = true;
417+
return true;
418+
}
419+
modifies_any_local(cx, stmt, &cond_locals)
420+
|| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)
421+
})
378422
.map_or(block.stmts.len(), |(i, _)| i);
379423

424+
if local_needs_ordered_drop {
425+
return BlockEq {
426+
start_end_eq,
427+
end_begin_eq: None,
428+
moved_locals,
429+
};
430+
}
431+
380432
// Walk backwards through the final expression/statements so long as their hashes are equal. Note
381433
// `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block
382434
// to match those in other blocks. e.g. If each block ends with the following the hash value will be

clippy_utils/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Captur
890890
Node::Expr(e) => match e.kind {
891891
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
892892
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
893-
ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
893+
ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
894894
return CaptureKind::Ref(Mutability::Mut);
895895
},
896896
ExprKind::Field(..) => {

clippy_utils/src/visitors.rs

+69-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,82 @@ use rustc_hir as hir;
55
use rustc_hir::def::{CtorKind, DefKind, Res};
66
use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
77
use rustc_hir::{
8-
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp,
8+
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp,
99
UnsafeSource, Unsafety,
1010
};
1111
use rustc_lint::LateContext;
1212
use rustc_middle::hir::map::Map;
1313
use rustc_middle::hir::nested_filter;
1414
use rustc_middle::ty::adjustment::Adjust;
1515
use rustc_middle::ty::{self, Ty, TypeckResults};
16+
use rustc_span::Span;
17+
18+
mod internal {
19+
/// Trait for visitor functions to control whether or not to descend to child nodes. Implemented
20+
/// for only two types. `()` always descends. `Descend` allows controlled descent.
21+
pub trait Continue {
22+
fn descend(&self) -> bool;
23+
}
24+
}
25+
use internal::Continue;
26+
27+
impl Continue for () {
28+
fn descend(&self) -> bool {
29+
true
30+
}
31+
}
32+
33+
/// Allows for controlled descent whe using visitor functions. Use `()` instead when always
34+
/// descending into child nodes.
35+
#[derive(Clone, Copy)]
36+
pub enum Descend {
37+
Yes,
38+
No,
39+
}
40+
impl From<bool> for Descend {
41+
fn from(from: bool) -> Self {
42+
if from { Self::Yes } else { Self::No }
43+
}
44+
}
45+
impl Continue for Descend {
46+
fn descend(&self) -> bool {
47+
matches!(self, Self::Yes)
48+
}
49+
}
50+
51+
/// Calls the given function once for each expression contained. This does not enter any bodies or
52+
/// nested items.
53+
pub fn for_each_expr<'tcx, B, C: Continue>(
54+
node: impl Visitable<'tcx>,
55+
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
56+
) -> Option<B> {
57+
struct V<B, F> {
58+
f: F,
59+
res: Option<B>,
60+
}
61+
impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<B, F> {
62+
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
63+
if self.res.is_some() {
64+
return;
65+
}
66+
match (self.f)(e) {
67+
ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
68+
ControlFlow::Break(b) => self.res = Some(b),
69+
ControlFlow::Continue(_) => (),
70+
}
71+
}
72+
73+
// Avoid unnecessary `walk_*` calls.
74+
fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
75+
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
76+
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
77+
// Avoid monomorphising all `visit_*` functions.
78+
fn visit_nested_item(&mut self, _: ItemId) {}
79+
}
80+
let mut v = V { f, res: None };
81+
node.visit(&mut v);
82+
v.res
83+
}
1684

1785
/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
1886
/// bodies (i.e. closures) are visited.

tests/ui/branches_sharing_code/false_positives.rs

+54
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![allow(dead_code)]
22
#![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
33

4+
use std::sync::Mutex;
5+
46
// ##################################
57
// # Issue clippy#7369
68
// ##################################
@@ -38,4 +40,56 @@ fn main() {
3840
let (y, x) = x;
3941
foo(x, y)
4042
};
43+
44+
let m = Mutex::new(0u32);
45+
let l = m.lock().unwrap();
46+
let _ = if true {
47+
drop(l);
48+
println!("foo");
49+
m.lock().unwrap();
50+
0
51+
} else if *l == 0 {
52+
drop(l);
53+
println!("foo");
54+
println!("bar");
55+
m.lock().unwrap();
56+
1
57+
} else {
58+
drop(l);
59+
println!("foo");
60+
println!("baz");
61+
m.lock().unwrap();
62+
2
63+
};
64+
65+
if true {
66+
let _guard = m.lock();
67+
println!("foo");
68+
} else {
69+
println!("foo");
70+
}
71+
72+
if true {
73+
let _guard = m.lock();
74+
println!("foo");
75+
println!("bar");
76+
} else {
77+
let _guard = m.lock();
78+
println!("foo");
79+
println!("baz");
80+
}
81+
82+
let mut c = 0;
83+
for _ in 0..5 {
84+
if c == 0 {
85+
c += 1;
86+
println!("0");
87+
} else if c == 1 {
88+
c += 1;
89+
println!("1");
90+
} else {
91+
c += 1;
92+
println!("more");
93+
}
94+
}
4195
}

0 commit comments

Comments
 (0)