about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-10-15 17:05:13 +0000
committerbors <bors@rust-lang.org>2021-10-15 17:05:13 +0000
commite1871ba0dabf9bf8c2f67e2e781031e2fcd53e6c (patch)
tree275ec122aeed2887d7f9b99b8bd308498ab6f995
parentdb403bb8899f6d2d2303f3ea8d1e2d5e04de5b8a (diff)
parent75e9f8cbd6d8c704ec89d12e3ddc382402a13119 (diff)
downloadrust-e1871ba0dabf9bf8c2f67e2e781031e2fcd53e6c.tar.gz
rust-e1871ba0dabf9bf8c2f67e2e781031e2fcd53e6c.zip
Auto merge of #7743 - smoelius:master, r=camsteffen
Add `format_in_format_args` and `to_string_in_format_args` lints

Fixes #7667 and #7729

I put these in `perf` since that was one of `@jplatte's` suggestions, and `redundant_clone` (which I consider to be similar) lives there as well.

However, I am open to changing the category or anything else.

r? `@camsteffen`

changelog: Add `format_in_format_args` and `to_string_in_format_args` lints
-rw-r--r--CHANGELOG.md2
-rw-r--r--clippy_dev/src/update_lints.rs4
-rw-r--r--clippy_lints/src/format.rs51
-rw-r--r--clippy_lints/src/format_args.rs223
-rw-r--r--clippy_lints/src/if_then_panic.rs4
-rw-r--r--clippy_lints/src/inherent_to_string.rs8
-rw-r--r--clippy_lints/src/lib.register_all.rs2
-rw-r--r--clippy_lints/src/lib.register_lints.rs2
-rw-r--r--clippy_lints/src/lib.register_perf.rs2
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/suspicious_operation_groupings.rs2
-rw-r--r--clippy_lints/src/transmute/transmute_int_to_char.rs2
-rw-r--r--clippy_lints/src/transmute/transmute_int_to_float.rs2
-rw-r--r--clippy_lints/src/transmute/transmute_num_to_bytes.rs2
-rw-r--r--clippy_utils/src/higher.rs102
-rw-r--r--clippy_utils/src/paths.rs20
-rw-r--r--tests/ui/expect_fun_call.fixed1
-rw-r--r--tests/ui/expect_fun_call.rs1
-rw-r--r--tests/ui/expect_fun_call.stderr24
-rw-r--r--tests/ui/format.fixed2
-rw-r--r--tests/ui/format.rs2
-rw-r--r--tests/ui/format_args.fixed105
-rw-r--r--tests/ui/format_args.rs105
-rw-r--r--tests/ui/format_args.stderr106
-rw-r--r--tests/ui/format_args_unfixable.rs60
-rw-r--r--tests/ui/format_args_unfixable.stderr175
-rw-r--r--tests/ui/to_string_in_display.rs2
27 files changed, 937 insertions, 76 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1e837d363a..f6a883311c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs
index 10d859988f6..23f58bc4915 100644
--- a/clippy_dev/src/update_lints.rs
+++ b/clippy_dev/src/update_lints.rs
@@ -619,8 +619,8 @@ mod tests {
             Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"),
         ];
         let expected = vec![
-            format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK.to_string()),
-            format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK.to_string()),
+            format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK),
+            format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK),
         ];
         assert_eq!(expected, gen_changelog_lint_list(lints.iter()));
     }
diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs
index 8df7f91ce59..472db6f5731 100644
--- a/clippy_lints/src/format.rs
+++ b/clippy_lints/src/format.rs
@@ -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
-}
diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs
new file mode 100644
index 00000000000..8b27442aa94
--- /dev/null
+++ b/clippy_lints/src/format_args.rs
@@ -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);
+        }
+    }
+}
diff --git a/clippy_lints/src/if_then_panic.rs b/clippy_lints/src/if_then_panic.rs
index 10bca59e6d0..e8cea5529e8 100644
--- a/clippy_lints/src/if_then_panic.rs
+++ b/clippy_lints/src/if_then_panic.rs
@@ -78,10 +78,10 @@ impl LateLintPass<'_> for IfThenPanic {
                     if let Expr{kind: ExprKind::Unary(UnOp::Not, not_expr), ..} = e {
                          sugg::Sugg::hir_with_applicability(cx, not_expr, "..", &mut applicability).maybe_par().to_string()
                     } else {
-                       format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par().to_string())
+                       format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par())
                     }
                 } else {
-                   format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par().to_string())
+                   format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par())
                 };
 
                 span_lint_and_sugg(
diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs
index 3c40ca50a09..61dd0eb4af7 100644
--- a/clippy_lints/src/inherent_to_string.rs
+++ b/clippy_lints/src/inherent_to_string.rs
@@ -138,10 +138,10 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
             item.span,
             &format!(
                 "type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
-                self_type.to_string()
+                self_type
             ),
             None,
-            &format!("remove the inherent method from type `{}`", self_type.to_string()),
+            &format!("remove the inherent method from type `{}`", self_type),
         );
     } else {
         span_lint_and_help(
@@ -150,10 +150,10 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
             item.span,
             &format!(
                 "implementation of inherent method `to_string(&self) -> String` for type `{}`",
-                self_type.to_string()
+                self_type
             ),
             None,
-            &format!("implement trait `Display` for type `{}` instead", self_type.to_string()),
+            &format!("implement trait `Display` for type `{}` instead", self_type),
         );
     }
 }
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs
index 7c8733f9022..4c2dfbd1d84 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 7d81fafe4c9..f334f723f6b 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -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,
diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs
index 5432345760b..a0d5cf9418e 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 6ab59b1b12f..9794d74e198 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -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]
diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs
index 44d5ff0b63a..201aa067824 100644
--- a/clippy_lints/src/suspicious_operation_groupings.rs
+++ b/clippy_lints/src/suspicious_operation_groupings.rs
@@ -678,7 +678,7 @@ fn suggestion_with_swapped_ident(
         Some(format!(
             "{}{}{}",
             snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability),
-            new_ident.to_string(),
+            new_ident,
             snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability),
         ))
     })
diff --git a/clippy_lints/src/transmute/transmute_int_to_char.rs b/clippy_lints/src/transmute/transmute_int_to_char.rs
index 8f884e6a4a1..e83d2e06b9a 100644
--- a/clippy_lints/src/transmute/transmute_int_to_char.rs
+++ b/clippy_lints/src/transmute/transmute_int_to_char.rs
@@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
                     diag.span_suggestion(
                         e.span,
                         "consider using",
-                        format!("std::char::from_u32({}).unwrap()", arg.to_string()),
+                        format!("std::char::from_u32({}).unwrap()", arg),
                         Applicability::Unspecified,
                     );
                 },
diff --git a/clippy_lints/src/transmute/transmute_int_to_float.rs b/clippy_lints/src/transmute/transmute_int_to_float.rs
index 2b6a4cff81e..05eee380d6f 100644
--- a/clippy_lints/src/transmute/transmute_int_to_float.rs
+++ b/clippy_lints/src/transmute/transmute_int_to_float.rs
@@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(
                     diag.span_suggestion(
                         e.span,
                         "consider using",
-                        format!("{}::from_bits({})", to_ty, arg.to_string()),
+                        format!("{}::from_bits({})", to_ty, arg),
                         Applicability::Unspecified,
                     );
                 },
diff --git a/clippy_lints/src/transmute/transmute_num_to_bytes.rs b/clippy_lints/src/transmute/transmute_num_to_bytes.rs
index dd5ded1c91a..5ba58a76494 100644
--- a/clippy_lints/src/transmute/transmute_num_to_bytes.rs
+++ b/clippy_lints/src/transmute/transmute_num_to_bytes.rs
@@ -37,7 +37,7 @@ pub(super) fn check<'tcx>(
                     diag.span_suggestion(
                         e.span,
                         "consider using `to_ne_bytes()`",
-                        format!("{}.to_ne_bytes()", arg.to_string()),
+                        format!("{}.to_ne_bytes()", arg),
                         Applicability::Unspecified,
                     );
                 },
diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs
index 1ff282de950..fb4d53e2845 100644
--- a/clippy_utils/src/higher.rs
+++ b/clippy_utils/src/higher.rs
@@ -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.
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index e43c5756021..7c61288f661 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -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"];
diff --git a/tests/ui/expect_fun_call.fixed b/tests/ui/expect_fun_call.fixed
index a756d1cf506..cf923a6a594 100644
--- a/tests/ui/expect_fun_call.fixed
+++ b/tests/ui/expect_fun_call.fixed
@@ -1,6 +1,7 @@
 // run-rustfix
 
 #![warn(clippy::expect_fun_call)]
+#![allow(clippy::to_string_in_format_args)]
 
 /// Checks implementation of the `EXPECT_FUN_CALL` lint
 
diff --git a/tests/ui/expect_fun_call.rs b/tests/ui/expect_fun_call.rs
index 60bbaa89d42..e6f252259df 100644
--- a/tests/ui/expect_fun_call.rs
+++ b/tests/ui/expect_fun_call.rs
@@ -1,6 +1,7 @@
 // run-rustfix
 
 #![warn(clippy::expect_fun_call)]
+#![allow(clippy::to_string_in_format_args)]
 
 /// Checks implementation of the `EXPECT_FUN_CALL` lint
 
diff --git a/tests/ui/expect_fun_call.stderr b/tests/ui/expect_fun_call.stderr
index 6dc796f5cee..ac48a06671c 100644
--- a/tests/ui/expect_fun_call.stderr
+++ b/tests/ui/expect_fun_call.stderr
@@ -1,5 +1,5 @@
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:28:26
+  --> $DIR/expect_fun_call.rs:29:26
    |
 LL |     with_none_and_format.expect(&format!("Error {}: fake error", error_code));
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
@@ -7,67 +7,67 @@ LL |     with_none_and_format.expect(&format!("Error {}: fake error", error_code
    = note: `-D clippy::expect-fun-call` implied by `-D warnings`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:31:26
+  --> $DIR/expect_fun_call.rs:32:26
    |
 LL |     with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:41:25
+  --> $DIR/expect_fun_call.rs:42:25
    |
 LL |     with_err_and_format.expect(&format!("Error {}: fake error", error_code));
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:44:25
+  --> $DIR/expect_fun_call.rs:45:25
    |
 LL |     with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:56:17
+  --> $DIR/expect_fun_call.rs:57:17
    |
 LL |     Some("foo").expect(format!("{} {}", 1, 2).as_ref());
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:77:21
+  --> $DIR/expect_fun_call.rs:78:21
    |
 LL |         Some("foo").expect(&get_string());
    |                     ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:78:21
+  --> $DIR/expect_fun_call.rs:79:21
    |
 LL |         Some("foo").expect(get_string().as_ref());
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:79:21
+  --> $DIR/expect_fun_call.rs:80:21
    |
 LL |         Some("foo").expect(get_string().as_str());
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:81:21
+  --> $DIR/expect_fun_call.rs:82:21
    |
 LL |         Some("foo").expect(get_static_str());
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:82:21
+  --> $DIR/expect_fun_call.rs:83:21
    |
 LL |         Some("foo").expect(get_non_static_str(&0));
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:86:16
+  --> $DIR/expect_fun_call.rs:87:16
    |
 LL |     Some(true).expect(&format!("key {}, {}", 1, 2));
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
 
 error: use of `expect` followed by a function call
-  --> $DIR/expect_fun_call.rs:92:17
+  --> $DIR/expect_fun_call.rs:93:17
    |
 LL |         opt_ref.expect(&format!("{:?}", opt_ref));
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))`
diff --git a/tests/ui/format.fixed b/tests/ui/format.fixed
index 5dd64140e81..73fc750511c 100644
--- a/tests/ui/format.fixed
+++ b/tests/ui/format.fixed
@@ -1,6 +1,6 @@
 // run-rustfix
 
-#![allow(clippy::print_literal, clippy::redundant_clone)]
+#![allow(clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args)]
 #![warn(clippy::useless_format)]
 
 struct Foo(pub String);
diff --git a/tests/ui/format.rs b/tests/ui/format.rs
index 4599fb5207e..2f4595650cb 100644
--- a/tests/ui/format.rs
+++ b/tests/ui/format.rs
@@ -1,6 +1,6 @@
 // run-rustfix
 
-#![allow(clippy::print_literal, clippy::redundant_clone)]
+#![allow(clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args)]
 #![warn(clippy::useless_format)]
 
 struct Foo(pub String);
diff --git a/tests/ui/format_args.fixed b/tests/ui/format_args.fixed
new file mode 100644
index 00000000000..8376566c4d6
--- /dev/null
+++ b/tests/ui/format_args.fixed
@@ -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!());
+}
diff --git a/tests/ui/format_args.rs b/tests/ui/format_args.rs
new file mode 100644
index 00000000000..164cc07066d
--- /dev/null
+++ b/tests/ui/format_args.rs
@@ -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!());
+}
diff --git a/tests/ui/format_args.stderr b/tests/ui/format_args.stderr
new file mode 100644
index 00000000000..9cfc97edeaf
--- /dev/null
+++ b/tests/ui/format_args.stderr
@@ -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
+
diff --git a/tests/ui/format_args_unfixable.rs b/tests/ui/format_args_unfixable.rs
new file mode 100644
index 00000000000..a8c06c2bde6
--- /dev/null
+++ b/tests/ui/format_args_unfixable.rs
@@ -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!());
+}
diff --git a/tests/ui/format_args_unfixable.stderr b/tests/ui/format_args_unfixable.stderr
new file mode 100644
index 00000000000..4476218ad58
--- /dev/null
+++ b/tests/ui/format_args_unfixable.stderr
@@ -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
+
diff --git a/tests/ui/to_string_in_display.rs b/tests/ui/to_string_in_display.rs
index eb8105c6b6d..3ccdcd1117b 100644
--- a/tests/ui/to_string_in_display.rs
+++ b/tests/ui/to_string_in_display.rs
@@ -1,5 +1,5 @@
 #![warn(clippy::to_string_in_display)]
-#![allow(clippy::inherent_to_string_shadow_display)]
+#![allow(clippy::inherent_to_string_shadow_display, clippy::to_string_in_format_args)]
 
 use std::fmt;