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 } ;
@@ -10,11 +11,10 @@ use rustc_hir::{
10
11
Pat , PatKind , UnOp ,
11
12
} ;
12
13
use rustc_lint:: { LateContext , LateLintPass } ;
13
- use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow } ;
14
+ use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow , AutoBorrowMutability } ;
14
15
use rustc_middle:: ty:: { self , Ty , TyCtxt , TyS , TypeckResults } ;
15
16
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
16
17
use rustc_span:: { symbol:: sym, Span } ;
17
- use std:: iter;
18
18
19
19
declare_clippy_lint ! {
20
20
/// ### What it does
@@ -131,8 +131,6 @@ pub struct Dereferencing {
131
131
struct StateData {
132
132
/// Span of the top level expression
133
133
span : Span ,
134
- /// The required mutability
135
- target_mut : Mutability ,
136
134
}
137
135
138
136
enum State {
@@ -141,9 +139,13 @@ enum State {
141
139
// The number of calls in a sequence which changed the referenced type
142
140
ty_changed_count : usize ,
143
141
is_final_ufcs : bool ,
142
+ /// The required mutability
143
+ target_mut : Mutability ,
144
144
} ,
145
145
DerefedBorrow {
146
- count : u32 ,
146
+ count : usize ,
147
+ required_precedence : i8 ,
148
+ msg : & ' static str ,
147
149
} ,
148
150
}
149
151
@@ -214,59 +216,98 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
214
216
1
215
217
} ,
216
218
is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
217
- } ,
218
- StateData {
219
- span : expr. span ,
220
219
target_mut,
221
220
} ,
221
+ StateData { span : expr. span } ,
222
222
) ) ;
223
223
} ,
224
224
RefOp :: AddrOf => {
225
225
// Find the number of times the borrow is auto-derefed.
226
226
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)
233
277
} 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)
263
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
+ ) ) ;
264
295
}
265
296
} ,
266
297
_ => ( ) ,
267
298
}
268
299
} ,
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
+ ) => {
270
311
self . state = Some ( (
271
312
State :: DerefMethod {
272
313
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 {
275
316
ty_changed_count + 1
276
317
} ,
277
318
is_final_ufcs : matches ! ( expr. kind, ExprKind :: Call ( ..) ) ,
319
+ target_mut,
278
320
} ,
279
321
data,
280
322
) ) ;
281
323
} ,
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
+ ) ) ;
284
343
} ,
285
344
286
345
( Some ( ( state, data) ) , _) => report ( cx, expr, state, data) ,
@@ -455,6 +514,30 @@ fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId,
455
514
}
456
515
}
457
516
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
+
458
541
/// Adjustments are sometimes made in the parent block rather than the expression itself.
459
542
fn find_adjustments < ' tcx > (
460
543
tcx : TyCtxt < ' tcx > ,
@@ -503,6 +586,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
503
586
State :: DerefMethod {
504
587
ty_changed_count,
505
588
is_final_ufcs,
589
+ target_mut,
506
590
} => {
507
591
let mut app = Applicability :: MachineApplicable ;
508
592
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)
517
601
} ;
518
602
let addr_of_str = if ty_changed_count < ref_count {
519
603
// 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 ) ) {
521
605
"&*"
522
606
} else {
523
607
""
524
608
}
525
- } else if data . target_mut == Mutability :: Mut {
609
+ } else if target_mut == Mutability :: Mut {
526
610
"&mut "
527
611
} else {
528
612
"&"
@@ -538,7 +622,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
538
622
cx,
539
623
EXPLICIT_DEREF_METHODS ,
540
624
data. span ,
541
- match data . target_mut {
625
+ match target_mut {
542
626
Mutability :: Not => "explicit `deref` method call" ,
543
627
Mutability :: Mut => "explicit `deref_mut` method call" ,
544
628
} ,
@@ -547,19 +631,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
547
631
app,
548
632
) ;
549
633
} ,
550
- State :: DerefedBorrow { .. } => {
634
+ State :: DerefedBorrow {
635
+ required_precedence,
636
+ msg,
637
+ ..
638
+ } => {
551
639
let mut app = Applicability :: MachineApplicable ;
552
640
let snip = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) . 0 ;
553
641
span_lint_and_sugg (
554
642
cx,
555
643
NEEDLESS_BORROW ,
556
644
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,
561
646
"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
+ } ,
563
652
app,
564
653
) ;
565
654
} ,
0 commit comments