1- use clippy_utils:: diagnostics:: span_lint_and_sugg ;
2- use clippy_utils:: source:: snippet ;
3- use clippy_utils:: { SpanlessEq , higher, is_integer_const, is_trait_method} ;
1+ use clippy_utils:: diagnostics:: span_lint_and_then ;
2+ use clippy_utils:: source:: { SpanRangeExt as _ , snippet_with_applicability } ;
3+ use clippy_utils:: { SpanlessEq , get_parent_expr , higher, is_integer_const, is_trait_method, sym } ;
44use rustc_errors:: Applicability ;
5- use rustc_hir:: { Expr , ExprKind , QPath } ;
5+ use rustc_hir:: { Expr , ExprKind , Node , Pat , PatKind , QPath } ;
66use rustc_lint:: LateContext ;
7- use rustc_span:: sym;
87
98use super :: RANGE_ZIP_WITH_LEN ;
109
@@ -21,14 +20,93 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'
2120 && let ExprKind :: Path ( QPath :: Resolved ( _, len_path) ) = len_recv. kind
2221 && SpanlessEq :: new ( cx) . eq_path_segments ( iter_path. segments , len_path. segments )
2322 {
24- span_lint_and_sugg (
23+ span_lint_and_then (
2524 cx,
2625 RANGE_ZIP_WITH_LEN ,
2726 expr. span ,
2827 "using `.zip()` with a range and `.len()`" ,
29- "try" ,
30- format ! ( "{}.iter().enumerate()" , snippet( cx, recv. span, "_" ) ) ,
31- Applicability :: MachineApplicable ,
28+ |diag| {
29+ // If the iterator content is consumed by a pattern with exactly two elements, swap
30+ // the order of those elements. Otherwise, the suggestion will be marked as
31+ // `Applicability::MaybeIncorrect` (because it will be), and a note will be added
32+ // to the diagnostic to underline the swapping of the index and the content.
33+ let pat = methods_pattern ( cx, expr) . or_else ( || for_loop_pattern ( cx, expr) ) ;
34+ let invert_bindings = if let Some ( pat) = pat
35+ && pat. span . eq_ctxt ( expr. span )
36+ && let PatKind :: Tuple ( [ first, second] , _) = pat. kind
37+ {
38+ Some ( ( first. span , second. span ) )
39+ } else {
40+ None
41+ } ;
42+ let mut app = Applicability :: MachineApplicable ;
43+ let mut suggestions = vec ! [ (
44+ expr. span,
45+ format!(
46+ "{}.iter().enumerate()" ,
47+ snippet_with_applicability( cx, recv. span, "_" , & mut app)
48+ ) ,
49+ ) ] ;
50+ if let Some ( ( left, right) ) = invert_bindings
51+ && let Some ( snip_left) = left. get_source_text ( cx)
52+ && let Some ( snip_right) = right. get_source_text ( cx)
53+ {
54+ suggestions. extend ( [ ( left, snip_right. to_string ( ) ) , ( right, snip_left. to_string ( ) ) ] ) ;
55+ } else {
56+ app = Applicability :: MaybeIncorrect ;
57+ }
58+ diag. multipart_suggestion ( "use" , suggestions, app) ;
59+ if app != Applicability :: MachineApplicable {
60+ diag. note ( "the order of the element and the index will be swapped" ) ;
61+ }
62+ } ,
3263 ) ;
3364 }
3465}
66+
67+ /// If `expr` is the argument of a `for` loop, return the loop pattern.
68+ fn for_loop_pattern < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Pat < ' tcx > > {
69+ cx. tcx . hir_parent_iter ( expr. hir_id ) . find_map ( |( _, node) | {
70+ if let Node :: Expr ( ancestor_expr) = node
71+ && let Some ( for_loop) = higher:: ForLoop :: hir ( ancestor_expr)
72+ && for_loop. arg . hir_id == expr. hir_id
73+ {
74+ Some ( for_loop. pat )
75+ } else {
76+ None
77+ }
78+ } )
79+ }
80+
81+ /// If `expr` is the receiver of an `Iterator` method which consumes the iterator elements and feed
82+ /// them to a closure, return the pattern of the closure.
83+ fn methods_pattern < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Pat < ' tcx > > {
84+ if let Some ( parent_expr) = get_parent_expr ( cx, expr)
85+ && is_trait_method ( cx, expr, sym:: Iterator )
86+ && let ExprKind :: MethodCall ( method, recv, [ arg] , _) = parent_expr. kind
87+ && recv. hir_id == expr. hir_id
88+ && matches ! (
89+ method. ident. name,
90+ sym:: all
91+ | sym:: any
92+ | sym:: filter_map
93+ | sym:: find_map
94+ | sym:: flat_map
95+ | sym:: for_each
96+ | sym:: is_partitioned
97+ | sym:: is_sorted_by_key
98+ | sym:: map
99+ | sym:: map_while
100+ | sym:: position
101+ | sym:: rposition
102+ | sym:: try_for_each
103+ )
104+ && let ExprKind :: Closure ( closure) = arg. kind
105+ && let body = cx. tcx . hir_body ( closure. body )
106+ && let [ param] = body. params
107+ {
108+ Some ( param. pat )
109+ } else {
110+ None
111+ }
112+ }
0 commit comments