1
1
use clippy_utils:: diagnostics:: span_lint_hir_and_then;
2
2
use clippy_utils:: ty:: is_type_diagnostic_item;
3
- use clippy_utils:: usage:: is_potentially_mutated ;
3
+ use clippy_utils:: usage:: is_potentially_local_place ;
4
4
use clippy_utils:: { higher, path_to_local} ;
5
5
use if_chain:: if_chain;
6
6
use rustc_errors:: Applicability ;
7
7
use rustc_hir:: intravisit:: { walk_expr, walk_fn, FnKind , Visitor } ;
8
- use rustc_hir:: { BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , PathSegment , UnOp } ;
8
+ use rustc_hir:: { BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , Node , PathSegment , UnOp } ;
9
+ use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , PlaceWithHirId } ;
10
+ use rustc_infer:: infer:: TyCtxtInferExt ;
9
11
use rustc_lint:: { LateContext , LateLintPass } ;
10
12
use rustc_middle:: hir:: nested_filter;
11
13
use rustc_middle:: lint:: in_external_macro;
12
- use rustc_middle:: ty:: Ty ;
14
+ use rustc_middle:: mir:: FakeReadCause ;
15
+ use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
13
16
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
14
17
use rustc_span:: def_id:: LocalDefId ;
15
18
use rustc_span:: source_map:: Span ;
@@ -192,6 +195,43 @@ fn collect_unwrap_info<'tcx>(
192
195
Vec :: new ( )
193
196
}
194
197
198
+ /// A HIR visitor delegate that checks if a local variable of type `Option<_>` is mutated,
199
+ /// unless `Option::as_mut` is called.
200
+ struct MutationVisitor < ' tcx > {
201
+ is_mutated : bool ,
202
+ local_id : HirId ,
203
+ tcx : TyCtxt < ' tcx > ,
204
+ }
205
+
206
+ fn is_option_as_mut_use ( tcx : TyCtxt < ' _ > , expr_id : HirId ) -> bool {
207
+ if let Node :: Expr ( mutating_expr) = tcx. hir ( ) . get_parent ( expr_id)
208
+ && let ExprKind :: MethodCall ( path, ..) = mutating_expr. kind
209
+ {
210
+ path. ident . name . as_str ( ) == "as_mut"
211
+ } else {
212
+ false
213
+ }
214
+ }
215
+
216
+ impl < ' tcx > Delegate < ' tcx > for MutationVisitor < ' tcx > {
217
+ fn borrow ( & mut self , cat : & PlaceWithHirId < ' tcx > , diag_expr_id : HirId , bk : ty:: BorrowKind ) {
218
+ if let ty:: BorrowKind :: MutBorrow = bk
219
+ && is_potentially_local_place ( self . local_id , & cat. place )
220
+ && !is_option_as_mut_use ( self . tcx , diag_expr_id)
221
+ {
222
+ self . is_mutated = true ;
223
+ }
224
+ }
225
+
226
+ fn mutate ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) {
227
+ self . is_mutated = true ;
228
+ }
229
+
230
+ fn consume ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
231
+
232
+ fn fake_read ( & mut self , _: & PlaceWithHirId < ' tcx > , _: FakeReadCause , _: HirId ) { }
233
+ }
234
+
195
235
impl < ' a , ' tcx > UnwrappableVariablesVisitor < ' a , ' tcx > {
196
236
fn visit_branch (
197
237
& mut self ,
@@ -202,9 +242,24 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
202
242
) {
203
243
let prev_len = self . unwrappables . len ( ) ;
204
244
for unwrap_info in collect_unwrap_info ( self . cx , if_expr, cond, branch, else_branch, true ) {
205
- if is_potentially_mutated ( unwrap_info. local_id , cond, self . cx )
206
- || is_potentially_mutated ( unwrap_info. local_id , branch, self . cx )
207
- {
245
+ let mut delegate = MutationVisitor {
246
+ tcx : self . cx . tcx ,
247
+ is_mutated : false ,
248
+ local_id : unwrap_info. local_id ,
249
+ } ;
250
+
251
+ let infcx = self . cx . tcx . infer_ctxt ( ) . build ( ) ;
252
+ let mut vis = ExprUseVisitor :: new (
253
+ & mut delegate,
254
+ & infcx,
255
+ cond. hir_id . owner . def_id ,
256
+ self . cx . param_env ,
257
+ self . cx . typeck_results ( ) ,
258
+ ) ;
259
+ vis. walk_expr ( cond) ;
260
+ vis. walk_expr ( branch) ;
261
+
262
+ if delegate. is_mutated {
208
263
// if the variable is mutated, we don't know whether it can be unwrapped:
209
264
continue ;
210
265
}
@@ -215,6 +270,27 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
215
270
}
216
271
}
217
272
273
+ enum AsRefKind {
274
+ AsRef ,
275
+ AsMut ,
276
+ }
277
+
278
+ /// Checks if the expression is a method call to `as_{ref,mut}` and returns the receiver of it.
279
+ /// If it isn't, the expression itself is returned.
280
+ fn consume_option_as_ref < ' tcx > ( expr : & ' tcx Expr < ' tcx > ) -> ( & ' tcx Expr < ' tcx > , Option < AsRefKind > ) {
281
+ if let ExprKind :: MethodCall ( path, recv, ..) = expr. kind {
282
+ if path. ident . name == sym:: as_ref {
283
+ ( recv, Some ( AsRefKind :: AsRef ) )
284
+ } else if path. ident . name . as_str ( ) == "as_mut" {
285
+ ( recv, Some ( AsRefKind :: AsMut ) )
286
+ } else {
287
+ ( expr, None )
288
+ }
289
+ } else {
290
+ ( expr, None )
291
+ }
292
+ }
293
+
218
294
impl < ' a , ' tcx > Visitor < ' tcx > for UnwrappableVariablesVisitor < ' a , ' tcx > {
219
295
type NestedFilter = nested_filter:: OnlyBodies ;
220
296
@@ -233,6 +309,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
233
309
// find `unwrap[_err]()` calls:
234
310
if_chain ! {
235
311
if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind;
312
+ let ( self_arg, as_ref_kind) = consume_option_as_ref( self_arg) ;
236
313
if let Some ( id) = path_to_local( self_arg) ;
237
314
if [ sym:: unwrap, sym:: expect, sym!( unwrap_err) ] . contains( & method_name. ident. name) ;
238
315
let call_to_unwrap = [ sym:: unwrap, sym:: expect] . contains( & method_name. ident. name) ;
@@ -268,7 +345,12 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
268
345
unwrappable. check. span. with_lo( unwrappable. if_expr. span. lo( ) ) ,
269
346
"try" ,
270
347
format!(
271
- "if let {suggested_pattern} = {unwrappable_variable_name}" ,
348
+ "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name}" ,
349
+ borrow_prefix = match as_ref_kind {
350
+ Some ( AsRefKind :: AsRef ) => "&" ,
351
+ Some ( AsRefKind :: AsMut ) => "&mut " ,
352
+ None => "" ,
353
+ } ,
272
354
) ,
273
355
// We don't track how the unwrapped value is used inside the
274
356
// block or suggest deleting the unwrap, so we can't offer a
0 commit comments