|
1 | 1 | use super::FILTER_MAP_BOOL_THEN;
|
2 |
| -use clippy_utils::diagnostics::span_lint_and_sugg; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_then; |
3 | 3 | use clippy_utils::source::SpanRangeExt;
|
4 | 4 | use clippy_utils::ty::is_copy;
|
5 |
| -use clippy_utils::{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; |
6 | 10 | use rustc_errors::Applicability;
|
7 |
| -use rustc_hir::{Expr, ExprKind}; |
| 11 | +use rustc_hir::{Expr, ExprKind, HirId, Param, Pat}; |
8 | 12 | use rustc_lint::{LateContext, LintContext};
|
9 | 13 | use rustc_middle::ty::Binder;
|
10 | 14 | use rustc_middle::ty::adjustment::Adjust;
|
@@ -44,17 +48,69 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
|
44 | 48 | && let Some(filter) = recv.span.get_source_text(cx)
|
45 | 49 | && let Some(map) = then_body.span.get_source_text(cx)
|
46 | 50 | {
|
47 |
| - span_lint_and_sugg( |
| 51 | + span_lint_and_then( |
48 | 52 | cx,
|
49 | 53 | FILTER_MAP_BOOL_THEN,
|
50 | 54 | call_span,
|
51 | 55 | "usage of `bool::then` in `filter_map`",
|
52 |
| - "use `filter` then `map` instead", |
53 |
| - format!( |
54 |
| - "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", |
55 |
| - derefs = "*".repeat(needed_derefs) |
56 |
| - ), |
57 |
| - Applicability::MachineApplicable, |
| 56 | + |diag| { |
| 57 | + if can_filter_and_then_move_to_closure(cx, ¶m, recv, then_body) { |
| 58 | + diag.span_suggestion( |
| 59 | + call_span, |
| 60 | + "use `filter` then `map` instead", |
| 61 | + format!( |
| 62 | + "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", |
| 63 | + derefs = "*".repeat(needed_derefs) |
| 64 | + ), |
| 65 | + Applicability::MachineApplicable, |
| 66 | + ); |
| 67 | + } else { |
| 68 | + diag.help("consider using `filter` then `map` instead"); |
| 69 | + } |
| 70 | + }, |
58 | 71 | );
|
59 | 72 | }
|
60 | 73 | }
|
| 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