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) ,
@@ -456,6 +515,30 @@ fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId,
456
515
}
457
516
}
458
517
518
+ /// Checks if the given expression is in a position which can be auto-reborrowed.
519
+ /// Note: This is only correct assuming auto-deref is already occurring.
520
+ fn is_auto_reborrow_position ( parent : Option < Node < ' _ > > ) -> bool {
521
+ match parent {
522
+ Some ( Node :: Expr ( parent) ) => matches ! ( parent. kind, ExprKind :: MethodCall ( ..) | ExprKind :: Call ( ..) ) ,
523
+ Some ( Node :: Local ( _) ) => true ,
524
+ _ => false ,
525
+ }
526
+ }
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
+
459
542
/// Adjustments are sometimes made in the parent block rather than the expression itself.
460
543
fn find_adjustments < ' tcx > (
461
544
tcx : TyCtxt < ' tcx > ,
@@ -504,6 +587,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
504
587
State :: DerefMethod {
505
588
ty_changed_count,
506
589
is_final_ufcs,
590
+ target_mut,
507
591
} => {
508
592
let mut app = Applicability :: MachineApplicable ;
509
593
let ( expr_str, expr_is_macro_call) = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) ;
@@ -518,12 +602,12 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
518
602
} ;
519
603
let addr_of_str = if ty_changed_count < ref_count {
520
604
// Check if a reborrow from &mut T -> &T is required.
521
- 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 ) ) {
522
606
"&*"
523
607
} else {
524
608
""
525
609
}
526
- } else if data . target_mut == Mutability :: Mut {
610
+ } else if target_mut == Mutability :: Mut {
527
611
"&mut "
528
612
} else {
529
613
"&"
@@ -539,7 +623,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
539
623
cx,
540
624
EXPLICIT_DEREF_METHODS ,
541
625
data. span ,
542
- match data . target_mut {
626
+ match target_mut {
543
627
Mutability :: Not => "explicit `deref` method call" ,
544
628
Mutability :: Mut => "explicit `deref_mut` method call" ,
545
629
} ,
@@ -548,19 +632,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
548
632
app,
549
633
) ;
550
634
} ,
551
- State :: DerefedBorrow { .. } => {
635
+ State :: DerefedBorrow {
636
+ required_precedence,
637
+ msg,
638
+ ..
639
+ } => {
552
640
let mut app = Applicability :: MachineApplicable ;
553
641
let snip = snippet_with_context ( cx, expr. span , data. span . ctxt ( ) , ".." , & mut app) . 0 ;
554
642
span_lint_and_sugg (
555
643
cx,
556
644
NEEDLESS_BORROW ,
557
645
data. span ,
558
- & format ! (
559
- "this expression borrows a reference (`{}`) that is immediately dereferenced by the compiler" ,
560
- cx. typeck_results( ) . expr_ty( expr) ,
561
- ) ,
646
+ msg,
562
647
"change this to" ,
563
- snip. into ( ) ,
648
+ if required_precedence > expr. precedence ( ) . order ( ) && !has_enclosing_paren ( & snip) {
649
+ format ! ( "({})" , snip)
650
+ } else {
651
+ snip. into ( )
652
+ } ,
564
653
app,
565
654
) ;
566
655
} ,
0 commit comments