diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ea0f9ab9d2..e4607b564d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2734,6 +2734,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 @@ -3011,6 +3012,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 diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 508cac33848f..bc3163a4e6c7 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::format::{check_unformatted, is_display_arg}; 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}; @@ -106,47 +106,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.as_str() == "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 -} diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs new file mode 100644 index 000000000000..edee93d189f2 --- /dev/null +++ b/clippy_lints/src/format_args.rs @@ -0,0 +1,262 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::format::{check_unformatted, is_display_arg}; +use clippy_utils::higher::{FormatArgsExpn, FormatExpn}; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::implements_trait; +use clippy_utils::{get_trait_def_id, 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_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{BytePos, ExpnKind, Span, Symbol}; + +declare_clippy_lint! { + /// ### What it does + /// Warns on `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_lint_pass!(FormatInFormatArgs => [FORMAT_IN_FORMAT_ARGS]); + +impl<'tcx> LateLintPass<'tcx> for FormatInFormatArgs { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_expr(cx, expr, format_in_format_args); + } +} + +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!(ToStringInFormatArgs => [TO_STRING_IN_FORMAT_ARGS]); + +impl<'tcx> LateLintPass<'tcx> for ToStringInFormatArgs { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_expr(cx, expr, to_string_in_format_args); + } +} + +fn check_expr<'tcx, F>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, check_value: F) +where + F: Fn(&LateContext<'_>, &FormatArgsExpn<'_>, Span, Symbol, usize, &Expr<'_>) -> bool, +{ + if_chain! { + if let Some(format_args) = FormatArgsExpn::parse(expr); + let call_site = expr.span.ctxt().outer_expn_data().call_site; + if call_site.from_expansion(); + let expn_data = call_site.ctxt().outer_expn_data(); + if let ExpnKind::Macro(_, name) = expn_data.kind; + if format_args.fmt_expr.map_or(true, check_unformatted); + then { + assert_eq!(format_args.args.len(), format_args.value_args.len()); + for (i, (arg, value)) in format_args.args.iter().zip(format_args.value_args.iter()).enumerate() { + if !is_display_arg(arg) { + continue; + } + if check_value(cx, &format_args, expn_data.call_site, name, i, value) { + break; + } + } + } + } +} + +fn format_in_format_args( + cx: &LateContext<'_>, + format_args: &FormatArgsExpn<'_>, + call_site: Span, + name: Symbol, + i: usize, + value: &Expr<'_>, +) -> bool { + if_chain! { + if let Some(FormatExpn{ format_args: inner_format_args, .. }) = FormatExpn::parse(value); + if let Some(format_string) = snippet_opt(cx, format_args.format_string_span); + if let Some(inner_format_string) = snippet_opt(cx, inner_format_args.format_string_span); + if let Some((sugg, applicability)) = format_in_format_args_sugg( + cx, + name, + &format_string, + &format_args.value_args, + i, + &inner_format_string, + &inner_format_args.value_args + ); + then { + span_lint_and_sugg( + cx, + FORMAT_IN_FORMAT_ARGS, + trim_semicolon(cx, call_site), + &format!("`format!` in `{}!` args", name), + "inline the `format!` call", + sugg, + applicability, + ); + // Report only the first instance. + return true; + } + } + false +} + +fn to_string_in_format_args( + cx: &LateContext<'_>, + _format_args: &FormatArgsExpn<'_>, + _call_site: Span, + name: Symbol, + _i: usize, + value: &Expr<'_>, +) -> bool { + if_chain! { + if let ExprKind::MethodCall(_, _, args, _) = value.kind; + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id); + if match_def_path(cx, method_def_id, &paths::TO_STRING_METHOD); + if let Some(receiver) = args.get(0); + let ty = cx.typeck_results().expr_ty(receiver); + if let Some(display_trait_id) = get_trait_def_id(cx, &paths::DISPLAY_TRAIT); + if implements_trait(cx, ty, display_trait_id, &[]); + if let Some(snippet) = snippet_opt(cx, value.span); + if let Some(dot) = snippet.rfind('.'); + then { + let span = value.span.with_lo( + value.span.lo() + BytePos(u32::try_from(dot).unwrap()) + ); + span_lint_and_sugg( + cx, + TO_STRING_IN_FORMAT_ARGS, + span, + &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name), + "remove this", + String::new(), + Applicability::MachineApplicable, + ); + } + } + false +} + +fn format_in_format_args_sugg( + cx: &LateContext<'_>, + name: Symbol, + format_string: &str, + values: &[&Expr<'_>], + i: usize, + inner_format_string: &str, + inner_values: &[&Expr<'_>], +) -> Option<(String, Applicability)> { + let (left, right) = split_format_string(format_string, i); + // If the inner format string is raw, the user is on their own. + let (new_format_string, applicability) = if inner_format_string.starts_with('r') { + (left + ".." + &right, Applicability::HasPlaceholders) + } else { + ( + left + &trim_quotes(inner_format_string) + &right, + Applicability::MachineApplicable, + ) + }; + let values = values + .iter() + .map(|value| snippet_opt(cx, value.span)) + .collect::>>()?; + let inner_values = inner_values + .iter() + .map(|value| snippet_opt(cx, value.span)) + .collect::>>()?; + let new_values = itertools::join( + values + .iter() + .take(i) + .chain(inner_values.iter()) + .chain(values.iter().skip(i + 1)), + ", ", + ); + Some(( + format!(r#"{}!({}, {})"#, name, new_format_string, new_values), + applicability, + )) +} + +fn split_format_string(format_string: &str, i: usize) -> (String, String) { + let mut iter = format_string.chars(); + for j in 0..=i { + assert!(advance(&mut iter) == '}' || j < i); + } + + let right = iter.collect::(); + + let size = format_string.len(); + let right_size = right.len(); + let left_size = size - right_size; + assert!(left_size >= 2); + let left = std::str::from_utf8(&format_string.as_bytes()[..left_size - 2]) + .unwrap() + .to_owned(); + (left, right) +} + +fn advance(iter: &mut std::str::Chars<'_>) -> char { + loop { + let first_char = iter.next().unwrap(); + if first_char != '{' { + continue; + } + let second_char = iter.next().unwrap(); + if second_char == '{' { + continue; + } + return second_char; + } +} + +fn trim_quotes(string_literal: &str) -> String { + let len = string_literal.chars().count(); + assert!(len >= 2); + string_literal.chars().skip(1).take(len - 2).collect() +} + +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())) + }) +} diff --git a/clippy_lints/src/lib.mods.rs b/clippy_lints/src/lib.mods.rs index 2718604f9051..e258b1a55358 100644 --- a/clippy_lints/src/lib.mods.rs +++ b/clippy_lints/src/lib.mods.rs @@ -63,6 +63,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; diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 3e6e0244754f..2a51ca0d37d3 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -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), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 1ad27870b1a0..26615bdff63d 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -137,6 +137,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, diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 5432345760bc..a0d5cf9418e0 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -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), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 4e21b03217df..1e4301ec8d3c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -535,6 +535,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(feature_name::FeatureName)); store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator)); store.register_late_pass(move || Box::new(if_then_panic::IfThenPanic)); + store.register_late_pass(move || Box::new(format_args::FormatInFormatArgs)); + store.register_late_pass(move || Box::new(format_args::ToStringInFormatArgs)); } #[rustfmt::skip] diff --git a/clippy_utils/src/format.rs b/clippy_utils/src/format.rs new file mode 100644 index 000000000000..b32cf2682dca --- /dev/null +++ b/clippy_utils/src/format.rs @@ -0,0 +1,49 @@ +use crate::last_path_segment; +use if_chain::if_chain; +use rustc_hir::QPath; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_span::sym; + +pub 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.as_str() == "Display"; + then { true } else { false } + } +} + +/// Checks if the expression matches +/// ```rust,ignore +/// &[_ { +/// format: _ { +/// width: _::Implied, +/// precision: _::Implied, +/// ... +/// }, +/// ..., +/// }] +/// ``` +pub 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 +} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 231b5b80d18a..25cdff6f9d13 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -43,6 +43,7 @@ pub mod comparisons; pub mod consts; pub mod diagnostics; pub mod eager_or_lazy; +pub mod format; pub mod higher; mod hir_utils; pub mod msrvs; diff --git a/tests/ui/format_args.fixed b/tests/ui/format_args.fixed new file mode 100644 index 000000000000..6cd6019b4d4e --- /dev/null +++ b/tests/ui/format_args.fixed @@ -0,0 +1,19 @@ +// run-rustfix + +#![warn(clippy::format_in_format_args)] +#![warn(clippy::to_string_in_format_args)] + +use std::io::{Error, ErrorKind}; +use std::panic::Location; + +fn main() { + let error = Error::new(ErrorKind::Other, "bad thing"); + println!("error: something failed at {}", Location::caller()); + println!("{}: something failed at {}", error, Location::caller()); + println!("{:?}: something failed at {}", error, Location::caller()); + println!(r#"error: "something failed at {}""#, Location::caller()); + println!("error: {}", format_args!("something failed at {}", Location::caller())); + println!("error: {:>70}", format!("something failed at {}", Location::caller())); + let _ = format!("error: something failed at {}", Location::caller()); + println!("error: something failed at {}", Location::caller()); +} diff --git a/tests/ui/format_args.rs b/tests/ui/format_args.rs new file mode 100644 index 000000000000..929597c82ce5 --- /dev/null +++ b/tests/ui/format_args.rs @@ -0,0 +1,19 @@ +// run-rustfix + +#![warn(clippy::format_in_format_args)] +#![warn(clippy::to_string_in_format_args)] + +use std::io::{Error, ErrorKind}; +use std::panic::Location; + +fn main() { + let error = Error::new(ErrorKind::Other, "bad thing"); + 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!(r#"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())); + let _ = format!("error: {}", format!("something failed at {}", Location::caller())); + println!("error: something failed at {}", Location::caller().to_string()); +} diff --git a/tests/ui/format_args.stderr b/tests/ui/format_args.stderr new file mode 100644 index 000000000000..17e2f27d189d --- /dev/null +++ b/tests/ui/format_args.stderr @@ -0,0 +1,42 @@ +error: `format!` in `println!` args + --> $DIR/format_args.rs:11:5 + | +LL | println!("error: {}", format!("something failed at {}", Location::caller())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: inline the `format!` call: `println!("error: something failed at {}", Location::caller())` + | + = note: `-D clippy::format-in-format-args` implied by `-D warnings` + +error: `format!` in `println!` args + --> $DIR/format_args.rs:12:5 + | +LL | println!("{}: {}", error, format!("something failed at {}", Location::caller())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: inline the `format!` call: `println!("{}: something failed at {}", error, Location::caller())` + +error: `format!` in `println!` args + --> $DIR/format_args.rs:13:5 + | +LL | println!("{:?}: {}", error, format!("something failed at {}", Location::caller())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: inline the `format!` call: `println!("{:?}: something failed at {}", error, Location::caller())` + +error: `format!` in `println!` args + --> $DIR/format_args.rs:14:5 + | +LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::caller())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: inline the `format!` call: `println!(r#"error: "something failed at {}""#, Location::caller())` + +error: `format!` in `format!` args + --> $DIR/format_args.rs:17:13 + | +LL | let _ = format!("error: {}", format!("something failed at {}", Location::caller())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: inline the `format!` call: `format!("error: something failed at {}", Location::caller())` + +error: `to_string` applied to a type that implements `Display` in `println!` args + --> $DIR/format_args.rs:18:65 + | +LL | println!("error: something failed at {}", Location::caller().to_string()); + | ^^^^^^^^^^^^ help: remove this + | + = note: `-D clippy::to-string-in-format-args` implied by `-D warnings` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/format_args_unfixable.rs b/tests/ui/format_args_unfixable.rs new file mode 100644 index 000000000000..4e2b118d1d31 --- /dev/null +++ b/tests/ui/format_args_unfixable.rs @@ -0,0 +1,8 @@ +#![warn(clippy::format_in_format_args)] +#![warn(clippy::to_string_in_format_args)] + +use std::panic::Location; + +fn main() { + println!("error: {}", format!(r#"something failed at "{}""#, Location::caller())); +} diff --git a/tests/ui/format_args_unfixable.stderr b/tests/ui/format_args_unfixable.stderr new file mode 100644 index 000000000000..1d6177a5d07e --- /dev/null +++ b/tests/ui/format_args_unfixable.stderr @@ -0,0 +1,10 @@ +error: `format!` in `println!` args + --> $DIR/format_args_unfixable.rs:7:5 + | +LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::caller())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: inline the `format!` call: `println!("error: ..", Location::caller())` + | + = note: `-D clippy::format-in-format-args` implied by `-D warnings` + +error: aborting due to previous error +