1
1
use clippy_utils:: {
2
+ can_move_expr_to_closure_no_visit,
2
3
diagnostics:: span_lint_and_sugg,
3
4
is_expr_final_block_expr, is_expr_used_or_unified, match_def_path, paths, peel_hir_expr_while,
4
5
source:: { reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context} ,
@@ -7,7 +8,7 @@ use clippy_utils::{
7
8
use rustc_errors:: Applicability ;
8
9
use rustc_hir:: {
9
10
intravisit:: { walk_expr, ErasedMap , NestedVisitorMap , Visitor } ,
10
- Expr , ExprKind , Guard , Local , Stmt , StmtKind , UnOp ,
11
+ Block , Expr , ExprKind , Guard , HirId , Local , Stmt , StmtKind , UnOp ,
11
12
} ;
12
13
use rustc_lint:: { LateContext , LateLintPass } ;
13
14
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
@@ -78,13 +79,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
78
79
let sugg = if let Some ( else_expr) = else_expr {
79
80
// if .. { .. } else { .. }
80
81
let else_search = match find_insert_calls ( cx, & contains_expr, else_expr) {
81
- Some ( search) if !( then_search. insertions . is_empty ( ) && search. insertions . is_empty ( ) ) => search,
82
+ Some ( search) if !( then_search. edits . is_empty ( ) && search. edits . is_empty ( ) ) => search,
82
83
_ => return ,
83
84
} ;
84
85
85
- if then_search. insertions . is_empty ( ) || else_search. insertions . is_empty ( ) {
86
+ if then_search. edits . is_empty ( ) || else_search. edits . is_empty ( ) {
86
87
// if .. { insert } else { .. } or if .. { .. } else { then } of
87
- let ( then_str, else_str, entry_kind) = if else_search. insertions . is_empty ( ) {
88
+ let ( then_str, else_str, entry_kind) = if else_search. edits . is_empty ( ) {
88
89
if contains_expr. negated {
89
90
(
90
91
then_search. snippet_vacant ( cx, then_expr. span , & mut app) ,
@@ -152,37 +153,48 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
152
153
indent = indent_str,
153
154
)
154
155
}
155
- } else if then_search. insertions . is_empty ( ) || !contains_expr. negated {
156
+ } else if then_search. edits . is_empty ( ) {
157
+ // no insertions
156
158
return ;
157
159
} else {
158
160
// if .. { insert }
159
- match then_search. as_single_insertion ( ) {
160
- Some ( insertion ) if !insertion . value . can_have_side_effects ( ) => {
161
- format ! (
162
- "{}.entry({}).or_insert({});" ,
163
- map_str ,
164
- key_str ,
165
- snippet_with_context ( cx , insertion . value . span , insertion . call . span . ctxt ( ) , ".." , & mut app ) . 0 ,
161
+ if ! then_search. allow_insert_closure {
162
+ let ( body_str , entry_kind ) = if contains_expr . negated {
163
+ ( then_search . snippet_vacant ( cx , then_expr . span , & mut app ) , "Vacant(e)" )
164
+ } else {
165
+ (
166
+ then_search . snippet_occupied ( cx , then_expr . span , & mut app ) ,
167
+ "Occupied( mut e)" ,
166
168
)
167
- } ,
168
- _ => {
169
- let ( body_str, entry_kind) = if contains_expr. negated {
170
- ( then_search. snippet_vacant ( cx, then_expr. span , & mut app) , "Vacant(e)" )
169
+ } ;
170
+ format ! (
171
+ "if let {}::{} = {}.entry({}) {}" ,
172
+ map_ty. entry_path( ) ,
173
+ entry_kind,
174
+ map_str,
175
+ key_str,
176
+ body_str,
177
+ )
178
+ } else if let Some ( insertion) = then_search. as_single_insertion ( ) {
179
+ let value_str = snippet_with_context ( cx, insertion. value . span , then_expr. span . ctxt ( ) , ".." , & mut app) . 0 ;
180
+ if contains_expr. negated {
181
+ if insertion. value . can_have_side_effects ( ) {
182
+ format ! ( "{}.entry({}).or_insert_with(|| {});" , map_str, key_str, value_str)
171
183
} else {
172
- (
173
- then_search . snippet_occupied ( cx , then_expr . span , & mut app ) ,
174
- "Occupied(mut e)" ,
175
- )
176
- } ;
177
- format ! (
178
- "if let {}::{} = {}.entry({}) {}" ,
179
- map_ty . entry_path ( ) ,
180
- entry_kind ,
181
- map_str,
182
- key_str ,
183
- body_str ,
184
- )
185
- } ,
184
+ format ! ( "{}.entry({}).or_insert({});" , map_str , key_str , value_str )
185
+ }
186
+ } else {
187
+ // Todo: if let Some(v) = map.get_mut(k )
188
+ return ;
189
+ }
190
+ } else {
191
+ let block_str = then_search . snippet_closure ( cx , then_expr . span , & mut app ) ;
192
+ if contains_expr . negated {
193
+ format ! ( "{}.entry({}).or_insert_with(|| {});" , map_str, key_str , block_str )
194
+ } else {
195
+ // Todo: if let Some(v) = map.get_mut(k)
196
+ return ;
197
+ }
186
198
}
187
199
} ;
188
200
@@ -281,6 +293,19 @@ fn try_parse_insert(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Inse
281
293
}
282
294
}
283
295
296
+ /// An edit that will need to be made to move the expression to use the entry api
297
+ #[ derive( Clone , Copy ) ]
298
+ enum Edit < ' tcx > {
299
+ /// A semicolon that needs to be removed. Used to create a closure for `insert_with`.
300
+ RemoveSemi ( Span ) ,
301
+ /// An insertion into the map.
302
+ Insertion ( Insertion < ' tcx > ) ,
303
+ }
304
+ impl Edit < ' tcx > {
305
+ fn as_insertion ( self ) -> Option < Insertion < ' tcx > > {
306
+ if let Self :: Insertion ( i) = self { Some ( i) } else { None }
307
+ }
308
+ }
284
309
#[ derive( Clone , Copy ) ]
285
310
struct Insertion < ' tcx > {
286
311
call : & ' tcx Expr < ' tcx > ,
@@ -303,24 +328,40 @@ struct InsertSearcher<'cx, 'i, 'tcx> {
303
328
key : & ' tcx Expr < ' tcx > ,
304
329
/// The context of the top level block. All insert calls must be in the same context.
305
330
ctxt : SyntaxContext ,
331
+ /// Whether this expression can be safely moved into a closure.
332
+ allow_insert_closure : bool ,
306
333
/// Whether this expression can use the entry api.
307
334
can_use_entry : bool ,
335
+ /// Whether this expression is the final expression in this code path. This may be a statement.
336
+ in_tail_pos : bool ,
308
337
// A single insert expression has a slightly different suggestion.
309
338
is_single_insert : bool ,
310
339
is_map_used : bool ,
311
- insertions : & ' i mut Vec < Insertion < ' tcx > > ,
340
+ edits : & ' i mut Vec < Edit < ' tcx > > ,
341
+ loops : Vec < HirId > ,
312
342
}
313
343
impl < ' tcx > InsertSearcher < ' _ , ' _ , ' tcx > {
314
344
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
315
345
/// only if they are on separate code paths. This will return whether the map was used in the
316
346
/// given expression.
317
347
fn visit_cond_arm ( & mut self , e : & ' tcx Expr < ' _ > ) -> bool {
318
348
let is_map_used = self . is_map_used ;
349
+ let in_tail_pos = self . in_tail_pos ;
319
350
self . visit_expr ( e) ;
320
351
let res = self . is_map_used ;
321
352
self . is_map_used = is_map_used;
353
+ self . in_tail_pos = in_tail_pos;
322
354
res
323
355
}
356
+
357
+ /// Visits an expression which is not itself in a tail position, but other sibling expressions
358
+ /// may be. e.g. if conditions
359
+ fn visit_non_tail_expr ( & mut self , e : & ' tcx Expr < ' _ > ) {
360
+ let in_tail_pos = self . in_tail_pos ;
361
+ self . in_tail_pos = false ;
362
+ self . visit_expr ( e) ;
363
+ self . in_tail_pos = in_tail_pos;
364
+ }
324
365
}
325
366
impl < ' tcx > Visitor < ' tcx > for InsertSearcher < ' _ , ' _ , ' tcx > {
326
367
type Map = ErasedMap < ' tcx > ;
@@ -330,17 +371,63 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
330
371
331
372
fn visit_stmt ( & mut self , stmt : & ' tcx Stmt < ' _ > ) {
332
373
match stmt. kind {
333
- StmtKind :: Semi ( e) | StmtKind :: Expr ( e) => self . visit_expr ( e) ,
374
+ StmtKind :: Semi ( e) => {
375
+ self . visit_expr ( e) ;
376
+
377
+ if self . in_tail_pos && self . allow_insert_closure {
378
+ // The spans are used to slice the top level expression into multiple parts. This requires that
379
+ // they all come from the same part of the source code.
380
+ if stmt. span . ctxt ( ) == self . ctxt && e. span . ctxt ( ) == self . ctxt {
381
+ self . edits
382
+ . push ( Edit :: RemoveSemi ( stmt. span . trim_start ( e. span ) . unwrap_or ( DUMMY_SP ) ) ) ;
383
+ } else {
384
+ self . allow_insert_closure = false ;
385
+ }
386
+ }
387
+ } ,
388
+ StmtKind :: Expr ( e) => self . visit_expr ( e) ,
334
389
StmtKind :: Local ( Local { init : Some ( e) , .. } ) => {
390
+ self . allow_insert_closure &= !self . in_tail_pos ;
391
+ self . in_tail_pos = false ;
335
392
self . is_single_insert = false ;
336
393
self . visit_expr ( e) ;
337
394
} ,
338
395
_ => {
396
+ self . allow_insert_closure &= !self . in_tail_pos ;
339
397
self . is_single_insert = false ;
340
398
} ,
341
399
}
342
400
}
343
401
402
+ fn visit_block ( & mut self , block : & ' tcx Block < ' _ > ) {
403
+ // If the block is in a tail position, then the last expression (possibly a statement) is in the
404
+ // tail position. The rest, however, are not.
405
+ match ( block. stmts , block. expr ) {
406
+ ( [ ] , None ) => {
407
+ self . allow_insert_closure &= !self . in_tail_pos ;
408
+ } ,
409
+ ( [ ] , Some ( expr) ) => self . visit_expr ( expr) ,
410
+ ( stmts, Some ( expr) ) => {
411
+ let in_tail_pos = self . in_tail_pos ;
412
+ self . in_tail_pos = false ;
413
+ for stmt in stmts {
414
+ self . visit_stmt ( stmt) ;
415
+ }
416
+ self . in_tail_pos = in_tail_pos;
417
+ self . visit_expr ( expr) ;
418
+ } ,
419
+ ( [ stmts @ .., stmt] , None ) => {
420
+ let in_tail_pos = self . in_tail_pos ;
421
+ self . in_tail_pos = false ;
422
+ for stmt in stmts {
423
+ self . visit_stmt ( stmt) ;
424
+ }
425
+ self . in_tail_pos = in_tail_pos;
426
+ self . visit_stmt ( stmt) ;
427
+ } ,
428
+ }
429
+ }
430
+
344
431
fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
345
432
if !self . can_use_entry {
346
433
return ;
@@ -357,15 +444,16 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
357
444
return ;
358
445
}
359
446
360
- self . insertions . push ( Insertion {
447
+ self . edits . push ( Edit :: Insertion ( Insertion {
361
448
call : expr,
362
449
value : insert_expr. value ,
363
- } ) ;
450
+ } ) ) ;
364
451
self . is_map_used = true ;
452
+ self . allow_insert_closure &= self . in_tail_pos ;
365
453
366
454
// The value doesn't affect whether there is only a single insert expression.
367
455
let is_single_insert = self . is_single_insert ;
368
- self . visit_expr ( insert_expr. value ) ;
456
+ self . visit_non_tail_expr ( insert_expr. value ) ;
369
457
self . is_single_insert = is_single_insert;
370
458
} ,
371
459
_ if SpanlessEq :: new ( self . cx ) . eq_expr ( self . map , expr) => {
@@ -374,39 +462,46 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
374
462
_ => match expr. kind {
375
463
ExprKind :: If ( cond_expr, then_expr, Some ( else_expr) ) => {
376
464
self . is_single_insert = false ;
377
- self . visit_expr ( cond_expr) ;
465
+ self . visit_non_tail_expr ( cond_expr) ;
378
466
// Each branch may contain it's own insert expression.
379
467
let mut is_map_used = self . visit_cond_arm ( then_expr) ;
380
468
is_map_used |= self . visit_cond_arm ( else_expr) ;
381
469
self . is_map_used = is_map_used;
382
470
} ,
383
471
ExprKind :: Match ( scrutinee_expr, arms, _) => {
384
472
self . is_single_insert = false ;
385
- self . visit_expr ( scrutinee_expr) ;
473
+ self . visit_non_tail_expr ( scrutinee_expr) ;
386
474
// Each branch may contain it's own insert expression.
387
475
let mut is_map_used = self . is_map_used ;
388
476
for arm in arms {
389
477
if let Some ( Guard :: If ( guard) | Guard :: IfLet ( _, guard) ) = arm. guard {
390
- self . visit_expr ( guard)
478
+ self . visit_non_tail_expr ( guard)
391
479
}
392
480
is_map_used |= self . visit_cond_arm ( arm. body ) ;
393
481
}
394
482
self . is_map_used = is_map_used;
395
483
} ,
396
484
ExprKind :: Loop ( block, ..) => {
485
+ self . loops . push ( expr. hir_id ) ;
486
+ self . allow_insert_closure &= !self . in_tail_pos ;
397
487
// Don't allow insertions inside of a loop.
398
- let insertions_len = self . insertions . len ( ) ;
488
+ let edit_len = self . edits . len ( ) ;
399
489
self . visit_block ( block) ;
400
- if self . insertions . len ( ) != insertions_len {
490
+ if self . edits . len ( ) != edit_len {
401
491
self . can_use_entry = false ;
402
492
}
493
+ self . loops . pop ( ) ;
403
494
} ,
404
495
ExprKind :: Block ( block, _) => self . visit_block ( block) ,
405
496
ExprKind :: InlineAsm ( _) | ExprKind :: LlvmInlineAsm ( _) => {
406
497
self . can_use_entry = false ;
407
498
} ,
408
499
_ => {
500
+ self . allow_insert_closure &= !self . in_tail_pos ;
501
+ self . allow_insert_closure &= can_move_expr_to_closure_no_visit ( self . cx , expr, & self . loops ) ;
502
+ // Sub expressions are no longer in the tail position.
409
503
self . is_single_insert = false ;
504
+ self . in_tail_pos = false ;
410
505
walk_expr ( self , expr) ;
411
506
} ,
412
507
} ,
@@ -415,18 +510,19 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
415
510
}
416
511
417
512
struct InsertSearchResults < ' tcx > {
418
- insertions : Vec < Insertion < ' tcx > > ,
513
+ edits : Vec < Edit < ' tcx > > ,
514
+ allow_insert_closure : bool ,
419
515
is_single_insert : bool ,
420
516
}
421
517
impl InsertSearchResults < ' tcx > {
422
518
fn as_single_insertion ( & self ) -> Option < Insertion < ' tcx > > {
423
- self . is_single_insert . then ( || self . insertions [ 0 ] )
519
+ self . is_single_insert . then ( || self . edits [ 0 ] . as_insertion ( ) . unwrap ( ) )
424
520
}
425
521
426
522
fn snippet_occupied ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
427
523
let ctxt = span. ctxt ( ) ;
428
524
let mut res = String :: new ( ) ;
429
- for insertion in self . insertions . iter ( ) {
525
+ for insertion in self . edits . iter ( ) . filter_map ( |e| e . as_insertion ( ) ) {
430
526
res. push_str ( & snippet_with_applicability (
431
527
cx,
432
528
span. until ( insertion. call . span ) ,
@@ -451,7 +547,7 @@ impl InsertSearchResults<'tcx> {
451
547
fn snippet_vacant ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
452
548
let ctxt = span. ctxt ( ) ;
453
549
let mut res = String :: new ( ) ;
454
- for insertion in self . insertions . iter ( ) {
550
+ for insertion in self . edits . iter ( ) . filter_map ( |e| e . as_insertion ( ) ) {
455
551
res. push_str ( & snippet_with_applicability (
456
552
cx,
457
553
span. until ( insertion. call . span ) ,
@@ -485,27 +581,57 @@ impl InsertSearchResults<'tcx> {
485
581
res. push_str ( & snippet_with_applicability ( cx, span, ".." , app) ) ;
486
582
res
487
583
}
584
+
585
+ fn snippet_closure ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
586
+ let ctxt = span. ctxt ( ) ;
587
+ let mut res = String :: new ( ) ;
588
+ for edit in & self . edits {
589
+ match * edit {
590
+ Edit :: Insertion ( insertion) => {
591
+ res. push_str ( & snippet_with_applicability (
592
+ cx,
593
+ span. until ( insertion. call . span ) ,
594
+ ".." ,
595
+ app,
596
+ ) ) ;
597
+ res. push_str ( & snippet_with_context ( cx, insertion. value . span , ctxt, ".." , app) . 0 ) ;
598
+ span = span. trim_start ( insertion. call . span ) . unwrap_or ( DUMMY_SP ) ;
599
+ } ,
600
+ Edit :: RemoveSemi ( semi_span) => {
601
+ res. push_str ( & snippet_with_applicability ( cx, span. until ( semi_span) , ".." , app) ) ;
602
+ span = span. trim_start ( semi_span) . unwrap_or ( DUMMY_SP ) ;
603
+ } ,
604
+ }
605
+ }
606
+ res. push_str ( & snippet_with_applicability ( cx, span, ".." , app) ) ;
607
+ res
608
+ }
488
609
}
489
610
fn find_insert_calls (
490
611
cx : & LateContext < ' tcx > ,
491
612
contains_expr : & ContainsExpr < ' tcx > ,
492
613
expr : & ' tcx Expr < ' _ > ,
493
614
) -> Option < InsertSearchResults < ' tcx > > {
494
- let mut insertions = Vec :: new ( ) ;
615
+ let mut edits = Vec :: new ( ) ;
495
616
let mut s = InsertSearcher {
496
617
cx,
497
618
map : contains_expr. map ,
498
619
key : contains_expr. key ,
499
620
ctxt : expr. span . ctxt ( ) ,
500
- insertions : & mut insertions ,
621
+ edits : & mut edits ,
501
622
is_map_used : false ,
623
+ allow_insert_closure : true ,
502
624
can_use_entry : true ,
625
+ in_tail_pos : true ,
503
626
is_single_insert : true ,
627
+ loops : Vec :: new ( ) ,
504
628
} ;
505
629
s. visit_expr ( expr) ;
630
+ let allow_insert_closure = s. allow_insert_closure ;
506
631
let is_single_insert = s. is_single_insert ;
507
632
s. can_use_entry . then ( || InsertSearchResults {
508
- insertions,
633
+ edits,
634
+ allow_insert_closure,
509
635
is_single_insert,
510
636
} )
511
637
}
0 commit comments