11use clippy_utils:: diagnostics:: span_lint_and_then;
2- use clippy_utils:: source:: snippet ;
2+ use clippy_utils:: source:: snippet_with_applicability ;
33use clippy_utils:: { get_parent_expr, is_trait_method} ;
44use rustc_errors:: Applicability ;
55use rustc_hir:: def_id:: DefId ;
@@ -16,50 +16,26 @@ declare_clippy_lint! {
1616 /// in a call argument that accepts `IntoIterator`.
1717 ///
1818 /// ### Why is this bad?
19- /// If a function has a generic parameter with an `IntoIterator` trait bound, it means that the function
20- /// will *have* to call `.into_iter()` to get an iterator out of it, thereby making the call to `.into_iter()`
21- /// at call site redundant.
22- ///
23- /// Consider this example:
24- /// ```rs,ignore
25- /// fn foo<T: IntoIterator<Item = u32>>(iter: T) {
26- /// let it = iter.into_iter();
27- /// ^^^^^^^^^^^^ the function has to call `.into_iter()` to get the iterator
28- /// }
29- ///
30- /// foo(vec![1, 2, 3].into_iter());
31- /// ^^^^^^^^^^^^ ... making this `.into_iter()` call redundant.
32- /// ```
33- ///
34- /// The reason for why calling `.into_iter()` twice (once at call site and again inside of the function) works in the first place
35- /// is because there is a blanket implementation of `IntoIterator` for all types that implement `Iterator` in the standard library,
36- /// in which it simply returns itself, effectively making the second call to `.into_iter()` a "no-op":
37- /// ```rust,ignore
38- /// impl<I: Iterator> IntoIterator for I {
39- /// type Item = I::Item;
40- /// type IntoIter = I;
41- ///
42- /// fn into_iter(self) -> I {
43- /// self
44- /// }
45- /// }
46- /// ```
19+ /// If a generic parameter has an `IntoIterator` bound, there is no need to call `.into_iter()` at call site.
20+ /// Calling `IntoIterator::into_iter()` on a value implies that its type already implements `IntoIterator`,
21+ /// so you can just pass the value as is.
4722 ///
4823 /// ### Example
4924 /// ```rust
50- /// fn even_sum<I: IntoIterator<Item = u32 >>(iter: I) -> u32 {
25+ /// fn even_sum<I: IntoIterator<Item = i32 >>(iter: I) -> i32 {
5126 /// iter.into_iter().filter(|&x| x % 2 == 0).sum()
5227 /// }
5328 ///
54- /// let _ = even_sum(vec![1, 2, 3].into_iter());
29+ /// let _ = even_sum([1, 2, 3].into_iter());
30+ /// // ^^^^^^^^^^^^ redundant. `[i32; 3]` implements `IntoIterator`
5531 /// ```
5632 /// Use instead:
5733 /// ```rust
58- /// fn even_sum<I: IntoIterator<Item = u32 >>(iter: I) -> u32 {
34+ /// fn even_sum<I: IntoIterator<Item = i32 >>(iter: I) -> i32 {
5935 /// iter.into_iter().filter(|&x| x % 2 == 0).sum()
6036 /// }
6137 ///
62- /// let _ = even_sum(vec! [1, 2, 3]);
38+ /// let _ = even_sum([1, 2, 3]);
6339 /// ```
6440 #[ clippy:: version = "1.71.0" ]
6541 pub EXPLICIT_INTO_ITER_FN_ARG ,
@@ -73,23 +49,41 @@ enum MethodOrFunction {
7349 Function ,
7450}
7551
52+ impl MethodOrFunction {
53+ /// Maps the argument position in `pos` to the parameter position.
54+ /// For methods, `self` is skipped.
55+ fn param_pos ( self , pos : usize ) -> usize {
56+ match self {
57+ MethodOrFunction :: Method => pos + 1 ,
58+ MethodOrFunction :: Function => pos,
59+ }
60+ }
61+ }
62+
7663/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`
77- fn into_iter_bound ( cx : & LateContext < ' _ > , fn_did : DefId , param_index : u32 ) -> Option < Span > {
78- if let Some ( into_iter_did) = cx. tcx . get_diagnostic_item ( sym:: IntoIterator ) {
79- cx. tcx
80- . predicates_of ( fn_did)
81- . predicates
82- . iter ( )
83- . find_map ( |& ( ref pred, span) | {
84- if let ty:: PredicateKind :: Clause ( ty:: Clause :: Trait ( tr) ) = pred. kind ( ) . skip_binder ( )
85- && tr. def_id ( ) == into_iter_did
86- && tr. self_ty ( ) . is_param ( param_index)
87- {
88- Some ( span)
89- } else {
90- None
91- }
92- } )
64+ fn into_iter_bound ( cx : & LateContext < ' _ > , fn_did : DefId , into_iter_did : DefId , param_index : u32 ) -> Option < Span > {
65+ cx. tcx
66+ . predicates_of ( fn_did)
67+ . predicates
68+ . iter ( )
69+ . find_map ( |& ( ref pred, span) | {
70+ if let ty:: PredicateKind :: Clause ( ty:: Clause :: Trait ( tr) ) = pred. kind ( ) . skip_binder ( )
71+ && tr. def_id ( ) == into_iter_did
72+ && tr. self_ty ( ) . is_param ( param_index)
73+ {
74+ Some ( span)
75+ } else {
76+ None
77+ }
78+ } )
79+ }
80+
81+ fn into_iter_call < ' hir > ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < & ' hir Expr < ' hir > > {
82+ if let ExprKind :: MethodCall ( name, recv, _, _) = expr. kind
83+ && is_trait_method ( cx, expr, sym:: IntoIterator )
84+ && name. ident . name == sym:: into_iter
85+ {
86+ Some ( recv)
9387 } else {
9488 None
9589 }
@@ -101,40 +95,44 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitIntoIterFnArg {
10195 return ;
10296 }
10397
104- if let ExprKind :: MethodCall ( name, recv, ..) = expr. kind
105- && is_trait_method ( cx, expr, sym:: IntoIterator )
106- && name. ident . name == sym:: into_iter
107- && let Some ( parent_expr) = get_parent_expr ( cx, expr)
98+ if let Some ( recv) = into_iter_call ( cx, expr)
99+ && let Some ( parent) = get_parent_expr ( cx, expr)
100+ // Make sure that this is not a chained into_iter call (i.e. `x.into_iter().into_iter()`)
101+ // That case is already covered by `useless_conversion` and we don't want to lint twice
102+ // with two contradicting suggestions.
103+ && into_iter_call ( cx, parent) . is_none ( )
104+ && into_iter_call ( cx, recv) . is_none ( )
105+ && let Some ( into_iter_did) = cx. tcx . get_diagnostic_item ( sym:: IntoIterator )
108106 {
109- let parent_expr = match parent_expr. kind {
107+
108+ let parent = match parent. kind {
110109 ExprKind :: Call ( recv, args) if let ExprKind :: Path ( ref qpath) = recv. kind => {
111110 cx. qpath_res ( qpath, recv. hir_id ) . opt_def_id ( )
112111 . map ( |did| ( did, args, MethodOrFunction :: Function ) )
113112 }
114113 ExprKind :: MethodCall ( .., args, _) => {
115- cx. typeck_results ( ) . type_dependent_def_id ( parent_expr . hir_id )
114+ cx. typeck_results ( ) . type_dependent_def_id ( parent . hir_id )
116115 . map ( |did| ( did, args, MethodOrFunction :: Method ) )
117116 }
118117 _ => None ,
119118 } ;
120119
121- if let Some ( ( parent_fn_did, args, kind) ) = parent_expr
120+ if let Some ( ( parent_fn_did, args, kind) ) = parent
122121 && let sig = cx. tcx . fn_sig ( parent_fn_did) . skip_binder ( ) . skip_binder ( )
123122 && let Some ( arg_pos) = args. iter ( ) . position ( |x| x. hir_id == expr. hir_id )
124- && let Some ( & into_iter_param) = sig. inputs ( ) . get ( match kind {
125- MethodOrFunction :: Function => arg_pos,
126- MethodOrFunction :: Method => arg_pos + 1 , // skip self arg
127- } )
123+ && let Some ( & into_iter_param) = sig. inputs ( ) . get ( kind. param_pos ( arg_pos) )
128124 && let ty:: Param ( param) = into_iter_param. kind ( )
129- && let Some ( span) = into_iter_bound ( cx, parent_fn_did, param. index )
125+ && let Some ( span) = into_iter_bound ( cx, parent_fn_did, into_iter_did , param. index )
130126 {
131- let sugg = snippet ( cx, recv. span . source_callsite ( ) , "<expr>" ) . into_owned ( ) ;
127+ let mut applicability = Applicability :: MachineApplicable ;
128+ let sugg = snippet_with_applicability ( cx, recv. span . source_callsite ( ) , "<expr>" , & mut applicability) . into_owned ( ) ;
129+
132130 span_lint_and_then ( cx, EXPLICIT_INTO_ITER_FN_ARG , expr. span , "explicit call to `.into_iter()` in function argument accepting `IntoIterator`" , |diag| {
133131 diag. span_suggestion (
134132 expr. span ,
135133 "consider removing `.into_iter()`" ,
136134 sugg,
137- Applicability :: MachineApplicable ,
135+ applicability ,
138136 ) ;
139137 diag. span_note ( span, "this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`" ) ;
140138 } ) ;
0 commit comments