1
1
use clippy_utils:: diagnostics:: span_lint_and_help;
2
2
use clippy_utils:: msrvs:: { self , Msrv } ;
3
+ use clippy_utils:: visitors:: for_each_local_use_after_expr;
3
4
use clippy_utils:: { is_from_proc_macro, path_to_local} ;
5
+ use itertools:: Itertools ;
4
6
use rustc_ast:: LitKind ;
5
- use rustc_hir:: { Expr , ExprKind , HirId , Node , Pat } ;
7
+ use rustc_hir:: { Expr , ExprKind , Node , PatKind } ;
6
8
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
7
9
use rustc_middle:: lint:: in_external_macro;
8
- use rustc_middle:: ty;
10
+ use rustc_middle:: ty:: { self , Ty } ;
9
11
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
10
12
use std:: iter:: once;
13
+ use std:: ops:: ControlFlow ;
11
14
12
15
declare_clippy_lint ! {
13
16
/// ### What it does
14
17
/// Checks for tuple<=>array conversions that are not done with `.into()`.
15
18
///
16
19
/// ### Why is this bad?
17
- /// It may be unnecessary complexity. `.into()` works for converting tuples
18
- /// <=> arrays of up to 12 elements and may convey intent more clearly.
20
+ /// It may be unnecessary complexity. `.into()` works for converting tuples<=> arrays of up to
21
+ /// 12 elements and conveys the intent more clearly, while also leaving less room for hard to
22
+ /// spot bugs!
23
+ ///
24
+ /// ### Known issues
25
+ /// The suggested code may hide potential asymmetry in some cases. See
26
+ /// [#11085](https://github.com/rust-lang/rust-clippy/issues/11085) for more info.
19
27
///
20
28
/// ### Example
21
29
/// ```rust,ignore
@@ -41,130 +49,152 @@ pub struct TupleArrayConversions {
41
49
42
50
impl LateLintPass < ' _ > for TupleArrayConversions {
43
51
fn check_expr < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
44
- if !in_external_macro ( cx. sess ( ) , expr. span ) && self . msrv . meets ( msrvs:: TUPLE_ARRAY_CONVERSIONS ) {
45
- match expr. kind {
46
- ExprKind :: Array ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_array ( cx, expr, elements) ,
47
- ExprKind :: Tup ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_tuple ( cx, expr, elements) ,
48
- _ => { } ,
49
- }
52
+ if in_external_macro ( cx. sess ( ) , expr. span ) || !self . msrv . meets ( msrvs:: TUPLE_ARRAY_CONVERSIONS ) {
53
+ return ;
54
+ }
55
+
56
+ match expr. kind {
57
+ ExprKind :: Array ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_array ( cx, expr, elements) ,
58
+ ExprKind :: Tup ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_tuple ( cx, expr, elements) ,
59
+ _ => { } ,
50
60
}
51
61
}
52
62
53
63
extract_msrv_attr ! ( LateContext ) ;
54
64
}
55
65
56
- #[ expect(
57
- clippy:: blocks_in_if_conditions,
58
- reason = "not a FP, but this is much easier to understand"
59
- ) ]
60
66
fn check_array < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , elements : & ' tcx [ Expr < ' tcx > ] ) {
61
- if should_lint (
62
- cx,
63
- elements,
64
- // This is cursed.
65
- Some ,
66
- |( first_id, local) | {
67
- if let Node :: Pat ( pat) = local
68
- && let parent = parent_pat ( cx, pat)
69
- && parent. hir_id == first_id
70
- {
71
- return matches ! (
72
- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
73
- ty:: Tuple ( len) if len. len( ) == elements. len( )
74
- ) ;
75
- }
76
-
77
- false
78
- } ,
79
- ) || should_lint (
80
- cx,
81
- elements,
82
- |( i, expr) | {
83
- if let ExprKind :: Field ( path, field) = expr. kind && field. as_str ( ) == i. to_string ( ) {
84
- return Some ( ( i, path) ) ;
85
- } ;
86
-
87
- None
88
- } ,
89
- |( first_id, local) | {
90
- if let Node :: Pat ( pat) = local
91
- && let parent = parent_pat ( cx, pat)
92
- && parent. hir_id == first_id
93
- {
94
- return matches ! (
95
- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
96
- ty:: Tuple ( len) if len. len( ) == elements. len( )
97
- ) ;
98
- }
67
+ let ( ty:: Array ( ty, _) | ty:: Slice ( ty) ) = cx. typeck_results ( ) . expr_ty ( expr) . kind ( ) else {
68
+ unreachable ! ( "`expr` must be an array or slice due to `ExprKind::Array`" ) ;
69
+ } ;
70
+
71
+ if let [ first, ..] = elements
72
+ && let Some ( locals) = ( match first. kind {
73
+ ExprKind :: Field ( _, _) => elements
74
+ . iter ( )
75
+ . enumerate ( )
76
+ . map ( |( i, f) | -> Option < & ' tcx Expr < ' tcx > > {
77
+ let ExprKind :: Field ( lhs, ident) = f. kind else {
78
+ return None ;
79
+ } ;
80
+ ( ident. name . as_str ( ) == i. to_string ( ) ) . then_some ( lhs)
81
+ } )
82
+ . collect :: < Option < Vec < _ > > > ( ) ,
83
+ ExprKind :: Path ( _) => Some ( elements. iter ( ) . collect ( ) ) ,
84
+ _ => None ,
85
+ } )
86
+ && all_bindings_are_for_conv ( cx, & [ * ty] , expr, elements, & locals, ToType :: Array )
87
+ && !is_from_proc_macro ( cx, expr)
88
+ {
89
+ span_lint_and_help (
90
+ cx,
91
+ TUPLE_ARRAY_CONVERSIONS ,
92
+ expr. span ,
93
+ "it looks like you're trying to convert a tuple to an array" ,
94
+ None ,
95
+ "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed" ,
96
+ ) ;
97
+ }
98
+ }
99
99
100
- false
101
- } ,
102
- ) {
103
- emit_lint ( cx, expr, ToType :: Array ) ;
100
+ fn check_tuple < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , elements : & ' tcx [ Expr < ' tcx > ] ) {
101
+ if let ty:: Tuple ( tys) = cx. typeck_results ( ) . expr_ty ( expr) . kind ( )
102
+ && let [ first, ..] = elements
103
+ // Fix #11100
104
+ && tys. iter ( ) . all_equal ( )
105
+ && let Some ( locals) = ( match first. kind {
106
+ ExprKind :: Index ( _, _) => elements
107
+ . iter ( )
108
+ . enumerate ( )
109
+ . map ( |( i, i_expr) | -> Option < & ' tcx Expr < ' tcx > > {
110
+ if let ExprKind :: Index ( lhs, index) = i_expr. kind
111
+ && let ExprKind :: Lit ( lit) = index. kind
112
+ && let LitKind :: Int ( val, _) = lit. node
113
+ {
114
+ return ( val == i as u128 ) . then_some ( lhs) ;
115
+ } ;
116
+
117
+ None
118
+ } )
119
+ . collect :: < Option < Vec < _ > > > ( ) ,
120
+ ExprKind :: Path ( _) => Some ( elements. iter ( ) . collect ( ) ) ,
121
+ _ => None ,
122
+ } )
123
+ && all_bindings_are_for_conv ( cx, tys, expr, elements, & locals, ToType :: Tuple )
124
+ && !is_from_proc_macro ( cx, expr)
125
+ {
126
+ span_lint_and_help (
127
+ cx,
128
+ TUPLE_ARRAY_CONVERSIONS ,
129
+ expr. span ,
130
+ "it looks like you're trying to convert an array to a tuple" ,
131
+ None ,
132
+ "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed" ,
133
+ ) ;
104
134
}
105
135
}
106
136
107
- #[ expect(
108
- clippy:: blocks_in_if_conditions,
109
- reason = "not a FP, but this is much easier to understand"
110
- ) ]
137
+ /// Checks that every binding in `elements` comes from the same parent `Pat` with the kind if there
138
+ /// is a parent `Pat`. Returns false in any of the following cases:
139
+ /// * `kind` does not match `pat.kind`
140
+ /// * one or more elements in `elements` is not a binding
141
+ /// * one or more bindings does not have the same parent `Pat`
142
+ /// * one or more bindings are used after `expr`
143
+ /// * the bindings do not all have the same type
111
144
#[ expect( clippy:: cast_possible_truncation) ]
112
- fn check_tuple < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , elements : & ' tcx [ Expr < ' tcx > ] ) {
113
- if should_lint ( cx, elements, Some , |( first_id, local) | {
114
- if let Node :: Pat ( pat) = local
115
- && let parent = parent_pat ( cx, pat)
116
- && parent. hir_id == first_id
117
- {
118
- return matches ! (
119
- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
120
- ty:: Array ( _, len) if len. eval_target_usize( cx. tcx, cx. param_env) as usize == elements. len( )
121
- ) ;
145
+ fn all_bindings_are_for_conv < ' tcx > (
146
+ cx : & LateContext < ' tcx > ,
147
+ final_tys : & [ Ty < ' tcx > ] ,
148
+ expr : & Expr < ' _ > ,
149
+ elements : & [ Expr < ' _ > ] ,
150
+ locals : & [ & Expr < ' _ > ] ,
151
+ kind : ToType ,
152
+ ) -> bool {
153
+ let Some ( locals) = locals. iter ( ) . map ( |e| path_to_local ( e) ) . collect :: < Option < Vec < _ > > > ( ) else {
154
+ return false ;
155
+ } ;
156
+ let Some ( local_parents) = locals
157
+ . iter ( )
158
+ . map ( |& l| cx. tcx . hir ( ) . find_parent ( l) )
159
+ . collect :: < Option < Vec < _ > > > ( )
160
+ else {
161
+ return false ;
162
+ } ;
163
+
164
+ local_parents
165
+ . iter ( )
166
+ . map ( |node| match node {
167
+ Node :: Pat ( pat) => kind. eq ( & pat. kind ) . then_some ( pat. hir_id ) ,
168
+ Node :: Local ( l) => Some ( l. hir_id ) ,
169
+ _ => None ,
170
+ } )
171
+ . all_equal ( )
172
+ // Fix #11124, very convenient utils function! ❤️
173
+ && locals
174
+ . iter ( )
175
+ . all ( |& l| for_each_local_use_after_expr ( cx, l, expr. hir_id , |_| ControlFlow :: Break :: < ( ) > ( ( ) ) ) . is_continue ( ) )
176
+ && local_parents. first ( ) . is_some_and ( |node| {
177
+ let Some ( ty) = match node {
178
+ Node :: Pat ( pat) => Some ( pat. hir_id ) ,
179
+ Node :: Local ( l) => Some ( l. hir_id ) ,
180
+ _ => None ,
122
181
}
123
-
124
- false
125
- } ) || should_lint (
126
- cx,
127
- elements,
128
- |( i, expr) | {
129
- if let ExprKind :: Index ( path, index) = expr. kind
130
- && let ExprKind :: Lit ( lit) = index. kind
131
- && let LitKind :: Int ( val, _) = lit. node
132
- && val as usize == i
133
- {
134
- return Some ( ( i, path) ) ;
182
+ . map ( |hir_id| cx. typeck_results ( ) . node_type ( hir_id) ) else {
183
+ return false ;
135
184
} ;
136
-
137
- None
138
- } ,
139
- |( first_id, local) | {
140
- if let Node :: Pat ( pat) = local
141
- && let parent = parent_pat ( cx, pat)
142
- && parent. hir_id == first_id
143
- {
144
- return matches ! (
145
- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
146
- ty:: Array ( _, len) if len. eval_target_usize( cx. tcx, cx. param_env) as usize == elements. len( )
147
- ) ;
185
+ match ( kind, ty. kind ( ) ) {
186
+ // Ensure the final type and the original type have the same length, and that there
187
+ // is no implicit `&mut`<=>`&` anywhere (#11100). Bit ugly, I know, but it works.
188
+ ( ToType :: Array , ty:: Tuple ( tys) ) => {
189
+ tys. len ( ) == elements. len ( ) && tys. iter ( ) . chain ( final_tys. iter ( ) . copied ( ) ) . all_equal ( )
190
+ } ,
191
+ ( ToType :: Tuple , ty:: Array ( ty, len) ) => {
192
+ len. eval_target_usize ( cx. tcx , cx. param_env ) as usize == elements. len ( )
193
+ && final_tys. iter ( ) . chain ( once ( ty) ) . all_equal ( )
194
+ } ,
195
+ _ => false ,
148
196
}
149
-
150
- false
151
- } ,
152
- ) {
153
- emit_lint ( cx, expr, ToType :: Tuple ) ;
154
- }
155
- }
156
-
157
- /// Walks up the `Pat` until it's reached the final containing `Pat`.
158
- fn parent_pat < ' tcx > ( cx : & LateContext < ' tcx > , start : & ' tcx Pat < ' tcx > ) -> & ' tcx Pat < ' tcx > {
159
- let mut end = start;
160
- for ( _, node) in cx. tcx . hir ( ) . parent_iter ( start. hir_id ) {
161
- if let Node :: Pat ( pat) = node {
162
- end = pat;
163
- } else {
164
- break ;
165
- }
166
- }
167
- end
197
+ } )
168
198
}
169
199
170
200
#[ derive( Clone , Copy ) ]
@@ -173,61 +203,11 @@ enum ToType {
173
203
Tuple ,
174
204
}
175
205
176
- impl ToType {
177
- fn msg ( self ) -> & ' static str {
178
- match self {
179
- ToType :: Array => "it looks like you're trying to convert a tuple to an array" ,
180
- ToType :: Tuple => "it looks like you're trying to convert an array to a tuple" ,
181
- }
182
- }
183
-
184
- fn help ( self ) -> & ' static str {
206
+ impl PartialEq < PatKind < ' _ > > for ToType {
207
+ fn eq ( & self , other : & PatKind < ' _ > ) -> bool {
185
208
match self {
186
- ToType :: Array => "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed" ,
187
- ToType :: Tuple => "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed" ,
209
+ ToType :: Array => matches ! ( other , PatKind :: Tuple ( _ , _ ) ) ,
210
+ ToType :: Tuple => matches ! ( other , PatKind :: Slice ( _ , _ , _ ) ) ,
188
211
}
189
212
}
190
213
}
191
-
192
- fn emit_lint < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , to_type : ToType ) -> bool {
193
- if !is_from_proc_macro ( cx, expr) {
194
- span_lint_and_help (
195
- cx,
196
- TUPLE_ARRAY_CONVERSIONS ,
197
- expr. span ,
198
- to_type. msg ( ) ,
199
- None ,
200
- to_type. help ( ) ,
201
- ) ;
202
-
203
- return true ;
204
- }
205
-
206
- false
207
- }
208
-
209
- fn should_lint < ' tcx > (
210
- cx : & LateContext < ' tcx > ,
211
- elements : & ' tcx [ Expr < ' tcx > ] ,
212
- map : impl FnMut ( ( usize , & ' tcx Expr < ' tcx > ) ) -> Option < ( usize , & Expr < ' _ > ) > ,
213
- predicate : impl FnMut ( ( HirId , & Node < ' tcx > ) ) -> bool ,
214
- ) -> bool {
215
- if let Some ( elements) = elements
216
- . iter ( )
217
- . enumerate ( )
218
- . map ( map)
219
- . collect :: < Option < Vec < _ > > > ( )
220
- && let Some ( locals) = elements
221
- . iter ( )
222
- . map ( |( _, element) | path_to_local ( element) . and_then ( |local| cx. tcx . hir ( ) . find ( local) ) )
223
- . collect :: < Option < Vec < _ > > > ( )
224
- && let [ first, rest @ ..] = & * locals
225
- && let Node :: Pat ( first_pat) = first
226
- && let parent = parent_pat ( cx, first_pat) . hir_id
227
- && rest. iter ( ) . chain ( once ( first) ) . map ( |i| ( parent, i) ) . all ( predicate)
228
- {
229
- return true ;
230
- }
231
-
232
- false
233
- }
0 commit comments