@@ -2,9 +2,13 @@ use super::FILTER_MAP_BOOL_THEN;
22use clippy_utils:: diagnostics:: span_lint_and_then;
33use clippy_utils:: source:: SpanRangeExt ;
44use clippy_utils:: ty:: is_copy;
5- use clippy_utils:: { contains_return, is_from_proc_macro, is_trait_method, peel_blocks} ;
5+ use clippy_utils:: {
6+ CaptureKind , can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks,
7+ } ;
8+ use rustc_ast:: Mutability ;
9+ use rustc_data_structures:: fx:: FxHashSet ;
610use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind } ;
11+ use rustc_hir:: { Expr , ExprKind , HirId , Param , Pat } ;
812use rustc_lint:: { LateContext , LintContext } ;
913use rustc_middle:: ty:: Binder ;
1014use rustc_middle:: ty:: adjustment:: Adjust ;
@@ -50,9 +54,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
5054 call_span,
5155 "usage of `bool::then` in `filter_map`" ,
5256 |diag| {
53- if contains_return ( recv) {
54- diag. help ( "consider using `filter` then `map` instead" ) ;
55- } else {
57+ if can_filter_and_then_move_to_closure ( cx, & param, recv, then_body) {
5658 diag. span_suggestion (
5759 call_span,
5860 "use `filter` then `map` instead" ,
@@ -62,8 +64,53 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
6264 ) ,
6365 Applicability :: MachineApplicable ,
6466 ) ;
67+ } else {
68+ diag. help ( "consider using `filter` then `map` instead" ) ;
6569 }
6670 } ,
6771 ) ;
6872 }
6973}
74+
75+ /// Returns a set of all bindings found in the given pattern.
76+ fn find_bindings_from_pat ( pat : & Pat < ' _ > ) -> FxHashSet < HirId > {
77+ let mut bindings = FxHashSet :: default ( ) ;
78+ pat. walk ( |p| {
79+ if let rustc_hir:: PatKind :: Binding ( _, hir_id, _, _) = p. kind {
80+ bindings. insert ( hir_id) ;
81+ }
82+ true
83+ } ) ;
84+ bindings
85+ }
86+
87+ /// Returns true if we can take a closure parameter and have it in both the `filter` function and
88+ /// the`map` function. This is not the case if:
89+ ///
90+ /// - The `filter` would contain an early return,
91+ /// - `filter` and `then` contain captures, and any of those are &mut
92+ fn can_filter_and_then_move_to_closure < ' tcx > (
93+ cx : & LateContext < ' tcx > ,
94+ param : & Param < ' tcx > ,
95+ filter : & ' tcx Expr < ' tcx > ,
96+ then : & ' tcx Expr < ' tcx > ,
97+ ) -> bool {
98+ if contains_return ( filter) {
99+ return false ;
100+ }
101+
102+ let Some ( filter_captures) = can_move_expr_to_closure ( cx, filter) else {
103+ return true ;
104+ } ;
105+ let Some ( then_captures) = can_move_expr_to_closure ( cx, then) else {
106+ return true ;
107+ } ;
108+
109+ let param_bindings = find_bindings_from_pat ( param. pat ) ;
110+ filter_captures. iter ( ) . all ( |( hir_id, filter_cap) | {
111+ param_bindings. contains ( hir_id)
112+ || !then_captures
113+ . get ( hir_id)
114+ . is_some_and ( |then_cap| matches ! ( * filter_cap | * then_cap, CaptureKind :: Ref ( Mutability :: Mut ) ) )
115+ } )
116+ }
0 commit comments