11use clippy_utils:: diagnostics:: span_lint_hir_and_then;
22use clippy_utils:: ty:: is_type_diagnostic_item;
3- use clippy_utils:: usage:: is_potentially_mutated ;
3+ use clippy_utils:: usage:: is_potentially_local_place ;
44use clippy_utils:: { higher, path_to_local} ;
55use if_chain:: if_chain;
66use rustc_errors:: Applicability ;
77use 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 ;
911use rustc_lint:: { LateContext , LateLintPass } ;
1012use rustc_middle:: hir:: nested_filter;
1113use 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 } ;
1316use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1417use rustc_span:: def_id:: LocalDefId ;
1518use rustc_span:: source_map:: Span ;
@@ -192,6 +195,43 @@ fn collect_unwrap_info<'tcx>(
192195 Vec :: new ( )
193196}
194197
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+
195235impl < ' a , ' tcx > UnwrappableVariablesVisitor < ' a , ' tcx > {
196236 fn visit_branch (
197237 & mut self ,
@@ -202,9 +242,24 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
202242 ) {
203243 let prev_len = self . unwrappables . len ( ) ;
204244 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 {
208263 // if the variable is mutated, we don't know whether it can be unwrapped:
209264 continue ;
210265 }
@@ -215,6 +270,27 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
215270 }
216271}
217272
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+
218294impl < ' a , ' tcx > Visitor < ' tcx > for UnwrappableVariablesVisitor < ' a , ' tcx > {
219295 type NestedFilter = nested_filter:: OnlyBodies ;
220296
@@ -233,6 +309,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
233309 // find `unwrap[_err]()` calls:
234310 if_chain ! {
235311 if let ExprKind :: MethodCall ( method_name, self_arg, ..) = expr. kind;
312+ let ( self_arg, as_ref_kind) = consume_option_as_ref( self_arg) ;
236313 if let Some ( id) = path_to_local( self_arg) ;
237314 if [ sym:: unwrap, sym:: expect, sym!( unwrap_err) ] . contains( & method_name. ident. name) ;
238315 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> {
268345 unwrappable. check. span. with_lo( unwrappable. if_expr. span. lo( ) ) ,
269346 "try" ,
270347 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+ } ,
272354 ) ,
273355 // We don't track how the unwrapped value is used inside the
274356 // block or suggest deleting the unwrap, so we can't offer a
0 commit comments