1
1
use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
2
2
use clippy_utils:: source:: { snippet_with_applicability, snippet_with_context} ;
3
+ use clippy_utils:: sugg:: has_enclosing_paren;
3
4
use clippy_utils:: ty:: peel_mid_ty_refs;
4
5
use clippy_utils:: { get_parent_expr, get_parent_node, is_lint_allowed, path_to_local} ;
5
6
use rustc_ast:: util:: parser:: { PREC_POSTFIX , PREC_PREFIX } ;
@@ -130,8 +131,6 @@ pub struct Dereferencing {
130
131
struct StateData {
131
132
/// Span of the top level expression
132
133
span : Span ,
133
- /// The required mutability
134
- target_mut : Mutability ,
135
134
}
136
135
137
136
enum State {
@@ -140,9 +139,13 @@ enum State {
140
139
// The number of calls in a sequence which changed the referenced type
141
140
ty_changed_count : usize ,
142
141
is_final_ufcs : bool ,
142
+ /// The required mutability
143
+ target_mut : Mutability ,
143
144
} ,
144
145
DerefedBorrow {
145
- count : u32 ,
146
+ count : usize ,
147
+ required_precedence : i8 ,
148
+ msg : & ' static str ,
146
149
} ,
147
150
}
148
151
@@ -213,77 +216,98 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
213
216
1
214
217
} ,
215
218
is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
216
- } ,
217
- StateData {
218
- span : expr. span ,
219
219
target_mut,
220
220
} ,
221
+ StateData { span : expr. span } ,
221
222
) ) ;
222
223
} ,
223
224
RefOp :: AddrOf => {
224
225
// Find the number of times the borrow is auto-derefed.
225
226
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 ( ) ;
249
236
}
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)
280
279
}
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
+ ) ) ;
281
295
}
282
296
} ,
283
297
_ => ( ) ,
284
298
}
285
299
} ,
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
+ ) => {
287
311
self . state = Some ( (
288
312
State :: DerefMethod {
289
313
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 {
292
316
ty_changed_count + 1
293
317
} ,
294
318
is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
319
+ target_mut,
295
320
} ,
296
321
data,
297
322
) ) ;
298
323
} ,
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
+ ) ) ;
301
343
} ,
302
344
303
345
( Some ( ( state, data) ) , _) => report ( cx, expr, state, data) ,
@@ -475,18 +517,28 @@ fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId,
475
517
476
518
/// Checks if the given expression is in a position which can be auto-reborrowed.
477
519
/// 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 {
479
521
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 ( ..) ) ,
485
523
Some ( Node :: Local ( _) ) => true ,
486
524
_ => false ,
487
525
}
488
526
}
489
527
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
+
490
542
/// Adjustments are sometimes made in the parent block rather than the expression itself.
491
543
fn find_adjustments < ' tcx > (
492
544
tcx : TyCtxt < ' tcx > ,
@@ -535,6 +587,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
535
587
State :: DerefMethod {
536
588
ty_changed_count,
537
589
is_final_ufcs,
590
+ target_mut,
538
591
} => {
539
592
let mut app = Applicability :: MachineApplicable ;
540
593
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)
549
602
} ;
550
603
let addr_of_str = if ty_changed_count < ref_count {
551
604
// 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 ) ) {
553
606
"&*"
554
607
} else {
555
608
""
556
609
}
557
- } else if data . target_mut == Mutability :: Mut {
610
+ } else if target_mut == Mutability :: Mut {
558
611
"&mut "
559
612
} else {
560
613
"&"
@@ -570,7 +623,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
570
623
cx,
571
624
EXPLICIT_DEREF_METHODS ,
572
625
data. span ,
573
- match data . target_mut {
626
+ match target_mut {
574
627
Mutability :: Not => "explicit `deref` method call" ,
575
628
Mutability :: Mut => "explicit `deref_mut` method call" ,
576
629
} ,
@@ -579,19 +632,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
579
632
app,
580
633
) ;
581
634
} ,
582
- State :: DerefedBorrow { .. } => {
635
+ State :: DerefedBorrow {
636
+ required_precedence,
637
+ msg,
638
+ ..
639
+ } => {
583
640
let mut app = Applicability :: MachineApplicable ;
584
641
let snip = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) . 0 ;
585
642
span_lint_and_sugg (
586
643
cx,
587
644
NEEDLESS_BORROW ,
588
645
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,
593
647
"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
+ } ,
595
653
app,
596
654
) ;
597
655
} ,
0 commit comments