@@ -3,14 +3,15 @@ use clippy_utils::fn_has_unsatisfiable_preds;
33use clippy_utils:: source:: snippet_opt;
44use itertools:: Itertools ;
55use rustc_const_eval:: interpret:: Scalar ;
6+ use rustc_data_structures:: fx:: FxHashSet ;
67use rustc_hir:: def_id:: LocalDefId ;
78use rustc_hir:: { intravisit:: FnKind , Body , FnDecl } ;
89use rustc_index:: IndexVec ;
910use rustc_lint:: { LateContext , LateLintPass } ;
1011use rustc_middle:: mir:: {
1112 self , interpret:: ConstValue , visit:: Visitor , Constant , Location , Mutability , Operand , Place , Rvalue ,
1213} ;
13- use rustc_middle:: mir:: { AggregateKind , PlaceElem , Terminator , TerminatorKind } ;
14+ use rustc_middle:: mir:: { AggregateKind , CastKind , PlaceElem , Terminator , TerminatorKind } ;
1415use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1516use rustc_span:: source_map:: Spanned ;
1617use rustc_span:: Span ;
@@ -61,78 +62,35 @@ impl LateLintPass<'_> for LocalAssignedSingleValue {
6162 } ;
6263 v. visit_body ( mir) ;
6364
64- // for (local, i) in v.map.iter_enumerated() {
65- // dbg!(local, i);
66- // }
67-
6865 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;
66+ let LocalUsageValues {
67+ usage,
68+ mut_ref_acquired,
69+ } = i;
8070
81- 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)
8274 && let [ dbg_info] = & * mir
8375 . var_debug_info
8476 . iter ( )
8577 . filter ( |info| info. source_info . span == local_decl. source_info . span )
8678 . 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;
112-
113- if let Some(local_decl) = mir.local_decls.get(*local)
114- && let [dbg_info] = &*mir
115- .var_debug_info
116- .iter()
117- .filter(|info| info.source_info.span == local_decl.source_info.span)
118- .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+ ) ;
13392 }
13493 }
135- */
13694 }
13795}
13896
@@ -145,13 +103,6 @@ struct LocalUsageValues<'tcx> {
145103 usage : Vec < Spanned < LocalUsage < ' tcx > > > ,
146104 /// Whether it's mutably borrowed, ever. We should not lint this.
147105 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 ,
155106}
156107
157108#[ derive( Debug ) ]
@@ -165,6 +116,10 @@ enum LocalUsage<'tcx> {
165116 /// When a `Local` depends on the value of another local by accessing any of its fields or
166117 /// indexing
167118 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 ,
168123}
169124
170125struct V < ' a , ' tcx > {
@@ -193,30 +148,28 @@ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
193148 }
194149
195150 let usage = & mut map[ place. local ] ;
151+ let mut spanned = Spanned {
152+ node : LocalUsage :: Pending ,
153+ span : stmt. source_info . span ,
154+ } ;
196155
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 )
208160 {
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+ }
220173 }
221174 }
222175 // Handle creation of tuples/arrays, otherwise the above would be useless
@@ -226,25 +179,45 @@ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
226179 // TODO: Let's remove this `cloned` call, if possible.
227180 && let Some ( scalars) = extract_scalars ( cx, fields. into_iter ( ) . cloned ( ) )
228181 {
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+ }
235204 } 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) ;
239210 }
240211 }
241212
242213 fn visit_terminator ( & mut self , terminator : & Terminator < ' _ > , _: Location ) {
243214 let Self { body : _, cx : _, map } = self ;
244215
245- // TODO: Maybe we can allow const fns, if we can evaluate them of course
246216 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+ } ) ;
248221 }
249222 }
250223}
@@ -269,87 +242,85 @@ where
269242 . collect :: < Option < Vec < _ > > > ( )
270243}
271244
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 ! [ ] ;
279247
280- if usage. len ( ) < = 1 {
248+ if local . usage . len ( ) = = 1 {
281249 return false ;
282250 }
283251
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) ,
296255 LocalUsage :: DependsOn ( local) => {
297- let [ assignment] = & * map[ * local] . usage else {
298- continue ;
256+ let [ assignment] = & * map[ local] . usage else {
257+ return false ;
299258 } ;
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 ;
304263 }
305264 } ,
306265 LocalUsage :: DependsOnWithProj ( local, base_proj) => {
307- let [ assignment] = & * map[ * local] . usage else {
308- continue ;
266+ let [ assignment] = & * map[ local] . usage else {
267+ return false ;
309268 } ;
310269 match base_proj {
311270 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 ( ) ] ) ;
313272 } ,
314273 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 ( ) ] ) ;
316275 } ,
317276 _ => return false ,
318277 }
319278 } ,
279+ _ => return false ,
320280 }
321281 }
322282
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+ {
324294 return true ;
325295 }
326296
327297 false
328298}
329299
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 ( ) )
334302}
335303
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 (
337306 map : & LocalUsageMap < ' _ > ,
338- usage : & LocalUsageValues < ' _ > ,
339- depth : usize ,
307+ local : & LocalUsageValues < ' _ > ,
308+ visited_locals : & mut FxHashSet < mir :: Local > ,
340309) -> 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+ _ => { } ,
350323 }
351- return all_good_instrs;
352324 }
353-
354- false
325+ constness
355326}
0 commit comments