1 use clippy_utils
::diagnostics
::span_lint_and_help
;
2 use clippy_utils
::ty
::is_type_diagnostic_item
;
3 use clippy_utils
::{match_def_path, paths, peel_hir_expr_refs}
;
4 use rustc_hir
::{BinOpKind, Expr, ExprKind}
;
5 use rustc_lint
::{LateContext, LateLintPass}
;
6 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
11 /// Detects cases where the result of a `format!` call is
12 /// appended to an existing `String`.
14 /// ### Why is this bad?
15 /// Introduces an extra, avoidable heap allocation.
19 /// let mut s = String::new();
20 /// s += &format!("0x{:X}", 1024);
21 /// s.push_str(&format!("0x{:X}", 1024));
25 /// use std::fmt::Write as _; // import without risk of name clashing
27 /// let mut s = String::new();
28 /// let _ = write!(s, "0x{:X}", 1024);
30 #[clippy::version = "1.61.0"]
31 pub FORMAT_PUSH_STRING
,
33 "`format!(..)` appended to existing `String`"
35 declare_lint_pass
!(FormatPushString
=> [FORMAT_PUSH_STRING
]);
37 fn is_string(cx
: &LateContext
<'_
>, e
: &Expr
<'_
>) -> bool
{
38 is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(e
).peel_refs(), sym
::String
)
40 fn is_format(cx
: &LateContext
<'_
>, e
: &Expr
<'_
>) -> bool
{
41 if let Some(macro_def_id
) = e
.span
.ctxt().outer_expn_data().macro_def_id
{
42 cx
.tcx
.get_diagnostic_name(macro_def_id
) == Some(sym
::format_macro
)
48 impl<'tcx
> LateLintPass
<'tcx
> for FormatPushString
{
49 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
50 let arg
= match expr
.kind
{
51 ExprKind
::MethodCall(_
, [_
, arg
], _
) => {
52 if let Some(fn_def_id
) = cx
.typeck_results().type_dependent_def_id(expr
.hir_id
) &&
53 match_def_path(cx
, fn_def_id
, &paths
::PUSH_STR
) {
59 ExprKind
::AssignOp(op
, left
, arg
)
60 if op
.node
== BinOpKind
::Add
&& is_string(cx
, left
) => {
65 let (arg
, _
) = peel_hir_expr_refs(arg
);
66 if is_format(cx
, arg
) {
71 "`format!(..)` appended to existing `String`",
73 "consider using `write!` to avoid the extra allocation",