From ace2e274db8165fc5bfeba1faf83e15095723229 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 23 Sep 2025 23:02:59 +0200 Subject: [PATCH] Cleanup: group code for `single_char_add_str` lint in one file A lot of code was duplicated instead of being shared. --- clippy_lints/src/methods/mod.rs | 2 - .../src/methods/single_char_add_str.rs | 80 +++++++++++++++++-- .../src/methods/single_char_insert_string.rs | 67 ---------------- .../src/methods/single_char_push_string.rs | 65 --------------- 4 files changed, 73 insertions(+), 141 deletions(-) delete mode 100644 clippy_lints/src/methods/single_char_insert_string.rs delete mode 100644 clippy_lints/src/methods/single_char_push_string.rs diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 8679689c8ad4..3755ec37fb96 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -103,8 +103,6 @@ mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; mod single_char_add_str; -mod single_char_insert_string; -mod single_char_push_string; mod skip_while_next; mod sliced_string_as_bytes; mod stable_sort_primitive; diff --git a/clippy_lints/src/methods/single_char_add_str.rs b/clippy_lints/src/methods/single_char_add_str.rs index ef3d7acdc01e..1248d1658d77 100644 --- a/clippy_lints/src/methods/single_char_add_str.rs +++ b/clippy_lints/src/methods/single_char_add_str.rs @@ -1,14 +1,80 @@ -use crate::methods::{single_char_insert_string, single_char_push_string}; -use rustc_hir as hir; +use super::SINGLE_CHAR_ADD_STR; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; +use rustc_ast::BorrowKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::sym; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - match cx.tcx.get_diagnostic_name(fn_def_id) { - Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args), - Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args), - _ => {}, + let mut applicability = Applicability::MachineApplicable; + let (short_name, arg, extra) = match cx.tcx.get_diagnostic_name(fn_def_id) { + Some(sym::string_insert_str) => ( + "insert", + &args[1], + Some(|applicability| { + format!( + "{}, ", + snippet_with_applicability(cx, args[0].span, "..", applicability) + ) + }), + ), + Some(sym::string_push_str) => ("push", &args[0], None), + _ => return, + }; + + if let Some(extension_string) = str_literal_to_char_literal(cx, arg, &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + format!("calling `{short_name}_str()` using a single-character string literal"), + format!("consider using `{short_name}` with a character literal"), + format!( + "{base_string_snippet}.{short_name}({}{extension_string})", + extra.map_or(String::new(), |f| f(&mut applicability)) + ), + applicability, + ); + } else if let ExprKind::AddrOf(BorrowKind::Ref, _, inner) = arg.kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = inner.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + let extension_string = match ( + snippet_with_applicability(cx, method_arg.span.source_callsite(), "_", &mut applicability), + is_ref_char(cx, method_arg), + ) { + (snippet, false) => snippet, + (snippet, true) => format!("*{snippet}").into(), + }; + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + format!("calling `{short_name}_str()` using a single-character converted to string"), + format!("consider using `{short_name}` without `to_string()`"), + format!( + "{base_string_snippet}.{short_name}({}{extension_string})", + extra.map_or(String::new(), |f| f(&mut applicability)) + ), + applicability, + ); } } } + +fn is_ref_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(cx.typeck_results().expr_ty(expr).kind(), ty::Ref(_, ty, _) if ty.is_char()) +} + +fn is_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() +} diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs deleted file mode 100644 index 4a1d25deade9..000000000000 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `insert_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character string literal", - "consider using `insert` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character converted to string", - "consider using `insert` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs deleted file mode 100644 index bc271d593925..000000000000 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ /dev/null @@ -1,65 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `push_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let sugg = format!("{base_string_snippet}.push({extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character string literal", - "consider using `push` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character converted to string", - "consider using `push` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -}