11use clippy_utils:: {
2+ can_move_expr_to_closure_no_visit,
23 diagnostics:: span_lint_and_sugg,
34 is_expr_final_block_expr, is_expr_used_or_unified, match_def_path, paths, peel_hir_expr_while,
45 source:: { reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context} ,
@@ -7,7 +8,7 @@ use clippy_utils::{
78use rustc_errors:: Applicability ;
89use rustc_hir:: {
910 intravisit:: { walk_expr, ErasedMap , NestedVisitorMap , Visitor } ,
10- Expr , ExprKind , Guard , Local , Stmt , StmtKind , UnOp ,
11+ Block , Expr , ExprKind , Guard , HirId , Local , Stmt , StmtKind , UnOp ,
1112} ;
1213use rustc_lint:: { LateContext , LateLintPass } ;
1314use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
@@ -78,13 +79,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
7879 let sugg = if let Some ( else_expr) = else_expr {
7980 // if .. { .. } else { .. }
8081 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,
8283 _ => return ,
8384 } ;
8485
85- if then_search. insertions . is_empty ( ) || else_search. insertions . is_empty ( ) {
86+ if then_search. edits . is_empty ( ) || else_search. edits . is_empty ( ) {
8687 // 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 ( ) {
8889 if contains_expr. negated {
8990 (
9091 then_search. snippet_vacant ( cx, then_expr. span , & mut app) ,
@@ -152,37 +153,48 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
152153 indent = indent_str,
153154 )
154155 }
155- } else if then_search. insertions . is_empty ( ) || !contains_expr. negated {
156+ } else if then_search. edits . is_empty ( ) {
157+ // no insertions
156158 return ;
157159 } else {
158160 // 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)" ,
166168 )
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)
171183 } 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+ }
186198 }
187199 } ;
188200
@@ -281,6 +293,19 @@ fn try_parse_insert(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Inse
281293 }
282294}
283295
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+ }
284309#[ derive( Clone , Copy ) ]
285310struct Insertion < ' tcx > {
286311 call : & ' tcx Expr < ' tcx > ,
@@ -303,24 +328,40 @@ struct InsertSearcher<'cx, 'i, 'tcx> {
303328 key : & ' tcx Expr < ' tcx > ,
304329 /// The context of the top level block. All insert calls must be in the same context.
305330 ctxt : SyntaxContext ,
331+ /// Whether this expression can be safely moved into a closure.
332+ allow_insert_closure : bool ,
306333 /// Whether this expression can use the entry api.
307334 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 ,
308337 // A single insert expression has a slightly different suggestion.
309338 is_single_insert : bool ,
310339 is_map_used : bool ,
311- insertions : & ' i mut Vec < Insertion < ' tcx > > ,
340+ edits : & ' i mut Vec < Edit < ' tcx > > ,
341+ loops : Vec < HirId > ,
312342}
313343impl < ' tcx > InsertSearcher < ' _ , ' _ , ' tcx > {
314344 /// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
315345 /// only if they are on separate code paths. This will return whether the map was used in the
316346 /// given expression.
317347 fn visit_cond_arm ( & mut self , e : & ' tcx Expr < ' _ > ) -> bool {
318348 let is_map_used = self . is_map_used ;
349+ let in_tail_pos = self . in_tail_pos ;
319350 self . visit_expr ( e) ;
320351 let res = self . is_map_used ;
321352 self . is_map_used = is_map_used;
353+ self . in_tail_pos = in_tail_pos;
322354 res
323355 }
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+ }
324365}
325366impl < ' tcx > Visitor < ' tcx > for InsertSearcher < ' _ , ' _ , ' tcx > {
326367 type Map = ErasedMap < ' tcx > ;
@@ -330,17 +371,63 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
330371
331372 fn visit_stmt ( & mut self , stmt : & ' tcx Stmt < ' _ > ) {
332373 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) ,
334389 StmtKind :: Local ( Local { init : Some ( e) , .. } ) => {
390+ self . allow_insert_closure &= !self . in_tail_pos ;
391+ self . in_tail_pos = false ;
335392 self . is_single_insert = false ;
336393 self . visit_expr ( e) ;
337394 } ,
338395 _ => {
396+ self . allow_insert_closure &= !self . in_tail_pos ;
339397 self . is_single_insert = false ;
340398 } ,
341399 }
342400 }
343401
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+
344431 fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
345432 if !self . can_use_entry {
346433 return ;
@@ -357,15 +444,16 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
357444 return ;
358445 }
359446
360- self . insertions . push ( Insertion {
447+ self . edits . push ( Edit :: Insertion ( Insertion {
361448 call : expr,
362449 value : insert_expr. value ,
363- } ) ;
450+ } ) ) ;
364451 self . is_map_used = true ;
452+ self . allow_insert_closure &= self . in_tail_pos ;
365453
366454 // The value doesn't affect whether there is only a single insert expression.
367455 let is_single_insert = self . is_single_insert ;
368- self . visit_expr ( insert_expr. value ) ;
456+ self . visit_non_tail_expr ( insert_expr. value ) ;
369457 self . is_single_insert = is_single_insert;
370458 } ,
371459 _ if SpanlessEq :: new ( self . cx ) . eq_expr ( self . map , expr) => {
@@ -374,39 +462,46 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
374462 _ => match expr. kind {
375463 ExprKind :: If ( cond_expr, then_expr, Some ( else_expr) ) => {
376464 self . is_single_insert = false ;
377- self . visit_expr ( cond_expr) ;
465+ self . visit_non_tail_expr ( cond_expr) ;
378466 // Each branch may contain it's own insert expression.
379467 let mut is_map_used = self . visit_cond_arm ( then_expr) ;
380468 is_map_used |= self . visit_cond_arm ( else_expr) ;
381469 self . is_map_used = is_map_used;
382470 } ,
383471 ExprKind :: Match ( scrutinee_expr, arms, _) => {
384472 self . is_single_insert = false ;
385- self . visit_expr ( scrutinee_expr) ;
473+ self . visit_non_tail_expr ( scrutinee_expr) ;
386474 // Each branch may contain it's own insert expression.
387475 let mut is_map_used = self . is_map_used ;
388476 for arm in arms {
389477 if let Some ( Guard :: If ( guard) | Guard :: IfLet ( _, guard) ) = arm. guard {
390- self . visit_expr ( guard)
478+ self . visit_non_tail_expr ( guard)
391479 }
392480 is_map_used |= self . visit_cond_arm ( arm. body ) ;
393481 }
394482 self . is_map_used = is_map_used;
395483 } ,
396484 ExprKind :: Loop ( block, ..) => {
485+ self . loops . push ( expr. hir_id ) ;
486+ self . allow_insert_closure &= !self . in_tail_pos ;
397487 // Don't allow insertions inside of a loop.
398- let insertions_len = self . insertions . len ( ) ;
488+ let edit_len = self . edits . len ( ) ;
399489 self . visit_block ( block) ;
400- if self . insertions . len ( ) != insertions_len {
490+ if self . edits . len ( ) != edit_len {
401491 self . can_use_entry = false ;
402492 }
493+ self . loops . pop ( ) ;
403494 } ,
404495 ExprKind :: Block ( block, _) => self . visit_block ( block) ,
405496 ExprKind :: InlineAsm ( _) | ExprKind :: LlvmInlineAsm ( _) => {
406497 self . can_use_entry = false ;
407498 } ,
408499 _ => {
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.
409503 self . is_single_insert = false ;
504+ self . in_tail_pos = false ;
410505 walk_expr ( self , expr) ;
411506 } ,
412507 } ,
@@ -415,18 +510,19 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
415510}
416511
417512struct InsertSearchResults < ' tcx > {
418- insertions : Vec < Insertion < ' tcx > > ,
513+ edits : Vec < Edit < ' tcx > > ,
514+ allow_insert_closure : bool ,
419515 is_single_insert : bool ,
420516}
421517impl InsertSearchResults < ' tcx > {
422518 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 ( ) )
424520 }
425521
426522 fn snippet_occupied ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
427523 let ctxt = span. ctxt ( ) ;
428524 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 ( ) ) {
430526 res. push_str ( & snippet_with_applicability (
431527 cx,
432528 span. until ( insertion. call . span ) ,
@@ -451,7 +547,7 @@ impl InsertSearchResults<'tcx> {
451547 fn snippet_vacant ( & self , cx : & LateContext < ' _ > , mut span : Span , app : & mut Applicability ) -> String {
452548 let ctxt = span. ctxt ( ) ;
453549 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 ( ) ) {
455551 res. push_str ( & snippet_with_applicability (
456552 cx,
457553 span. until ( insertion. call . span ) ,
@@ -485,27 +581,57 @@ impl InsertSearchResults<'tcx> {
485581 res. push_str ( & snippet_with_applicability ( cx, span, ".." , app) ) ;
486582 res
487583 }
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+ }
488609}
489610fn find_insert_calls (
490611 cx : & LateContext < ' tcx > ,
491612 contains_expr : & ContainsExpr < ' tcx > ,
492613 expr : & ' tcx Expr < ' _ > ,
493614) -> Option < InsertSearchResults < ' tcx > > {
494- let mut insertions = Vec :: new ( ) ;
615+ let mut edits = Vec :: new ( ) ;
495616 let mut s = InsertSearcher {
496617 cx,
497618 map : contains_expr. map ,
498619 key : contains_expr. key ,
499620 ctxt : expr. span . ctxt ( ) ,
500- insertions : & mut insertions ,
621+ edits : & mut edits ,
501622 is_map_used : false ,
623+ allow_insert_closure : true ,
502624 can_use_entry : true ,
625+ in_tail_pos : true ,
503626 is_single_insert : true ,
627+ loops : Vec :: new ( ) ,
504628 } ;
505629 s. visit_expr ( expr) ;
630+ let allow_insert_closure = s. allow_insert_closure ;
506631 let is_single_insert = s. is_single_insert ;
507632 s. can_use_entry . then ( || InsertSearchResults {
508- insertions,
633+ edits,
634+ allow_insert_closure,
509635 is_single_insert,
510636 } )
511637}
0 commit comments