@@ -14,6 +14,8 @@ use rustc_session::declare_lint_pass;
14
14
use rustc_span:: source_map:: Spanned ;
15
15
use rustc_span:: sym;
16
16
17
+ use std:: ops:: ControlFlow ;
18
+
17
19
declare_clippy_lint ! {
18
20
/// ### What it does
19
21
/// Checks for string appends of the form `x = x + y` (without
@@ -438,27 +440,94 @@ declare_clippy_lint! {
438
440
439
441
declare_lint_pass ! ( StringToString => [ STRING_TO_STRING ] ) ;
440
442
443
+ fn is_parent_map_like ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> Option < rustc_span:: Span > {
444
+ if let Some ( parent_expr) = get_parent_expr ( cx, expr)
445
+ && let ExprKind :: MethodCall ( name, _, _, parent_span) = parent_expr. kind
446
+ && name. ident . name == sym:: map
447
+ && let Some ( caller_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( parent_expr. hir_id )
448
+ && ( clippy_utils:: is_diag_item_method ( cx, caller_def_id, sym:: Result )
449
+ || clippy_utils:: is_diag_item_method ( cx, caller_def_id, sym:: Option )
450
+ || clippy_utils:: is_diag_trait_item ( cx, caller_def_id, sym:: Iterator ) )
451
+ {
452
+ Some ( parent_span)
453
+ } else {
454
+ None
455
+ }
456
+ }
457
+
458
+ fn is_called_from_map_like ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> Option < rustc_span:: Span > {
459
+ // Look for a closure as parent of `expr`, discarding simple blocks
460
+ let parent_closure = cx
461
+ . tcx
462
+ . hir_parent_iter ( expr. hir_id )
463
+ . try_fold ( expr. hir_id , |child_hir_id, ( _, node) | match node {
464
+ // Check that the child expression is the only expression in the block
465
+ Node :: Block ( block) if block. stmts . is_empty ( ) && block. expr . map ( |e| e. hir_id ) == Some ( child_hir_id) => {
466
+ ControlFlow :: Continue ( block. hir_id )
467
+ } ,
468
+ Node :: Expr ( expr) if matches ! ( expr. kind, ExprKind :: Block ( ..) ) => ControlFlow :: Continue ( expr. hir_id ) ,
469
+ Node :: Expr ( expr) if matches ! ( expr. kind, ExprKind :: Closure ( _) ) => ControlFlow :: Break ( Some ( expr) ) ,
470
+ _ => ControlFlow :: Break ( None ) ,
471
+ } )
472
+ . break_value ( ) ?;
473
+ is_parent_map_like ( cx, parent_closure?)
474
+ }
475
+
476
+ fn suggest_cloned_string_to_string ( cx : & LateContext < ' _ > , span : rustc_span:: Span ) {
477
+ span_lint_and_sugg (
478
+ cx,
479
+ STRING_TO_STRING ,
480
+ span,
481
+ "`to_string()` called on a `String`" ,
482
+ "try" ,
483
+ "cloned()" . to_string ( ) ,
484
+ Applicability :: MachineApplicable ,
485
+ ) ;
486
+ }
487
+
441
488
impl < ' tcx > LateLintPass < ' tcx > for StringToString {
442
489
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' _ > ) {
443
490
if expr. span . from_expansion ( ) {
444
491
return ;
445
492
}
446
493
447
- if let ExprKind :: MethodCall ( path, self_arg, [ ] , _) = & expr. kind
448
- && path. ident . name == sym:: to_string
449
- && let ty = cx. typeck_results ( ) . expr_ty ( self_arg)
450
- && is_type_lang_item ( cx, ty, LangItem :: String )
451
- {
452
- #[ expect( clippy:: collapsible_span_lint_calls, reason = "rust-clippy#7797" ) ]
453
- span_lint_and_then (
454
- cx,
455
- STRING_TO_STRING ,
456
- expr. span ,
457
- "`to_string()` called on a `String`" ,
458
- |diag| {
459
- diag. help ( "consider using `.clone()`" ) ;
460
- } ,
461
- ) ;
494
+ match & expr. kind {
495
+ ExprKind :: MethodCall ( path, self_arg, [ ] , _) => {
496
+ if path. ident . name == sym:: to_string
497
+ && let ty = cx. typeck_results ( ) . expr_ty ( self_arg)
498
+ && is_type_lang_item ( cx, ty. peel_refs ( ) , LangItem :: String )
499
+ {
500
+ if let Some ( parent_span) = is_called_from_map_like ( cx, expr) {
501
+ suggest_cloned_string_to_string ( cx, parent_span) ;
502
+ } else {
503
+ #[ expect( clippy:: collapsible_span_lint_calls, reason = "rust-clippy#7797" ) ]
504
+ span_lint_and_then (
505
+ cx,
506
+ STRING_TO_STRING ,
507
+ expr. span ,
508
+ "`to_string()` called on a `String`" ,
509
+ |diag| {
510
+ diag. help ( "consider using `.clone()`" ) ;
511
+ } ,
512
+ ) ;
513
+ }
514
+ }
515
+ } ,
516
+ ExprKind :: Path ( QPath :: TypeRelative ( ty, segment) ) => {
517
+ if segment. ident . name == sym:: to_string
518
+ && let rustc_hir:: TyKind :: Path ( QPath :: Resolved ( _, path) ) = ty. peel_refs ( ) . kind
519
+ && let rustc_hir:: def:: Res :: Def ( _, def_id) = path. res
520
+ && cx
521
+ . tcx
522
+ . lang_items ( )
523
+ . get ( LangItem :: String )
524
+ . is_some_and ( |lang_id| lang_id == def_id)
525
+ && let Some ( parent_span) = is_parent_map_like ( cx, expr)
526
+ {
527
+ suggest_cloned_string_to_string ( cx, parent_span) ;
528
+ }
529
+ } ,
530
+ _ => { } ,
462
531
}
463
532
}
464
533
}
0 commit comments