Skip to content

Commit e511476

Browse files
committed
Auto merge of rust-lang#8479 - smoelius:unnecessary-filter-map, r=llogiq
Fix some `unnecessary_filter_map` false positives This is a proposed fix for rust-lang#4433. It moves `clone_or_copy_needed` out of `unnecessary_iter_cloned.rs` and into `methods::utils`. It then adds a check of this function to `unnecessary_filter_map::check`. Fixes rust-lang#4433 changelog: none
2 parents 8d12cd4 + d123ffc commit e511476

File tree

5 files changed

+228
-94
lines changed

5 files changed

+228
-94
lines changed

clippy_lints/src/methods/unnecessary_filter_map.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use super::utils::clone_or_copy_needed;
12
use clippy_utils::diagnostics::span_lint;
3+
use clippy_utils::ty::is_copy;
24
use clippy_utils::usage::mutated_variables;
35
use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
46
use rustc_hir as hir;
@@ -20,6 +22,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
2022
let arg_id = body.params[0].pat.hir_id;
2123
let mutates_arg =
2224
mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
25+
let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, &body.value);
2326

2427
let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value);
2528

@@ -28,10 +31,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
2831
found_mapping |= return_visitor.found_mapping;
2932
found_filtering |= return_visitor.found_filtering;
3033

34+
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
3135
let sugg = if !found_filtering {
3236
"map"
33-
} else if !found_mapping && !mutates_arg {
34-
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
37+
} else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
3538
match cx.typeck_results().expr_ty(&body.value).kind() {
3639
ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) => {
3740
"filter"

clippy_lints/src/methods/unnecessary_iter_cloned.rs

Lines changed: 3 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1+
use super::utils::clone_or_copy_needed;
12
use clippy_utils::diagnostics::span_lint_and_then;
23
use clippy_utils::higher::ForLoop;
34
use clippy_utils::source::snippet_opt;
45
use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
5-
use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage};
6+
use clippy_utils::{fn_def_id, get_parent_expr};
67
use rustc_errors::Applicability;
7-
use rustc_hir::intravisit::{walk_expr, Visitor};
8-
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat};
8+
use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem};
99
use rustc_lint::LateContext;
10-
use rustc_middle::hir::nested_filter;
11-
use rustc_middle::ty;
1210
use rustc_span::{sym, Symbol};
1311

1412
use super::UNNECESSARY_TO_OWNED;
@@ -100,89 +98,6 @@ pub fn check_for_loop_iter(
10098
false
10199
}
102100

103-
/// The core logic of `check_for_loop_iter` above, this function wraps a use of
104-
/// `CloneOrCopyVisitor`.
105-
fn clone_or_copy_needed<'tcx>(
106-
cx: &LateContext<'tcx>,
107-
pat: &Pat<'tcx>,
108-
body: &'tcx Expr<'tcx>,
109-
) -> (bool, Vec<&'tcx Expr<'tcx>>) {
110-
let mut visitor = CloneOrCopyVisitor {
111-
cx,
112-
binding_hir_ids: pat_bindings(pat),
113-
clone_or_copy_needed: false,
114-
addr_of_exprs: Vec::new(),
115-
};
116-
visitor.visit_expr(body);
117-
(visitor.clone_or_copy_needed, visitor.addr_of_exprs)
118-
}
119-
120-
/// Returns a vector of all `HirId`s bound by the pattern.
121-
fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
122-
let mut collector = usage::ParamBindingIdCollector {
123-
binding_hir_ids: Vec::new(),
124-
};
125-
collector.visit_pat(pat);
126-
collector.binding_hir_ids
127-
}
128-
129-
/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
130-
/// operations performed on `binding_hir_ids` are:
131-
/// * to take non-mutable references to them
132-
/// * to use them as non-mutable `&self` in method calls
133-
/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
134-
/// when `CloneOrCopyVisitor` is done visiting.
135-
struct CloneOrCopyVisitor<'cx, 'tcx> {
136-
cx: &'cx LateContext<'tcx>,
137-
binding_hir_ids: Vec<HirId>,
138-
clone_or_copy_needed: bool,
139-
addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
140-
}
141-
142-
impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
143-
type NestedFilter = nested_filter::OnlyBodies;
144-
145-
fn nested_visit_map(&mut self) -> Self::Map {
146-
self.cx.tcx.hir()
147-
}
148-
149-
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
150-
walk_expr(self, expr);
151-
if self.is_binding(expr) {
152-
if let Some(parent) = get_parent_expr(self.cx, expr) {
153-
match parent.kind {
154-
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
155-
self.addr_of_exprs.push(parent);
156-
return;
157-
},
158-
ExprKind::MethodCall(_, args, _) => {
159-
if_chain! {
160-
if args.iter().skip(1).all(|arg| !self.is_binding(arg));
161-
if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
162-
let method_ty = self.cx.tcx.type_of(method_def_id);
163-
let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
164-
if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
165-
then {
166-
return;
167-
}
168-
}
169-
},
170-
_ => {},
171-
}
172-
}
173-
self.clone_or_copy_needed = true;
174-
}
175-
}
176-
}
177-
178-
impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
179-
fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
180-
self.binding_hir_ids
181-
.iter()
182-
.any(|hir_id| path_to_local_id(expr, *hir_id))
183-
}
184-
}
185-
186101
/// Returns true if the named method is `IntoIterator::into_iter`.
187102
pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
188103
cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)

clippy_lints/src/methods/utils.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use clippy_utils::source::snippet_with_applicability;
22
use clippy_utils::ty::is_type_diagnostic_item;
3+
use clippy_utils::{get_parent_expr, path_to_local_id, usage};
34
use if_chain::if_chain;
45
use rustc_ast::ast;
56
use rustc_errors::Applicability;
67
use rustc_hir as hir;
8+
use rustc_hir::intravisit::{walk_expr, Visitor};
9+
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat};
710
use rustc_lint::LateContext;
11+
use rustc_middle::hir::nested_filter;
812
use rustc_middle::ty::{self, Ty};
913
use rustc_span::symbol::sym;
1014

@@ -79,3 +83,86 @@ pub(super) fn get_hint_if_single_char_arg(
7983
}
8084
}
8185
}
86+
87+
/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a
88+
/// use of `CloneOrCopyVisitor`.
89+
pub(super) fn clone_or_copy_needed<'tcx>(
90+
cx: &LateContext<'tcx>,
91+
pat: &Pat<'tcx>,
92+
body: &'tcx Expr<'tcx>,
93+
) -> (bool, Vec<&'tcx Expr<'tcx>>) {
94+
let mut visitor = CloneOrCopyVisitor {
95+
cx,
96+
binding_hir_ids: pat_bindings(pat),
97+
clone_or_copy_needed: false,
98+
addr_of_exprs: Vec::new(),
99+
};
100+
visitor.visit_expr(body);
101+
(visitor.clone_or_copy_needed, visitor.addr_of_exprs)
102+
}
103+
104+
/// Returns a vector of all `HirId`s bound by the pattern.
105+
fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
106+
let mut collector = usage::ParamBindingIdCollector {
107+
binding_hir_ids: Vec::new(),
108+
};
109+
collector.visit_pat(pat);
110+
collector.binding_hir_ids
111+
}
112+
113+
/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
114+
/// operations performed on `binding_hir_ids` are:
115+
/// * to take non-mutable references to them
116+
/// * to use them as non-mutable `&self` in method calls
117+
/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
118+
/// when `CloneOrCopyVisitor` is done visiting.
119+
struct CloneOrCopyVisitor<'cx, 'tcx> {
120+
cx: &'cx LateContext<'tcx>,
121+
binding_hir_ids: Vec<HirId>,
122+
clone_or_copy_needed: bool,
123+
addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
124+
}
125+
126+
impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
127+
type NestedFilter = nested_filter::OnlyBodies;
128+
129+
fn nested_visit_map(&mut self) -> Self::Map {
130+
self.cx.tcx.hir()
131+
}
132+
133+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
134+
walk_expr(self, expr);
135+
if self.is_binding(expr) {
136+
if let Some(parent) = get_parent_expr(self.cx, expr) {
137+
match parent.kind {
138+
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
139+
self.addr_of_exprs.push(parent);
140+
return;
141+
},
142+
ExprKind::MethodCall(_, args, _) => {
143+
if_chain! {
144+
if args.iter().skip(1).all(|arg| !self.is_binding(arg));
145+
if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
146+
let method_ty = self.cx.tcx.type_of(method_def_id);
147+
let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
148+
if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
149+
then {
150+
return;
151+
}
152+
}
153+
},
154+
_ => {},
155+
}
156+
}
157+
self.clone_or_copy_needed = true;
158+
}
159+
}
160+
}
161+
162+
impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
163+
fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
164+
self.binding_hir_ids
165+
.iter()
166+
.any(|hir_id| path_to_local_id(expr, *hir_id))
167+
}
168+
}

tests/ui/unnecessary_filter_map.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(dead_code)]
2+
13
fn main() {
24
let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
35
let _ = (0..4).filter_map(|x| {
@@ -19,3 +21,130 @@ fn main() {
1921
fn filter_map_none_changes_item_type() -> impl Iterator<Item = bool> {
2022
"".chars().filter_map(|_| None)
2123
}
24+
25+
// https://github.com/rust-lang/rust-clippy/issues/4433#issue-483920107
26+
mod comment_483920107 {
27+
enum Severity {
28+
Warning,
29+
Other,
30+
}
31+
32+
struct ServerError;
33+
34+
impl ServerError {
35+
fn severity(&self) -> Severity {
36+
Severity::Warning
37+
}
38+
}
39+
40+
struct S {
41+
warnings: Vec<ServerError>,
42+
}
43+
44+
impl S {
45+
fn foo(&mut self, server_errors: Vec<ServerError>) {
46+
#[allow(unused_variables)]
47+
let errors: Vec<ServerError> = server_errors
48+
.into_iter()
49+
.filter_map(|se| match se.severity() {
50+
Severity::Warning => {
51+
self.warnings.push(se);
52+
None
53+
},
54+
_ => Some(se),
55+
})
56+
.collect();
57+
}
58+
}
59+
}
60+
61+
// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-611006622
62+
mod comment_611006622 {
63+
struct PendingRequest {
64+
reply_to: u8,
65+
token: u8,
66+
expires: u8,
67+
group_id: u8,
68+
}
69+
70+
enum Value {
71+
Null,
72+
}
73+
74+
struct Node;
75+
76+
impl Node {
77+
fn send_response(&self, _reply_to: u8, _token: u8, _value: Value) -> &Self {
78+
self
79+
}
80+
fn on_error_warn(&self) -> &Self {
81+
self
82+
}
83+
}
84+
85+
struct S {
86+
pending_requests: Vec<PendingRequest>,
87+
}
88+
89+
impl S {
90+
fn foo(&mut self, node: Node, now: u8, group_id: u8) {
91+
// "drain_filter"
92+
self.pending_requests = self
93+
.pending_requests
94+
.drain(..)
95+
.filter_map(|pending| {
96+
if pending.expires <= now {
97+
return None; // Expired, remove
98+
}
99+
100+
if pending.group_id == group_id {
101+
// Matched - reuse strings and remove
102+
node.send_response(pending.reply_to, pending.token, Value::Null)
103+
.on_error_warn();
104+
None
105+
} else {
106+
// Keep waiting
107+
Some(pending)
108+
}
109+
})
110+
.collect();
111+
}
112+
}
113+
}
114+
115+
// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-621925270
116+
// This extrapolation doesn't reproduce the false positive. Additional context seems necessary.
117+
mod comment_621925270 {
118+
struct Signature(u8);
119+
120+
fn foo(sig_packets: impl Iterator<Item = Result<Signature, ()>>) -> impl Iterator<Item = u8> {
121+
sig_packets.filter_map(|res| match res {
122+
Ok(Signature(sig_packet)) => Some(sig_packet),
123+
_ => None,
124+
})
125+
}
126+
}
127+
128+
// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-1052978898
129+
mod comment_1052978898 {
130+
#![allow(clippy::redundant_closure)]
131+
132+
pub struct S(u8);
133+
134+
impl S {
135+
pub fn consume(self) {
136+
println!("yum");
137+
}
138+
}
139+
140+
pub fn filter_owned() -> impl Iterator<Item = S> {
141+
(0..10).map(|i| S(i)).filter_map(|s| {
142+
if s.0 & 1 == 0 {
143+
s.consume();
144+
None
145+
} else {
146+
Some(s)
147+
}
148+
})
149+
}
150+
}

0 commit comments

Comments
 (0)