1- use clippy_utils:: diagnostics:: span_lint_and_sugg;
1+ use clippy_utils:: diagnostics:: { span_lint , span_lint_and_sugg} ;
22use clippy_utils:: source:: { reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context} ;
3+ use clippy_utils:: ty:: is_copy;
34use clippy_utils:: visitors:: for_each_expr;
45use clippy_utils:: {
56 SpanlessEq , can_move_expr_to_closure_no_visit, higher, is_expr_final_block_expr, is_expr_used_or_unified,
@@ -84,14 +85,21 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
8485 return ;
8586 } ;
8687
88+ let lint_msg = format ! ( "usage of `contains_key` followed by `insert` on a `{}`" , map_ty. name( ) ) ;
8789 let mut app = Applicability :: MachineApplicable ;
8890 let map_str = snippet_with_context ( cx, contains_expr. map . span , contains_expr. call_ctxt , ".." , & mut app) . 0 ;
8991 let key_str = snippet_with_context ( cx, contains_expr. key . span , contains_expr. call_ctxt , ".." , & mut app) . 0 ;
92+
9093 let sugg = if let Some ( else_expr) = else_expr {
9194 let Some ( else_search) = find_insert_calls ( cx, & contains_expr, else_expr) else {
9295 return ;
9396 } ;
9497
98+ if then_search. is_key_used_and_no_copy || else_search. is_key_used_and_no_copy {
99+ span_lint ( cx, MAP_ENTRY , expr. span , lint_msg) ;
100+ return ;
101+ }
102+
95103 if then_search. edits . is_empty ( ) && else_search. edits . is_empty ( ) {
96104 // No insertions
97105 return ;
@@ -184,15 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
184192 }
185193 } ;
186194
187- span_lint_and_sugg (
188- cx,
189- MAP_ENTRY ,
190- expr. span ,
191- format ! ( "usage of `contains_key` followed by `insert` on a `{}`" , map_ty. name( ) ) ,
192- "try" ,
193- sugg,
194- app,
195- ) ;
195+ span_lint_and_sugg ( cx, MAP_ENTRY , expr. span , lint_msg, "try" , sugg, app) ;
196196 }
197197}
198198
@@ -354,6 +354,8 @@ struct InsertSearcher<'cx, 'tcx> {
354354 key : & ' tcx Expr < ' tcx > ,
355355 /// The context of the top level block. All insert calls must be in the same context.
356356 ctxt : SyntaxContext ,
357+ /// The spanless equality utility used to compare expressions.
358+ spanless_eq : SpanlessEq < ' cx , ' tcx > ,
357359 /// Whether this expression can be safely moved into a closure.
358360 allow_insert_closure : bool ,
359361 /// Whether this expression can use the entry api.
@@ -364,6 +366,8 @@ struct InsertSearcher<'cx, 'tcx> {
364366 is_single_insert : bool ,
365367 /// If the visitor has seen the map being used.
366368 is_map_used : bool ,
369+ /// If the visitor has seen the key being used.
370+ is_key_used : bool ,
367371 /// The locations where changes need to be made for the suggestion.
368372 edits : Vec < Edit < ' tcx > > ,
369373 /// A stack of loops the visitor is currently in.
@@ -479,11 +483,11 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
479483 }
480484
481485 match try_parse_insert ( self . cx , expr) {
482- Some ( insert_expr) if SpanlessEq :: new ( self . cx ) . eq_expr ( self . map , insert_expr. map ) => {
486+ Some ( insert_expr) if self . spanless_eq . eq_expr ( self . map , insert_expr. map ) => {
483487 self . visit_insert_expr_arguments ( & insert_expr) ;
484488 // Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
485489 if self . is_map_used
486- || !SpanlessEq :: new ( self . cx ) . eq_expr ( self . key , insert_expr. key )
490+ || !self . spanless_eq . eq_expr ( self . key , insert_expr. key )
487491 || expr. span . ctxt ( ) != self . ctxt
488492 {
489493 self . can_use_entry = false ;
@@ -502,9 +506,12 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
502506 self . visit_non_tail_expr ( insert_expr. value ) ;
503507 self . is_single_insert = is_single_insert;
504508 } ,
505- _ if is_any_expr_in_map_used ( self . cx , self . map , expr) => {
509+ _ if is_any_expr_in_map_used ( self . cx , & mut self . spanless_eq , self . map , expr) => {
506510 self . is_map_used = true ;
507511 } ,
512+ _ if self . spanless_eq . eq_expr ( self . key , expr) => {
513+ self . is_key_used = true ;
514+ } ,
508515 _ => match expr. kind {
509516 ExprKind :: If ( cond_expr, then_expr, Some ( else_expr) ) => {
510517 self . is_single_insert = false ;
@@ -568,9 +575,14 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
568575/// Check if the given expression is used for each sub-expression in the given map.
569576/// For example, in map `a.b.c.my_map`, The expression `a.b.c.my_map`, `a.b.c`, `a.b`, and `a` are
570577/// all checked.
571- fn is_any_expr_in_map_used < ' tcx > ( cx : & LateContext < ' tcx > , map : & ' tcx Expr < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> bool {
578+ fn is_any_expr_in_map_used < ' tcx > (
579+ cx : & LateContext < ' tcx > ,
580+ spanless_eq : & mut SpanlessEq < ' _ , ' tcx > ,
581+ map : & ' tcx Expr < ' tcx > ,
582+ expr : & ' tcx Expr < ' tcx > ,
583+ ) -> bool {
572584 for_each_expr ( cx, map, |e| {
573- if SpanlessEq :: new ( cx ) . eq_expr ( e, expr) {
585+ if spanless_eq . eq_expr ( e, expr) {
574586 return ControlFlow :: Break ( ( ) ) ;
575587 }
576588 ControlFlow :: Continue ( ( ) )
@@ -582,6 +594,7 @@ struct InsertSearchResults<'tcx> {
582594 edits : Vec < Edit < ' tcx > > ,
583595 allow_insert_closure : bool ,
584596 is_single_insert : bool ,
597+ is_key_used_and_no_copy : bool ,
585598}
586599impl < ' tcx > InsertSearchResults < ' tcx > {
587600 fn as_single_insertion ( & self ) -> Option < Insertion < ' tcx > > {
@@ -694,22 +707,26 @@ fn find_insert_calls<'tcx>(
694707 map : contains_expr. map ,
695708 key : contains_expr. key ,
696709 ctxt : expr. span . ctxt ( ) ,
710+ spanless_eq : SpanlessEq :: new ( cx) ,
697711 allow_insert_closure : true ,
698712 can_use_entry : true ,
699713 in_tail_pos : true ,
700714 is_single_insert : true ,
701715 is_map_used : false ,
716+ is_key_used : false ,
702717 edits : Vec :: new ( ) ,
703718 loops : Vec :: new ( ) ,
704719 locals : HirIdSet :: default ( ) ,
705720 } ;
706721 s. visit_expr ( expr) ;
707722 let allow_insert_closure = s. allow_insert_closure ;
708723 let is_single_insert = s. is_single_insert ;
724+ let is_key_used_and_no_copy = s. is_key_used && !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( contains_expr. key ) ) ;
709725 let edits = s. edits ;
710726 s. can_use_entry . then_some ( InsertSearchResults {
711727 edits,
712728 allow_insert_closure,
713729 is_single_insert,
730+ is_key_used_and_no_copy,
714731 } )
715732}
0 commit comments