@@ -79,12 +79,14 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
7979 let sugg = if let Some ( else_expr) = else_expr {
8080 // if .. { .. } else { .. }
8181 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 ,
8484 } ;
8585
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
8890 let ( then_str, else_str, entry_kind) = if else_search. edits . is_empty ( ) {
8991 if contains_expr. negated {
9092 (
@@ -184,15 +186,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
184186 format ! ( "{}.entry({}).or_insert({});" , map_str, key_str, value_str)
185187 }
186188 } 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.
188191 return ;
189192 }
190193 } else {
191194 let block_str = then_search. snippet_closure ( cx, then_expr. span , & mut app) ;
192195 if contains_expr. negated {
193196 format ! ( "{}.entry({}).or_insert_with(|| {});" , map_str, key_str, block_str)
194197 } 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.
196200 return ;
197201 }
198202 }
@@ -222,7 +226,7 @@ impl MapType {
222226 Self :: BTree => "BTreeMap" ,
223227 }
224228 }
225- fn entry_path ( self ) -> & ' staic str {
229+ fn entry_path ( self ) -> & ' static str {
226230 match self {
227231 Self :: Hash => "std::collections::hash_map::Entry" ,
228232 Self :: BTree => "std::collections::btree_map::Entry" ,
@@ -312,15 +316,16 @@ struct Insertion<'tcx> {
312316 value : & ' tcx Expr < ' tcx > ,
313317}
314318
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.
322327#[ allow( clippy:: struct_excessive_bools) ]
323- struct InsertSearcher < ' cx , ' i , ' tcx > {
328+ struct InsertSearcher < ' cx , ' tcx > {
324329 cx : & ' cx LateContext < ' tcx > ,
325330 /// The map expression used in the contains call.
326331 map : & ' tcx Expr < ' tcx > ,
@@ -334,13 +339,16 @@ struct InsertSearcher<'cx, 'i, 'tcx> {
334339 can_use_entry : bool ,
335340 /// Whether this expression is the final expression in this code path. This may be a statement.
336341 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 .
338343 is_single_insert : bool ,
344+ /// If the visitor has seen the map being used.
339345 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.
341349 loops : Vec < HirId > ,
342350}
343- impl < ' tcx > InsertSearcher < ' _ , ' _ , ' tcx > {
351+ impl < ' tcx > InsertSearcher < ' _ , ' tcx > {
344352 /// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
345353 /// only if they are on separate code paths. This will return whether the map was used in the
346354 /// given expression.
@@ -363,7 +371,7 @@ impl<'tcx> InsertSearcher<'_, '_, 'tcx> {
363371 self . in_tail_pos = in_tail_pos;
364372 }
365373}
366- impl < ' tcx > Visitor < ' tcx > for InsertSearcher < ' _ , ' _ , ' tcx > {
374+ impl < ' tcx > Visitor < ' tcx > for InsertSearcher < ' _ , ' tcx > {
367375 type Map = ErasedMap < ' tcx > ;
368376 fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
369377 NestedVisitorMap :: None
@@ -483,6 +491,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
483491 } ,
484492 ExprKind :: Loop ( block, ..) => {
485493 self . loops . push ( expr. hir_id ) ;
494+ self . is_single_insert = false ;
486495 self . allow_insert_closure &= !self . in_tail_pos ;
487496 // Don't allow insertions inside of a loop.
488497 let edit_len = self . edits . len ( ) ;
@@ -519,7 +528,13 @@ impl InsertSearchResults<'tcx> {
519528 self . is_single_insert . then ( || self . edits [ 0 ] . as_insertion ( ) . unwrap ( ) )
520529 }
521530
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 {
523538 let ctxt = span. ctxt ( ) ;
524539 let mut res = String :: new ( ) ;
525540 for insertion in self . edits . iter ( ) . filter_map ( |e| e. as_insertion ( ) ) {
@@ -530,56 +545,47 @@ impl InsertSearchResults<'tcx> {
530545 app,
531546 ) ) ;
532547 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) ;
536549 } 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+ ) ;
540555 }
541556 span = span. trim_start ( insertion. call . span ) . unwrap_or ( DUMMY_SP ) ;
542557 }
543558 res. push_str ( & snippet_with_applicability ( cx, span, ".." , app) ) ;
544559 res
545560 }
546561
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+ )
572581 } else {
573- let _ = write ! (
582+ write ! (
574583 res,
575- "e.insert({})" ,
584+ "{{ e.insert({}); None }} " ,
576585 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+ } )
583589 }
584590
585591 fn snippet_closure ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
@@ -607,18 +613,18 @@ impl InsertSearchResults<'tcx> {
607613 res
608614 }
609615}
616+
610617fn find_insert_calls (
611618 cx : & LateContext < ' tcx > ,
612619 contains_expr : & ContainsExpr < ' tcx > ,
613620 expr : & ' tcx Expr < ' _ > ,
614621) -> Option < InsertSearchResults < ' tcx > > {
615- let mut edits = Vec :: new ( ) ;
616622 let mut s = InsertSearcher {
617623 cx,
618624 map : contains_expr. map ,
619625 key : contains_expr. key ,
620626 ctxt : expr. span . ctxt ( ) ,
621- edits : & mut edits ,
627+ edits : Vec :: new ( ) ,
622628 is_map_used : false ,
623629 allow_insert_closure : true ,
624630 can_use_entry : true ,
@@ -629,6 +635,7 @@ fn find_insert_calls(
629635 s. visit_expr ( expr) ;
630636 let allow_insert_closure = s. allow_insert_closure ;
631637 let is_single_insert = s. is_single_insert ;
638+ let edits = s. edits ;
632639 s. can_use_entry . then ( || InsertSearchResults {
633640 edits,
634641 allow_insert_closure,
0 commit comments