1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
- use clippy_utils:: source:: snippet ;
2
+ use clippy_utils:: source:: snippet_with_applicability ;
3
3
use clippy_utils:: { get_parent_expr, is_trait_method} ;
4
4
use rustc_errors:: Applicability ;
5
5
use rustc_hir:: def_id:: DefId ;
@@ -16,50 +16,26 @@ declare_clippy_lint! {
16
16
/// in a call argument that accepts `IntoIterator`.
17
17
///
18
18
/// ### 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.
47
22
///
48
23
/// ### Example
49
24
/// ```rust
50
- /// fn even_sum<I: IntoIterator<Item = u32 >>(iter: I) -> u32 {
25
+ /// fn even_sum<I: IntoIterator<Item = i32 >>(iter: I) -> i32 {
51
26
/// iter.into_iter().filter(|&x| x % 2 == 0).sum()
52
27
/// }
53
28
///
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`
55
31
/// ```
56
32
/// Use instead:
57
33
/// ```rust
58
- /// fn even_sum<I: IntoIterator<Item = u32 >>(iter: I) -> u32 {
34
+ /// fn even_sum<I: IntoIterator<Item = i32 >>(iter: I) -> i32 {
59
35
/// iter.into_iter().filter(|&x| x % 2 == 0).sum()
60
36
/// }
61
37
///
62
- /// let _ = even_sum(vec! [1, 2, 3]);
38
+ /// let _ = even_sum([1, 2, 3]);
63
39
/// ```
64
40
#[ clippy:: version = "1.71.0" ]
65
41
pub EXPLICIT_INTO_ITER_FN_ARG ,
@@ -73,23 +49,41 @@ enum MethodOrFunction {
73
49
Function ,
74
50
}
75
51
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
+
76
63
/// 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)
93
87
} else {
94
88
None
95
89
}
@@ -101,40 +95,44 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitIntoIterFnArg {
101
95
return ;
102
96
}
103
97
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 )
108
106
{
109
- let parent_expr = match parent_expr. kind {
107
+
108
+ let parent = match parent. kind {
110
109
ExprKind :: Call ( recv, args) if let ExprKind :: Path ( ref qpath) = recv. kind => {
111
110
cx. qpath_res ( qpath, recv. hir_id ) . opt_def_id ( )
112
111
. map ( |did| ( did, args, MethodOrFunction :: Function ) )
113
112
}
114
113
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 )
116
115
. map ( |did| ( did, args, MethodOrFunction :: Method ) )
117
116
}
118
117
_ => None ,
119
118
} ;
120
119
121
- if let Some ( ( parent_fn_did, args, kind) ) = parent_expr
120
+ if let Some ( ( parent_fn_did, args, kind) ) = parent
122
121
&& let sig = cx. tcx . fn_sig ( parent_fn_did) . skip_binder ( ) . skip_binder ( )
123
122
&& 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) )
128
124
&& 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 )
130
126
{
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
+
132
130
span_lint_and_then ( cx, EXPLICIT_INTO_ITER_FN_ARG , expr. span , "explicit call to `.into_iter()` in function argument accepting `IntoIterator`" , |diag| {
133
131
diag. span_suggestion (
134
132
expr. span ,
135
133
"consider removing `.into_iter()`" ,
136
134
sugg,
137
- Applicability :: MachineApplicable ,
135
+ applicability ,
138
136
) ;
139
137
diag. span_note ( span, "this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`" ) ;
140
138
} ) ;
0 commit comments