Add format_in_format_args and to_string_in_format_args lints

Fixes #7667 and #7729
This commit is contained in:
Samuel E. Moelius III 2021-09-30 07:45:03 -04:00
parent 4996e17b14
commit c9599d79a3
14 changed files with 908 additions and 49 deletions

View file

@ -2736,6 +2736,7 @@ Released 2018-09-13
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
@ -3015,6 +3016,7 @@ Released 2018-09-13
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines

View file

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::FormatExpn;
use clippy_utils::last_path_segment;
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, QPath};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -69,8 +68,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
ty::Str => true,
_ => false,
};
if format_args.args.iter().all(is_display_arg);
if format_args.fmt_expr.map_or(true, check_unformatted);
if let Some(args) = format_args.args();
if args.iter().all(|arg| arg.is_display() && !arg.has_string_formatting());
then {
let is_new_string = match value.kind {
ExprKind::Binary(..) => true,
@ -106,47 +105,3 @@ fn span_useless_format(cx: &LateContext<'_>, span: Span, mut sugg: String, mut a
applicability,
);
}
fn is_display_arg(expr: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::Call(_, [_, fmt]) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = fmt.kind;
if let [.., t, _] = path.segments;
if t.ident.name == sym::Display;
then { true } else { false }
}
}
/// Checks if the expression matches
/// ```rust,ignore
/// &[_ {
/// format: _ {
/// width: _::Implied,
/// precision: _::Implied,
/// ...
/// },
/// ...,
/// }]
/// ```
fn check_unformatted(expr: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array([expr]) = expr.kind;
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = expr.kind;
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
// struct `core::fmt::rt::v1::FormatSpec`
if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
if last_path_segment(precision_path).ident.name == sym::Implied;
if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym::width);
if let ExprKind::Path(ref width_qpath) = width_field.expr.kind;
if last_path_segment(width_qpath).ident.name == sym::Implied;
then {
return true;
}
}
false
}

View file

@ -0,0 +1,223 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_diag_trait_item, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol};
declare_clippy_lint! {
/// ### What it does
/// Detects `format!` within the arguments of another macro that does
/// formatting such as `format!` itself, `write!` or `println!`. Suggests
/// inlining the `format!` call.
///
/// ### Why is this bad?
/// The recommended code is both shorter and avoids a temporary allocation.
///
/// ### Example
/// ```rust
/// # use std::panic::Location;
/// println!("error: {}", format!("something failed at {}", Location::caller()));
/// ```
/// Use instead:
/// ```rust
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller());
/// ```
pub FORMAT_IN_FORMAT_ARGS,
perf,
"`format!` used in a macro that does formatting"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
/// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
/// in a macro that does formatting.
///
/// ### Why is this bad?
/// Since the type implements `Display`, the use of `to_string` is
/// unnecessary.
///
/// ### Example
/// ```rust
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller().to_string());
/// ```
/// Use instead:
/// ```rust
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller());
/// ```
pub TO_STRING_IN_FORMAT_ARGS,
perf,
"`to_string` applied to a type that implements `Display` in format args"
}
declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
const FORMAT_MACRO_PATHS: &[&[&str]] = &[
&paths::FORMAT_ARGS_MACRO,
&paths::ASSERT_EQ_MACRO,
&paths::ASSERT_MACRO,
&paths::ASSERT_NE_MACRO,
&paths::EPRINT_MACRO,
&paths::EPRINTLN_MACRO,
&paths::PRINT_MACRO,
&paths::PRINTLN_MACRO,
&paths::WRITE_MACRO,
&paths::WRITELN_MACRO,
];
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro];
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let Some(format_args) = FormatArgsExpn::parse(expr);
let expr_expn_data = expr.span.ctxt().outer_expn_data();
let outermost_expn_data = outermost_expn_data(expr_expn_data);
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
if FORMAT_MACRO_PATHS
.iter()
.any(|path| match_def_path(cx, macro_def_id, path))
|| FORMAT_MACRO_DIAG_ITEMS
.iter()
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id));
if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
if let Some(args) = format_args.args();
then {
for (i, arg) in args.iter().enumerate() {
if !arg.is_display() {
continue;
}
if arg.has_string_formatting() {
continue;
}
if is_aliased(&args, i) {
continue;
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
check_to_string_in_format_args(cx, name, arg);
}
}
}
}
}
fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
if expn_data.call_site.from_expansion() {
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
} else {
expn_data
}
}
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
if_chain! {
if FormatExpn::parse(arg.value).is_some();
if !arg.value.span.ctxt().outer_expn_data().call_site.from_expansion();
then {
span_lint_and_then(
cx,
FORMAT_IN_FORMAT_ARGS,
trim_semicolon(cx, call_site),
&format!("`format!` in `{}!` args", name),
|diag| {
diag.help(&format!(
"combine the `format!(..)` arguments with the outer `{}!(..)` call",
name
));
diag.help("or consider changing `format!` to `format_args!`");
},
);
}
}
}
fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
let value = arg.value;
if_chain! {
if !value.span.from_expansion();
if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
if is_diag_trait_item(cx, method_def_id, sym::ToString);
let receiver_ty = cx.typeck_results().expr_ty(receiver);
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
let (n_needed_derefs, target) = count_needed_derefs(
receiver_ty,
cx.typeck_results().expr_adjustments(receiver).iter(),
);
if implements_trait(cx, target, display_trait_id, &[]) {
if n_needed_derefs == 0 {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span.with_lo(receiver.span.hi()),
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
"use this",
format!("{:*>width$}{}", "", receiver_snippet, width = n_needed_derefs),
Applicability::MachineApplicable,
);
}
}
}
}
}
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
let value = args[i].value;
args.iter()
.enumerate()
.any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
}
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
snippet_opt(cx, span).map_or(span, |snippet| {
let snippet = snippet.trim_end_matches(';');
span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap()))
})
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
where
I: Iterator<Item = &'tcx Adjustment<'tcx>>,
{
let mut n_total = 0;
let mut n_needed = 0;
loop {
if let Some(Adjustment {
kind: Adjust::Deref(overloaded_deref),
target,
}) = iter.next()
{
n_total += 1;
if overloaded_deref.is_some() {
n_needed = n_total;
}
ty = target;
} else {
return (n_needed, ty);
}
}
}

View file

@ -60,6 +60,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(float_literal::EXCESSIVE_PRECISION),
LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),

View file

@ -139,6 +139,8 @@ store.register_lints(&[
floating_point_arithmetic::IMPRECISE_FLOPS,
floating_point_arithmetic::SUBOPTIMAL_FLOPS,
format::USELESS_FORMAT,
format_args::FORMAT_IN_FORMAT_ARGS,
format_args::TO_STRING_IN_FORMAT_ARGS,
formatting::POSSIBLE_MISSING_COMMA,
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
formatting::SUSPICIOUS_ELSE_FORMATTING,

View file

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

View file

@ -218,6 +218,7 @@ mod float_equality_without_abs;
mod float_literal;
mod floating_point_arithmetic;
mod format;
mod format_args;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
@ -775,6 +776,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
store.register_late_pass(move || Box::new(format_args::FormatArgs));
}
#[rustfmt::skip]

View file

@ -3,7 +3,7 @@
#![deny(clippy::missing_docs_in_private_items)]
use crate::ty::is_type_diagnostic_item;
use crate::{is_expn_of, match_def_path, paths};
use crate::{is_expn_of, last_path_segment, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind};
use rustc_hir as hir;
@ -572,6 +572,106 @@ impl FormatArgsExpn<'tcx> {
}
}
}
/// Returns a vector of `FormatArgsArg`.
pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
if let Some(expr) = self.fmt_expr {
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array(exprs) = expr.kind;
then {
exprs.iter().map(|fmt| {
if_chain! {
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = fmt.kind;
if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
if let ExprKind::Lit(lit) = &position_field.expr.kind;
if let LitKind::Int(position, _) = lit.node;
then {
let i = usize::try_from(position).unwrap();
Some(FormatArgsArg { value: self.value_args[i], arg: &self.args[i], fmt: Some(fmt) })
} else {
None
}
}
}).collect()
} else {
None
}
}
} else {
Some(
self.value_args
.iter()
.zip(self.args.iter())
.map(|(value, arg)| FormatArgsArg { value, arg, fmt: None })
.collect(),
)
}
}
}
/// Type representing a `FormatArgsExpn`'s format arguments
pub struct FormatArgsArg<'tcx> {
/// An element of `value_args` according to `position`
pub value: &'tcx Expr<'tcx>,
/// An element of `args` according to `position`
pub arg: &'tcx Expr<'tcx>,
/// An element of `fmt_expn`
pub fmt: Option<&'tcx Expr<'tcx>>,
}
impl<'tcx> FormatArgsArg<'tcx> {
/// Returns true if any formatting parameters are used that would have an effect on strings,
/// like `{:+2}` instead of just `{}`.
pub fn has_string_formatting(&self) -> bool {
self.fmt.map_or(false, |fmt| {
// `!` because these conditions check that `self` is unformatted.
!if_chain! {
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = fmt.kind;
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
// struct `core::fmt::rt::v1::FormatSpec`
if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind;
let mut precision_found = false;
let mut width_found = false;
if subfields.iter().all(|field| {
match field.ident.name {
sym::precision => {
precision_found = true;
if let ExprKind::Path(ref precision_path) = field.expr.kind {
last_path_segment(precision_path).ident.name == sym::Implied
} else {
false
}
}
sym::width => {
width_found = true;
if let ExprKind::Path(ref width_qpath) = field.expr.kind {
last_path_segment(width_qpath).ident.name == sym::Implied
} else {
false
}
}
_ => true,
}
});
if precision_found && width_found;
then { true } else { false }
}
})
}
/// Returns true if the argument is formatted using `Display::fmt`.
pub fn is_display(&self) -> bool {
if_chain! {
if let ExprKind::Call(_, [_, format_field]) = self.arg.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = format_field.kind;
if let [.., t, _] = path.segments;
if t.ident.name == sym::Display;
then { true } else { false }
}
}
}
/// Checks if a `let` statement is from a `for` loop desugaring.

View file

@ -17,6 +17,12 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
#[cfg(feature = "metadata-collector-lint")]
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const ASSERT_EQ_MACRO: [&str; 3] = ["core", "macros", "assert_eq"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const ASSERT_MACRO: [&str; 4] = ["core", "macros", "builtin", "assert"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const ASSERT_NE_MACRO: [&str; 3] = ["core", "macros", "assert_ne"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
@ -42,11 +48,17 @@ pub const DROP: [&str; 3] = ["core", "mem", "drop"];
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
#[cfg(feature = "internal-lints")]
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const EPRINT_MACRO: [&str; 3] = ["std", "macros", "eprint"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const EPRINTLN_MACRO: [&str; 3] = ["std", "macros", "eprintln"];
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
pub const FILE: [&str; 3] = ["std", "fs", "File"];
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const FORMAT_ARGS_MACRO: [&str; 4] = ["core", "macros", "builtin", "format_args"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
@ -109,6 +121,10 @@ pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "Permis
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const PRINT_MACRO: [&str; 3] = ["std", "macros", "print"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const PRINTLN_MACRO: [&str; 3] = ["std", "macros", "println"];
pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
@ -185,3 +201,7 @@ pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const WRITE_MACRO: [&str; 3] = ["core", "macros", "write"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const WRITELN_MACRO: [&str; 3] = ["core", "macros", "writeln"];

105
tests/ui/format_args.fixed Normal file
View file

@ -0,0 +1,105 @@
// run-rustfix
#![allow(unreachable_code)]
#![allow(unused_macros)]
#![allow(unused_variables)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::eq_op)]
#![warn(clippy::to_string_in_format_args)]
use std::io::{stdout, Write};
use std::ops::Deref;
use std::panic::Location;
struct Somewhere;
impl ToString for Somewhere {
fn to_string(&self) -> String {
String::from("somewhere")
}
}
struct X(u32);
impl Deref for X {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
struct Y<'a>(&'a X);
impl<'a> Deref for Y<'a> {
type Target = &'a X;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct Z(u32);
impl Deref for Z {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
impl std::fmt::Display for Z {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Z")
}
}
macro_rules! my_macro {
() => {
// here be dragons, do not enter (or lint)
println!("error: something failed at {}", Location::caller().to_string());
};
}
macro_rules! my_other_macro {
() => {
Location::caller().to_string()
};
}
fn main() {
let x = &X(1);
let x_ref = &x;
let _ = format!("error: something failed at {}", Location::caller());
let _ = write!(
stdout(),
"error: something failed at {}",
Location::caller()
);
let _ = writeln!(
stdout(),
"error: something failed at {}",
Location::caller()
);
print!("error: something failed at {}", Location::caller());
println!("error: something failed at {}", Location::caller());
eprint!("error: something failed at {}", Location::caller());
eprintln!("error: something failed at {}", Location::caller());
let _ = format_args!("error: something failed at {}", Location::caller());
assert!(true, "error: something failed at {}", Location::caller());
assert_eq!(0, 0, "error: something failed at {}", Location::caller());
assert_ne!(0, 0, "error: something failed at {}", Location::caller());
panic!("error: something failed at {}", Location::caller());
println!("{}", *X(1));
println!("{}", ***Y(&X(1)));
println!("{}", Z(1));
println!("{}", **x);
println!("{}", ***x_ref);
println!("error: something failed at {}", Somewhere.to_string());
println!("{} and again {0}", x.to_string());
my_macro!();
println!("error: something failed at {}", my_other_macro!());
}

105
tests/ui/format_args.rs Normal file
View file

@ -0,0 +1,105 @@
// run-rustfix
#![allow(unreachable_code)]
#![allow(unused_macros)]
#![allow(unused_variables)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::eq_op)]
#![warn(clippy::to_string_in_format_args)]
use std::io::{stdout, Write};
use std::ops::Deref;
use std::panic::Location;
struct Somewhere;
impl ToString for Somewhere {
fn to_string(&self) -> String {
String::from("somewhere")
}
}
struct X(u32);
impl Deref for X {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
struct Y<'a>(&'a X);
impl<'a> Deref for Y<'a> {
type Target = &'a X;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct Z(u32);
impl Deref for Z {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
impl std::fmt::Display for Z {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Z")
}
}
macro_rules! my_macro {
() => {
// here be dragons, do not enter (or lint)
println!("error: something failed at {}", Location::caller().to_string());
};
}
macro_rules! my_other_macro {
() => {
Location::caller().to_string()
};
}
fn main() {
let x = &X(1);
let x_ref = &x;
let _ = format!("error: something failed at {}", Location::caller().to_string());
let _ = write!(
stdout(),
"error: something failed at {}",
Location::caller().to_string()
);
let _ = writeln!(
stdout(),
"error: something failed at {}",
Location::caller().to_string()
);
print!("error: something failed at {}", Location::caller().to_string());
println!("error: something failed at {}", Location::caller().to_string());
eprint!("error: something failed at {}", Location::caller().to_string());
eprintln!("error: something failed at {}", Location::caller().to_string());
let _ = format_args!("error: something failed at {}", Location::caller().to_string());
assert!(true, "error: something failed at {}", Location::caller().to_string());
assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
panic!("error: something failed at {}", Location::caller().to_string());
println!("{}", X(1).to_string());
println!("{}", Y(&X(1)).to_string());
println!("{}", Z(1).to_string());
println!("{}", x.to_string());
println!("{}", x_ref.to_string());
println!("error: something failed at {}", Somewhere.to_string());
println!("{} and again {0}", x.to_string());
my_macro!();
println!("error: something failed at {}", my_other_macro!());
}

106
tests/ui/format_args.stderr Normal file
View file

@ -0,0 +1,106 @@
error: `to_string` applied to a type that implements `Display` in `format!` args
--> $DIR/format_args.rs:75:72
|
LL | let _ = format!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
|
= note: `-D clippy::to-string-in-format-args` implied by `-D warnings`
error: `to_string` applied to a type that implements `Display` in `write!` args
--> $DIR/format_args.rs:79:27
|
LL | Location::caller().to_string()
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `writeln!` args
--> $DIR/format_args.rs:84:27
|
LL | Location::caller().to_string()
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `print!` args
--> $DIR/format_args.rs:86:63
|
LL | print!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:87:65
|
LL | println!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `eprint!` args
--> $DIR/format_args.rs:88:64
|
LL | eprint!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `eprintln!` args
--> $DIR/format_args.rs:89:66
|
LL | eprintln!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `format_args!` args
--> $DIR/format_args.rs:90:77
|
LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert!` args
--> $DIR/format_args.rs:91:70
|
LL | assert!(true, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert_eq!` args
--> $DIR/format_args.rs:92:73
|
LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert_ne!` args
--> $DIR/format_args.rs:93:73
|
LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `panic!` args
--> $DIR/format_args.rs:94:63
|
LL | panic!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:95:20
|
LL | println!("{}", X(1).to_string());
| ^^^^^^^^^^^^^^^^ help: use this: `*X(1)`
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:96:20
|
LL | println!("{}", Y(&X(1)).to_string());
| ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))`
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:97:24
|
LL | println!("{}", Z(1).to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:98:20
|
LL | println!("{}", x.to_string());
| ^^^^^^^^^^^^^ help: use this: `**x`
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:99:20
|
LL | println!("{}", x_ref.to_string());
| ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
error: aborting due to 17 previous errors

View file

@ -0,0 +1,60 @@
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::eq_op)]
#![warn(clippy::format_in_format_args)]
#![warn(clippy::to_string_in_format_args)]
use std::io::{stdout, Error, ErrorKind, Write};
use std::ops::Deref;
use std::panic::Location;
macro_rules! my_macro {
() => {
// here be dragons, do not enter (or lint)
println!("error: {}", format!("something failed at {}", Location::caller()));
};
}
macro_rules! my_other_macro {
() => {
format!("something failed at {}", Location::caller())
};
}
fn main() {
let error = Error::new(ErrorKind::Other, "bad thing");
let x = 'x';
println!("error: {}", format!("something failed at {}", Location::caller()));
println!("{}: {}", error, format!("something failed at {}", Location::caller()));
println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
println!("{{}}: {}", format!("something failed at {}", Location::caller()));
println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
println!("error: {}", format!("something failed at {} {0}", Location::caller()));
let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
let _ = write!(
stdout(),
"error: {}",
format!("something failed at {}", Location::caller())
);
let _ = writeln!(
stdout(),
"error: {}",
format!("something failed at {}", Location::caller())
);
print!("error: {}", format!("something failed at {}", Location::caller()));
eprint!("error: {}", format!("something failed at {}", Location::caller()));
eprintln!("error: {}", format!("something failed at {}", Location::caller()));
let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
panic!("error: {}", format!("something failed at {}", Location::caller()));
println!("error: {}", format_args!("something failed at {}", Location::caller()));
println!("error: {:>70}", format!("something failed at {}", Location::caller()));
println!("error: {} {0}", format!("something failed at {}", Location::caller()));
println!("{} and again {0}", format!("hi {}", x));
my_macro!();
println!("error: {}", my_other_macro!());
}

View file

@ -0,0 +1,175 @@
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:27:5
|
LL | println!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::format-in-format-args` implied by `-D warnings`
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:28:5
|
LL | println!("{}: {}", error, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:29:5
|
LL | println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:30:5
|
LL | println!("{{}}: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:31:5
|
LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:32:5
|
LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:33:5
|
LL | println!("error: {}", format!("something failed at {} {0}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `format!` args
--> $DIR/format_args_unfixable.rs:34:13
|
LL | let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `format!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `write!` args
--> $DIR/format_args_unfixable.rs:35:13
|
LL | let _ = write!(
| _____________^
LL | | stdout(),
LL | | "error: {}",
LL | | format!("something failed at {}", Location::caller())
LL | | );
| |_____^
|
= help: combine the `format!(..)` arguments with the outer `write!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `writeln!` args
--> $DIR/format_args_unfixable.rs:40:13
|
LL | let _ = writeln!(
| _____________^
LL | | stdout(),
LL | | "error: {}",
LL | | format!("something failed at {}", Location::caller())
LL | | );
| |_____^
|
= help: combine the `format!(..)` arguments with the outer `writeln!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `print!` args
--> $DIR/format_args_unfixable.rs:45:5
|
LL | print!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `print!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `eprint!` args
--> $DIR/format_args_unfixable.rs:46:5
|
LL | eprint!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `eprint!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `eprintln!` args
--> $DIR/format_args_unfixable.rs:47:5
|
LL | eprintln!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `eprintln!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `format_args!` args
--> $DIR/format_args_unfixable.rs:48:13
|
LL | let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `format_args!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `assert!` args
--> $DIR/format_args_unfixable.rs:49:5
|
LL | assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `assert!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `assert_eq!` args
--> $DIR/format_args_unfixable.rs:50:5
|
LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `assert_eq!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `assert_ne!` args
--> $DIR/format_args_unfixable.rs:51:5
|
LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `assert_ne!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `panic!` args
--> $DIR/format_args_unfixable.rs:52:5
|
LL | panic!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `panic!(..)` call
= help: or consider changing `format!` to `format_args!`
error: aborting due to 18 previous errors