about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-08-19 15:55:05 +0000
committerbors <bors@rust-lang.org>2022-08-19 15:55:05 +0000
commit3e594de8ec79f89415c610f32c4f1b05a9e393b8 (patch)
tree0adf7006560ef4250a540f287730e6a08ed2f0fc
parent477c16d45bb93cc92611b56614ce2b823bffab8d (diff)
parent4f049f5a695b003168125a3251f5c0295e64f261 (diff)
downloadrust-3e594de8ec79f89415c610f32c4f1b05a9e393b8.tar.gz
rust-3e594de8ec79f89415c610f32c4f1b05a9e393b8.zip
Auto merge of #9349 - Alexendoo:format-args-expn, r=flip1995
Refactor `FormatArgsExpn`

It now for each format argument `{..}` has:
- The `Expr` it points to, and how it does so (named/named inline/numbered/implicit)
- The parsed `FormatSpec` (format trait/fill/align/etc., the precision/width and any value they point to)
- Many spans

The caller no longer needs to pair up arguments to their value, or separately interpret the `specs` `Expr`s when it isn't `None`

The gist is that it combines the result of [`rustc_parse_format::Parser`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse_format/struct.Parser.html) with the macro expansion itself

This unfortunately makes the code a bit longer, however we need to use both as neither have all the information we're after. `rustc_parse_format` doesn't have the information to resolve named arguments to their values. The macro expansion doesn't contain whether the positions are implicit/numbered/named, or the spans for format arguments

Wanted by #9233 and #8518 to be able to port the changes from #9040

Also fixes #8643, previously the format args seem to have been paired up with the wrong values somehow

changelog: [`format_in_format_args`]: Fix false positive due to misattributed arguments

r? `@flip1995`
cc `@nyurik`
-rw-r--r--clippy_lints/src/format.rs24
-rw-r--r--clippy_lints/src/format_args.rs31
-rw-r--r--clippy_lints/src/format_impl.rs11
-rw-r--r--clippy_lints/src/methods/uninit_assumed_init.rs4
-rw-r--r--clippy_lints/src/transmute/transmuting_null.rs4
-rw-r--r--clippy_utils/Cargo.toml1
-rw-r--r--clippy_utils/src/lib.rs15
-rw-r--r--clippy_utils/src/macros.rs674
-rw-r--r--clippy_utils/src/paths.rs1
-rw-r--r--tests/ui/format_args.fixed13
-rw-r--r--tests/ui/format_args.rs13
-rw-r--r--tests/ui/format_args.stderr42
12 files changed, 580 insertions, 253 deletions
diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs
index 925a8cb8dee..0c5851cdbed 100644
--- a/clippy_lints/src/format.rs
+++ b/clippy_lints/src/format.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
-use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::sugg::Sugg;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
@@ -56,29 +56,27 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
         };
 
         let mut applicability = Applicability::MachineApplicable;
-        if format_args.value_args.is_empty() {
-            match *format_args.format_string_parts {
+        if format_args.args.is_empty() {
+            match *format_args.format_string.parts {
                 [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
                 [_] => {
-                    if let Some(s_src) = snippet_opt(cx, format_args.format_string_span) {
-                        // Simulate macro expansion, converting {{ and }} to { and }.
-                        let s_expand = s_src.replace("{{", "{").replace("}}", "}");
-                        let sugg = format!("{}.to_string()", s_expand);
-                        span_useless_format(cx, call_site, sugg, applicability);
-                    }
+                    // Simulate macro expansion, converting {{ and }} to { and }.
+                    let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}");
+                    let sugg = format!("{}.to_string()", s_expand);
+                    span_useless_format(cx, call_site, sugg, applicability);
                 },
                 [..] => {},
             }
-        } else if let [value] = *format_args.value_args {
+        } else if let [arg] = &*format_args.args {
+            let value = arg.param.value;
             if_chain! {
-                if format_args.format_string_parts == [kw::Empty];
+                if format_args.format_string.parts == [kw::Empty];
                 if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
                     ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()),
                     ty::Str => true,
                     _ => false,
                 };
-                if let Some(args) = format_args.args();
-                if args.iter().all(|arg| arg.format_trait == sym::Display && !arg.has_string_formatting());
+                if !arg.format.has_string_formatting();
                 then {
                     let is_new_string = match value.kind {
                         ExprKind::Binary(..) => true,
diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs
index 1e6feaac26c..5347ff880ce 100644
--- a/clippy_lints/src/format_args.rs
+++ b/clippy_lints/src/format_args.rs
@@ -1,11 +1,12 @@
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::is_diag_trait_item;
-use clippy_utils::macros::{is_format_macro, FormatArgsArg, FormatArgsExpn};
+use clippy_utils::macros::{is_format_macro, FormatArgsExpn};
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::implements_trait;
 use if_chain::if_chain;
+use itertools::Itertools;
 use rustc_errors::Applicability;
-use rustc_hir::{Expr, ExprKind};
+use rustc_hir::{Expr, ExprKind, HirId};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty::adjustment::{Adjust, Adjustment};
 use rustc_middle::ty::Ty;
@@ -74,20 +75,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
             if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
             if is_format_macro(cx, 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.format_trait != sym::Display {
+                for arg in &format_args.args {
+                    if arg.format.has_string_formatting() {
                         continue;
                     }
-                    if arg.has_string_formatting() {
+                    if is_aliased(&format_args, arg.param.value.hir_id) {
                         continue;
                     }
-                    if is_aliased(&args, i) {
-                        continue;
-                    }
-                    check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.value);
-                    check_to_string_in_format_args(cx, name, arg.value);
+                    check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
+                    check_to_string_in_format_args(cx, name, arg.param.value);
                 }
             }
         }
@@ -167,12 +164,12 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
     }
 }
 
-// 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))
+// Returns true if `hir_id` is referred to by multiple format params
+fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
+    args.params()
+        .filter(|param| param.value.hir_id == hir_id)
+        .at_most_one()
+        .is_err()
 }
 
 fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs
index 04b5be6c80e..d8bc0bf08f2 100644
--- a/clippy_lints/src/format_impl.rs
+++ b/clippy_lints/src/format_impl.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
-use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn};
+use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
 use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
@@ -168,10 +168,9 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
         if let macro_def_id = outer_macro.def_id;
         if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
         if is_format_macro(cx, macro_def_id);
-        if let Some(args) = format_args.args();
         then {
-            for arg in args {
-                if arg.format_trait != impl_trait.name {
+            for arg in format_args.args {
+                if arg.format.r#trait != impl_trait.name {
                     continue;
                 }
                 check_format_arg_self(cx, expr, &arg, impl_trait);
@@ -180,11 +179,11 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
     }
 }
 
-fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) {
+fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
     // Handle multiple dereferencing of references e.g. &&self
     // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
     // Since the argument to fmt is itself a reference: &self
-    let reference = peel_ref_operators(cx, arg.value);
+    let reference = peel_ref_operators(cx, arg.param.value);
     let map = cx.tcx.hir();
     // Is the reference self?
     if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs
index 77d21f1d373..a1c6294737c 100644
--- a/clippy_lints/src/methods/uninit_assumed_init.rs
+++ b/clippy_lints/src/methods/uninit_assumed_init.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint;
-use clippy_utils::{is_expr_diagnostic_item, ty::is_uninit_value_valid_for_ty};
+use clippy_utils::{is_path_diagnostic_item, ty::is_uninit_value_valid_for_ty};
 use if_chain::if_chain;
 use rustc_hir as hir;
 use rustc_lint::LateContext;
@@ -12,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
     if_chain! {
         if let hir::ExprKind::Call(callee, args) = recv.kind;
         if args.is_empty();
-        if is_expr_diagnostic_item(cx, callee, sym::maybe_uninit_uninit);
+        if is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit);
         if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr));
         then {
             span_lint(
diff --git a/clippy_lints/src/transmute/transmuting_null.rs b/clippy_lints/src/transmute/transmuting_null.rs
index c4981124f39..d8e349af7af 100644
--- a/clippy_lints/src/transmute/transmuting_null.rs
+++ b/clippy_lints/src/transmute/transmuting_null.rs
@@ -1,6 +1,6 @@
 use clippy_utils::consts::{constant_context, Constant};
 use clippy_utils::diagnostics::span_lint;
-use clippy_utils::is_expr_diagnostic_item;
+use clippy_utils::is_path_diagnostic_item;
 use if_chain::if_chain;
 use rustc_ast::LitKind;
 use rustc_hir::{Expr, ExprKind};
@@ -45,7 +45,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
     // `std::mem::transmute(std::ptr::null::<i32>())`
     if_chain! {
         if let ExprKind::Call(func1, []) = arg.kind;
-        if is_expr_diagnostic_item(cx, func1, sym::ptr_null);
+        if is_path_diagnostic_item(cx, func1, sym::ptr_null);
         then {
             span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
             return true;
diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml
index a688050f63a..c36bca06507 100644
--- a/clippy_utils/Cargo.toml
+++ b/clippy_utils/Cargo.toml
@@ -7,6 +7,7 @@ publish = false
 [dependencies]
 arrayvec = { version = "0.7", default-features = false }
 if_chain = "1.0"
+itertools = "0.10.1"
 rustc-semver = "1.1"
 
 [features]
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index dc772e5efee..9308f085214 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -27,6 +27,7 @@ extern crate rustc_infer;
 extern crate rustc_lexer;
 extern crate rustc_lint;
 extern crate rustc_middle;
+extern crate rustc_parse_format;
 extern crate rustc_session;
 extern crate rustc_span;
 extern crate rustc_target;
@@ -371,15 +372,19 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
 
 /// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
 ///
-/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item.
+/// Please use `is_path_diagnostic_item` if the target is a diagnostic item.
 pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
     path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments))
 }
 
-/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given
-/// diagnostic item.
-pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
-    path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
+/// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if
+/// it matches the given diagnostic item.
+pub fn is_path_diagnostic_item<'tcx>(
+    cx: &LateContext<'_>,
+    maybe_path: &impl MaybePath<'tcx>,
+    diag_item: Symbol,
+) -> bool {
+    path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
 }
 
 /// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs
index a268e339bb1..37d8f1e458d 100644
--- a/clippy_utils/src/macros.rs
+++ b/clippy_utils/src/macros.rs
@@ -1,16 +1,21 @@
 #![allow(clippy::similar_names)] // `expr` and `expn`
 
+use crate::is_path_diagnostic_item;
+use crate::source::snippet_opt;
 use crate::visitors::expr_visitor_no_bodies;
 
 use arrayvec::ArrayVec;
-use if_chain::if_chain;
+use itertools::{izip, Either, Itertools};
 use rustc_ast::ast::LitKind;
 use rustc_hir::intravisit::Visitor;
 use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
+use rustc_lexer::unescape::unescape_literal;
+use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
 use rustc_lint::LateContext;
+use rustc_parse_format::{self as rpf, Alignment};
 use rustc_span::def_id::DefId;
 use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
-use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
+use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
 use std::ops::ControlFlow;
 
 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
@@ -332,121 +337,495 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
     }
 }
 
-/// A parsed `format_args!` expansion
+/// The format string doesn't exist in the HIR, so we reassemble it from source code
 #[derive(Debug)]
-pub struct FormatArgsExpn<'tcx> {
-    /// Span of the first argument, the format string
-    pub format_string_span: Span,
-    /// The format string split by formatted args like `{..}`
-    pub format_string_parts: Vec<Symbol>,
-    /// Values passed after the format string
-    pub value_args: Vec<&'tcx Expr<'tcx>>,
-    /// Each element is a `value_args` index and a formatting trait (e.g. `sym::Debug`)
-    pub formatters: Vec<(usize, Symbol)>,
-    /// List of `fmt::v1::Argument { .. }` expressions. If this is empty,
-    /// then `formatters` represents the format args (`{..}`).
-    /// If this is non-empty, it represents the format args, and the `position`
-    /// parameters within the struct expressions are indexes of `formatters`.
-    pub specs: Vec<&'tcx Expr<'tcx>>,
+pub struct FormatString {
+    /// Span of the whole format string literal, including `[r#]"`.
+    pub span: Span,
+    /// Snippet of the whole format string literal, including `[r#]"`.
+    pub snippet: String,
+    /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
+    pub style: Option<usize>,
+    /// The unescaped value of the format string, e.g. `"val – {}"` for the literal
+    /// `"val \u{2013} {}"`.
+    pub unescaped: String,
+    /// The format string split by format args like `{..}`.
+    pub parts: Vec<Symbol>,
 }
 
-impl<'tcx> FormatArgsExpn<'tcx> {
-    /// Parses an expanded `format_args!` or `format_args_nl!` invocation
-    pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
-        macro_backtrace(expr.span).find(|macro_call| {
-            matches!(
-                cx.tcx.item_name(macro_call.def_id),
-                sym::const_format_args | sym::format_args | sym::format_args_nl
-            )
-        })?;
-        let mut format_string_span: Option<Span> = None;
-        let mut format_string_parts: Vec<Symbol> = Vec::new();
-        let mut value_args: Vec<&Expr<'_>> = Vec::new();
-        let mut formatters: Vec<(usize, Symbol)> = Vec::new();
-        let mut specs: Vec<&Expr<'_>> = Vec::new();
-        expr_visitor_no_bodies(|e| {
-            // if we're still inside of the macro definition...
-            if e.span.ctxt() == expr.span.ctxt() {
-                // ArgumentV1::new_<format_trait>(<value>)
-                if_chain! {
-                    if let ExprKind::Call(callee, [val]) = e.kind;
-                    if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind;
-                    if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
-                    if path.segments.last().unwrap().ident.name == sym::ArgumentV1;
-                    if seg.ident.name.as_str().starts_with("new_");
-                    then {
-                        let val_idx = if_chain! {
-                            if val.span.ctxt() == expr.span.ctxt();
-                            if let ExprKind::Field(_, field) = val.kind;
-                            if let Ok(idx) = field.name.as_str().parse();
-                            then {
-                                // tuple index
-                                idx
-                            } else {
-                                // assume the value expression is passed directly
-                                formatters.len()
-                            }
-                        };
-                        let fmt_trait = match seg.ident.name.as_str() {
-                            "new_display" => "Display",
-                            "new_debug" => "Debug",
-                            "new_lower_exp" => "LowerExp",
-                            "new_upper_exp" => "UpperExp",
-                            "new_octal" => "Octal",
-                            "new_pointer" => "Pointer",
-                            "new_binary" => "Binary",
-                            "new_lower_hex" => "LowerHex",
-                            "new_upper_hex" => "UpperHex",
-                            _ => unreachable!(),
-                        };
-                        formatters.push((val_idx, Symbol::intern(fmt_trait)));
-                    }
-                }
-                if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind {
-                    if path.segments.last().unwrap().ident.name == sym::Argument {
-                        specs.push(e);
-                    }
+impl FormatString {
+    fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
+        // format_args!(r"a {} b \", 1);
+        //
+        // expands to
+        //
+        // ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
+        //      &[::core::fmt::ArgumentV1::new_display(&1)]);
+        //
+        // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
+        let span = pieces.span;
+        let snippet = snippet_opt(cx, span)?;
+
+        let (inner, style) = match tokenize(&snippet).next()?.kind {
+            TokenKind::Literal { kind, .. } => {
+                let style = match kind {
+                    LiteralKind::Str { .. } => None,
+                    LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
+                    _ => return None,
+                };
+
+                let start = style.map_or(1, |n| 2 + n);
+                let end = snippet.len() - style.map_or(1, |n| 1 + n);
+
+                (&snippet[start..end], style)
+            },
+            _ => return None,
+        };
+
+        let mode = if style.is_some() {
+            unescape::Mode::RawStr
+        } else {
+            unescape::Mode::Str
+        };
+
+        let mut unescaped = String::with_capacity(inner.len());
+        unescape_literal(inner, mode, &mut |_, ch| {
+            unescaped.push(ch.unwrap());
+        });
+
+        let mut parts = Vec::new();
+        expr_visitor_no_bodies(|expr| {
+            if let ExprKind::Lit(lit) = &expr.kind {
+                if let LitKind::Str(symbol, _) = lit.node {
+                    parts.push(symbol);
                 }
-                // walk through the macro expansion
-                return true;
             }
-            // assume that the first expr with a differing context represents
-            // (and has the span of) the format string
-            if format_string_span.is_none() {
-                format_string_span = Some(e.span);
-                let span = e.span;
-                // walk the expr and collect string literals which are format string parts
-                expr_visitor_no_bodies(|e| {
-                    if e.span.ctxt() != span.ctxt() {
-                        // defensive check, probably doesn't happen
-                        return false;
-                    }
-                    if let ExprKind::Lit(lit) = &e.kind {
-                        if let LitKind::Str(symbol, _s) = lit.node {
-                            format_string_parts.push(symbol);
-                        }
-                    }
-                    true
-                })
-                .visit_expr(e);
+
+            true
+        })
+        .visit_expr(pieces);
+
+        Some(Self {
+            span,
+            snippet,
+            style,
+            unescaped,
+            parts,
+        })
+    }
+}
+
+struct FormatArgsValues<'tcx> {
+    /// See `FormatArgsExpn::value_args`
+    value_args: Vec<&'tcx Expr<'tcx>>,
+    /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
+    /// `value_args`
+    pos_to_value_index: Vec<usize>,
+    /// Used to check if a value is declared inline & to resolve `InnerSpan`s.
+    format_string_span: SpanData,
+}
+
+impl<'tcx> FormatArgsValues<'tcx> {
+    fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
+        let mut pos_to_value_index = Vec::new();
+        let mut value_args = Vec::new();
+        expr_visitor_no_bodies(|expr| {
+            if expr.span.ctxt() == args.span.ctxt() {
+                // ArgumentV1::new_<format_trait>(<val>)
+                // ArgumentV1::from_usize(<val>)
+                if let ExprKind::Call(callee, [val]) = expr.kind
+                    && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
+                    && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind
+                    && path.segments.last().unwrap().ident.name == sym::ArgumentV1
+                {
+                    let val_idx = if val.span.ctxt() == expr.span.ctxt()
+                        && let ExprKind::Field(_, field) = val.kind
+                        && let Ok(idx) = field.name.as_str().parse()
+                    {
+                        // tuple index
+                        idx
+                    } else {
+                        // assume the value expression is passed directly
+                        pos_to_value_index.len()
+                    };
+
+                    pos_to_value_index.push(val_idx);
+                }
+
+                true
             } else {
-                // assume that any further exprs with a differing context are value args
-                value_args.push(e);
+                // assume that any expr with a differing span is a value
+                value_args.push(expr);
+
+                false
             }
-            // don't walk anything not from the macro expansion (e.a. inputs)
-            false
         })
-        .visit_expr(expr);
-        Some(FormatArgsExpn {
-            format_string_span: format_string_span?,
-            format_string_parts,
+        .visit_expr(args);
+
+        Self {
             value_args,
-            formatters,
-            specs,
+            pos_to_value_index,
+            format_string_span,
+        }
+    }
+}
+
+/// The positions of a format argument's value, precision and width
+///
+/// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
+#[derive(Debug, Default, Copy, Clone)]
+struct ParamPosition {
+    /// The position stored in `rt::v1::Argument::position`.
+    value: usize,
+    /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
+    width: Option<usize>,
+    /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
+    precision: Option<usize>,
+}
+
+/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
+fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
+    fn parse_count(expr: &Expr<'_>) -> Option<usize> {
+        // ::core::fmt::rt::v1::Count::Param(1usize),
+        if let ExprKind::Call(ctor, [val]) = expr.kind
+            && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
+            && path.segments.last()?.ident.name == sym::Param
+            && let ExprKind::Lit(lit) = &val.kind
+            && let LitKind::Int(pos, _) = lit.node
+        {
+            Some(pos as usize)
+        } else {
+            None
+        }
+    }
+
+    if let ExprKind::AddrOf(.., array) = fmt_arg.kind
+        && let ExprKind::Array(specs) = array.kind
+    {
+        Some(specs.iter().map(|spec| {
+            let mut position = ParamPosition::default();
+
+            // ::core::fmt::rt::v1::Argument {
+            //     position: 0usize,
+            //     format: ::core::fmt::rt::v1::FormatSpec {
+            //         ..
+            //         precision: ::core::fmt::rt::v1::Count::Implied,
+            //         width: ::core::fmt::rt::v1::Count::Implied,
+            //     },
+            // }
+
+            // TODO: this can be made much nicer next sync with `Visitor::visit_expr_field`
+            if let ExprKind::Struct(_, fields, _) = spec.kind {
+                for field in fields {
+                    match (field.ident.name, &field.expr.kind) {
+                        (sym::position, ExprKind::Lit(lit)) => {
+                            if let LitKind::Int(pos, _) = lit.node {
+                                position.value = pos as usize;
+                            }
+                        },
+                        (sym::format, &ExprKind::Struct(_, spec_fields, _)) => {
+                            for spec_field in spec_fields {
+                                match spec_field.ident.name {
+                                    sym::precision => {
+                                        position.precision = parse_count(spec_field.expr);
+                                    },
+                                    sym::width => {
+                                        position.width = parse_count(spec_field.expr);
+                                    },
+                                    _ => {},
+                                }
+                            }
+                        },
+                        _ => {},
+                    }
+                }
+            }
+
+            position
+        }))
+    } else {
+        None
+    }
+}
+
+/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
+fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
+    Span::new(
+        base.lo + BytePos::from_usize(inner.start),
+        base.lo + BytePos::from_usize(inner.end),
+        base.ctxt,
+        base.parent,
+    )
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FormatParamKind {
+    /// An implicit parameter , such as `{}` or `{:?}`.
+    Implicit,
+    /// A parameter with an explicit number, or an asterisk precision. e.g. `{1}`, `{0:?}`,
+    /// `{:.0$}` or `{:.*}`.
+    Numbered,
+    /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
+    Named(Symbol),
+    /// An implicit named paramter, such as the `y` in `format!("{y}")`.
+    NamedInline(Symbol),
+}
+
+/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
+///
+/// ```
+/// let precision = 2;
+/// format!("{:.precision$}", 0.1234);
+/// ```
+///
+/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
+/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
+#[derive(Debug, Copy, Clone)]
+pub struct FormatParam<'tcx> {
+    /// The expression this parameter refers to.
+    pub value: &'tcx Expr<'tcx>,
+    /// How this paramter refers to its `value`.
+    pub kind: FormatParamKind,
+    /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
+    ///
+    /// ```text
+    /// format!("{}, {  }, {0}, {name}", ...);
+    ///          ^    ~~    ~    ~~~~
+    /// ```
+    pub span: Span,
+}
+
+impl<'tcx> FormatParam<'tcx> {
+    fn new(
+        mut kind: FormatParamKind,
+        position: usize,
+        inner: rpf::InnerSpan,
+        values: &FormatArgsValues<'tcx>,
+    ) -> Option<Self> {
+        let value_index = *values.pos_to_value_index.get(position)?;
+        let value = *values.value_args.get(value_index)?;
+        let span = span_from_inner(values.format_string_span, inner);
+
+        // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
+        // into the format string
+        if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
+            kind = FormatParamKind::NamedInline(name);
+        }
+
+        Some(Self { value, kind, span })
+    }
+}
+
+/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
+/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
+#[derive(Debug, Copy, Clone)]
+pub enum Count<'tcx> {
+    /// Specified with a literal number, stores the value.
+    Is(usize, Span),
+    /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
+    /// `FormatParamKind::Numbered`.
+    Param(FormatParam<'tcx>),
+    /// Not specified.
+    Implied,
+}
+
+impl<'tcx> Count<'tcx> {
+    fn new(
+        count: rpf::Count<'_>,
+        position: Option<usize>,
+        inner: Option<rpf::InnerSpan>,
+        values: &FormatArgsValues<'tcx>,
+    ) -> Option<Self> {
+        Some(match count {
+            rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)),
+            rpf::Count::CountIsName(name, span) => Self::Param(FormatParam::new(
+                FormatParamKind::Named(Symbol::intern(name)),
+                position?,
+                span,
+                values,
+            )?),
+            rpf::Count::CountIsParam(_) => {
+                Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
+            },
+            rpf::Count::CountImplied => Self::Implied,
+        })
+    }
+
+    pub fn is_implied(self) -> bool {
+        matches!(self, Count::Implied)
+    }
+
+    pub fn param(self) -> Option<FormatParam<'tcx>> {
+        match self {
+            Count::Param(param) => Some(param),
+            _ => None,
+        }
+    }
+}
+
+/// Specification for the formatting of an argument in the format string. See
+/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
+#[derive(Debug)]
+pub struct FormatSpec<'tcx> {
+    /// Optionally specified character to fill alignment with.
+    pub fill: Option<char>,
+    /// Optionally specified alignment.
+    pub align: Alignment,
+    /// Packed version of various flags provided, see [`rustc_parse_format::Flag`].
+    pub flags: u32,
+    /// Represents either the maximum width or the integer precision.
+    pub precision: Count<'tcx>,
+    /// The minimum width, will be padded according to `width`/`align`
+    pub width: Count<'tcx>,
+    /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
+    /// `{:?}`.
+    pub r#trait: Symbol,
+    pub trait_span: Option<Span>,
+}
+
+impl<'tcx> FormatSpec<'tcx> {
+    fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
+        Some(Self {
+            fill: spec.fill,
+            align: spec.align,
+            flags: spec.flags,
+            precision: Count::new(spec.precision, positions.precision, spec.precision_span, values)?,
+            width: Count::new(spec.width, positions.width, spec.width_span, values)?,
+            r#trait: match spec.ty {
+                "" => sym::Display,
+                "?" => sym::Debug,
+                "o" => sym!(Octal),
+                "x" => sym!(LowerHex),
+                "X" => sym!(UpperHex),
+                "p" => sym::Pointer,
+                "b" => sym!(Binary),
+                "e" => sym!(LowerExp),
+                "E" => sym!(UpperExp),
+                _ => return None,
+            },
+            trait_span: spec
+                .ty_span
+                .map(|span| span_from_inner(values.format_string_span, span)),
         })
     }
 
-    /// Finds a nested call to `format_args!` within a `format!`-like macro call
+    /// Returns true if this format spec would change the contents of a string when formatted
+    pub fn has_string_formatting(&self) -> bool {
+        self.r#trait != sym::Display || !self.width.is_implied() || !self.precision.is_implied()
+    }
+}
+
+/// A format argument, such as `{}`, `{foo:?}`.
+#[derive(Debug)]
+pub struct FormatArg<'tcx> {
+    /// The parameter the argument refers to.
+    pub param: FormatParam<'tcx>,
+    /// How to format `param`.
+    pub format: FormatSpec<'tcx>,
+    /// span of the whole argument, `{..}`.
+    pub span: Span,
+}
+
+/// A parsed `format_args!` expansion.
+#[derive(Debug)]
+pub struct FormatArgsExpn<'tcx> {
+    /// The format string literal.
+    pub format_string: FormatString,
+    // The format arguments, such as `{:?}`.
+    pub args: Vec<FormatArg<'tcx>>,
+    /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
+    /// include this added newline.
+    pub newline: bool,
+    /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
+    /// `format!("{x} {} {y}", 1, z + 2)`.
+    value_args: Vec<&'tcx Expr<'tcx>>,
+}
+
+impl<'tcx> FormatArgsExpn<'tcx> {
+    pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
+        let macro_name = macro_backtrace(expr.span)
+            .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
+            .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
+        let newline = macro_name == sym::format_args_nl;
+
+        // ::core::fmt::Arguments::new_v1(pieces, args)
+        // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
+        if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
+            && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
+            && is_path_diagnostic_item(cx, ty, sym::Arguments)
+            && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
+        {
+            let format_string = FormatString::new(cx, pieces)?;
+
+            let mut parser = rpf::Parser::new(
+                &format_string.unescaped,
+                format_string.style,
+                Some(format_string.snippet.clone()),
+                // `format_string.unescaped` does not contain the appended newline
+                false,
+                rpf::ParseMode::Format,
+            );
+
+            let parsed_args = parser
+                .by_ref()
+                .filter_map(|piece| match piece {
+                    rpf::Piece::NextArgument(a) => Some(a),
+                    rpf::Piece::String(_) => None,
+                })
+                .collect_vec();
+            if !parser.errors.is_empty() {
+                return None;
+            }
+
+            let positions = if let Some(fmt_arg) = rest.first() {
+                // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
+                // them.
+
+                Either::Left(parse_rt_fmt(fmt_arg)?)
+            } else {
+                // If no format specs are given, the positions are in the given order and there are
+                // no `precision`/`width`s to consider.
+
+                Either::Right((0..).map(|n| ParamPosition {
+                    value: n,
+                    width: None,
+                    precision: None,
+                }))
+            };
+
+            let values = FormatArgsValues::new(args, format_string.span.data());
+
+            let args = izip!(positions, parsed_args, parser.arg_places)
+                .map(|(position, parsed_arg, arg_span)| {
+                    Some(FormatArg {
+                        param: FormatParam::new(
+                            match parsed_arg.position {
+                                rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
+                                rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
+                                // NamedInline is handled by `FormatParam::new()`
+                                rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
+                            },
+                            position.value,
+                            parsed_arg.position_span,
+                            &values,
+                        )?,
+                        format: FormatSpec::new(parsed_arg.format, position, &values)?,
+                        span: span_from_inner(values.format_string_span, arg_span),
+                    })
+                })
+                .collect::<Option<Vec<_>>>()?;
+
+            Some(Self {
+                format_string,
+                args,
+                value_args: values.value_args,
+                newline,
+            })
+        } else {
+            None
+        }
+    }
+
     pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
         let mut format_args = None;
         expr_visitor_no_bodies(|e| {
@@ -466,88 +845,23 @@ impl<'tcx> FormatArgsExpn<'tcx> {
         format_args
     }
 
-    /// Returns a vector of `FormatArgsArg`.
-    pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
-        if self.specs.is_empty() {
-            let args = std::iter::zip(&self.value_args, &self.formatters)
-                .map(|(value, &(_, format_trait))| FormatArgsArg {
-                    value,
-                    format_trait,
-                    spec: None,
-                })
-                .collect();
-            return Some(args);
-        }
-        self.specs
-            .iter()
-            .map(|spec| {
-                if_chain! {
-                    // struct `core::fmt::rt::v1::Argument`
-                    if let ExprKind::Struct(_, fields, _) = spec.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;
-                    if let Ok(i) = usize::try_from(position);
-                    if let Some(&(j, format_trait)) = self.formatters.get(i);
-                    then {
-                        Some(FormatArgsArg {
-                            value: self.value_args[j],
-                            format_trait,
-                            spec: Some(spec),
-                        })
-                    } else {
-                        None
-                    }
-                }
-            })
-            .collect()
-    }
-
     /// Source callsite span of all inputs
     pub fn inputs_span(&self) -> Span {
         match *self.value_args {
-            [] => self.format_string_span,
+            [] => self.format_string.span,
             [.., last] => self
-                .format_string_span
-                .to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())),
+                .format_string
+                .span
+                .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
         }
     }
-}
 
-/// 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 format_trait: Symbol,
-    /// An element of `specs`
-    pub spec: 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.spec.map_or(false, |spec| {
-            // `!` because these conditions check that `self` is unformatted.
-            !if_chain! {
-                // struct `core::fmt::rt::v1::Argument`
-                if let ExprKind::Struct(_, fields, _) = spec.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;
-                if subfields.iter().all(|field| match field.ident.name {
-                    sym::precision | sym::width => match field.expr.kind {
-                        ExprKind::Path(QPath::Resolved(_, path)) => {
-                            path.segments.last().unwrap().ident.name == sym::Implied
-                        }
-                        _ => false,
-                    }
-                    _ => true,
-                });
-                then { true } else { false }
-            }
-        })
+    /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
+    pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
+        self.args
+            .iter()
+            .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
+            .flatten()
     }
 }
 
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 8d697a301c4..199a3ab12ae 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -71,7 +71,6 @@ pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
 pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"];
 pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"];
 pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
 pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
 #[cfg(feature = "internal")]
 pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
diff --git a/tests/ui/format_args.fixed b/tests/ui/format_args.fixed
index 69b5e1c722e..4322891db76 100644
--- a/tests/ui/format_args.fixed
+++ b/tests/ui/format_args.fixed
@@ -1,8 +1,6 @@
 // run-rustfix
 
-#![allow(unreachable_code)]
-#![allow(unused_macros)]
-#![allow(unused_variables)]
+#![allow(unused)]
 #![allow(clippy::assertions_on_constants)]
 #![allow(clippy::eq_op)]
 #![allow(clippy::print_literal)]
@@ -115,3 +113,12 @@ fn main() {
     // https://github.com/rust-lang/rust-clippy/issues/7903
     println!("{foo}{foo:?}", foo = "foo".to_string());
 }
+
+fn issue8643(vendor_id: usize, product_id: usize, name: &str) {
+    println!(
+        "{:<9}  {:<10}  {}",
+        format!("0x{:x}", vendor_id),
+        format!("0x{:x}", product_id),
+        name
+    );
+}
diff --git a/tests/ui/format_args.rs b/tests/ui/format_args.rs
index 3a434c5bf00..61ad04612cd 100644
--- a/tests/ui/format_args.rs
+++ b/tests/ui/format_args.rs
@@ -1,8 +1,6 @@
 // run-rustfix
 
-#![allow(unreachable_code)]
-#![allow(unused_macros)]
-#![allow(unused_variables)]
+#![allow(unused)]
 #![allow(clippy::assertions_on_constants)]
 #![allow(clippy::eq_op)]
 #![allow(clippy::print_literal)]
@@ -115,3 +113,12 @@ fn main() {
     // https://github.com/rust-lang/rust-clippy/issues/7903
     println!("{foo}{foo:?}", foo = "foo".to_string());
 }
+
+fn issue8643(vendor_id: usize, product_id: usize, name: &str) {
+    println!(
+        "{:<9}  {:<10}  {}",
+        format!("0x{:x}", vendor_id),
+        format!("0x{:x}", product_id),
+        name
+    );
+}
diff --git a/tests/ui/format_args.stderr b/tests/ui/format_args.stderr
index c0cbca50795..0aca1c1a0df 100644
--- a/tests/ui/format_args.stderr
+++ b/tests/ui/format_args.stderr
@@ -1,5 +1,5 @@
 error: `to_string` applied to a type that implements `Display` in `format!` args
-  --> $DIR/format_args.rs:76:72
+  --> $DIR/format_args.rs:74:72
    |
 LL |     let _ = format!("error: something failed at {}", Location::caller().to_string());
    |                                                                        ^^^^^^^^^^^^ help: remove this
@@ -7,121 +7,121 @@ LL |     let _ = format!("error: something failed at {}", Location::caller().to_
    = 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:80:27
+  --> $DIR/format_args.rs:78: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:85:27
+  --> $DIR/format_args.rs:83: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:87:63
+  --> $DIR/format_args.rs:85: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:88:65
+  --> $DIR/format_args.rs:86: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:89:64
+  --> $DIR/format_args.rs:87: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:90:66
+  --> $DIR/format_args.rs:88: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:91:77
+  --> $DIR/format_args.rs:89: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:92:70
+  --> $DIR/format_args.rs:90: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:93:73
+  --> $DIR/format_args.rs:91: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:94:73
+  --> $DIR/format_args.rs:92: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:95:63
+  --> $DIR/format_args.rs:93: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:96:20
+  --> $DIR/format_args.rs:94: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:97:20
+  --> $DIR/format_args.rs:95: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:98:24
+  --> $DIR/format_args.rs:96: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:99:20
+  --> $DIR/format_args.rs:97: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:100:20
+  --> $DIR/format_args.rs:98:20
    |
 LL |     println!("{}", x_ref.to_string());
    |                    ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
 
 error: `to_string` applied to a type that implements `Display` in `println!` args
-  --> $DIR/format_args.rs:102:39
+  --> $DIR/format_args.rs:100:39
    |
 LL |     println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
    |                                       ^^^^^^^^^^^^ help: remove this
 
 error: `to_string` applied to a type that implements `Display` in `println!` args
-  --> $DIR/format_args.rs:103:52
+  --> $DIR/format_args.rs:101:52
    |
 LL |     println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
    |                                                    ^^^^^^^^^^^^ help: remove this
 
 error: `to_string` applied to a type that implements `Display` in `println!` args
-  --> $DIR/format_args.rs:104:39
+  --> $DIR/format_args.rs:102:39
    |
 LL |     println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
    |                                       ^^^^^^^^^^^^ help: remove this
 
 error: `to_string` applied to a type that implements `Display` in `println!` args
-  --> $DIR/format_args.rs:105:52
+  --> $DIR/format_args.rs:103:52
    |
 LL |     println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
    |                                                    ^^^^^^^^^^^^ help: remove this