Skip to content

Commit 21601fd

Browse files
Improve string_to_string lint in case it is in a map call
1 parent 0730678 commit 21601fd

File tree

1 file changed

+74
-16
lines changed

1 file changed

+74
-16
lines changed

clippy_lints/src/strings.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
1+
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::source::{snippet, snippet_with_applicability};
33
use clippy_utils::ty::is_type_lang_item;
44
use clippy_utils::{
@@ -438,27 +438,85 @@ declare_clippy_lint! {
438438

439439
declare_lint_pass!(StringToString => [STRING_TO_STRING]);
440440

441+
fn is_parent_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<rustc_span::Span> {
442+
if let Some(parent_expr) = get_parent_expr(cx, expr)
443+
&& let ExprKind::MethodCall(name, ..) = parent_expr.kind
444+
&& name.ident.name == sym::map
445+
&& let Some(caller_def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
446+
&& (clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Result)
447+
|| clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Option)
448+
|| clippy_utils::is_diag_trait_item(cx, caller_def_id, sym::Iterator))
449+
{
450+
Some(parent_expr.span)
451+
} else {
452+
None
453+
}
454+
}
455+
456+
fn is_called_from_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<rustc_span::Span> {
457+
let parent = get_parent_expr(cx, expr)?;
458+
459+
if matches!(parent.kind, ExprKind::Closure(_)) {
460+
is_parent_map_like(cx, parent)
461+
} else {
462+
None
463+
}
464+
}
465+
466+
fn suggest_cloned_string_to_string(cx: &LateContext<'_>, span: rustc_span::Span) {
467+
span_lint_and_help(
468+
cx,
469+
STRING_TO_STRING,
470+
span,
471+
"`to_string()` called on a `String`",
472+
None,
473+
"consider using `.cloned()`",
474+
);
475+
}
476+
441477
impl<'tcx> LateLintPass<'tcx> for StringToString {
442478
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
443479
if expr.span.from_expansion() {
444480
return;
445481
}
446482

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-
);
483+
match &expr.kind {
484+
ExprKind::MethodCall(path, self_arg, [], _) => {
485+
if path.ident.name == sym::to_string
486+
&& let ty = cx.typeck_results().expr_ty(self_arg)
487+
&& is_type_lang_item(cx, ty.peel_refs(), LangItem::String)
488+
{
489+
if let Some(parent_span) = is_called_from_map_like(cx, expr) {
490+
suggest_cloned_string_to_string(cx, parent_span);
491+
} else {
492+
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
493+
span_lint_and_then(
494+
cx,
495+
STRING_TO_STRING,
496+
expr.span,
497+
"`to_string()` called on a `String`",
498+
|diag| {
499+
diag.help("consider using `.clone()`");
500+
},
501+
);
502+
}
503+
}
504+
},
505+
ExprKind::Path(QPath::TypeRelative(ty, segment)) => {
506+
if segment.ident.name == sym::to_string
507+
&& let rustc_hir::TyKind::Path(QPath::Resolved(_, path)) = ty.peel_refs().kind
508+
&& let rustc_hir::def::Res::Def(_, def_id) = path.res
509+
&& cx
510+
.tcx
511+
.lang_items()
512+
.get(LangItem::String)
513+
.is_some_and(|lang_id| lang_id == def_id)
514+
&& let Some(parent_span) = is_parent_map_like(cx, expr)
515+
{
516+
suggest_cloned_string_to_string(cx, parent_span);
517+
}
518+
},
519+
_ => {},
462520
}
463521
}
464522
}

0 commit comments

Comments
 (0)