11use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
22use clippy_utils:: source:: { snippet_with_applicability, snippet_with_context} ;
3+ use clippy_utils:: sugg:: has_enclosing_paren;
34use clippy_utils:: ty:: peel_mid_ty_refs;
45use clippy_utils:: { get_parent_expr, get_parent_node, is_lint_allowed, path_to_local} ;
56use rustc_ast:: util:: parser:: { PREC_POSTFIX , PREC_PREFIX } ;
@@ -130,8 +131,6 @@ pub struct Dereferencing {
130131struct StateData {
131132 /// Span of the top level expression
132133 span : Span ,
133- /// The required mutability
134- target_mut : Mutability ,
135134}
136135
137136enum State {
@@ -140,9 +139,13 @@ enum State {
140139 // The number of calls in a sequence which changed the referenced type
141140 ty_changed_count : usize ,
142141 is_final_ufcs : bool ,
142+ /// The required mutability
143+ target_mut : Mutability ,
143144 } ,
144145 DerefedBorrow {
145- count : u32 ,
146+ count : usize ,
147+ required_precedence : i8 ,
148+ msg : & ' static str ,
146149 } ,
147150}
148151
@@ -213,77 +216,98 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
213216 1
214217 } ,
215218 is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
216- } ,
217- StateData {
218- span : expr. span ,
219219 target_mut,
220220 } ,
221+ StateData { span : expr. span } ,
221222 ) ) ;
222223 } ,
223224 RefOp :: AddrOf => {
224225 // Find the number of times the borrow is auto-derefed.
225226 let mut iter = find_adjustments ( cx. tcx , typeck, expr) . iter ( ) ;
226- if let Some ( ( i, adjust) ) = iter. by_ref ( ) . enumerate ( ) . find_map ( |( i, adjust) | {
227- if !matches ! ( adjust. kind, Adjust :: Deref ( _) ) {
228- Some ( ( i, Some ( adjust) ) )
229- } else if !adjust. target . is_ref ( ) {
230- // Include the current deref.
231- Some ( ( i + 1 , None ) )
232- } else {
233- None
234- }
235- } ) {
236- if i > 1 {
237- // If the next adjustment is a mutable borrow, then check to see if the compiler will
238- // insert a re-borrow here. If not, leave an extra borrow here to avoid attempting to
239- // move the a mutable reference.
240- let ( i, target_mut) = if let Some ( & Adjust :: Borrow ( AutoBorrow :: Ref ( _, mutability) ) ) =
241- adjust. or_else ( || iter. next ( ) ) . map ( |a| & a. kind )
242- {
243- if matches ! ( mutability, AutoBorrowMutability :: Mut { .. } )
244- && !is_auto_reborrow_position ( parent, expr. hir_id )
245- {
246- ( i - 1 , Mutability :: Mut )
247- } else {
248- ( i, mutability. into ( ) )
227+ let mut deref_count = 0usize ;
228+ let next_adjust = loop {
229+ match iter. next ( ) {
230+ Some ( adjust) => {
231+ if !matches ! ( adjust. kind, Adjust :: Deref ( _) ) {
232+ break Some ( adjust) ;
233+ } else if !adjust. target . is_ref ( ) {
234+ deref_count += 1 ;
235+ break iter. next ( ) ;
249236 }
250- } else {
251- (
252- i,
253- iter. find_map ( |adjust| match adjust. kind {
254- Adjust :: Borrow ( AutoBorrow :: Ref ( _, m) ) => Some ( m. into ( ) ) ,
255- _ => None ,
256- } )
257- // This default should never happen. Auto-deref always reborrows.
258- . unwrap_or ( Mutability :: Not ) ,
259- )
260- } ;
261-
262- if i > 1 {
263- self . state = Some ( (
264- // Subtract one for the current borrow expression, and one to cover the last
265- // reference which can't be removed (it's either reborrowed, or needed for
266- // auto-deref to happen).
267- State :: DerefedBorrow {
268- count :
269- // Truncation here would require more than a `u32::MAX` level reference. The compiler
270- // does not support this.
271- #[ allow ( clippy:: cast_possible_truncation) ]
272- { i as u32 - 2 }
273- } ,
274- StateData {
275- span : expr. span ,
276- target_mut,
277- } ,
278- ) ) ;
279- }
237+ deref_count += 1 ;
238+ } ,
239+ None => break None ,
240+ } ;
241+ } ;
242+
243+ // Determine the required number of references before any can be removed. In all cases the
244+ // reference made by the current expression will be removed. After that there are four cases to
245+ // handle.
246+ //
247+ // 1. Auto-borrow will trigger in the current position, so no further references are required.
248+ // 2. Auto-deref ends at a reference, or the underlying type, so one extra needs to be left to
249+ // handle the automatically inserted re-borrow.
250+ // 3. Auto-deref hits a user-defined `Deref` impl, so at least one reference needs to exist to
251+ // start auto-deref.
252+ // 4. If the chain of non-user-defined derefs ends with a mutable re-borrow, and re-borrow
253+ // adjustments will not be inserted automatically, then leave one further reference to avoid
254+ // moving a mutable borrow.
255+ // e.g.
256+ // fn foo<T>(x: &mut Option<&mut T>, y: &mut T) {
257+ // let x = match x {
258+ // // Removing the borrow will cause `x` to be moved
259+ // Some(x) => &mut *x,
260+ // None => y
261+ // };
262+ // }
263+ let deref_msg =
264+ "this expression creates a reference which is immediately dereferenced by the compiler" ;
265+ let borrow_msg = "this expression borrows a value the compiler would automatically borrow" ;
266+
267+ let ( required_refs, required_precedence, msg) = if is_auto_borrow_position ( parent, expr. hir_id )
268+ {
269+ ( 1 , PREC_POSTFIX , if deref_count == 1 { borrow_msg } else { deref_msg } )
270+ } else if let Some ( & Adjust :: Borrow ( AutoBorrow :: Ref ( _, mutability) ) ) =
271+ next_adjust. map ( |a| & a. kind )
272+ {
273+ if matches ! ( mutability, AutoBorrowMutability :: Mut { .. } )
274+ && !is_auto_reborrow_position ( parent)
275+ {
276+ ( 3 , 0 , deref_msg)
277+ } else {
278+ ( 2 , 0 , deref_msg)
280279 }
280+ } else {
281+ ( 2 , 0 , deref_msg)
282+ } ;
283+
284+ if deref_count >= required_refs {
285+ self . state = Some ( (
286+ State :: DerefedBorrow {
287+ // One of the required refs is for the current borrow expression, the remaining ones
288+ // can't be removed without breaking the code. See earlier comment.
289+ count : deref_count - required_refs,
290+ required_precedence,
291+ msg,
292+ } ,
293+ StateData { span : expr. span } ,
294+ ) ) ;
281295 }
282296 } ,
283297 _ => ( ) ,
284298 }
285299 } ,
286- ( Some ( ( State :: DerefMethod { ty_changed_count, .. } , data) ) , RefOp :: Method ( _) ) => {
300+ (
301+ Some ( (
302+ State :: DerefMethod {
303+ target_mut,
304+ ty_changed_count,
305+ ..
306+ } ,
307+ data,
308+ ) ) ,
309+ RefOp :: Method ( _) ,
310+ ) => {
287311 self . state = Some ( (
288312 State :: DerefMethod {
289313 ty_changed_count : if deref_method_same_type ( typeck. expr_ty ( expr) , typeck. expr_ty ( sub_expr) ) {
@@ -292,12 +316,30 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
292316 ty_changed_count + 1
293317 } ,
294318 is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
319+ target_mut,
295320 } ,
296321 data,
297322 ) ) ;
298323 } ,
299- ( Some ( ( State :: DerefedBorrow { count } , data) ) , RefOp :: AddrOf ) if count != 0 => {
300- self . state = Some ( ( State :: DerefedBorrow { count : count - 1 } , data) ) ;
324+ (
325+ Some ( (
326+ State :: DerefedBorrow {
327+ count,
328+ required_precedence,
329+ msg,
330+ } ,
331+ data,
332+ ) ) ,
333+ RefOp :: AddrOf ,
334+ ) if count != 0 => {
335+ self . state = Some ( (
336+ State :: DerefedBorrow {
337+ count : count - 1 ,
338+ required_precedence,
339+ msg,
340+ } ,
341+ data,
342+ ) ) ;
301343 } ,
302344
303345 ( Some ( ( state, data) ) , _) => report ( cx, expr, state, data) ,
@@ -475,18 +517,28 @@ fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId,
475517
476518/// Checks if the given expression is in a position which can be auto-reborrowed.
477519/// Note: This is only correct assuming auto-deref is already occurring.
478- fn is_auto_reborrow_position ( parent : Option < Node < ' _ > > , child_id : HirId ) -> bool {
520+ fn is_auto_reborrow_position ( parent : Option < Node < ' _ > > ) -> bool {
479521 match parent {
480- Some ( Node :: Expr ( parent) ) => match parent. kind {
481- ExprKind :: MethodCall ( ..) => true ,
482- ExprKind :: Call ( callee, _) => callee. hir_id != child_id,
483- _ => false ,
484- } ,
522+ Some ( Node :: Expr ( parent) ) => matches ! ( parent. kind, ExprKind :: MethodCall ( ..) | ExprKind :: Call ( ..) ) ,
485523 Some ( Node :: Local ( _) ) => true ,
486524 _ => false ,
487525 }
488526}
489527
528+ /// Checks if the given expression is a position which can auto-borrow.
529+ fn is_auto_borrow_position ( parent : Option < Node < ' _ > > , child_id : HirId ) -> bool {
530+ if let Some ( Node :: Expr ( parent) ) = parent {
531+ match parent. kind {
532+ ExprKind :: MethodCall ( _, _, [ self_arg, ..] , _) => self_arg. hir_id == child_id,
533+ ExprKind :: Field ( ..) => true ,
534+ ExprKind :: Call ( f, _) => f. hir_id == child_id,
535+ _ => false ,
536+ }
537+ } else {
538+ false
539+ }
540+ }
541+
490542/// Adjustments are sometimes made in the parent block rather than the expression itself.
491543fn find_adjustments < ' tcx > (
492544 tcx : TyCtxt < ' tcx > ,
@@ -535,6 +587,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
535587 State :: DerefMethod {
536588 ty_changed_count,
537589 is_final_ufcs,
590+ target_mut,
538591 } => {
539592 let mut app = Applicability :: MachineApplicable ;
540593 let ( expr_str, expr_is_macro_call) = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) ;
@@ -549,12 +602,12 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
549602 } ;
550603 let addr_of_str = if ty_changed_count < ref_count {
551604 // Check if a reborrow from &mut T -> &T is required.
552- if data . target_mut == Mutability :: Not && matches ! ( ty. kind( ) , ty:: Ref ( _, _, Mutability :: Mut ) ) {
605+ if target_mut == Mutability :: Not && matches ! ( ty. kind( ) , ty:: Ref ( _, _, Mutability :: Mut ) ) {
553606 "&*"
554607 } else {
555608 ""
556609 }
557- } else if data . target_mut == Mutability :: Mut {
610+ } else if target_mut == Mutability :: Mut {
558611 "&mut "
559612 } else {
560613 "&"
@@ -570,7 +623,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
570623 cx,
571624 EXPLICIT_DEREF_METHODS ,
572625 data. span ,
573- match data . target_mut {
626+ match target_mut {
574627 Mutability :: Not => "explicit `deref` method call" ,
575628 Mutability :: Mut => "explicit `deref_mut` method call" ,
576629 } ,
@@ -579,19 +632,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
579632 app,
580633 ) ;
581634 } ,
582- State :: DerefedBorrow { .. } => {
635+ State :: DerefedBorrow {
636+ required_precedence,
637+ msg,
638+ ..
639+ } => {
583640 let mut app = Applicability :: MachineApplicable ;
584641 let snip = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) . 0 ;
585642 span_lint_and_sugg (
586643 cx,
587644 NEEDLESS_BORROW ,
588645 data. span ,
589- & format ! (
590- "this expression borrows a reference (`{}`) that is immediately dereferenced by the compiler" ,
591- cx. typeck_results( ) . expr_ty( expr) ,
592- ) ,
646+ msg,
593647 "change this to" ,
594- snip. into ( ) ,
648+ if required_precedence > expr. precedence ( ) . order ( ) && !has_enclosing_paren ( & snip) {
649+ format ! ( "({})" , snip)
650+ } else {
651+ snip. into ( )
652+ } ,
595653 app,
596654 ) ;
597655 } ,
0 commit comments