11use std:: borrow:: Cow ;
2+ use std:: iter;
23
34use clippy_config:: Conf ;
45use clippy_utils:: diagnostics:: span_lint_hir_and_then;
@@ -119,12 +120,15 @@ impl UnwrappableKind {
119120 }
120121}
121122
122- #[ derive( Copy , Clone , Debug , Eq ) ]
123+ #[ derive( Clone , Debug , Eq ) ]
123124enum Local {
124- /// `x.opt `
125+ /// `x.field1.field2.field3 `
125126 WithFieldAccess {
126127 local_id : HirId ,
127- field_idx : FieldIdx ,
128+ /// The indices of the field accessed.
129+ ///
130+ /// Stored last-to-first, i.e. for the example above: `[field3, field2, field1]`
131+ field_indices : Vec < FieldIdx > ,
128132 /// The span of the whole expression
129133 span : Span ,
130134 } ,
@@ -139,15 +143,15 @@ impl PartialEq for Local {
139143 (
140144 Self :: WithFieldAccess {
141145 local_id : self_local_id,
142- field_idx : self_field_idx ,
146+ field_indices : self_field_indices ,
143147 ..
144148 } ,
145149 Self :: WithFieldAccess {
146150 local_id : other_local_id,
147- field_idx : other_field_idx ,
151+ field_indices : other_field_indices ,
148152 ..
149153 } ,
150- ) => self_local_id == other_local_id && self_field_idx == other_field_idx ,
154+ ) => self_local_id == other_local_id && self_field_indices == other_field_indices ,
151155 (
152156 Self :: Pure {
153157 local_id : self_local_id,
@@ -162,43 +166,42 @@ impl PartialEq for Local {
162166}
163167
164168impl Local {
165- fn snippet ( self , cx : & LateContext < ' _ > ) -> Cow < ' static , str > {
166- match self {
169+ fn snippet ( & self , cx : & LateContext < ' _ > ) -> Cow < ' static , str > {
170+ match * self {
167171 Self :: WithFieldAccess { span, .. } => snippet ( cx. sess ( ) , span, "_" ) ,
168172 Self :: Pure { local_id } => cx. tcx . hir_name ( local_id) . to_string ( ) . into ( ) ,
169173 }
170174 }
171175
172- fn is_potentially_local_place ( self , place : & Place < ' _ > ) -> bool {
176+ fn is_potentially_local_place ( & self , place : & Place < ' _ > ) -> bool {
173177 match self {
174178 Self :: WithFieldAccess {
175- local_id, field_idx, ..
179+ local_id,
180+ field_indices,
181+ ..
176182 } => {
177- if is_potentially_local_place ( local_id, place)
178- // If there were projections other than the field projection , err on the side of caution and say
179- // that they _might_ have mutated the field
183+ is_potentially_local_place ( * local_id, place)
184+ // If there were projections other than field projections , err on the side of caution and say that they
185+ // _might_ be mutating something.
180186 //
181- // The reason we use `<=` and not `==` is that, if there were no projections, then the whole local
182- // was mutated, which means that our field was mutated as well
183- && place. projections . len ( ) <= 1
184- && place. projections . last ( ) . is_none_or ( |proj| match proj. kind {
185- ProjectionKind :: Field ( f_idx, _) => f_idx == field_idx,
186- // If this is a projection we don't expect, it _might_ be mutating something
187- _ => false ,
187+ // The reason we use `<=` and not `==` is that a mutation of `struct` or `struct.field1` should count as
188+ // mutation of the child fields such as `struct.field1.field2`
189+ && place. projections . len ( ) <= field_indices. len ( )
190+ && iter:: zip ( & place. projections , field_indices. iter ( ) . copied ( ) . rev ( ) ) . all ( |( proj, field_idx) | {
191+ match proj. kind {
192+ ProjectionKind :: Field ( f_idx, _) => f_idx == field_idx,
193+ // If this is a projection we don't expect, it _might_ be mutating something
194+ _ => false ,
195+ }
188196 } )
189- {
190- true
191- } else {
192- false
193- }
194197 } ,
195- Self :: Pure { local_id } => is_potentially_local_place ( local_id, place) ,
198+ Self :: Pure { local_id } => is_potentially_local_place ( * local_id, place) ,
196199 }
197200 }
198201}
199202
200203/// Contains information about whether a variable can be unwrapped.
201- #[ derive( Copy , Clone , Debug ) ]
204+ #[ derive( Clone , Debug ) ]
202205struct UnwrapInfo < ' tcx > {
203206 /// The variable that is checked
204207 local : Local ,
@@ -287,20 +290,27 @@ fn collect_unwrap_info<'tcx>(
287290 out
288291}
289292
290- /// Extracts either a local used by itself ([`Local::Pure`]), or a field access to a local
291- /// ([`Local::WithFieldAccess`])
292- fn extract_local ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> Option < Local > {
293- if let Some ( local_id) = expr. res_local_id ( ) {
294- Some ( Local :: Pure { local_id } )
295- } else if let ExprKind :: Field ( recv, _) = expr. kind
296- && let Some ( local_id) = recv. res_local_id ( )
293+ /// Extracts either a local used by itself ([`Local::Pure`]), or (one or more levels of) field
294+ /// access to a local ([`Local::WithFieldAccess`])
295+ fn extract_local ( cx : & LateContext < ' _ > , mut expr : & Expr < ' _ > ) -> Option < Local > {
296+ let span = expr. span ;
297+ let mut field_indices = vec ! [ ] ;
298+ while let ExprKind :: Field ( recv, _) = expr. kind
297299 && let Some ( field_idx) = cx. typeck_results ( ) . opt_field_index ( expr. hir_id )
298300 {
299- Some ( Local :: WithFieldAccess {
300- local_id,
301- field_idx,
302- span : expr. span ,
303- } )
301+ field_indices. push ( field_idx) ;
302+ expr = recv;
303+ }
304+ if let Some ( local_id) = expr. res_local_id ( ) {
305+ if field_indices. is_empty ( ) {
306+ Some ( Local :: Pure { local_id } )
307+ } else {
308+ Some ( Local :: WithFieldAccess {
309+ local_id,
310+ field_indices,
311+ span,
312+ } )
313+ }
304314 } else {
305315 None
306316 }
@@ -313,9 +323,9 @@ fn extract_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Local> {
313323/// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if
314324/// the option is changed to None between `is_some` and `unwrap`, ditto for `Result`.
315325/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
316- struct MutationVisitor < ' tcx > {
326+ struct MutationVisitor < ' tcx , ' lcl > {
317327 is_mutated : bool ,
318- local : Local ,
328+ local : & ' lcl Local ,
319329 tcx : TyCtxt < ' tcx > ,
320330}
321331
@@ -336,7 +346,7 @@ fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
336346 }
337347}
338348
339- impl < ' tcx > Delegate < ' tcx > for MutationVisitor < ' tcx > {
349+ impl < ' tcx > Delegate < ' tcx > for MutationVisitor < ' tcx , ' _ > {
340350 fn borrow ( & mut self , cat : & PlaceWithHirId < ' tcx > , diag_expr_id : HirId , bk : ty:: BorrowKind ) {
341351 if let ty:: BorrowKind :: Mutable = bk
342352 && self . local . is_potentially_local_place ( & cat. place )
@@ -371,7 +381,7 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> {
371381 for unwrap_info in collect_unwrap_info ( self . cx , if_expr, cond, branch, else_branch, true ) {
372382 let mut delegate = MutationVisitor {
373383 is_mutated : false ,
374- local : unwrap_info. local ,
384+ local : & unwrap_info. local ,
375385 tcx : self . cx . tcx ,
376386 } ;
377387
0 commit comments