@@ -3,14 +3,15 @@ use clippy_utils::fn_has_unsatisfiable_preds;
3
3
use clippy_utils:: source:: snippet_opt;
4
4
use itertools:: Itertools ;
5
5
use rustc_const_eval:: interpret:: Scalar ;
6
+ use rustc_data_structures:: fx:: FxHashSet ;
6
7
use rustc_hir:: def_id:: LocalDefId ;
7
8
use rustc_hir:: { intravisit:: FnKind , Body , FnDecl } ;
8
9
use rustc_index:: IndexVec ;
9
10
use rustc_lint:: { LateContext , LateLintPass } ;
10
11
use rustc_middle:: mir:: {
11
12
self , interpret:: ConstValue , visit:: Visitor , Constant , Location , Mutability , Operand , Place , Rvalue ,
12
13
} ;
13
- use rustc_middle:: mir:: { AggregateKind , PlaceElem , Terminator , TerminatorKind } ;
14
+ use rustc_middle:: mir:: { AggregateKind , CastKind , PlaceElem , Terminator , TerminatorKind } ;
14
15
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
15
16
use rustc_span:: source_map:: Spanned ;
16
17
use rustc_span:: Span ;
@@ -61,78 +62,35 @@ impl LateLintPass<'_> for LocalAssignedSingleValue {
61
62
} ;
62
63
v. visit_body ( mir) ;
63
64
64
- // for (local, i) in v.map.iter_enumerated() {
65
- // dbg!(local, i);
66
- // }
67
-
68
65
for ( local, i) in v. map . iter_enumerated ( ) {
69
- if !i. assigned_non_const_rvalue
70
- && !i. mut_ref_acquired
71
- && nested_locals_are_not_from_bad_instr ( & v. map , i)
72
- && assignments_all_same_value ( & v. map , i)
73
- {
74
- let LocalUsageValues {
75
- usage,
76
- mut_ref_acquired : _,
77
- assigned_non_const_rvalue : _,
78
- is_from_bad_instr : _,
79
- } = i;
80
-
81
- if let Some ( local_decl) = mir. local_decls . get ( local)
82
- && let [ dbg_info] = & * mir
83
- . var_debug_info
84
- . iter ( )
85
- . filter ( |info| info. source_info . span == local_decl. source_info . span )
86
- . collect_vec ( )
87
- // Don't handle function arguments.
88
- && dbg_info. argument_index . is_none ( )
89
- // Ignore anything from a procedural macro, or locals we cannot prove aren't
90
- // temporaries
91
- && let Some ( snippet) = snippet_opt ( cx, dbg_info. source_info . span )
92
- && snippet. ends_with ( dbg_info. name . as_str ( ) )
93
- {
94
- span_lint (
95
- cx,
96
- LOCAL_ASSIGNED_SINGLE_VALUE ,
97
- usage. iter ( ) . map ( |i| i. span ) . collect_vec ( ) ,
98
- "local only ever assigned single value" ,
99
- ) ;
100
- }
101
- }
102
- }
103
-
104
- /*
105
- for (local, usage) in &v.local_usage {
106
- if should_lint(&v.local_usage, *local, usage) {
107
- let LocalUsageValues {
108
- usage,
109
- mut_ref_acquired: _,
110
- assigned_non_const: _,
111
- } = usage;
66
+ let LocalUsageValues {
67
+ usage,
68
+ mut_ref_acquired,
69
+ } = i;
112
70
113
- if let Some(local_decl) = mir.local_decls.get(*local)
71
+ if !mut_ref_acquired && eval_nested_locals_are_constant ( & v. map , i)
72
+ && eval_local ( & v. map , i)
73
+ && let Some ( local_decl) = mir. local_decls . get ( local)
114
74
&& let [ dbg_info] = & * mir
115
75
. var_debug_info
116
76
. iter ( )
117
77
. filter ( |info| info. source_info . span == local_decl. source_info . span )
118
78
. collect_vec ( )
119
- // Don't handle function arguments.
120
- && dbg_info.argument_index.is_none()
121
- // Ignore anything from a procedural macro, or locals we cannot prove aren't
122
- // temporaries
123
- && let Some(snippet) = snippet_opt(cx, dbg_info.source_info.span)
124
- && snippet.ends_with(dbg_info.name.as_str())
125
- {
126
- span_lint(
127
- cx,
128
- LOCAL_ASSIGNED_SINGLE_VALUE,
129
- usage.iter().map(|(span, _)| *span).collect_vec(),
130
- "local only ever assigned single value",
131
- );
132
- }
79
+ // Don't handle function arguments.
80
+ && dbg_info. argument_index . is_none ( )
81
+ // Ignore anything from a procedural macro, or locals we cannot prove aren't
82
+ // temporaries
83
+ && let Some ( snippet) = snippet_opt ( cx, dbg_info. source_info . span )
84
+ && snippet. ends_with ( dbg_info. name . as_str ( ) )
85
+ {
86
+ span_lint (
87
+ cx,
88
+ LOCAL_ASSIGNED_SINGLE_VALUE ,
89
+ usage. iter ( ) . map ( |i| i. span ) . collect_vec ( ) ,
90
+ "local only ever assigned single value" ,
91
+ ) ;
133
92
}
134
93
}
135
- */
136
94
}
137
95
}
138
96
@@ -145,13 +103,6 @@ struct LocalUsageValues<'tcx> {
145
103
usage : Vec < Spanned < LocalUsage < ' tcx > > > ,
146
104
/// Whether it's mutably borrowed, ever. We should not lint this.
147
105
mut_ref_acquired : bool ,
148
- /// Whether it's assigned a value we cannot prove is constant, ever. We should not lint this.
149
- assigned_non_const_rvalue : bool ,
150
- /// Whether it's assigned a value that we know cannot be constant. This is differentiated from
151
- /// `assigned_non_const` since we check this for nested locals.
152
- ///
153
- /// This is set to `true` for function calls or binary operations.
154
- is_from_bad_instr : bool ,
155
106
}
156
107
157
108
#[ derive( Debug ) ]
@@ -165,6 +116,10 @@ enum LocalUsage<'tcx> {
165
116
/// When a `Local` depends on the value of another local by accessing any of its fields or
166
117
/// indexing
167
118
DependsOnWithProj ( mir:: Local , & ' tcx PlaceElem < ' tcx > ) ,
119
+ /// Used when a local's assigned a value we cannot prove is constant.
120
+ NonConst ,
121
+ /// This is always overwritten.
122
+ Pending ,
168
123
}
169
124
170
125
struct V < ' a , ' tcx > {
@@ -193,30 +148,28 @@ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
193
148
}
194
149
195
150
let usage = & mut map[ place. local ] ;
151
+ let mut spanned = Spanned {
152
+ node : LocalUsage :: Pending ,
153
+ span : stmt. source_info . span ,
154
+ } ;
196
155
197
- if let Rvalue :: Use ( Operand :: Constant ( constant) ) = rvalue
198
- && let Constant { literal, .. } = * * constant
199
- && let Some ( ConstValue :: Scalar ( scalar) ) = literal. try_to_value ( cx. tcx )
200
- {
201
- usage. usage . push ( Spanned { node : LocalUsage :: Scalar ( scalar) , span : stmt. source_info . span } ) ;
202
- } else if let Rvalue :: Use ( operand) = rvalue
203
- && let Some ( place) = operand. place ( )
204
- {
205
- if let [ base_proj, ..] = place. projection . as_slice ( )
206
- // Handle `let [x, y] = [1, 1]` and `let (x, y) = (1, 1)`
207
- && matches ! ( base_proj, PlaceElem :: Field ( ..) | PlaceElem :: Index ( ..) )
156
+ if let Rvalue :: Use ( operand) = rvalue {
157
+ if let Operand :: Constant ( constant) = operand
158
+ && let Constant { literal, .. } = * * constant
159
+ && let Some ( ConstValue :: Scalar ( scalar) ) = literal. try_to_value ( cx. tcx )
208
160
{
209
- usage. usage . push ( Spanned {
210
- node : LocalUsage :: DependsOnWithProj ( place. local , base_proj) ,
211
- span : stmt. source_info . span ,
212
- } ) ;
213
- } else {
214
- // It seems sometimes a local's just moved, no projections, so let's make sure we
215
- // don't set `assigned_non_const` to true for these cases
216
- usage. usage . push ( Spanned {
217
- node : LocalUsage :: DependsOn ( place. local ) ,
218
- span : stmt. source_info . span
219
- } ) ;
161
+ spanned. node = LocalUsage :: Scalar ( scalar) ;
162
+ } else if let Some ( place) = operand. place ( ) {
163
+ if let [ base_proj, ..] = place. projection . as_slice ( )
164
+ // Handle `let [x, y] = [1, 1]` and `let (x, y) = (1, 1)`
165
+ && matches ! ( base_proj, PlaceElem :: Field ( ..) | PlaceElem :: Index ( ..) )
166
+ {
167
+ spanned. node = LocalUsage :: DependsOnWithProj ( place. local , base_proj) ;
168
+ } else {
169
+ // It seems sometimes a local's just moved, no projections, so let's make sure we
170
+ // don't set `assigned_non_const` to true for these cases
171
+ spanned. node = LocalUsage :: DependsOn ( place. local ) ;
172
+ }
220
173
}
221
174
}
222
175
// Handle creation of tuples/arrays, otherwise the above would be useless
@@ -226,25 +179,45 @@ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
226
179
// TODO: Let's remove this `cloned` call, if possible.
227
180
&& let Some ( scalars) = extract_scalars ( cx, fields. into_iter ( ) . cloned ( ) )
228
181
{
229
- usage. usage . push ( Spanned {
230
- node : LocalUsage :: Scalars ( scalars) ,
231
- span : stmt. source_info . span ,
232
- } )
233
- } else if let Rvalue :: BinaryOp ( ..) | Rvalue :: CheckedBinaryOp ( ..) = rvalue {
234
- usage. is_from_bad_instr = true ;
182
+ spanned. node = LocalUsage :: Scalars ( scalars) ;
183
+ } else if let Rvalue :: Cast ( kind, operand, _) = rvalue {
184
+ if let Operand :: Constant ( constant) = operand
185
+ && matches ! (
186
+ kind,
187
+ CastKind :: IntToInt | CastKind :: FloatToInt | CastKind :: FloatToFloat | CastKind :: IntToFloat ,
188
+ )
189
+ && let Constant { literal, .. } = * * constant
190
+ && let Some ( ConstValue :: Scalar ( scalar) ) = literal. try_to_value ( cx. tcx )
191
+ {
192
+ spanned. node = LocalUsage :: Scalar ( scalar) ;
193
+ } else if let Operand :: Move ( place) = operand {
194
+ if let [ base_proj, ..] = place. projection . as_slice ( )
195
+ && matches ! ( base_proj, PlaceElem :: Field ( ..) | PlaceElem :: Index ( ..) )
196
+ {
197
+ // Probably unnecessary
198
+ spanned. node = LocalUsage :: DependsOnWithProj ( place. local , base_proj) ;
199
+ } else {
200
+ // Handle casts from enum discriminants
201
+ spanned. node = LocalUsage :: DependsOn ( place. local ) ;
202
+ }
203
+ }
235
204
} else {
236
- // We can also probably handle stuff like `x += 1` here, maybe. But this would be
237
- // very very complex. Let's keep it simple enough.
238
- usage. assigned_non_const_rvalue = true ;
205
+ spanned. node = LocalUsage :: NonConst ;
206
+ }
207
+
208
+ if !matches ! ( spanned. node, LocalUsage :: Pending ) {
209
+ usage. usage . push ( spanned) ;
239
210
}
240
211
}
241
212
242
213
fn visit_terminator ( & mut self , terminator : & Terminator < ' _ > , _: Location ) {
243
214
let Self { body : _, cx : _, map } = self ;
244
215
245
- // TODO: Maybe we can allow const fns, if we can evaluate them of course
246
216
if let TerminatorKind :: Call { destination, .. } = terminator. kind {
247
- map[ destination. local ] . is_from_bad_instr = true ;
217
+ map[ destination. local ] . usage . push ( Spanned {
218
+ node : LocalUsage :: NonConst ,
219
+ span : terminator. source_info . span ,
220
+ } ) ;
248
221
}
249
222
}
250
223
}
@@ -269,87 +242,85 @@ where
269
242
. collect :: < Option < Vec < _ > > > ( )
270
243
}
271
244
272
- fn assignments_all_same_value ( map : & LocalUsageMap < ' _ > , usage : & LocalUsageValues < ' _ > ) -> bool {
273
- let LocalUsageValues {
274
- usage,
275
- mut_ref_acquired : _,
276
- assigned_non_const_rvalue : _,
277
- is_from_bad_instr : _,
278
- } = usage;
245
+ fn eval_local ( map : & LocalUsageMap < ' _ > , local : & LocalUsageValues < ' _ > ) -> bool {
246
+ let mut assignments = vec ! [ ] ;
279
247
280
- if usage. len ( ) < = 1 {
248
+ if local . usage . len ( ) = = 1 {
281
249
return false ;
282
250
}
283
251
284
- // TODO: This code is clone-hell.
285
-
286
- let mut all_assignments = vec ! [ ] ;
287
- for assignment in usage {
288
- match & assignment. node {
289
- LocalUsage :: Scalar ( scalar) => {
290
- all_assignments. push ( scalar. clone ( ) ) ;
291
- } ,
292
- // FIXME: This doesn't handle assignment of tuples, arrays or anything else currently.
293
- LocalUsage :: Scalars ( _) => { } ,
294
- // FIXME: This doesn't allow nested dependencies, currently.
295
- // FIXME: This only allows one assignment for dependencies.
252
+ for assignment in & local. usage {
253
+ match assignment. node {
254
+ LocalUsage :: Scalar ( scalar) => assignments. push ( scalar) ,
296
255
LocalUsage :: DependsOn ( local) => {
297
- let [ assignment] = & * map[ * local] . usage else {
298
- continue ;
256
+ let [ assignment] = & * map[ local] . usage else {
257
+ return false ;
299
258
} ;
300
- match assignment. node {
301
- LocalUsage :: Scalar ( scalar ) => all_assignments . push ( scalar. clone ( ) ) ,
302
- LocalUsage :: Scalars ( _ ) => { } ,
303
- _ => return false ,
259
+ if let LocalUsage :: Scalar ( scalar ) = assignment. node {
260
+ assignments . push ( scalar) ;
261
+ } else {
262
+ return false ;
304
263
}
305
264
} ,
306
265
LocalUsage :: DependsOnWithProj ( local, base_proj) => {
307
- let [ assignment] = & * map[ * local] . usage else {
308
- continue ;
266
+ let [ assignment] = & * map[ local] . usage else {
267
+ return false ;
309
268
} ;
310
269
match base_proj {
311
270
PlaceElem :: Field ( idx, _) if let LocalUsage :: Scalars ( scalars) = & assignment. node => {
312
- all_assignments . push ( scalars[ idx. as_usize ( ) ] . clone ( ) ) ;
271
+ assignments . push ( scalars[ idx. as_usize ( ) ] ) ;
313
272
} ,
314
273
PlaceElem :: Index ( idx) if let LocalUsage :: Scalars ( scalars) = & assignment. node => {
315
- all_assignments . push ( scalars[ idx. as_usize ( ) ] . clone ( ) ) ;
274
+ assignments . push ( scalars[ idx. as_usize ( ) ] ) ;
316
275
} ,
317
276
_ => return false ,
318
277
}
319
278
} ,
279
+ _ => return false ,
320
280
}
321
281
}
322
282
323
- if let [ head, tail @ ..] = & * all_assignments && tail. iter ( ) . all ( |i| i == head) {
283
+ if let Some ( assignments) = assignments. iter ( ) . map ( |i| {
284
+ if let Scalar :: Int ( int) = i {
285
+ return Some ( int. to_bits ( int. size ( ) ) . ok ( ) ) . flatten ( ) ;
286
+ } ;
287
+
288
+ None
289
+ } )
290
+ . collect :: < Option < Vec < _ > > > ( )
291
+ && let [ head, tail @ ..] = & * assignments
292
+ && tail. iter ( ) . all ( |& i| i == * head)
293
+ {
324
294
return true ;
325
295
}
326
296
327
297
false
328
298
}
329
299
330
- fn nested_locals_are_not_from_bad_instr ( map : & LocalUsageMap < ' _ > , usage : & LocalUsageValues < ' _ > ) -> bool {
331
- // FIXME: This is a hacky workaround to not have a stack overflow. Instead, we should fix the root
332
- // cause.
333
- nested_locals_are_not_from_bad_instr_inner ( map, usage, 0 )
300
+ fn eval_nested_locals_are_constant ( map : & LocalUsageMap < ' _ > , local : & LocalUsageValues < ' _ > ) -> bool {
301
+ eval_nested_locals_are_constant_with_visited_locals ( map, local, & mut FxHashSet :: default ( ) )
334
302
}
335
303
336
- fn nested_locals_are_not_from_bad_instr_inner (
304
+ /// Do not call this manually - use `eval_nested_locals_are_constant` instead.
305
+ fn eval_nested_locals_are_constant_with_visited_locals (
337
306
map : & LocalUsageMap < ' _ > ,
338
- usage : & LocalUsageValues < ' _ > ,
339
- depth : usize ,
307
+ local : & LocalUsageValues < ' _ > ,
308
+ visited_locals : & mut FxHashSet < mir :: Local > ,
340
309
) -> bool {
341
- if depth < 10 && !usage. is_from_bad_instr {
342
- let mut all_good_instrs = true ;
343
- for assignment in & usage. usage {
344
- match assignment. node {
345
- LocalUsage :: Scalar ( _) | LocalUsage :: Scalars ( _) => continue ,
346
- LocalUsage :: DependsOn ( local) | LocalUsage :: DependsOnWithProj ( local, _) => {
347
- all_good_instrs &= nested_locals_are_not_from_bad_instr_inner ( map, & map[ local] , depth + 1 ) ;
348
- } ,
349
- }
310
+ let mut constness = true ;
311
+ for assignment in & local. usage {
312
+ match assignment. node {
313
+ LocalUsage :: DependsOn ( local) | LocalUsage :: DependsOnWithProj ( local, _) => {
314
+ if !visited_locals. insert ( local) {
315
+ // Short-circuit to ensure we don't get stuck in a loop
316
+ return false ;
317
+ }
318
+
319
+ constness &= eval_nested_locals_are_constant_with_visited_locals ( map, & map[ local] , visited_locals) ;
320
+ } ,
321
+ LocalUsage :: NonConst => return false ,
322
+ _ => { } ,
350
323
}
351
- return all_good_instrs;
352
324
}
353
-
354
- false
325
+ constness
355
326
}
0 commit comments