1+ use std:: borrow:: Cow ;
2+
13use clippy_config:: Conf ;
24use clippy_utils:: diagnostics:: span_lint_hir_and_then;
35use clippy_utils:: msrvs:: Msrv ;
46use clippy_utils:: res:: { MaybeDef , MaybeResPath } ;
7+ use clippy_utils:: source:: snippet;
58use clippy_utils:: usage:: is_potentially_local_place;
69use clippy_utils:: { can_use_if_let_chains, higher, sym} ;
10+ use rustc_abi:: FieldIdx ;
711use rustc_errors:: Applicability ;
812use rustc_hir:: intravisit:: { FnKind , Visitor , walk_expr, walk_fn} ;
913use rustc_hir:: { BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , Node , UnOp } ;
10- use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , PlaceWithHirId } ;
11- use rustc_lint:: { LateContext , LateLintPass } ;
14+ use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , Place , PlaceWithHirId } ;
15+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1216use rustc_middle:: hir:: nested_filter;
17+ use rustc_middle:: hir:: place:: ProjectionKind ;
1318use rustc_middle:: mir:: FakeReadCause ;
1419use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
1520use rustc_session:: impl_lint_pass;
@@ -114,11 +119,89 @@ impl UnwrappableKind {
114119 }
115120}
116121
122+ #[ derive( Copy , Clone , Debug , Eq ) ]
123+ enum Local {
124+ /// `x.opt`
125+ WithFieldAccess {
126+ local_id : HirId ,
127+ field_idx : FieldIdx ,
128+ /// The span of the whole expression
129+ span : Span ,
130+ } ,
131+ /// `x`
132+ Pure { local_id : HirId } ,
133+ }
134+
135+ /// Identical to derived impl, but ignores `span` on [`Local::WithFieldAccess`]
136+ impl PartialEq for Local {
137+ fn eq ( & self , other : & Self ) -> bool {
138+ match ( self , other) {
139+ (
140+ Self :: WithFieldAccess {
141+ local_id : self_local_id,
142+ field_idx : self_field_idx,
143+ ..
144+ } ,
145+ Self :: WithFieldAccess {
146+ local_id : other_local_id,
147+ field_idx : other_field_idx,
148+ ..
149+ } ,
150+ ) => self_local_id == other_local_id && self_field_idx == other_field_idx,
151+ (
152+ Self :: Pure {
153+ local_id : self_local_id,
154+ } ,
155+ Self :: Pure {
156+ local_id : other_local_id,
157+ } ,
158+ ) => self_local_id == other_local_id,
159+ _ => false ,
160+ }
161+ }
162+ }
163+
164+ impl Local {
165+ fn snippet ( self , cx : & LateContext < ' _ > ) -> Cow < ' static , str > {
166+ match self {
167+ Self :: WithFieldAccess { span, .. } => snippet ( cx. sess ( ) , span, "_" ) ,
168+ Self :: Pure { local_id } => cx. tcx . hir_name ( local_id) . to_string ( ) . into ( ) ,
169+ }
170+ }
171+
172+ fn is_potentially_local_place ( self , place : & Place < ' _ > ) -> bool {
173+ match self {
174+ Self :: WithFieldAccess {
175+ local_id, field_idx, ..
176+ } => {
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
180+ //
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 ,
188+ } )
189+ {
190+ true
191+ } else {
192+ false
193+ }
194+ } ,
195+ Self :: Pure { local_id } => is_potentially_local_place ( local_id, place) ,
196+ }
197+ }
198+ }
199+
117200/// Contains information about whether a variable can be unwrapped.
118201#[ derive( Copy , Clone , Debug ) ]
119202struct UnwrapInfo < ' tcx > {
120203 /// The variable that is checked
121- local_id : HirId ,
204+ local : Local ,
122205 /// The if itself
123206 if_expr : & ' tcx Expr < ' tcx > ,
124207 /// The check, like `x.is_ok()`
@@ -177,15 +260,15 @@ fn collect_unwrap_info<'tcx>(
177260 } ,
178261 ExprKind :: Unary ( UnOp :: Not , expr) => inner ( cx, if_expr, expr, branch, !invert, false , out) ,
179262 ExprKind :: MethodCall ( method_name, receiver, [ ] , _)
180- if let Some ( local_id ) = receiver . res_local_id ( )
263+ if let Some ( local ) = extract_local ( cx , receiver )
181264 && let ty = cx. typeck_results ( ) . expr_ty ( receiver)
182265 && let name = method_name. ident . name
183266 && let Some ( ( kind, unwrappable) ) = option_or_result_call ( cx, ty, name) =>
184267 {
185268 let safe_to_unwrap = unwrappable != invert;
186269
187270 out. push ( UnwrapInfo {
188- local_id ,
271+ local ,
189272 if_expr,
190273 check : expr,
191274 check_name : name,
@@ -204,6 +287,25 @@ fn collect_unwrap_info<'tcx>(
204287 out
205288}
206289
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 ( )
297+ && let Some ( field_idx) = cx. typeck_results ( ) . opt_field_index ( expr. hir_id )
298+ {
299+ Some ( Local :: WithFieldAccess {
300+ local_id,
301+ field_idx,
302+ span : expr. span ,
303+ } )
304+ } else {
305+ None
306+ }
307+ }
308+
207309/// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated,
208310/// *except* for if `.as_mut()` is called.
209311/// The reason for why we allow that one specifically is that `.as_mut()` cannot change
@@ -213,7 +315,7 @@ fn collect_unwrap_info<'tcx>(
213315/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
214316struct MutationVisitor < ' tcx > {
215317 is_mutated : bool ,
216- local_id : HirId ,
318+ local : Local ,
217319 tcx : TyCtxt < ' tcx > ,
218320}
219321
@@ -237,15 +339,15 @@ fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
237339impl < ' tcx > Delegate < ' tcx > for MutationVisitor < ' tcx > {
238340 fn borrow ( & mut self , cat : & PlaceWithHirId < ' tcx > , diag_expr_id : HirId , bk : ty:: BorrowKind ) {
239341 if let ty:: BorrowKind :: Mutable = bk
240- && is_potentially_local_place ( self . local_id , & cat. place )
342+ && self . local . is_potentially_local_place ( & cat. place )
241343 && !is_as_mut_use ( self . tcx , diag_expr_id)
242344 {
243345 self . is_mutated = true ;
244346 }
245347 }
246348
247349 fn mutate ( & mut self , cat : & PlaceWithHirId < ' tcx > , _: HirId ) {
248- if is_potentially_local_place ( self . local_id , & cat. place ) {
350+ if self . local . is_potentially_local_place ( & cat. place ) {
249351 self . is_mutated = true ;
250352 }
251353 }
@@ -269,7 +371,7 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> {
269371 for unwrap_info in collect_unwrap_info ( self . cx , if_expr, cond, branch, else_branch, true ) {
270372 let mut delegate = MutationVisitor {
271373 is_mutated : false ,
272- local_id : unwrap_info. local_id ,
374+ local : unwrap_info. local ,
273375 tcx : self . cx . tcx ,
274376 } ;
275377
@@ -331,25 +433,25 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
331433 // find `unwrap[_err]()` or `expect("...")` calls:
332434 if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind
333435 && let ( self_arg, as_ref_kind) = consume_option_as_ref ( self_arg)
334- && let Some ( id ) = self_arg . res_local_id ( )
436+ && let Some ( local ) = extract_local ( self . cx , self_arg )
335437 && matches ! ( method_name. ident. name, sym:: unwrap | sym:: expect | sym:: unwrap_err)
336438 && let call_to_unwrap = matches ! ( method_name. ident. name, sym:: unwrap | sym:: expect)
337- && let Some ( unwrappable) = self . unwrappables . iter ( ) . find ( |u| u. local_id == id )
439+ && let Some ( unwrappable) = self . unwrappables . iter ( ) . find ( |u| u. local == local )
338440 // Span contexts should not differ with the conditional branch
339441 && let span_ctxt = expr. span . ctxt ( )
340442 && unwrappable. branch . span . ctxt ( ) == span_ctxt
341443 && unwrappable. check . span . ctxt ( ) == span_ctxt
342444 {
343445 if call_to_unwrap == unwrappable. safe_to_unwrap {
344- let unwrappable_variable_name = self . cx . tcx . hir_name ( unwrappable . local_id ) ;
446+ let unwrappable_variable_str = unwrappable . local . snippet ( self . cx ) ;
345447
346448 span_lint_hir_and_then (
347449 self . cx ,
348450 UNNECESSARY_UNWRAP ,
349451 expr. hir_id ,
350452 expr. span ,
351453 format ! (
352- "called `{}` on `{unwrappable_variable_name }` after checking its variant with `{}`" ,
454+ "called `{}` on `{unwrappable_variable_str }` after checking its variant with `{}`" ,
353455 method_name. ident. name, unwrappable. check_name,
354456 ) ,
355457 |diag| {
@@ -358,7 +460,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
358460 unwrappable. check . span . with_lo ( unwrappable. if_expr . span . lo ( ) ) ,
359461 "try" ,
360462 format ! (
361- "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name }" ,
463+ "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_str }" ,
362464 suggested_pattern = if call_to_unwrap {
363465 unwrappable. kind. success_variant_pattern( )
364466 } else {
0 commit comments