@@ -14,6 +14,8 @@ use rustc_session::declare_lint_pass;
1414use rustc_span:: source_map:: Spanned ;
1515use rustc_span:: sym;
1616
17+ use std:: ops:: ControlFlow ;
18+
1719declare_clippy_lint ! {
1820 /// ### What it does
1921 /// Checks for string appends of the form `x = x + y` (without
@@ -438,27 +440,94 @@ declare_clippy_lint! {
438440
439441declare_lint_pass ! ( StringToString => [ STRING_TO_STRING ] ) ;
440442
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+
441488impl < ' tcx > LateLintPass < ' tcx > for StringToString {
442489 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' _ > ) {
443490 if expr. span . from_expansion ( ) {
444491 return ;
445492 }
446493
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+ _ => { } ,
462531 }
463532 }
464533}
0 commit comments