Skip to content

Commit 0fc9a65

Browse files
committed
Auto merge of rust-lang#12694 - J-ZhengLi:issue11783, r=dswij
check if closure as method arg has read access in [`collection_is_never_read`] fixes: rust-lang#11783 --- changelog: fix [`collection_is_never_read`] misfires when use `retain` for iteration
2 parents e68fcb0 + adab7d0 commit 0fc9a65

File tree

2 files changed

+37
-4
lines changed

2 files changed

+37
-4
lines changed

clippy_lints/src/collection_is_never_read.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use clippy_utils::diagnostics::span_lint;
22
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
3-
use clippy_utils::visitors::for_each_expr_with_closures;
3+
use clippy_utils::visitors::{for_each_expr_with_closures, Visitable};
44
use clippy_utils::{get_enclosing_block, path_to_local_id};
55
use core::ops::ControlFlow;
6-
use rustc_hir::{Block, ExprKind, HirId, LangItem, LetStmt, Node, PatKind};
6+
use rustc_hir::{Body, ExprKind, HirId, LangItem, LetStmt, Node, PatKind};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_session::declare_lint_pass;
99
use rustc_span::symbol::sym;
@@ -77,7 +77,7 @@ fn match_acceptable_type(cx: &LateContext<'_>, local: &LetStmt<'_>, collections:
7777
|| is_type_lang_item(cx, ty, LangItem::String)
7878
}
7979

80-
fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
80+
fn has_no_read_access<'tcx, T: Visitable<'tcx>>(cx: &LateContext<'tcx>, id: HirId, block: T) -> bool {
8181
let mut has_access = false;
8282
let mut has_read_access = false;
8383

@@ -109,11 +109,30 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
109109
// traits (identified as local, based on the orphan rule), pessimistically assume that they might
110110
// have side effects, so consider them a read.
111111
if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id)
112-
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
112+
&& let ExprKind::MethodCall(_, receiver, args, _) = parent.kind
113113
&& path_to_local_id(receiver, id)
114114
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
115115
&& !method_def_id.is_local()
116116
{
117+
// If this "official" method takes closures,
118+
// it has read access if one of the closures has read access.
119+
//
120+
// items.retain(|item| send_item(item).is_ok());
121+
let is_read_in_closure_arg = args.iter().any(|arg| {
122+
if let ExprKind::Closure(closure) = arg.kind
123+
// To keep things simple, we only check the first param to see if its read.
124+
&& let Body { params: [param, ..], value } = cx.tcx.hir().body(closure.body)
125+
{
126+
!has_no_read_access(cx, param.hir_id, *value)
127+
} else {
128+
false
129+
}
130+
});
131+
if is_read_in_closure_arg {
132+
has_read_access = true;
133+
return ControlFlow::Break(());
134+
}
135+
117136
// The method call is a statement, so the return value is not used. That's not a read access:
118137
//
119138
// id.foo(args);

tests/ui/collection_is_never_read.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,17 @@ fn supported_types() {
222222
//~^ ERROR: collection is never read
223223
x.push_front(1);
224224
}
225+
226+
fn issue11783() {
227+
struct Sender;
228+
impl Sender {
229+
fn send(&self, msg: String) -> Result<(), ()> {
230+
// pretend to send message
231+
println!("{msg}");
232+
Ok(())
233+
}
234+
}
235+
236+
let mut users: Vec<Sender> = vec![];
237+
users.retain(|user| user.send("hello".to_string()).is_ok());
238+
}

0 commit comments

Comments
 (0)