@@ -79,12 +79,14 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
79
79
let sugg = if let Some ( else_expr) = else_expr {
80
80
// if .. { .. } else { .. }
81
81
let else_search = match find_insert_calls ( cx, & contains_expr, else_expr) {
82
- Some ( search) if ! ( then_search . edits . is_empty ( ) && search . edits . is_empty ( ) ) => search,
83
- _ => return ,
82
+ Some ( search) => search,
83
+ None => return ,
84
84
} ;
85
85
86
- if then_search. edits . is_empty ( ) || else_search. edits . is_empty ( ) {
87
- // if .. { insert } else { .. } or if .. { .. } else { then } of
86
+ if then_search. edits . is_empty ( ) && else_search. edits . is_empty ( ) {
87
+ return ;
88
+ } else if then_search. edits . is_empty ( ) || else_search. edits . is_empty ( ) {
89
+ // if .. { insert } else { .. } or if .. { .. } else { insert } of
88
90
let ( then_str, else_str, entry_kind) = if else_search. edits . is_empty ( ) {
89
91
if contains_expr. negated {
90
92
(
@@ -184,15 +186,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
184
186
format ! ( "{}.entry({}).or_insert({});" , map_str, key_str, value_str)
185
187
}
186
188
} else {
187
- // Todo: if let Some(v) = map.get_mut(k)
189
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
190
+ // This would need to be a different lint.
188
191
return ;
189
192
}
190
193
} else {
191
194
let block_str = then_search. snippet_closure ( cx, then_expr. span , & mut app) ;
192
195
if contains_expr. negated {
193
196
format ! ( "{}.entry({}).or_insert_with(|| {});" , map_str, key_str, block_str)
194
197
} else {
195
- // Todo: if let Some(v) = map.get_mut(k)
198
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
199
+ // This would need to be a different lint.
196
200
return ;
197
201
}
198
202
}
@@ -222,7 +226,7 @@ impl MapType {
222
226
Self :: BTree => "BTreeMap" ,
223
227
}
224
228
}
225
- fn entry_path ( self ) -> & ' staic str {
229
+ fn entry_path ( self ) -> & ' static str {
226
230
match self {
227
231
Self :: Hash => "std::collections::hash_map::Entry" ,
228
232
Self :: BTree => "std::collections::btree_map::Entry" ,
@@ -312,15 +316,16 @@ struct Insertion<'tcx> {
312
316
value : & ' tcx Expr < ' tcx > ,
313
317
}
314
318
315
- // This visitor needs to do a multiple things:
316
- // * Find all usages of the map. Only insertions into the map which share the same key are
317
- // permitted. All others will prevent the lint.
318
- // * Determine if the final statement executed is an insertion. This is needed to use `insert_with`.
319
- // * Determine if there's any sub-expression that can't be placed in a closure.
320
- // * Determine if there's only a single insert statement. This is needed to give better suggestions.
321
-
319
+ /// This visitor needs to do a multiple things:
320
+ /// * Find all usages of the map. An insertion can only be made before any other usages of the map.
321
+ /// * Determine if there's an insertion using the same key. There's no need for the entry api
322
+ /// otherwise.
323
+ /// * Determine if the final statement executed is an insertion. This is needed to use
324
+ /// `or_insert_with`.
325
+ /// * Determine if there's any sub-expression that can't be placed in a closure.
326
+ /// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
322
327
#[ allow( clippy:: struct_excessive_bools) ]
323
- struct InsertSearcher < ' cx , ' i , ' tcx > {
328
+ struct InsertSearcher < ' cx , ' tcx > {
324
329
cx : & ' cx LateContext < ' tcx > ,
325
330
/// The map expression used in the contains call.
326
331
map : & ' tcx Expr < ' tcx > ,
@@ -334,13 +339,16 @@ struct InsertSearcher<'cx, 'i, 'tcx> {
334
339
can_use_entry : bool ,
335
340
/// Whether this expression is the final expression in this code path. This may be a statement.
336
341
in_tail_pos : bool ,
337
- // A single insert expression has a slightly different suggestion.
342
+ // Is this expression a single insert. A slightly better suggestion can be made in this case .
338
343
is_single_insert : bool ,
344
+ /// If the visitor has seen the map being used.
339
345
is_map_used : bool ,
340
- edits : & ' i mut Vec < Edit < ' tcx > > ,
346
+ /// The locations where changes need to be made for the suggestion.
347
+ edits : Vec < Edit < ' tcx > > ,
348
+ /// A stack of loops the visitor is currently in.
341
349
loops : Vec < HirId > ,
342
350
}
343
- impl < ' tcx > InsertSearcher < ' _ , ' _ , ' tcx > {
351
+ impl < ' tcx > InsertSearcher < ' _ , ' tcx > {
344
352
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
345
353
/// only if they are on separate code paths. This will return whether the map was used in the
346
354
/// given expression.
@@ -363,7 +371,7 @@ impl<'tcx> InsertSearcher<'_, '_, 'tcx> {
363
371
self . in_tail_pos = in_tail_pos;
364
372
}
365
373
}
366
- impl < ' tcx > Visitor < ' tcx > for InsertSearcher < ' _ , ' _ , ' tcx > {
374
+ impl < ' tcx > Visitor < ' tcx > for InsertSearcher < ' _ , ' tcx > {
367
375
type Map = ErasedMap < ' tcx > ;
368
376
fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
369
377
NestedVisitorMap :: None
@@ -483,6 +491,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
483
491
} ,
484
492
ExprKind :: Loop ( block, ..) => {
485
493
self . loops . push ( expr. hir_id ) ;
494
+ self . is_single_insert = false ;
486
495
self . allow_insert_closure &= !self . in_tail_pos ;
487
496
// Don't allow insertions inside of a loop.
488
497
let edit_len = self . edits . len ( ) ;
@@ -519,7 +528,13 @@ impl InsertSearchResults<'tcx> {
519
528
self . is_single_insert . then ( || self . edits [ 0 ] . as_insertion ( ) . unwrap ( ) )
520
529
}
521
530
522
- fn snippet_occupied ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
531
+ fn snippet (
532
+ & self ,
533
+ cx : & LateContext < ' _ > ,
534
+ mut span : Span ,
535
+ app : & mut Applicability ,
536
+ write_wrapped : impl Fn ( & mut String , Insertion < ' _ > , SyntaxContext , & mut Applicability ) ,
537
+ ) -> String {
523
538
let ctxt = span. ctxt ( ) ;
524
539
let mut res = String :: new ( ) ;
525
540
for insertion in self . edits . iter ( ) . filter_map ( |e| e. as_insertion ( ) ) {
@@ -530,56 +545,47 @@ impl InsertSearchResults<'tcx> {
530
545
app,
531
546
) ) ;
532
547
if is_expr_used_or_unified ( cx. tcx , insertion. call ) {
533
- res. push_str ( "Some(e.insert(" ) ;
534
- res. push_str ( & snippet_with_context ( cx, insertion. value . span , ctxt, ".." , app) . 0 ) ;
535
- res. push_str ( "))" ) ;
548
+ write_wrapped ( & mut res, insertion, ctxt, app) ;
536
549
} else {
537
- res. push_str ( "e.insert(" ) ;
538
- res. push_str ( & snippet_with_context ( cx, insertion. value . span , ctxt, ".." , app) . 0 ) ;
539
- res. push ( ')' ) ;
550
+ let _ = write ! (
551
+ res,
552
+ "e.insert({})" ,
553
+ snippet_with_context( cx, insertion. value. span, ctxt, ".." , app) . 0
554
+ ) ;
540
555
}
541
556
span = span. trim_start ( insertion. call . span ) . unwrap_or ( DUMMY_SP ) ;
542
557
}
543
558
res. push_str ( & snippet_with_applicability ( cx, span, ".." , app) ) ;
544
559
res
545
560
}
546
561
547
- fn snippet_vacant ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
548
- let ctxt = span. ctxt ( ) ;
549
- let mut res = String :: new ( ) ;
550
- for insertion in self . edits . iter ( ) . filter_map ( |e| e. as_insertion ( ) ) {
551
- res. push_str ( & snippet_with_applicability (
552
- cx,
553
- span. until ( insertion. call . span ) ,
554
- ".." ,
555
- app,
556
- ) ) ;
557
- if is_expr_used_or_unified ( cx. tcx , insertion. call ) {
558
- if is_expr_final_block_expr ( cx. tcx , insertion. call ) {
559
- let _ = write ! (
560
- res,
561
- "e.insert({});\n {}None" ,
562
- snippet_with_context( cx, insertion. value. span, ctxt, ".." , app) . 0 ,
563
- snippet_indent( cx, insertion. call. span) . as_deref( ) . unwrap_or( "" ) ,
564
- ) ;
565
- } else {
566
- let _ = write ! (
567
- res,
568
- "{{ e.insert({}); None }}" ,
569
- snippet_with_context( cx, insertion. value. span, ctxt, ".." , app) . 0 ,
570
- ) ;
571
- }
562
+ fn snippet_occupied ( & self , cx : & LateContext < ' _ > , span : Span , app : & mut Applicability ) -> String {
563
+ self . snippet ( cx, span, app, |res, insertion, ctxt, app| {
564
+ let _ = write ! (
565
+ res,
566
+ "Some(e.insert({}))" ,
567
+ snippet_with_context( cx, insertion. value. span, ctxt, ".." , app) . 0
568
+ ) ;
569
+ } )
570
+ }
571
+
572
+ fn snippet_vacant ( & self , cx : & LateContext < ' _ > , span : Span , app : & mut Applicability ) -> String {
573
+ self . snippet ( cx, span, app, |res, insertion, ctxt, app| {
574
+ let _ = if is_expr_final_block_expr ( cx. tcx , insertion. call ) {
575
+ write ! (
576
+ res,
577
+ "e.insert({});\n {}None" ,
578
+ snippet_with_context( cx, insertion. value. span, ctxt, ".." , app) . 0 ,
579
+ snippet_indent( cx, insertion. call. span) . as_deref( ) . unwrap_or( "" ) ,
580
+ )
572
581
} else {
573
- let _ = write ! (
582
+ write ! (
574
583
res,
575
- "e.insert({})" ,
584
+ "{{ e.insert({}); None }} " ,
576
585
snippet_with_context( cx, insertion. value. span, ctxt, ".." , app) . 0 ,
577
- ) ;
578
- }
579
- span = span. trim_start ( insertion. call . span ) . unwrap_or ( DUMMY_SP ) ;
580
- }
581
- res. push_str ( & snippet_with_applicability ( cx, span, ".." , app) ) ;
582
- res
586
+ )
587
+ } ;
588
+ } )
583
589
}
584
590
585
591
fn snippet_closure ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
@@ -607,18 +613,18 @@ impl InsertSearchResults<'tcx> {
607
613
res
608
614
}
609
615
}
616
+
610
617
fn find_insert_calls (
611
618
cx : & LateContext < ' tcx > ,
612
619
contains_expr : & ContainsExpr < ' tcx > ,
613
620
expr : & ' tcx Expr < ' _ > ,
614
621
) -> Option < InsertSearchResults < ' tcx > > {
615
- let mut edits = Vec :: new ( ) ;
616
622
let mut s = InsertSearcher {
617
623
cx,
618
624
map : contains_expr. map ,
619
625
key : contains_expr. key ,
620
626
ctxt : expr. span . ctxt ( ) ,
621
- edits : & mut edits ,
627
+ edits : Vec :: new ( ) ,
622
628
is_map_used : false ,
623
629
allow_insert_closure : true ,
624
630
can_use_entry : true ,
@@ -629,6 +635,7 @@ fn find_insert_calls(
629
635
s. visit_expr ( expr) ;
630
636
let allow_insert_closure = s. allow_insert_closure ;
631
637
let is_single_insert = s. is_single_insert ;
638
+ let edits = s. edits ;
632
639
s. can_use_entry . then ( || InsertSearchResults {
633
640
edits,
634
641
allow_insert_closure,
0 commit comments