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 } ;
@@ -10,11 +11,10 @@ use rustc_hir::{
1011 Pat , PatKind , UnOp ,
1112} ;
1213use rustc_lint:: { LateContext , LateLintPass } ;
13- use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow } ;
14+ use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow , AutoBorrowMutability } ;
1415use rustc_middle:: ty:: { self , Ty , TyCtxt , TyS , TypeckResults } ;
1516use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1617use rustc_span:: { symbol:: sym, Span } ;
17- use std:: iter;
1818
1919declare_clippy_lint ! {
2020 /// ### What it does
@@ -131,8 +131,6 @@ pub struct Dereferencing {
131131struct StateData {
132132 /// Span of the top level expression
133133 span : Span ,
134- /// The required mutability
135- target_mut : Mutability ,
136134}
137135
138136enum State {
@@ -141,9 +139,13 @@ enum State {
141139 // The number of calls in a sequence which changed the referenced type
142140 ty_changed_count : usize ,
143141 is_final_ufcs : bool ,
142+ /// The required mutability
143+ target_mut : Mutability ,
144144 } ,
145145 DerefedBorrow {
146- count : u32 ,
146+ count : usize ,
147+ required_precedence : i8 ,
148+ msg : & ' static str ,
147149 } ,
148150}
149151
@@ -214,59 +216,98 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
214216 1
215217 } ,
216218 is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
217- } ,
218- StateData {
219- span : expr. span ,
220219 target_mut,
221220 } ,
221+ StateData { span : expr. span } ,
222222 ) ) ;
223223 } ,
224224 RefOp :: AddrOf => {
225225 // Find the number of times the borrow is auto-derefed.
226226 let mut iter = find_adjustments ( cx. tcx , typeck, expr) . iter ( ) ;
227- if let Some ( ( i, adjust) ) = iter. by_ref ( ) . enumerate ( ) . find_map ( |( i, adjust) | {
228- if !matches ! ( adjust. kind, Adjust :: Deref ( _) ) {
229- Some ( ( i, adjust) )
230- } else if !adjust. target . is_ref ( ) {
231- // Add one to the number of references found.
232- Some ( ( i + 1 , adjust) )
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 ( ) ;
236+ }
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)
233277 } else {
234- None
235- }
236- } ) {
237- // Found two consecutive derefs. At least one can be removed.
238- if i > 1 {
239- let target_mut = iter:: once ( adjust)
240- . chain ( iter)
241- . find_map ( |adjust| match adjust. kind {
242- Adjust :: Borrow ( AutoBorrow :: Ref ( _, m) ) => Some ( m. into ( ) ) ,
243- _ => None ,
244- } )
245- // This default should never happen. Auto-deref always reborrows.
246- . unwrap_or ( Mutability :: Not ) ;
247- self . state = Some ( (
248- // Subtract one for the current borrow expression, and one to cover the last
249- // reference which can't be removed (it's either reborrowed, or needed for
250- // auto-deref to happen).
251- State :: DerefedBorrow {
252- count :
253- // Truncation here would require more than a `u32::MAX` level reference. The compiler
254- // does not support this.
255- #[ allow ( clippy:: cast_possible_truncation) ]
256- { i as u32 - 2 }
257- } ,
258- StateData {
259- span : expr. span ,
260- target_mut,
261- } ,
262- ) ) ;
278+ ( 2 , 0 , deref_msg)
263279 }
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+ ) ) ;
264295 }
265296 } ,
266297 _ => ( ) ,
267298 }
268299 } ,
269- ( 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+ ) => {
270311 self . state = Some ( (
271312 State :: DerefMethod {
272313 ty_changed_count : if deref_method_same_type ( typeck. expr_ty ( expr) , typeck. expr_ty ( sub_expr) ) {
@@ -275,12 +316,30 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
275316 ty_changed_count + 1
276317 } ,
277318 is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
319+ target_mut,
278320 } ,
279321 data,
280322 ) ) ;
281323 } ,
282- ( Some ( ( State :: DerefedBorrow { count } , data) ) , RefOp :: AddrOf ) if count != 0 => {
283- 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+ ) ) ;
284343 } ,
285344
286345 ( Some ( ( state, data) ) , _) => report ( cx, expr, state, data) ,
@@ -455,6 +514,30 @@ fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId,
455514 }
456515}
457516
517+ /// Checks if the given expression is in a position which can be auto-reborrowed.
518+ /// Note: This is only correct assuming auto-deref is already occurring.
519+ fn is_auto_reborrow_position ( parent : Option < Node < ' _ > > ) -> bool {
520+ match parent {
521+ Some ( Node :: Expr ( parent) ) => matches ! ( parent. kind, ExprKind :: MethodCall ( ..) | ExprKind :: Call ( ..) ) ,
522+ Some ( Node :: Local ( _) ) => true ,
523+ _ => false ,
524+ }
525+ }
526+
527+ /// Checks if the given expression is a position which can auto-borrow.
528+ fn is_auto_borrow_position ( parent : Option < Node < ' _ > > , child_id : HirId ) -> bool {
529+ if let Some ( Node :: Expr ( parent) ) = parent {
530+ match parent. kind {
531+ ExprKind :: MethodCall ( _, [ self_arg, ..] , _) => self_arg. hir_id == child_id,
532+ ExprKind :: Field ( ..) => true ,
533+ ExprKind :: Call ( f, _) => f. hir_id == child_id,
534+ _ => false ,
535+ }
536+ } else {
537+ false
538+ }
539+ }
540+
458541/// Adjustments are sometimes made in the parent block rather than the expression itself.
459542fn find_adjustments < ' tcx > (
460543 tcx : TyCtxt < ' tcx > ,
@@ -503,6 +586,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
503586 State :: DerefMethod {
504587 ty_changed_count,
505588 is_final_ufcs,
589+ target_mut,
506590 } => {
507591 let mut app = Applicability :: MachineApplicable ;
508592 let ( expr_str, expr_is_macro_call) = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) ;
@@ -517,12 +601,12 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
517601 } ;
518602 let addr_of_str = if ty_changed_count < ref_count {
519603 // Check if a reborrow from &mut T -> &T is required.
520- if data . target_mut == Mutability :: Not && matches ! ( ty. kind( ) , ty:: Ref ( _, _, Mutability :: Mut ) ) {
604+ if target_mut == Mutability :: Not && matches ! ( ty. kind( ) , ty:: Ref ( _, _, Mutability :: Mut ) ) {
521605 "&*"
522606 } else {
523607 ""
524608 }
525- } else if data . target_mut == Mutability :: Mut {
609+ } else if target_mut == Mutability :: Mut {
526610 "&mut "
527611 } else {
528612 "&"
@@ -538,7 +622,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
538622 cx,
539623 EXPLICIT_DEREF_METHODS ,
540624 data. span ,
541- match data . target_mut {
625+ match target_mut {
542626 Mutability :: Not => "explicit `deref` method call" ,
543627 Mutability :: Mut => "explicit `deref_mut` method call" ,
544628 } ,
@@ -547,19 +631,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
547631 app,
548632 ) ;
549633 } ,
550- State :: DerefedBorrow { .. } => {
634+ State :: DerefedBorrow {
635+ required_precedence,
636+ msg,
637+ ..
638+ } => {
551639 let mut app = Applicability :: MachineApplicable ;
552640 let snip = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) . 0 ;
553641 span_lint_and_sugg (
554642 cx,
555643 NEEDLESS_BORROW ,
556644 data. span ,
557- & format ! (
558- "this expression borrows a reference (`{}`) that is immediately dereferenced by the compiler" ,
559- cx. typeck_results( ) . expr_ty( expr) ,
560- ) ,
645+ msg,
561646 "change this to" ,
562- snip. into ( ) ,
647+ if required_precedence > expr. precedence ( ) . order ( ) && !has_enclosing_paren ( & snip) {
648+ format ! ( "({})" , snip)
649+ } else {
650+ snip. into ( )
651+ } ,
563652 app,
564653 ) ;
565654 } ,
0 commit comments