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:
commit
aade96f902
16 changed files with 163 additions and 45 deletions
77
clippy_lints/src/format_push_string.rs
Normal file
77
clippy_lints/src/format_push_string.rs
Normal 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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue