Auto merge of #8626 - pitaj:format_add_string, r=llogiq

New lint `format_add_strings`

Closes #6261

changelog: Added [`format_add_string`]: recommend using `write!` instead of appending the result of  `format!`
This commit is contained in:
bors 2022-04-14 14:29:22 +00:00
commit aade96f902
16 changed files with 163 additions and 45 deletions

View file

@ -0,0 +1,77 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_def_path, paths, peel_hir_expr_refs};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detects cases where the result of a `format!` call is
/// appended to an existing `String`.
///
/// ### Why is this bad?
/// Introduces an extra, avoidable heap allocation.
///
/// ### Example
/// ```rust
/// let mut s = String::new();
/// s += &format!("0x{:X}", 1024);
/// s.push_str(&format!("0x{:X}", 1024));
/// ```
/// Use instead:
/// ```rust
/// use std::fmt::Write as _; // import without risk of name clashing
///
/// let mut s = String::new();
/// let _ = write!(s, "0x{:X}", 1024);
/// ```
#[clippy::version = "1.61.0"]
pub FORMAT_PUSH_STRING,
perf,
"`format!(..)` appended to existing `String`"
}
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::String)
}
fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
if let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id {
cx.tcx.get_diagnostic_name(macro_def_id) == Some(sym::format_macro)
} else {
false
}
}
impl<'tcx> LateLintPass<'tcx> for FormatPushString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let arg = match expr.kind {
ExprKind::MethodCall(_, [_, arg], _) => {
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) &&
match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
arg
} else {
return;
}
}
ExprKind::AssignOp(op, left, arg)
if op.node == BinOpKind::Add && is_string(cx, left) => {
arg
},
_ => return,
};
let (arg, _) = peel_hir_expr_refs(arg);
if is_format(cx, arg) {
span_lint_and_help(
cx,
FORMAT_PUSH_STRING,
expr.span,
"`format!(..)` appended to existing `String`",
None,
"consider using `write!` to avoid the extra allocation",
);
}
}
}

View file

@ -7,6 +7,7 @@ use rustc_hir::{self as hir, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
use std::fmt::Write as _;
declare_clippy_lint! {
/// ### What it does
@ -89,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
fields_snippet.push_str(&format!("{}, ", ident));
let _ = write!(fields_snippet, "{}, ", ident);
}
fields_snippet.push_str(&last_ident.to_string());

View file

@ -77,6 +77,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),

View file

@ -165,6 +165,7 @@ store.register_lints(&[
format_args::TO_STRING_IN_FORMAT_ARGS,
format_impl::PRINT_IN_FORMAT_IMPL,
format_impl::RECURSIVE_FORMAT_IMPL,
format_push_string::FORMAT_PUSH_STRING,
formatting::POSSIBLE_MISSING_COMMA,
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
formatting::SUSPICIOUS_ELSE_FORMATTING,

View file

@ -7,6 +7,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(escape::BOXED_LOCAL),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
LintId::of(loops::MANUAL_MEMCPY),

View file

@ -231,6 +231,7 @@ mod floating_point_arithmetic;
mod format;
mod format_args;
mod format_impl;
mod format_push_string;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
@ -873,6 +874,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
store.register_late_pass(|| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
store.register_early_pass(|| Box::new(pub_use::PubUse));
store.register_late_pass(|| Box::new(format_push_string::FormatPushString));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -14,6 +14,7 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use std::fmt::Write as _;
declare_clippy_lint! {
/// ### What it does
@ -184,19 +185,19 @@ impl TraitBounds {
for b in v.iter() {
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
let path = &poly_trait_ref.trait_ref.path;
hint_string.push_str(&format!(
let _ = write!(hint_string,
" {} +",
snippet_with_applicability(cx, path.span, "..", &mut applicability)
));
);
}
}
for b in p.bounds.iter() {
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
let path = &poly_trait_ref.trait_ref.path;
hint_string.push_str(&format!(
let _ = write!(hint_string,
" {} +",
snippet_with_applicability(cx, path.span, "..", &mut applicability)
));
);
}
}
hint_string.truncate(hint_string.len() - 2);