1+ use std:: borrow:: Cow ;
2+ use std:: iter;
3+
14use clippy_config:: Conf ;
25use clippy_utils:: diagnostics:: span_lint_hir_and_then;
36use clippy_utils:: msrvs:: Msrv ;
47use clippy_utils:: res:: { MaybeDef , MaybeResPath } ;
8+ use clippy_utils:: source:: snippet;
59use clippy_utils:: usage:: is_potentially_local_place;
610use clippy_utils:: { can_use_if_let_chains, higher, sym} ;
11+ use rustc_abi:: FieldIdx ;
712use rustc_errors:: Applicability ;
813use rustc_hir:: intravisit:: { FnKind , Visitor , walk_expr, walk_fn} ;
914use 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 } ;
15+ use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , Place , PlaceWithHirId } ;
16+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1217use rustc_middle:: hir:: nested_filter;
18+ use rustc_middle:: hir:: place:: ProjectionKind ;
1319use rustc_middle:: mir:: FakeReadCause ;
1420use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
1521use rustc_session:: impl_lint_pass;
@@ -114,11 +120,91 @@ impl UnwrappableKind {
114120 }
115121}
116122
123+ #[ derive( Clone , Debug , Eq ) ]
124+ enum Local {
125+ /// `x.field1.field2.field3`
126+ WithFieldAccess {
127+ local_id : HirId ,
128+ /// The indices of the field accessed.
129+ ///
130+ /// Stored last-to-first, e.g. for the example above: `[field3, field2, field1]`
131+ field_indices : Vec < FieldIdx > ,
132+ /// The span of the whole expression
133+ span : Span ,
134+ } ,
135+ /// `x`
136+ Pure { local_id : HirId } ,
137+ }
138+
139+ /// Identical to derived impl, but ignores `span` on [`Local::WithFieldAccess`]
140+ impl PartialEq for Local {
141+ fn eq ( & self , other : & Self ) -> bool {
142+ match ( self , other) {
143+ (
144+ Self :: WithFieldAccess {
145+ local_id : self_local_id,
146+ field_indices : self_field_indices,
147+ ..
148+ } ,
149+ Self :: WithFieldAccess {
150+ local_id : other_local_id,
151+ field_indices : other_field_indices,
152+ ..
153+ } ,
154+ ) => self_local_id == other_local_id && self_field_indices == other_field_indices,
155+ (
156+ Self :: Pure {
157+ local_id : self_local_id,
158+ } ,
159+ Self :: Pure {
160+ local_id : other_local_id,
161+ } ,
162+ ) => self_local_id == other_local_id,
163+ _ => false ,
164+ }
165+ }
166+ }
167+
168+ impl Local {
169+ fn snippet ( & self , cx : & LateContext < ' _ > ) -> Cow < ' static , str > {
170+ match * self {
171+ Self :: WithFieldAccess { span, .. } => snippet ( cx. sess ( ) , span, "_" ) ,
172+ Self :: Pure { local_id } => cx. tcx . hir_name ( local_id) . to_string ( ) . into ( ) ,
173+ }
174+ }
175+
176+ fn is_potentially_local_place ( & self , place : & Place < ' _ > ) -> bool {
177+ match self {
178+ Self :: WithFieldAccess {
179+ local_id,
180+ field_indices,
181+ ..
182+ } => {
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.
186+ //
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+ }
196+ } )
197+ } ,
198+ Self :: Pure { local_id } => is_potentially_local_place ( * local_id, place) ,
199+ }
200+ }
201+ }
202+
117203/// Contains information about whether a variable can be unwrapped.
118- #[ derive( Copy , Clone , Debug ) ]
204+ #[ derive( Clone , Debug ) ]
119205struct UnwrapInfo < ' tcx > {
120206 /// The variable that is checked
121- local_id : HirId ,
207+ local : Local ,
122208 /// The if itself
123209 if_expr : & ' tcx Expr < ' tcx > ,
124210 /// The check, like `x.is_ok()`
@@ -156,38 +242,77 @@ fn collect_unwrap_info<'tcx>(
156242 }
157243 }
158244
159- match expr. kind {
160- ExprKind :: Binary ( op, left, right)
161- if matches ! (
162- ( invert, op. node) ,
163- ( false , BinOpKind :: And | BinOpKind :: BitAnd ) | ( true , BinOpKind :: Or | BinOpKind :: BitOr )
164- ) =>
165- {
166- let mut unwrap_info = collect_unwrap_info ( cx, if_expr, left, branch, invert, false ) ;
167- unwrap_info. extend ( collect_unwrap_info ( cx, if_expr, right, branch, invert, false ) ) ;
168- unwrap_info
169- } ,
170- ExprKind :: Unary ( UnOp :: Not , expr) => collect_unwrap_info ( cx, if_expr, expr, branch, !invert, false ) ,
171- ExprKind :: MethodCall ( method_name, receiver, [ ] , _)
172- if let Some ( local_id) = receiver. res_local_id ( )
173- && let ty = cx. typeck_results ( ) . expr_ty ( receiver)
174- && let name = method_name. ident . name
175- && let Some ( ( kind, unwrappable) ) = option_or_result_call ( cx, ty, name) =>
176- {
177- let safe_to_unwrap = unwrappable != invert;
245+ fn inner < ' tcx > (
246+ cx : & LateContext < ' tcx > ,
247+ if_expr : & ' tcx Expr < ' _ > ,
248+ expr : & ' tcx Expr < ' _ > ,
249+ branch : & ' tcx Expr < ' _ > ,
250+ invert : bool ,
251+ is_entire_condition : bool ,
252+ out : & mut Vec < UnwrapInfo < ' tcx > > ,
253+ ) {
254+ match expr. kind {
255+ ExprKind :: Binary ( op, left, right)
256+ if matches ! (
257+ ( invert, op. node) ,
258+ ( false , BinOpKind :: And | BinOpKind :: BitAnd ) | ( true , BinOpKind :: Or | BinOpKind :: BitOr )
259+ ) =>
260+ {
261+ inner ( cx, if_expr, left, branch, invert, false , out) ;
262+ inner ( cx, if_expr, right, branch, invert, false , out) ;
263+ } ,
264+ ExprKind :: Unary ( UnOp :: Not , expr) => inner ( cx, if_expr, expr, branch, !invert, false , out) ,
265+ ExprKind :: MethodCall ( method_name, receiver, [ ] , _)
266+ if let Some ( local) = extract_local ( cx, receiver)
267+ && let ty = cx. typeck_results ( ) . expr_ty ( receiver)
268+ && let name = method_name. ident . name
269+ && let Some ( ( kind, unwrappable) ) = option_or_result_call ( cx, ty, name) =>
270+ {
271+ let safe_to_unwrap = unwrappable != invert;
272+
273+ out. push ( UnwrapInfo {
274+ local,
275+ if_expr,
276+ check : expr,
277+ check_name : name,
278+ branch,
279+ safe_to_unwrap,
280+ kind,
281+ is_entire_condition,
282+ } ) ;
283+ } ,
284+ _ => { } ,
285+ }
286+ }
287+
288+ let mut out = vec ! [ ] ;
289+ inner ( cx, if_expr, expr, branch, invert, is_entire_condition, & mut out) ;
290+ out
291+ }
178292
179- vec ! [ UnwrapInfo {
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
299+ && let Some ( field_idx) = cx. typeck_results ( ) . opt_field_index ( expr. hir_id )
300+ {
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 {
180309 local_id,
181- if_expr,
182- check: expr,
183- check_name: name,
184- branch,
185- safe_to_unwrap,
186- kind,
187- is_entire_condition,
188- } ]
189- } ,
190- _ => vec ! [ ] ,
310+ field_indices,
311+ span,
312+ } )
313+ }
314+ } else {
315+ None
191316 }
192317}
193318
@@ -198,9 +323,9 @@ fn collect_unwrap_info<'tcx>(
198323/// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if
199324/// the option is changed to None between `is_some` and `unwrap`, ditto for `Result`.
200325/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
201- struct MutationVisitor < ' tcx > {
326+ struct MutationVisitor < ' tcx , ' lcl > {
202327 is_mutated : bool ,
203- local_id : HirId ,
328+ local : & ' lcl Local ,
204329 tcx : TyCtxt < ' tcx > ,
205330}
206331
@@ -221,18 +346,18 @@ fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
221346 }
222347}
223348
224- impl < ' tcx > Delegate < ' tcx > for MutationVisitor < ' tcx > {
349+ impl < ' tcx > Delegate < ' tcx > for MutationVisitor < ' tcx , ' _ > {
225350 fn borrow ( & mut self , cat : & PlaceWithHirId < ' tcx > , diag_expr_id : HirId , bk : ty:: BorrowKind ) {
226351 if let ty:: BorrowKind :: Mutable = bk
227- && is_potentially_local_place ( self . local_id , & cat. place )
352+ && self . local . is_potentially_local_place ( & cat. place )
228353 && !is_as_mut_use ( self . tcx , diag_expr_id)
229354 {
230355 self . is_mutated = true ;
231356 }
232357 }
233358
234359 fn mutate ( & mut self , cat : & PlaceWithHirId < ' tcx > , _: HirId ) {
235- if is_potentially_local_place ( self . local_id , & cat. place ) {
360+ if self . local . is_potentially_local_place ( & cat. place ) {
236361 self . is_mutated = true ;
237362 }
238363 }
@@ -256,7 +381,7 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> {
256381 for unwrap_info in collect_unwrap_info ( self . cx , if_expr, cond, branch, else_branch, true ) {
257382 let mut delegate = MutationVisitor {
258383 is_mutated : false ,
259- local_id : unwrap_info. local_id ,
384+ local : & unwrap_info. local ,
260385 tcx : self . cx . tcx ,
261386 } ;
262387
@@ -318,41 +443,39 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
318443 // find `unwrap[_err]()` or `expect("...")` calls:
319444 if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind
320445 && let ( self_arg, as_ref_kind) = consume_option_as_ref ( self_arg)
321- && let Some ( id ) = self_arg . res_local_id ( )
446+ && let Some ( local ) = extract_local ( self . cx , self_arg )
322447 && matches ! ( method_name. ident. name, sym:: unwrap | sym:: expect | sym:: unwrap_err)
323448 && let call_to_unwrap = matches ! ( method_name. ident. name, sym:: unwrap | sym:: expect)
324- && let Some ( unwrappable) = self . unwrappables . iter ( )
325- . find ( |u| u. local_id == id)
449+ && let Some ( unwrappable) = self . unwrappables . iter ( ) . find ( |u| u. local == local)
326450 // Span contexts should not differ with the conditional branch
327451 && let span_ctxt = expr. span . ctxt ( )
328452 && unwrappable. branch . span . ctxt ( ) == span_ctxt
329453 && unwrappable. check . span . ctxt ( ) == span_ctxt
330454 {
331455 if call_to_unwrap == unwrappable. safe_to_unwrap {
332- let is_entire_condition = unwrappable. is_entire_condition ;
333- let unwrappable_variable_name = self . cx . tcx . hir_name ( unwrappable. local_id ) ;
334- let suggested_pattern = if call_to_unwrap {
335- unwrappable. kind . success_variant_pattern ( )
336- } else {
337- unwrappable. kind . error_variant_pattern ( )
338- } ;
456+ let unwrappable_variable_str = unwrappable. local . snippet ( self . cx ) ;
339457
340458 span_lint_hir_and_then (
341459 self . cx ,
342460 UNNECESSARY_UNWRAP ,
343461 expr. hir_id ,
344462 expr. span ,
345463 format ! (
346- "called `{}` on `{unwrappable_variable_name }` after checking its variant with `{}`" ,
464+ "called `{}` on `{unwrappable_variable_str }` after checking its variant with `{}`" ,
347465 method_name. ident. name, unwrappable. check_name,
348466 ) ,
349467 |diag| {
350- if is_entire_condition {
468+ if unwrappable . is_entire_condition {
351469 diag. span_suggestion (
352470 unwrappable. check . span . with_lo ( unwrappable. if_expr . span . lo ( ) ) ,
353471 "try" ,
354472 format ! (
355- "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name}" ,
473+ "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_str}" ,
474+ suggested_pattern = if call_to_unwrap {
475+ unwrappable. kind. success_variant_pattern( )
476+ } else {
477+ unwrappable. kind. error_variant_pattern( )
478+ } ,
356479 borrow_prefix = match as_ref_kind {
357480 Some ( AsRefKind :: AsRef ) => "&" ,
358481 Some ( AsRefKind :: AsMut ) => "&mut " ,
0 commit comments