From 5a71bbdf3faeedfe5227aecc2a97e566cbbbaf70 Mon Sep 17 00:00:00 2001
From: Yuri Astrakhan <YuriAstrakhan@gmail.com>
Date: Wed, 14 Sep 2022 12:25:48 -0400
Subject: [PATCH] new uninlined_format_args lint to inline explicit arguments

Implement https://github.com/rust-lang/rust-clippy/issues/8368 - a new
lint to inline format arguments such as `print!("{}", var)` into
`print!("{var}")`.

code | suggestion | comment
---|---|---
`print!("{}", var)` | `print!("{var}")` |  simple variables
`print!("{0}", var)` | `print!("{var}")` |  positional variables
`print!("{v}", v=var)` | `print!("{var}")` |  named variables
`print!("{0} {0}", var)` | `print!("{var} {var}")` |  aliased variables
`print!("{0:1$}", var, width)` | `print!("{var:width$}")` |  width
support
`print!("{0:.1$}", var, prec)` | `print!("{var:.prec$}")` |  precision
support
`print!("{:.*}", prec, var)` | `print!("{var:.prec$}")` |  asterisk
support

code | suggestion | comment
---|---|---
`print!("{0}={1}", var, 1+2)` | `print!("{var}={0}", 1+2)` | Format
string uses an indexed argument that cannot be inlined.  Supporting this
case requires re-indexing of the format string.

changelog: [`uninlined_format_args`]: A new lint to inline format
arguments, i.e. `print!("{}", var)` into `print!("{var}")`
---
 CHANGELOG.md                              |   1 +
 clippy_lints/src/format_args.rs           | 140 +++-
 clippy_lints/src/lib.register_lints.rs    |   1 +
 clippy_lints/src/lib.register_pedantic.rs |   1 +
 clippy_lints/src/lib.rs                   |   2 +-
 clippy_lints/src/utils/conf.rs            |   2 +-
 clippy_lints/src/write.rs                 |  11 +-
 clippy_utils/src/macros.rs                |  69 +-
 clippy_utils/src/msrvs.rs                 |   1 +
 clippy_utils/src/source.rs                |  10 +
 src/docs.rs                               |   1 +
 src/docs/uninlined_format_args.txt        |  36 +
 tests/ui/uninlined_format_args.fixed      | 168 +++++
 tests/ui/uninlined_format_args.rs         | 171 +++++
 tests/ui/uninlined_format_args.stderr     | 866 ++++++++++++++++++++++
 15 files changed, 1451 insertions(+), 29 deletions(-)
 create mode 100644 src/docs/uninlined_format_args.txt
 create mode 100644 tests/ui/uninlined_format_args.fixed
 create mode 100644 tests/ui/uninlined_format_args.rs
 create mode 100644 tests/ui/uninlined_format_args.stderr

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a56be486217..325b9bd8469 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4274,6 +4274,7 @@ Released 2018-09-13
 [`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
 [`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
 [`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec
+[`uninlined_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
 [`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
 [`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
 [`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs
index 192f6258aa5..89d81bdd485 100644
--- a/clippy_lints/src/format_args.rs
+++ b/clippy_lints/src/format_args.rs
@@ -1,16 +1,18 @@
 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, FormatArgsExpn};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::macros::FormatParamKind::{Implicit, Named, Numbered, Starred};
+use clippy_utils::macros::{is_format_macro, FormatArgsExpn, FormatParam, FormatParamUsage};
+use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
 use clippy_utils::ty::implements_trait;
+use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs};
 use if_chain::if_chain;
 use itertools::Itertools;
 use rustc_errors::Applicability;
-use rustc_hir::{Expr, ExprKind, HirId};
+use rustc_hir::{Expr, ExprKind, HirId, Path, QPath};
 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_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
 
 declare_clippy_lint! {
@@ -64,7 +66,67 @@ declare_clippy_lint! {
     "`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]);
+declare_clippy_lint! {
+    /// ### What it does
+    /// Detect when a variable is not inlined in a format string,
+    /// and suggests to inline it.
+    ///
+    /// ### Why is this bad?
+    /// Non-inlined code is slightly more difficult to read and understand,
+    /// as it requires arguments to be matched against the format string.
+    /// The inlined syntax, where allowed, is simpler.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # let var = 42;
+    /// # let width = 1;
+    /// # let prec = 2;
+    /// format!("{}", var);
+    /// format!("{v:?}", v = var);
+    /// format!("{0} {0}", var);
+    /// format!("{0:1$}", var, width);
+    /// format!("{:.*}", prec, var);
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// # let var = 42;
+    /// # let width = 1;
+    /// # let prec = 2;
+    /// format!("{var}");
+    /// format!("{var:?}");
+    /// format!("{var} {var}");
+    /// format!("{var:width$}");
+    /// format!("{var:.prec$}");
+    /// ```
+    ///
+    /// ### Known Problems
+    ///
+    /// There may be a false positive if the format string is expanded from certain proc macros:
+    ///
+    /// ```ignore
+    /// println!(indoc!("{}"), var);
+    /// ```
+    ///
+    /// If a format string contains a numbered argument that cannot be inlined
+    /// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
+    #[clippy::version = "1.65.0"]
+    pub UNINLINED_FORMAT_ARGS,
+    pedantic,
+    "using non-inlined variables in `format!` calls"
+}
+
+impl_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, UNINLINED_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
+
+pub struct FormatArgs {
+    msrv: Option<RustcVersion>,
+}
+
+impl FormatArgs {
+    #[must_use]
+    pub fn new(msrv: Option<RustcVersion>) -> Self {
+        Self { msrv }
+    }
+}
 
 impl<'tcx> LateLintPass<'tcx> for FormatArgs {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
@@ -86,9 +148,73 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
                     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);
                 }
+                if meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
+                    check_uninlined_args(cx, &format_args, outermost_expn_data.call_site);
+                }
             }
         }
     }
+
+    extract_msrv_attr!(LateContext);
+}
+
+fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_site: Span) {
+    if args.format_string.span.from_expansion() {
+        return;
+    }
+
+    let mut fixes = Vec::new();
+    // If any of the arguments are referenced by an index number,
+    // and that argument is not a simple variable and cannot be inlined,
+    // we cannot remove any other arguments in the format string,
+    // because the index numbers might be wrong after inlining.
+    // Example of an un-inlinable format:  print!("{}{1}", foo, 2)
+    if !args.params().all(|p| check_one_arg(cx, &p, &mut fixes)) || fixes.is_empty() {
+        return;
+    }
+
+    // FIXME: Properly ignore a rare case where the format string is wrapped in a macro.
+    // Example:  `format!(indoc!("{}"), foo);`
+    // If inlined, they will cause a compilation error:
+    //     > to avoid ambiguity, `format_args!` cannot capture variables
+    //     > when the format string is expanded from a macro
+    // @Alexendoo explanation:
+    //     > indoc! is a proc macro that is producing a string literal with its span
+    //     > set to its input it's not marked as from expansion, and since it's compatible
+    //     > tokenization wise clippy_utils::is_from_proc_macro wouldn't catch it either
+    // This might be a relatively expensive test, so do it only we are ready to replace.
+    // See more examples in tests/ui/uninlined_format_args.rs
+
+    span_lint_and_then(
+        cx,
+        UNINLINED_FORMAT_ARGS,
+        call_site,
+        "variables can be used directly in the `format!` string",
+        |diag| {
+            diag.multipart_suggestion("change this to", fixes, Applicability::MachineApplicable);
+        },
+    );
+}
+
+fn check_one_arg(cx: &LateContext<'_>, param: &FormatParam<'_>, fixes: &mut Vec<(Span, String)>) -> bool {
+    if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
+        && let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
+        && let Path { span, segments, .. } = path
+        && let [segment] = segments
+    {
+        let replacement = match param.usage {
+            FormatParamUsage::Argument => segment.ident.name.to_string(),
+            FormatParamUsage::Width => format!("{}$", segment.ident.name),
+            FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
+        };
+        fixes.push((param.span, replacement));
+        let arg_span = expand_past_previous_comma(cx, *span);
+        fixes.push((arg_span, String::new()));
+        true  // successful inlining, continue checking
+    } else {
+        // if we can't inline a numbered argument, we can't continue
+        param.kind != Numbered
+    }
 }
 
 fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
@@ -170,7 +296,7 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
     }
 }
 
-// Returns true if `hir_id` is referred to by multiple format params
+/// 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)
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index 02fcc8de507..c6ac0178ffa 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -159,6 +159,7 @@ store.register_lints(&[
     format::USELESS_FORMAT,
     format_args::FORMAT_IN_FORMAT_ARGS,
     format_args::TO_STRING_IN_FORMAT_ARGS,
+    format_args::UNINLINED_FORMAT_ARGS,
     format_impl::PRINT_IN_FORMAT_IMPL,
     format_impl::RECURSIVE_FORMAT_IMPL,
     format_push_string::FORMAT_PUSH_STRING,
diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs
index 03c3c202e0a..4eaabfbcc5f 100644
--- a/clippy_lints/src/lib.register_pedantic.rs
+++ b/clippy_lints/src/lib.register_pedantic.rs
@@ -29,6 +29,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
     LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
     LintId::of(excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
     LintId::of(excessive_bools::STRUCT_EXCESSIVE_BOOLS),
+    LintId::of(format_args::UNINLINED_FORMAT_ARGS),
     LintId::of(functions::MUST_USE_CANDIDATE),
     LintId::of(functions::TOO_MANY_LINES),
     LintId::of(if_not_else::IF_NOT_ELSE),
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 8a2a1682eca..73349ace882 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -855,7 +855,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         ))
     });
     store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
-    store.register_late_pass(move || Box::new(format_args::FormatArgs));
+    store.register_late_pass(move || Box::new(format_args::FormatArgs::new(msrv)));
     store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
     store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
     store.register_late_pass(|| Box::new(needless_late_init::NeedlessLateInit));
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 6f5a638be12..a8265b50f27 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -213,7 +213,7 @@ define_Conf! {
     ///
     /// Suppress lints whenever the suggested change would cause breakage for other crates.
     (avoid_breaking_exported_api: bool = true),
-    /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED.
+    /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS.
     ///
     /// The minimum rust version that the project supports
     (msrv: Option<String> = None),
diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs
index 06e7d701701..3d3686604b7 100644
--- a/clippy_lints/src/write.rs
+++ b/clippy_lints/src/write.rs
@@ -1,12 +1,12 @@
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
 use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn, MacroCall};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
 use rustc_ast::LitKind;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, HirIdMap, Impl, Item, ItemKind};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::{sym, BytePos, Span};
+use rustc_span::{sym, BytePos};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -542,10 +542,3 @@ fn conservative_unescape(literal: &str) -> Result<String, UnescapeErr> {
 
     if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
 }
-
-// Expand from `writeln!(o, "")` to `writeln!(o, "")`
-//                          ^^                 ^^^^
-fn expand_past_previous_comma(cx: &LateContext<'_>, span: Span) -> Span {
-    let extended = cx.sess().source_map().span_extend_to_prev_char(span, ',', true);
-    extended.with_lo(extended.lo() - BytePos(1))
-}
diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs
index b9e6462a55e..079c8f50f12 100644
--- a/clippy_utils/src/macros.rs
+++ b/clippy_utils/src/macros.rs
@@ -545,19 +545,32 @@ fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
     )
 }
 
+/// How a format parameter is used in the format string
 #[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 `{:.*}`.
+    /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
     Numbered,
+    /// A parameter with an asterisk precision. e.g. `{:.*}`.
+    Starred,
     /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
     Named(Symbol),
     /// An implicit named parameter, such as the `y` in `format!("{y}")`.
     NamedInline(Symbol),
 }
 
+/// Where a format parameter is being used in the format string
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FormatParamUsage {
+    /// Appears as an argument, e.g. `format!("{}", foo)`
+    Argument,
+    /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
+    Width,
+    /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
+    Precision,
+}
+
 /// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
 ///
 /// ```
@@ -573,6 +586,8 @@ pub struct FormatParam<'tcx> {
     pub value: &'tcx Expr<'tcx>,
     /// How this parameter refers to its `value`.
     pub kind: FormatParamKind,
+    /// Where this format param is being used - argument/width/precision
+    pub usage: FormatParamUsage,
     /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
     ///
     /// ```text
@@ -585,6 +600,7 @@ pub struct FormatParam<'tcx> {
 impl<'tcx> FormatParam<'tcx> {
     fn new(
         mut kind: FormatParamKind,
+        usage: FormatParamUsage,
         position: usize,
         inner: rpf::InnerSpan,
         values: &FormatArgsValues<'tcx>,
@@ -599,7 +615,12 @@ impl<'tcx> FormatParam<'tcx> {
             kind = FormatParamKind::NamedInline(name);
         }
 
-        Some(Self { value, kind, span })
+        Some(Self {
+            value,
+            kind,
+            usage,
+            span,
+        })
     }
 }
 
@@ -618,6 +639,7 @@ pub enum Count<'tcx> {
 
 impl<'tcx> Count<'tcx> {
     fn new(
+        usage: FormatParamUsage,
         count: rpf::Count<'_>,
         position: Option<usize>,
         inner: Option<rpf::InnerSpan>,
@@ -625,15 +647,27 @@ impl<'tcx> Count<'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(
+            rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
                 FormatParamKind::Named(Symbol::intern(name)),
+                usage,
                 position?,
-                span,
+                inner?,
+                values,
+            )?),
+            rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
+                FormatParamKind::Numbered,
+                usage,
+                position?,
+                inner?,
+                values,
+            )?),
+            rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
+                FormatParamKind::Starred,
+                usage,
+                position?,
+                inner?,
                 values,
             )?),
-            rpf::Count::CountIsParam(_) | rpf::Count::CountIsStar(_) => {
-                Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
-            },
             rpf::Count::CountImplied => Self::Implied,
         })
     }
@@ -676,8 +710,20 @@ impl<'tcx> FormatSpec<'tcx> {
             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)?,
+            precision: Count::new(
+                FormatParamUsage::Precision,
+                spec.precision,
+                positions.precision,
+                spec.precision_span,
+                values,
+            )?,
+            width: Count::new(
+                FormatParamUsage::Width,
+                spec.width,
+                positions.width,
+                spec.width_span,
+                values,
+            )?,
             r#trait: match spec.ty {
                 "" => sym::Display,
                 "?" => sym::Debug,
@@ -723,7 +769,7 @@ pub struct FormatArg<'tcx> {
 pub struct FormatArgsExpn<'tcx> {
     /// The format string literal.
     pub format_string: FormatString,
-    // The format arguments, such as `{:?}`.
+    /// 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.
@@ -797,6 +843,7 @@ impl<'tcx> FormatArgsExpn<'tcx> {
                                 // NamedInline is handled by `FormatParam::new()`
                                 rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
                             },
+                            FormatParamUsage::Argument,
                             position.value,
                             parsed_arg.position_span,
                             &values,
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index 62020e21c81..904091c57e8 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -13,6 +13,7 @@ macro_rules! msrv_aliases {
 // names may refer to stabilized feature flags or library items
 msrv_aliases! {
     1,62,0 { BOOL_THEN_SOME }
+    1,58,0 { FORMAT_ARGS_CAPTURE }
     1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
     1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
     1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs
index 64c3f70efa5..d28bd92d708 100644
--- a/clippy_utils/src/source.rs
+++ b/clippy_utils/src/source.rs
@@ -392,6 +392,16 @@ pub fn trim_span(sm: &SourceMap, span: Span) -> Span {
     .span()
 }
 
+/// Expand a span to include a preceding comma
+/// ```rust,ignore
+/// writeln!(o, "")   ->   writeln!(o, "")
+///             ^^                   ^^^^
+/// ```
+pub fn expand_past_previous_comma(cx: &LateContext<'_>, span: Span) -> Span {
+    let extended = cx.sess().source_map().span_extend_to_prev_char(span, ',', true);
+    extended.with_lo(extended.lo() - BytePos(1))
+}
+
 #[cfg(test)]
 mod test {
     use super::{reindent_multiline, without_block_comments};
diff --git a/src/docs.rs b/src/docs.rs
index 6c89b4dde37..a501b56ffbe 100644
--- a/src/docs.rs
+++ b/src/docs.rs
@@ -521,6 +521,7 @@ docs! {
     "unimplemented",
     "uninit_assumed_init",
     "uninit_vec",
+    "uninlined_format_args",
     "unit_arg",
     "unit_cmp",
     "unit_hash",
diff --git a/src/docs/uninlined_format_args.txt b/src/docs/uninlined_format_args.txt
new file mode 100644
index 00000000000..3d2966c84db
--- /dev/null
+++ b/src/docs/uninlined_format_args.txt
@@ -0,0 +1,36 @@
+### What it does
+Detect when a variable is not inlined in a format string,
+and suggests to inline it.
+
+### Why is this bad?
+Non-inlined code is slightly more difficult to read and understand,
+as it requires arguments to be matched against the format string.
+The inlined syntax, where allowed, is simpler.
+
+### Example
+```
+format!("{}", var);
+format!("{v:?}", v = var);
+format!("{0} {0}", var);
+format!("{0:1$}", var, width);
+format!("{:.*}", prec, var);
+```
+Use instead:
+```
+format!("{var}");
+format!("{var:?}");
+format!("{var} {var}");
+format!("{var:width$}");
+format!("{var:.prec$}");
+```
+
+### Known Problems
+
+There may be a false positive if the format string is expanded from certain proc macros:
+
+```
+println!(indoc!("{}"), var);
+```
+
+If a format string contains a numbered argument that cannot be inlined
+nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
\ No newline at end of file
diff --git a/tests/ui/uninlined_format_args.fixed b/tests/ui/uninlined_format_args.fixed
new file mode 100644
index 00000000000..bade0ed8ad6
--- /dev/null
+++ b/tests/ui/uninlined_format_args.fixed
@@ -0,0 +1,168 @@
+// run-rustfix
+
+#![allow(clippy::eq_op)]
+#![allow(clippy::format_in_format_args)]
+#![allow(clippy::print_literal)]
+#![allow(named_arguments_used_positionally)]
+#![allow(unused_variables, unused_imports, unused_macros)]
+#![warn(clippy::uninlined_format_args)]
+#![feature(custom_inner_attributes)]
+
+macro_rules! no_param_str {
+    () => {
+        "{}"
+    };
+}
+
+macro_rules! pass_through {
+    ($expr:expr) => {
+        $expr
+    };
+}
+
+macro_rules! my_println {
+   ($($args:tt),*) => {{
+        println!($($args),*)
+    }};
+}
+
+macro_rules! my_println_args {
+    ($($args:tt),*) => {{
+        println!("foo: {}", format_args!($($args),*))
+    }};
+}
+
+fn tester(fn_arg: i32) {
+    let local_i32 = 1;
+    let local_f64 = 2.0;
+    let local_opt: Option<i32> = Some(3);
+    let width = 4;
+    let prec = 5;
+    let val = 6;
+
+    // make sure this file hasn't been corrupted with tabs converted to spaces
+    // let _ = '	';  // <- this is a single tab character
+    let _: &[u8; 3] = b"	 	"; // <- <tab><space><tab>
+
+    println!("val='{local_i32}'");
+    println!("val='{local_i32}'"); // 3 spaces
+    println!("val='{local_i32}'"); // tab
+    println!("val='{local_i32}'"); // space+tab
+    println!("val='{local_i32}'"); // tab+space
+    println!(
+        "val='{local_i32}'"
+    );
+    println!("{local_i32}");
+    println!("{fn_arg}");
+    println!("{local_i32:?}");
+    println!("{local_i32:#?}");
+    println!("{local_i32:4}");
+    println!("{local_i32:04}");
+    println!("{local_i32:<3}");
+    println!("{local_i32:#010x}");
+    println!("{local_f64:.1}");
+    println!("Hello {} is {local_f64:.local_i32$}", "x");
+    println!("Hello {local_i32} is {local_f64:.*}", 5);
+    println!("Hello {local_i32} is {local_f64:.*}", 5);
+    println!("{local_i32} {local_f64}");
+    println!("{local_i32}, {}", local_opt.unwrap());
+    println!("{val}");
+    println!("{val}");
+    println!("{} {1}", local_i32, 42);
+    println!("val='{local_i32}'");
+    println!("val='{local_i32}'");
+    println!("val='{local_i32}'");
+    println!("val='{fn_arg}'");
+    println!("{local_i32}");
+    println!("{local_i32:?}");
+    println!("{local_i32:#?}");
+    println!("{local_i32:04}");
+    println!("{local_i32:<3}");
+    println!("{local_i32:#010x}");
+    println!("{local_f64:.1}");
+    println!("{local_i32} {local_i32}");
+    println!("{local_f64} {local_i32} {local_i32} {local_f64}");
+    println!("{local_i32} {local_f64}");
+    println!("{local_f64} {local_i32}");
+    println!("{local_f64} {local_i32} {local_f64} {local_i32}");
+    println!("{1} {0}", "str", local_i32);
+    println!("{local_i32}");
+    println!("{local_i32:width$}");
+    println!("{local_i32:width$}");
+    println!("{local_i32:.prec$}");
+    println!("{local_i32:.prec$}");
+    println!("{val:val$}");
+    println!("{val:val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{val:val$.val$}");
+    println!("{width:width$}");
+    println!("{local_i32:width$}");
+    println!("{width:width$}");
+    println!("{local_i32:width$}");
+    println!("{prec:.prec$}");
+    println!("{local_i32:.prec$}");
+    println!("{prec:.prec$}");
+    println!("{local_i32:.prec$}");
+    println!("{width:width$.prec$}");
+    println!("{width:width$.prec$}");
+    println!("{local_f64:width$.prec$}");
+    println!("{local_f64:width$.prec$} {local_f64} {width} {prec}");
+    println!(
+        "{local_i32:width$.prec$} {local_i32:prec$.width$} {width:local_i32$.prec$} {width:prec$.local_i32$} {prec:local_i32$.width$} {prec:width$.local_i32$}",
+    );
+    println!(
+        "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$} {3}",
+        local_i32,
+        width,
+        prec,
+        1 + 2
+    );
+    println!("Width = {local_i32}, value with width = {local_f64:local_i32$}");
+    println!("{local_i32:width$.prec$}");
+    println!("{width:width$.prec$}");
+    println!("{}", format!("{local_i32}"));
+    my_println!("{}", local_i32);
+    my_println_args!("{}", local_i32);
+
+    // these should NOT be modified by the lint
+    println!(concat!("nope ", "{}"), local_i32);
+    println!("val='{local_i32}'");
+    println!("val='{local_i32 }'");
+    println!("val='{local_i32	}'"); // with tab
+    println!("val='{local_i32\n}'");
+    println!("{}", usize::MAX);
+    println!("{}", local_opt.unwrap());
+    println!(
+        "val='{local_i32
+    }'"
+    );
+    println!(no_param_str!(), local_i32);
+
+    // FIXME: bugs!
+    // println!(pass_through!("foo={local_i32}"), local_i32 = local_i32);
+    // println!(pass_through!("foo={}"), local_i32);
+    // println!(indoc!("foo={}"), local_i32);
+    // printdoc!("foo={}", local_i32);
+}
+
+fn main() {
+    tester(42);
+}
+
+fn _under_msrv() {
+    #![clippy::msrv = "1.57"]
+    let local_i32 = 1;
+    println!("don't expand='{}'", local_i32);
+}
+
+fn _meets_msrv() {
+    #![clippy::msrv = "1.58"]
+    let local_i32 = 1;
+    println!("expand='{local_i32}'");
+}
diff --git a/tests/ui/uninlined_format_args.rs b/tests/ui/uninlined_format_args.rs
new file mode 100644
index 00000000000..ac958f9e5b6
--- /dev/null
+++ b/tests/ui/uninlined_format_args.rs
@@ -0,0 +1,171 @@
+// run-rustfix
+
+#![allow(clippy::eq_op)]
+#![allow(clippy::format_in_format_args)]
+#![allow(clippy::print_literal)]
+#![allow(named_arguments_used_positionally)]
+#![allow(unused_variables, unused_imports, unused_macros)]
+#![warn(clippy::uninlined_format_args)]
+#![feature(custom_inner_attributes)]
+
+macro_rules! no_param_str {
+    () => {
+        "{}"
+    };
+}
+
+macro_rules! pass_through {
+    ($expr:expr) => {
+        $expr
+    };
+}
+
+macro_rules! my_println {
+   ($($args:tt),*) => {{
+        println!($($args),*)
+    }};
+}
+
+macro_rules! my_println_args {
+    ($($args:tt),*) => {{
+        println!("foo: {}", format_args!($($args),*))
+    }};
+}
+
+fn tester(fn_arg: i32) {
+    let local_i32 = 1;
+    let local_f64 = 2.0;
+    let local_opt: Option<i32> = Some(3);
+    let width = 4;
+    let prec = 5;
+    let val = 6;
+
+    // make sure this file hasn't been corrupted with tabs converted to spaces
+    // let _ = '	';  // <- this is a single tab character
+    let _: &[u8; 3] = b"	 	"; // <- <tab><space><tab>
+
+    println!("val='{}'", local_i32);
+    println!("val='{   }'", local_i32); // 3 spaces
+    println!("val='{	}'", local_i32); // tab
+    println!("val='{ 	}'", local_i32); // space+tab
+    println!("val='{	 }'", local_i32); // tab+space
+    println!(
+        "val='{
+    }'",
+        local_i32
+    );
+    println!("{}", local_i32);
+    println!("{}", fn_arg);
+    println!("{:?}", local_i32);
+    println!("{:#?}", local_i32);
+    println!("{:4}", local_i32);
+    println!("{:04}", local_i32);
+    println!("{:<3}", local_i32);
+    println!("{:#010x}", local_i32);
+    println!("{:.1}", local_f64);
+    println!("Hello {} is {:.*}", "x", local_i32, local_f64);
+    println!("Hello {} is {:.*}", local_i32, 5, local_f64);
+    println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
+    println!("{} {}", local_i32, local_f64);
+    println!("{}, {}", local_i32, local_opt.unwrap());
+    println!("{}", val);
+    println!("{}", v = val);
+    println!("{} {1}", local_i32, 42);
+    println!("val='{\t }'", local_i32);
+    println!("val='{\n }'", local_i32);
+    println!("val='{local_i32}'", local_i32 = local_i32);
+    println!("val='{local_i32}'", local_i32 = fn_arg);
+    println!("{0}", local_i32);
+    println!("{0:?}", local_i32);
+    println!("{0:#?}", local_i32);
+    println!("{0:04}", local_i32);
+    println!("{0:<3}", local_i32);
+    println!("{0:#010x}", local_i32);
+    println!("{0:.1}", local_f64);
+    println!("{0} {0}", local_i32);
+    println!("{1} {} {0} {}", local_i32, local_f64);
+    println!("{0} {1}", local_i32, local_f64);
+    println!("{1} {0}", local_i32, local_f64);
+    println!("{1} {0} {1} {0}", local_i32, local_f64);
+    println!("{1} {0}", "str", local_i32);
+    println!("{v}", v = local_i32);
+    println!("{local_i32:0$}", width);
+    println!("{local_i32:w$}", w = width);
+    println!("{local_i32:.0$}", prec);
+    println!("{local_i32:.p$}", p = prec);
+    println!("{:0$}", v = val);
+    println!("{0:0$}", v = val);
+    println!("{:0$.0$}", v = val);
+    println!("{0:0$.0$}", v = val);
+    println!("{0:0$.v$}", v = val);
+    println!("{0:v$.0$}", v = val);
+    println!("{v:0$.0$}", v = val);
+    println!("{v:v$.0$}", v = val);
+    println!("{v:0$.v$}", v = val);
+    println!("{v:v$.v$}", v = val);
+    println!("{:0$}", width);
+    println!("{:1$}", local_i32, width);
+    println!("{:w$}", w = width);
+    println!("{:w$}", local_i32, w = width);
+    println!("{:.0$}", prec);
+    println!("{:.1$}", local_i32, prec);
+    println!("{:.p$}", p = prec);
+    println!("{:.p$}", local_i32, p = prec);
+    println!("{:0$.1$}", width, prec);
+    println!("{:0$.w$}", width, w = prec);
+    println!("{:1$.2$}", local_f64, width, prec);
+    println!("{:1$.2$} {0} {1} {2}", local_f64, width, prec);
+    println!(
+        "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}",
+        local_i32, width, prec,
+    );
+    println!(
+        "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$} {3}",
+        local_i32,
+        width,
+        prec,
+        1 + 2
+    );
+    println!("Width = {}, value with width = {:0$}", local_i32, local_f64);
+    println!("{:w$.p$}", local_i32, w = width, p = prec);
+    println!("{:w$.p$}", w = width, p = prec);
+    println!("{}", format!("{}", local_i32));
+    my_println!("{}", local_i32);
+    my_println_args!("{}", local_i32);
+
+    // these should NOT be modified by the lint
+    println!(concat!("nope ", "{}"), local_i32);
+    println!("val='{local_i32}'");
+    println!("val='{local_i32 }'");
+    println!("val='{local_i32	}'"); // with tab
+    println!("val='{local_i32\n}'");
+    println!("{}", usize::MAX);
+    println!("{}", local_opt.unwrap());
+    println!(
+        "val='{local_i32
+    }'"
+    );
+    println!(no_param_str!(), local_i32);
+
+    // FIXME: bugs!
+    // println!(pass_through!("foo={local_i32}"), local_i32 = local_i32);
+    // println!(pass_through!("foo={}"), local_i32);
+    // println!(indoc!("foo={}"), local_i32);
+    // printdoc!("foo={}", local_i32);
+}
+
+fn main() {
+    tester(42);
+}
+
+fn _under_msrv() {
+    #![clippy::msrv = "1.57"]
+    let local_i32 = 1;
+    println!("don't expand='{}'", local_i32);
+}
+
+fn _meets_msrv() {
+    #![clippy::msrv = "1.58"]
+    let local_i32 = 1;
+    println!("expand='{}'", local_i32);
+}
diff --git a/tests/ui/uninlined_format_args.stderr b/tests/ui/uninlined_format_args.stderr
new file mode 100644
index 00000000000..7e652d0354f
--- /dev/null
+++ b/tests/ui/uninlined_format_args.stderr
@@ -0,0 +1,866 @@
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:47:5
+   |
+LL |     println!("val='{}'", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::uninlined-format-args` implied by `-D warnings`
+help: change this to
+   |
+LL -     println!("val='{}'", local_i32);
+LL +     println!("val='{local_i32}'");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:48:5
+   |
+LL |     println!("val='{   }'", local_i32); // 3 spaces
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{   }'", local_i32); // 3 spaces
+LL +     println!("val='{local_i32}'"); // 3 spaces
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:49:5
+   |
+LL |     println!("val='{    }'", local_i32); // tab
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{    }'", local_i32); // tab
+LL +     println!("val='{local_i32}'"); // tab
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:50:5
+   |
+LL |     println!("val='{     }'", local_i32); // space+tab
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{     }'", local_i32); // space+tab
+LL +     println!("val='{local_i32}'"); // space+tab
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:51:5
+   |
+LL |     println!("val='{     }'", local_i32); // tab+space
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{     }'", local_i32); // tab+space
+LL +     println!("val='{local_i32}'"); // tab+space
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:52:5
+   |
+LL | /     println!(
+LL | |         "val='{
+LL | |     }'",
+LL | |         local_i32
+LL | |     );
+   | |_____^
+   |
+help: change this to
+   |
+LL -         "val='{
+LL +         "val='{local_i32}'"
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:57:5
+   |
+LL |     println!("{}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{}", local_i32);
+LL +     println!("{local_i32}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:58:5
+   |
+LL |     println!("{}", fn_arg);
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{}", fn_arg);
+LL +     println!("{fn_arg}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:59:5
+   |
+LL |     println!("{:?}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:?}", local_i32);
+LL +     println!("{local_i32:?}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:60:5
+   |
+LL |     println!("{:#?}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:#?}", local_i32);
+LL +     println!("{local_i32:#?}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:61:5
+   |
+LL |     println!("{:4}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:4}", local_i32);
+LL +     println!("{local_i32:4}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:62:5
+   |
+LL |     println!("{:04}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:04}", local_i32);
+LL +     println!("{local_i32:04}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:63:5
+   |
+LL |     println!("{:<3}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:<3}", local_i32);
+LL +     println!("{local_i32:<3}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:64:5
+   |
+LL |     println!("{:#010x}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:#010x}", local_i32);
+LL +     println!("{local_i32:#010x}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:65:5
+   |
+LL |     println!("{:.1}", local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:.1}", local_f64);
+LL +     println!("{local_f64:.1}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:66:5
+   |
+LL |     println!("Hello {} is {:.*}", "x", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("Hello {} is {:.*}", "x", local_i32, local_f64);
+LL +     println!("Hello {} is {local_f64:.local_i32$}", "x");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:67:5
+   |
+LL |     println!("Hello {} is {:.*}", local_i32, 5, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("Hello {} is {:.*}", local_i32, 5, local_f64);
+LL +     println!("Hello {local_i32} is {local_f64:.*}", 5);
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:68:5
+   |
+LL |     println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
+LL +     println!("Hello {local_i32} is {local_f64:.*}", 5);
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:69:5
+   |
+LL |     println!("{} {}", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{} {}", local_i32, local_f64);
+LL +     println!("{local_i32} {local_f64}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:70:5
+   |
+LL |     println!("{}, {}", local_i32, local_opt.unwrap());
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{}, {}", local_i32, local_opt.unwrap());
+LL +     println!("{local_i32}, {}", local_opt.unwrap());
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:71:5
+   |
+LL |     println!("{}", val);
+   |     ^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{}", val);
+LL +     println!("{val}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:72:5
+   |
+LL |     println!("{}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{}", v = val);
+LL +     println!("{val}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:74:5
+   |
+LL |     println!("val='{/t }'", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{/t }'", local_i32);
+LL +     println!("val='{local_i32}'");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:75:5
+   |
+LL |     println!("val='{/n }'", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{/n }'", local_i32);
+LL +     println!("val='{local_i32}'");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:76:5
+   |
+LL |     println!("val='{local_i32}'", local_i32 = local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{local_i32}'", local_i32 = local_i32);
+LL +     println!("val='{local_i32}'");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:77:5
+   |
+LL |     println!("val='{local_i32}'", local_i32 = fn_arg);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("val='{local_i32}'", local_i32 = fn_arg);
+LL +     println!("val='{fn_arg}'");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:78:5
+   |
+LL |     println!("{0}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0}", local_i32);
+LL +     println!("{local_i32}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:79:5
+   |
+LL |     println!("{0:?}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:?}", local_i32);
+LL +     println!("{local_i32:?}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:80:5
+   |
+LL |     println!("{0:#?}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:#?}", local_i32);
+LL +     println!("{local_i32:#?}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:81:5
+   |
+LL |     println!("{0:04}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:04}", local_i32);
+LL +     println!("{local_i32:04}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:82:5
+   |
+LL |     println!("{0:<3}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:<3}", local_i32);
+LL +     println!("{local_i32:<3}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:83:5
+   |
+LL |     println!("{0:#010x}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:#010x}", local_i32);
+LL +     println!("{local_i32:#010x}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:84:5
+   |
+LL |     println!("{0:.1}", local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:.1}", local_f64);
+LL +     println!("{local_f64:.1}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:85:5
+   |
+LL |     println!("{0} {0}", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0} {0}", local_i32);
+LL +     println!("{local_i32} {local_i32}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:86:5
+   |
+LL |     println!("{1} {} {0} {}", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{1} {} {0} {}", local_i32, local_f64);
+LL +     println!("{local_f64} {local_i32} {local_i32} {local_f64}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:87:5
+   |
+LL |     println!("{0} {1}", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0} {1}", local_i32, local_f64);
+LL +     println!("{local_i32} {local_f64}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:88:5
+   |
+LL |     println!("{1} {0}", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{1} {0}", local_i32, local_f64);
+LL +     println!("{local_f64} {local_i32}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:89:5
+   |
+LL |     println!("{1} {0} {1} {0}", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{1} {0} {1} {0}", local_i32, local_f64);
+LL +     println!("{local_f64} {local_i32} {local_f64} {local_i32}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:91:5
+   |
+LL |     println!("{v}", v = local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{v}", v = local_i32);
+LL +     println!("{local_i32}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:92:5
+   |
+LL |     println!("{local_i32:0$}", width);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{local_i32:0$}", width);
+LL +     println!("{local_i32:width$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:93:5
+   |
+LL |     println!("{local_i32:w$}", w = width);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{local_i32:w$}", w = width);
+LL +     println!("{local_i32:width$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:94:5
+   |
+LL |     println!("{local_i32:.0$}", prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{local_i32:.0$}", prec);
+LL +     println!("{local_i32:.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:95:5
+   |
+LL |     println!("{local_i32:.p$}", p = prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{local_i32:.p$}", p = prec);
+LL +     println!("{local_i32:.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:96:5
+   |
+LL |     println!("{:0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:0$}", v = val);
+LL +     println!("{val:val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:97:5
+   |
+LL |     println!("{0:0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:0$}", v = val);
+LL +     println!("{val:val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:98:5
+   |
+LL |     println!("{:0$.0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:0$.0$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:99:5
+   |
+LL |     println!("{0:0$.0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:0$.0$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:100:5
+   |
+LL |     println!("{0:0$.v$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:0$.v$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:101:5
+   |
+LL |     println!("{0:v$.0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{0:v$.0$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:102:5
+   |
+LL |     println!("{v:0$.0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{v:0$.0$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:103:5
+   |
+LL |     println!("{v:v$.0$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{v:v$.0$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:104:5
+   |
+LL |     println!("{v:0$.v$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{v:0$.v$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:105:5
+   |
+LL |     println!("{v:v$.v$}", v = val);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{v:v$.v$}", v = val);
+LL +     println!("{val:val$.val$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:106:5
+   |
+LL |     println!("{:0$}", width);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:0$}", width);
+LL +     println!("{width:width$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:107:5
+   |
+LL |     println!("{:1$}", local_i32, width);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:1$}", local_i32, width);
+LL +     println!("{local_i32:width$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:108:5
+   |
+LL |     println!("{:w$}", w = width);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:w$}", w = width);
+LL +     println!("{width:width$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:109:5
+   |
+LL |     println!("{:w$}", local_i32, w = width);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:w$}", local_i32, w = width);
+LL +     println!("{local_i32:width$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:110:5
+   |
+LL |     println!("{:.0$}", prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:.0$}", prec);
+LL +     println!("{prec:.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:111:5
+   |
+LL |     println!("{:.1$}", local_i32, prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:.1$}", local_i32, prec);
+LL +     println!("{local_i32:.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:112:5
+   |
+LL |     println!("{:.p$}", p = prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:.p$}", p = prec);
+LL +     println!("{prec:.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:113:5
+   |
+LL |     println!("{:.p$}", local_i32, p = prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:.p$}", local_i32, p = prec);
+LL +     println!("{local_i32:.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:114:5
+   |
+LL |     println!("{:0$.1$}", width, prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:0$.1$}", width, prec);
+LL +     println!("{width:width$.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:115:5
+   |
+LL |     println!("{:0$.w$}", width, w = prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:0$.w$}", width, w = prec);
+LL +     println!("{width:width$.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:116:5
+   |
+LL |     println!("{:1$.2$}", local_f64, width, prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:1$.2$}", local_f64, width, prec);
+LL +     println!("{local_f64:width$.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:117:5
+   |
+LL |     println!("{:1$.2$} {0} {1} {2}", local_f64, width, prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:1$.2$} {0} {1} {2}", local_f64, width, prec);
+LL +     println!("{local_f64:width$.prec$} {local_f64} {width} {prec}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:118:5
+   |
+LL | /     println!(
+LL | |         "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}",
+LL | |         local_i32, width, prec,
+LL | |     );
+   | |_____^
+   |
+help: change this to
+   |
+LL ~         "{local_i32:width$.prec$} {local_i32:prec$.width$} {width:local_i32$.prec$} {width:prec$.local_i32$} {prec:local_i32$.width$} {prec:width$.local_i32$}", width, prec,
+LL ~         "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
+LL ~         "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
+LL ~         "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
+LL ~         "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
+LL ~         "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}",
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:129:5
+   |
+LL |     println!("Width = {}, value with width = {:0$}", local_i32, local_f64);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("Width = {}, value with width = {:0$}", local_i32, local_f64);
+LL +     println!("Width = {local_i32}, value with width = {local_f64:local_i32$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:130:5
+   |
+LL |     println!("{:w$.p$}", local_i32, w = width, p = prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:w$.p$}", local_i32, w = width, p = prec);
+LL +     println!("{local_i32:width$.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:131:5
+   |
+LL |     println!("{:w$.p$}", w = width, p = prec);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{:w$.p$}", w = width, p = prec);
+LL +     println!("{width:width$.prec$}");
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:132:20
+   |
+LL |     println!("{}", format!("{}", local_i32));
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("{}", format!("{}", local_i32));
+LL +     println!("{}", format!("{local_i32}"));
+   |
+
+error: variables can be used directly in the `format!` string
+  --> $DIR/uninlined_format_args.rs:170:5
+   |
+LL |     println!("expand='{}'", local_i32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: change this to
+   |
+LL -     println!("expand='{}'", local_i32);
+LL +     println!("expand='{local_i32}'");
+   |
+
+error: aborting due to 71 previous errors
+