|
1 | 1 | use clippy_utils::diagnostics::span_lint;
|
2 | 2 | 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}; |
4 | 4 | use clippy_utils::{get_enclosing_block, path_to_local_id};
|
5 | 5 | 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}; |
7 | 7 | use rustc_lint::{LateContext, LateLintPass};
|
8 | 8 | use rustc_session::declare_lint_pass;
|
9 | 9 | use rustc_span::symbol::sym;
|
@@ -77,7 +77,7 @@ fn match_acceptable_type(cx: &LateContext<'_>, local: &LetStmt<'_>, collections:
|
77 | 77 | || is_type_lang_item(cx, ty, LangItem::String)
|
78 | 78 | }
|
79 | 79 |
|
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 { |
81 | 81 | let mut has_access = false;
|
82 | 82 | let mut has_read_access = false;
|
83 | 83 |
|
@@ -109,11 +109,30 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
|
109 | 109 | // traits (identified as local, based on the orphan rule), pessimistically assume that they might
|
110 | 110 | // have side effects, so consider them a read.
|
111 | 111 | 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 |
113 | 113 | && path_to_local_id(receiver, id)
|
114 | 114 | && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
|
115 | 115 | && !method_def_id.is_local()
|
116 | 116 | {
|
| 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 | + |
117 | 136 | // The method call is a statement, so the return value is not used. That's not a read access:
|
118 | 137 | //
|
119 | 138 | // id.foo(args);
|
|
0 commit comments