]> git.lizzy.rs Git - rust.git/commitdiff
Merge commit '8f1ebdd18bdecc621f16baaf779898cc08cc2766' into clippyup
authorPhilipp Krones <hello@philkrones.com>
Thu, 6 Oct 2022 15:41:53 +0000 (17:41 +0200)
committerPhilipp Krones <hello@philkrones.com>
Thu, 6 Oct 2022 15:41:53 +0000 (17:41 +0200)
1  2 
src/tools/clippy/clippy_lints/src/attrs.rs
src/tools/clippy/clippy_lints/src/format_args.rs
src/tools/clippy/clippy_utils/src/macros.rs
src/tools/clippy/tests/ui/uninlined_format_args.fixed
src/tools/clippy/tests/ui/uninlined_format_args.stderr
src/tools/clippy/tests/ui/unsafe_removed_from_name.rs

index 5f45c69d7f98d1aa11a474688aa655c8e1904d02,0000000000000000000000000000000000000000..0bd1f8b784e8f3f5552c79c2af82a4c23a5c46fa
mode 100644,000000..100644
--- /dev/null
@@@ -1,735 -1,0 +1,736 @@@
-                                                         | "macro_use_imports",
 +//! checks for attributes
 +
 +use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
 +use clippy_utils::macros::{is_panic, macro_backtrace};
 +use clippy_utils::msrvs;
 +use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
 +use clippy_utils::{extract_msrv_attr, meets_msrv};
 +use if_chain::if_chain;
 +use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
 +use rustc_errors::Applicability;
 +use rustc_hir::{
 +    Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
 +};
 +use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 +use rustc_middle::lint::in_external_macro;
 +use rustc_middle::ty;
 +use rustc_semver::RustcVersion;
 +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
 +use rustc_span::source_map::Span;
 +use rustc_span::sym;
 +use rustc_span::symbol::Symbol;
 +use semver::Version;
 +
 +static UNIX_SYSTEMS: &[&str] = &[
 +    "android",
 +    "dragonfly",
 +    "emscripten",
 +    "freebsd",
 +    "fuchsia",
 +    "haiku",
 +    "illumos",
 +    "ios",
 +    "l4re",
 +    "linux",
 +    "macos",
 +    "netbsd",
 +    "openbsd",
 +    "redox",
 +    "solaris",
 +    "vxworks",
 +];
 +
 +// NOTE: windows is excluded from the list because it's also a valid target family.
 +static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for items annotated with `#[inline(always)]`,
 +    /// unless the annotated function is empty or simply panics.
 +    ///
 +    /// ### Why is this bad?
 +    /// While there are valid uses of this annotation (and once
 +    /// you know when to use it, by all means `allow` this lint), it's a common
 +    /// newbie-mistake to pepper one's code with it.
 +    ///
 +    /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
 +    /// measure if that additional function call really affects your runtime profile
 +    /// sufficiently to make up for the increase in compile time.
 +    ///
 +    /// ### Known problems
 +    /// False positives, big time. This lint is meant to be
 +    /// deactivated by everyone doing serious performance work. This means having
 +    /// done the measurement.
 +    ///
 +    /// ### Example
 +    /// ```ignore
 +    /// #[inline(always)]
 +    /// fn not_quite_hot_code(..) { ... }
 +    /// ```
 +    #[clippy::version = "pre 1.29.0"]
 +    pub INLINE_ALWAYS,
 +    pedantic,
 +    "use of `#[inline(always)]`"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for `extern crate` and `use` items annotated with
 +    /// lint attributes.
 +    ///
 +    /// This lint permits lint attributes for lints emitted on the items themself.
 +    /// For `use` items these lints are:
 +    /// * deprecated
 +    /// * unreachable_pub
 +    /// * unused_imports
 +    /// * clippy::enum_glob_use
 +    /// * clippy::macro_use_imports
 +    /// * clippy::wildcard_imports
 +    ///
 +    /// For `extern crate` items these lints are:
 +    /// * `unused_imports` on items with `#[macro_use]`
 +    ///
 +    /// ### Why is this bad?
 +    /// Lint attributes have no effect on crate imports. Most
 +    /// likely a `!` was forgotten.
 +    ///
 +    /// ### Example
 +    /// ```ignore
 +    /// #[deny(dead_code)]
 +    /// extern crate foo;
 +    /// #[forbid(dead_code)]
 +    /// use foo::bar;
 +    /// ```
 +    ///
 +    /// Use instead:
 +    /// ```rust,ignore
 +    /// #[allow(unused_imports)]
 +    /// use foo::baz;
 +    /// #[allow(unused_imports)]
 +    /// #[macro_use]
 +    /// extern crate baz;
 +    /// ```
 +    #[clippy::version = "pre 1.29.0"]
 +    pub USELESS_ATTRIBUTE,
 +    correctness,
 +    "use of lint attributes on `extern crate` items"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for `#[deprecated]` annotations with a `since`
 +    /// field that is not a valid semantic version.
 +    ///
 +    /// ### Why is this bad?
 +    /// For checking the version of the deprecation, it must be
 +    /// a valid semver. Failing that, the contained information is useless.
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// #[deprecated(since = "forever")]
 +    /// fn something_else() { /* ... */ }
 +    /// ```
 +    #[clippy::version = "pre 1.29.0"]
 +    pub DEPRECATED_SEMVER,
 +    correctness,
 +    "use of `#[deprecated(since = \"x\")]` where x is not semver"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for empty lines after outer attributes
 +    ///
 +    /// ### Why is this bad?
 +    /// Most likely the attribute was meant to be an inner attribute using a '!'.
 +    /// If it was meant to be an outer attribute, then the following item
 +    /// should not be separated by empty lines.
 +    ///
 +    /// ### Known problems
 +    /// Can cause false positives.
 +    ///
 +    /// From the clippy side it's difficult to detect empty lines between an attributes and the
 +    /// following item because empty lines and comments are not part of the AST. The parsing
 +    /// currently works for basic cases but is not perfect.
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// #[allow(dead_code)]
 +    ///
 +    /// fn not_quite_good_code() { }
 +    /// ```
 +    ///
 +    /// Use instead:
 +    /// ```rust
 +    /// // Good (as inner attribute)
 +    /// #![allow(dead_code)]
 +    ///
 +    /// fn this_is_fine() { }
 +    ///
 +    /// // or
 +    ///
 +    /// // Good (as outer attribute)
 +    /// #[allow(dead_code)]
 +    /// fn this_is_fine_too() { }
 +    /// ```
 +    #[clippy::version = "pre 1.29.0"]
 +    pub EMPTY_LINE_AFTER_OUTER_ATTR,
 +    nursery,
 +    "empty line after outer attribute"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
 +    ///
 +    /// ### Why is this bad?
 +    /// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
 +    /// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// #![deny(clippy::restriction)]
 +    /// ```
 +    ///
 +    /// Use instead:
 +    /// ```rust
 +    /// #![deny(clippy::as_conversions)]
 +    /// ```
 +    #[clippy::version = "1.47.0"]
 +    pub BLANKET_CLIPPY_RESTRICTION_LINTS,
 +    suspicious,
 +    "enabling the complete restriction group"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
 +    /// with `#[rustfmt::skip]`.
 +    ///
 +    /// ### Why is this bad?
 +    /// Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
 +    /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
 +    ///
 +    /// ### Known problems
 +    /// This lint doesn't detect crate level inner attributes, because they get
 +    /// processed before the PreExpansionPass lints get executed. See
 +    /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// #[cfg_attr(rustfmt, rustfmt_skip)]
 +    /// fn main() { }
 +    /// ```
 +    ///
 +    /// Use instead:
 +    /// ```rust
 +    /// #[rustfmt::skip]
 +    /// fn main() { }
 +    /// ```
 +    #[clippy::version = "1.32.0"]
 +    pub DEPRECATED_CFG_ATTR,
 +    complexity,
 +    "usage of `cfg_attr(rustfmt)` instead of tool attributes"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for cfg attributes having operating systems used in target family position.
 +    ///
 +    /// ### Why is this bad?
 +    /// The configuration option will not be recognised and the related item will not be included
 +    /// by the conditional compilation engine.
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// #[cfg(linux)]
 +    /// fn conditional() { }
 +    /// ```
 +    ///
 +    /// Use instead:
 +    /// ```rust
 +    /// # mod hidden {
 +    /// #[cfg(target_os = "linux")]
 +    /// fn conditional() { }
 +    /// # }
 +    ///
 +    /// // or
 +    ///
 +    /// #[cfg(unix)]
 +    /// fn conditional() { }
 +    /// ```
 +    /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
 +    #[clippy::version = "1.45.0"]
 +    pub MISMATCHED_TARGET_OS,
 +    correctness,
 +    "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for attributes that allow lints without a reason.
 +    ///
 +    /// (This requires the `lint_reasons` feature)
 +    ///
 +    /// ### Why is this bad?
 +    /// Allowing a lint should always have a reason. This reason should be documented to
 +    /// ensure that others understand the reasoning
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// #![feature(lint_reasons)]
 +    ///
 +    /// #![allow(clippy::some_lint)]
 +    /// ```
 +    ///
 +    /// Use instead:
 +    /// ```rust
 +    /// #![feature(lint_reasons)]
 +    ///
 +    /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
 +    /// ```
 +    #[clippy::version = "1.61.0"]
 +    pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
 +    restriction,
 +    "ensures that all `allow` and `expect` attributes have a reason"
 +}
 +
 +declare_lint_pass!(Attributes => [
 +    ALLOW_ATTRIBUTES_WITHOUT_REASON,
 +    INLINE_ALWAYS,
 +    DEPRECATED_SEMVER,
 +    USELESS_ATTRIBUTE,
 +    BLANKET_CLIPPY_RESTRICTION_LINTS,
 +]);
 +
 +impl<'tcx> LateLintPass<'tcx> for Attributes {
 +    fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
 +        if let Some(items) = &attr.meta_item_list() {
 +            if let Some(ident) = attr.ident() {
 +                if is_lint_level(ident.name) {
 +                    check_clippy_lint_names(cx, ident.name, items);
 +                }
 +                if matches!(ident.name, sym::allow | sym::expect) {
 +                    check_lint_reason(cx, ident.name, items, attr);
 +                }
 +                if items.is_empty() || !attr.has_name(sym::deprecated) {
 +                    return;
 +                }
 +                for item in items {
 +                    if_chain! {
 +                        if let NestedMetaItem::MetaItem(mi) = &item;
 +                        if let MetaItemKind::NameValue(lit) = &mi.kind;
 +                        if mi.has_name(sym::since);
 +                        then {
 +                            check_semver(cx, item.span(), lit);
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
 +        let attrs = cx.tcx.hir().attrs(item.hir_id());
 +        if is_relevant_item(cx, item) {
 +            check_attrs(cx, item.span, item.ident.name, attrs);
 +        }
 +        match item.kind {
 +            ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
 +                let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
 +
 +                for attr in attrs {
 +                    if in_external_macro(cx.sess(), attr.span) {
 +                        return;
 +                    }
 +                    if let Some(lint_list) = &attr.meta_item_list() {
 +                        if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
 +                            for lint in lint_list {
 +                                match item.kind {
 +                                    ItemKind::Use(..) => {
 +                                        if is_word(lint, sym::unused_imports)
 +                                            || is_word(lint, sym::deprecated)
 +                                            || is_word(lint, sym!(unreachable_pub))
 +                                            || is_word(lint, sym!(unused))
 +                                            || extract_clippy_lint(lint).map_or(false, |s| {
 +                                                matches!(
 +                                                    s.as_str(),
 +                                                    "wildcard_imports"
 +                                                        | "enum_glob_use"
 +                                                        | "redundant_pub_crate"
++                                                        | "macro_use_imports"
++                                                        | "unsafe_removed_from_name",
 +                                                )
 +                                            })
 +                                        {
 +                                            return;
 +                                        }
 +                                    },
 +                                    ItemKind::ExternCrate(..) => {
 +                                        if is_word(lint, sym::unused_imports) && skip_unused_imports {
 +                                            return;
 +                                        }
 +                                        if is_word(lint, sym!(unused_extern_crates)) {
 +                                            return;
 +                                        }
 +                                    },
 +                                    _ => {},
 +                                }
 +                            }
 +                            let line_span = first_line_of_span(cx, attr.span);
 +
 +                            if let Some(mut sugg) = snippet_opt(cx, line_span) {
 +                                if sugg.contains("#[") {
 +                                    span_lint_and_then(
 +                                        cx,
 +                                        USELESS_ATTRIBUTE,
 +                                        line_span,
 +                                        "useless lint attribute",
 +                                        |diag| {
 +                                            sugg = sugg.replacen("#[", "#![", 1);
 +                                            diag.span_suggestion(
 +                                                line_span,
 +                                                "if you just forgot a `!`, use",
 +                                                sugg,
 +                                                Applicability::MaybeIncorrect,
 +                                            );
 +                                        },
 +                                    );
 +                                }
 +                            }
 +                        }
 +                    }
 +                }
 +            },
 +            _ => {},
 +        }
 +    }
 +
 +    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
 +        if is_relevant_impl(cx, item) {
 +            check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
 +        }
 +    }
 +
 +    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
 +        if is_relevant_trait(cx, item) {
 +            check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
 +        }
 +    }
 +}
 +
 +/// Returns the lint name if it is clippy lint.
 +fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
 +    if_chain! {
 +        if let Some(meta_item) = lint.meta_item();
 +        if meta_item.path.segments.len() > 1;
 +        if let tool_name = meta_item.path.segments[0].ident;
 +        if tool_name.name == sym::clippy;
 +        then {
 +            let lint_name = meta_item.path.segments.last().unwrap().ident.name;
 +            return Some(lint_name);
 +        }
 +    }
 +    None
 +}
 +
 +fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
 +    for lint in items {
 +        if let Some(lint_name) = extract_clippy_lint(lint) {
 +            if lint_name.as_str() == "restriction" && name != sym::allow {
 +                span_lint_and_help(
 +                    cx,
 +                    BLANKET_CLIPPY_RESTRICTION_LINTS,
 +                    lint.span(),
 +                    "restriction lints are not meant to be all enabled",
 +                    None,
 +                    "try enabling only the lints you really need",
 +                );
 +            }
 +        }
 +    }
 +}
 +
 +fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
 +    // Check for the feature
 +    if !cx.tcx.sess.features_untracked().lint_reasons {
 +        return;
 +    }
 +
 +    // Check if the reason is present
 +    if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
 +        && let MetaItemKind::NameValue(_) = &item.kind
 +        && item.path == sym::reason
 +    {
 +        return;
 +    }
 +
 +    span_lint_and_help(
 +        cx,
 +        ALLOW_ATTRIBUTES_WITHOUT_REASON,
 +        attr.span,
 +        &format!("`{}` attribute without specifying a reason", name.as_str()),
 +        None,
 +        "try adding a reason at the end with `, reason = \"..\"`",
 +    );
 +}
 +
 +fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
 +    if let ItemKind::Fn(_, _, eid) = item.kind {
 +        is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
 +    } else {
 +        true
 +    }
 +}
 +
 +fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
 +    match item.kind {
 +        ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value),
 +        _ => false,
 +    }
 +}
 +
 +fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
 +    match item.kind {
 +        TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
 +        TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
 +            is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
 +        },
 +        _ => false,
 +    }
 +}
 +
 +fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
 +    block.stmts.first().map_or(
 +        block
 +            .expr
 +            .as_ref()
 +            .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
 +        |stmt| match &stmt.kind {
 +            StmtKind::Local(_) => true,
 +            StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
 +            StmtKind::Item(_) => false,
 +        },
 +    )
 +}
 +
 +fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
 +    if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
 +        is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable
 +    }) {
 +        return false;
 +    }
 +    match &expr.kind {
 +        ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
 +        ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
 +        ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
 +        _ => true,
 +    }
 +}
 +
 +fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
 +    if span.from_expansion() {
 +        return;
 +    }
 +
 +    for attr in attrs {
 +        if let Some(values) = attr.meta_item_list() {
 +            if values.len() != 1 || !attr.has_name(sym::inline) {
 +                continue;
 +            }
 +            if is_word(&values[0], sym::always) {
 +                span_lint(
 +                    cx,
 +                    INLINE_ALWAYS,
 +                    attr.span,
 +                    &format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
 +                );
 +            }
 +        }
 +    }
 +}
 +
 +fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) {
 +    if let LitKind::Str(is, _) = lit.kind {
 +        if Version::parse(is.as_str()).is_ok() {
 +            return;
 +        }
 +    }
 +    span_lint(
 +        cx,
 +        DEPRECATED_SEMVER,
 +        span,
 +        "the since field must contain a semver-compliant version",
 +    );
 +}
 +
 +fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
 +    if let NestedMetaItem::MetaItem(mi) = &nmi {
 +        mi.is_word() && mi.has_name(expected)
 +    } else {
 +        false
 +    }
 +}
 +
 +pub struct EarlyAttributes {
 +    pub msrv: Option<RustcVersion>,
 +}
 +
 +impl_lint_pass!(EarlyAttributes => [
 +    DEPRECATED_CFG_ATTR,
 +    MISMATCHED_TARGET_OS,
 +    EMPTY_LINE_AFTER_OUTER_ATTR,
 +]);
 +
 +impl EarlyLintPass for EarlyAttributes {
 +    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
 +        check_empty_line_after_outer_attr(cx, item);
 +    }
 +
 +    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
 +        check_deprecated_cfg_attr(cx, attr, self.msrv);
 +        check_mismatched_target_os(cx, attr);
 +    }
 +
 +    extract_msrv_attr!(EarlyContext);
 +}
 +
 +fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
 +    let mut iter = item.attrs.iter().peekable();
 +    while let Some(attr) = iter.next() {
 +        if matches!(attr.kind, AttrKind::Normal(..))
 +            && attr.style == AttrStyle::Outer
 +            && is_present_in_source(cx, attr.span)
 +        {
 +            let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
 +            let end_of_attr_to_next_attr_or_item = Span::new(
 +                attr.span.hi(),
 +                iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
 +                item.span.ctxt(),
 +                item.span.parent(),
 +            );
 +
 +            if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
 +                let lines = snippet.split('\n').collect::<Vec<_>>();
 +                let lines = without_block_comments(lines);
 +
 +                if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
 +                    span_lint(
 +                        cx,
 +                        EMPTY_LINE_AFTER_OUTER_ATTR,
 +                        begin_of_attr_to_item,
 +                        "found an empty line after an outer attribute. \
 +                        Perhaps you forgot to add a `!` to make it an inner attribute?",
 +                    );
 +                }
 +            }
 +        }
 +    }
 +}
 +
 +fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Option<RustcVersion>) {
 +    if_chain! {
 +        if meets_msrv(msrv, msrvs::TOOL_ATTRIBUTES);
 +        // check cfg_attr
 +        if attr.has_name(sym::cfg_attr);
 +        if let Some(items) = attr.meta_item_list();
 +        if items.len() == 2;
 +        // check for `rustfmt`
 +        if let Some(feature_item) = items[0].meta_item();
 +        if feature_item.has_name(sym::rustfmt);
 +        // check for `rustfmt_skip` and `rustfmt::skip`
 +        if let Some(skip_item) = &items[1].meta_item();
 +        if skip_item.has_name(sym!(rustfmt_skip))
 +            || skip_item
 +                .path
 +                .segments
 +                .last()
 +                .expect("empty path in attribute")
 +                .ident
 +                .name
 +                == sym::skip;
 +        // Only lint outer attributes, because custom inner attributes are unstable
 +        // Tracking issue: https://github.com/rust-lang/rust/issues/54726
 +        if attr.style == AttrStyle::Outer;
 +        then {
 +            span_lint_and_sugg(
 +                cx,
 +                DEPRECATED_CFG_ATTR,
 +                attr.span,
 +                "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
 +                "use",
 +                "#[rustfmt::skip]".to_string(),
 +                Applicability::MachineApplicable,
 +            );
 +        }
 +    }
 +}
 +
 +fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
 +    fn find_os(name: &str) -> Option<&'static str> {
 +        UNIX_SYSTEMS
 +            .iter()
 +            .chain(NON_UNIX_SYSTEMS.iter())
 +            .find(|&&os| os == name)
 +            .copied()
 +    }
 +
 +    fn is_unix(name: &str) -> bool {
 +        UNIX_SYSTEMS.iter().any(|&os| os == name)
 +    }
 +
 +    fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
 +        let mut mismatched = Vec::new();
 +
 +        for item in items {
 +            if let NestedMetaItem::MetaItem(meta) = item {
 +                match &meta.kind {
 +                    MetaItemKind::List(list) => {
 +                        mismatched.extend(find_mismatched_target_os(list));
 +                    },
 +                    MetaItemKind::Word => {
 +                        if_chain! {
 +                            if let Some(ident) = meta.ident();
 +                            if let Some(os) = find_os(ident.name.as_str());
 +                            then {
 +                                mismatched.push((os, ident.span));
 +                            }
 +                        }
 +                    },
 +                    MetaItemKind::NameValue(..) => {},
 +                }
 +            }
 +        }
 +
 +        mismatched
 +    }
 +
 +    if_chain! {
 +        if attr.has_name(sym::cfg);
 +        if let Some(list) = attr.meta_item_list();
 +        let mismatched = find_mismatched_target_os(&list);
 +        if !mismatched.is_empty();
 +        then {
 +            let mess = "operating system used in target family position";
 +
 +            span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
 +                // Avoid showing the unix suggestion multiple times in case
 +                // we have more than one mismatch for unix-like systems
 +                let mut unix_suggested = false;
 +
 +                for (os, span) in mismatched {
 +                    let sugg = format!("target_os = \"{os}\"");
 +                    diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
 +
 +                    if !unix_suggested && is_unix(os) {
 +                        diag.help("did you mean `unix`?");
 +                        unix_suggested = true;
 +                    }
 +                }
 +            });
 +        }
 +    }
 +}
 +
 +fn is_lint_level(symbol: Symbol) -> bool {
 +    matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
 +}
index cefebc2a98a2d5e58c85a6fe554d874ba3b0af71,0000000000000000000000000000000000000000..99bef62f81436d48343eee7a921585b0664270de
mode 100644,000000..100644
--- /dev/null
@@@ -1,325 -1,0 +1,318 @@@
- use rustc_lint::{LateContext, LateLintPass};
 +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 +use clippy_utils::macros::FormatParamKind::{Implicit, Named, Numbered, Starred};
 +use clippy_utils::macros::{is_format_macro, FormatArgsExpn, FormatParam, FormatParamUsage};
 +use clippy_utils::source::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, QPath};
-     // 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
++use rustc_lint::{LateContext, LateLintPass, LintContext};
 +use rustc_middle::ty::adjustment::{Adjust, Adjustment};
 +use rustc_middle::ty::Ty;
 +use rustc_semver::RustcVersion;
 +use rustc_session::{declare_tool_lint, impl_lint_pass};
 +use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Detects `format!` within the arguments of another macro that does
 +    /// formatting such as `format!` itself, `write!` or `println!`. Suggests
 +    /// inlining the `format!` call.
 +    ///
 +    /// ### Why is this bad?
 +    /// The recommended code is both shorter and avoids a temporary allocation.
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// # use std::panic::Location;
 +    /// println!("error: {}", format!("something failed at {}", Location::caller()));
 +    /// ```
 +    /// Use instead:
 +    /// ```rust
 +    /// # use std::panic::Location;
 +    /// println!("error: something failed at {}", Location::caller());
 +    /// ```
 +    #[clippy::version = "1.58.0"]
 +    pub FORMAT_IN_FORMAT_ARGS,
 +    perf,
 +    "`format!` used in a macro that does formatting"
 +}
 +
 +declare_clippy_lint! {
 +    /// ### What it does
 +    /// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
 +    /// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
 +    /// in a macro that does formatting.
 +    ///
 +    /// ### Why is this bad?
 +    /// Since the type implements `Display`, the use of `to_string` is
 +    /// unnecessary.
 +    ///
 +    /// ### Example
 +    /// ```rust
 +    /// # use std::panic::Location;
 +    /// println!("error: something failed at {}", Location::caller().to_string());
 +    /// ```
 +    /// Use instead:
 +    /// ```rust
 +    /// # use std::panic::Location;
 +    /// println!("error: something failed at {}", Location::caller());
 +    /// ```
 +    #[clippy::version = "1.58.0"]
 +    pub TO_STRING_IN_FORMAT_ARGS,
 +    perf,
 +    "`to_string` applied to a type that implements `Display` 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>) {
 +        if_chain! {
 +            if let Some(format_args) = FormatArgsExpn::parse(cx, expr);
 +            let expr_expn_data = expr.span.ctxt().outer_expn_data();
 +            let outermost_expn_data = outermost_expn_data(expr_expn_data);
 +            if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
 +            if is_format_macro(cx, macro_def_id);
 +            if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
 +            then {
 +                for arg in &format_args.args {
 +                    if !arg.format.is_default() {
 +                        continue;
 +                    }
 +                    if is_aliased(&format_args, arg.param.value.hir_id) {
 +                        continue;
 +                    }
 +                    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(args, &p, &mut fixes)) || fixes.is_empty() {
 +        return;
 +    }
 +
++    // Temporarily ignore multiline spans: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
++    if fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span)) {
++        return;
++    }
 +
 +    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(args: &FormatArgsExpn<'_>, 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 [segment] = path.segments
 +        && let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id)
 +    {
 +        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));
 +        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 {
 +    if expn_data.call_site.from_expansion() {
 +        outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
 +    } else {
 +        expn_data
 +    }
 +}
 +
 +fn check_format_in_format_args(
 +    cx: &LateContext<'_>,
 +    call_site: Span,
 +    name: Symbol,
 +    arg: &Expr<'_>,
 +) {
 +    let expn_data = arg.span.ctxt().outer_expn_data();
 +    if expn_data.call_site.from_expansion() {
 +        return;
 +    }
 +    let Some(mac_id) = expn_data.macro_def_id else { return };
 +    if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
 +        return;
 +    }
 +    span_lint_and_then(
 +        cx,
 +        FORMAT_IN_FORMAT_ARGS,
 +        call_site,
 +        &format!("`format!` in `{name}!` args"),
 +        |diag| {
 +            diag.help(&format!(
 +                "combine the `format!(..)` arguments with the outer `{name}!(..)` call"
 +            ));
 +            diag.help("or consider changing `format!` to `format_args!`");
 +        },
 +    );
 +}
 +
 +fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
 +    if_chain! {
 +        if !value.span.from_expansion();
 +        if let ExprKind::MethodCall(_, receiver, [], _) = value.kind;
 +        if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
 +        if is_diag_trait_item(cx, method_def_id, sym::ToString);
 +        let receiver_ty = cx.typeck_results().expr_ty(receiver);
 +        if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
 +        let (n_needed_derefs, target) =
 +            count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter());
 +        if implements_trait(cx, target, display_trait_id, &[]);
 +        if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait();
 +        if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
 +        then {
 +            let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
 +            if n_needed_derefs == 0 && !needs_ref {
 +                span_lint_and_sugg(
 +                    cx,
 +                    TO_STRING_IN_FORMAT_ARGS,
 +                    value.span.with_lo(receiver.span.hi()),
 +                    &format!(
 +                        "`to_string` applied to a type that implements `Display` in `{name}!` args"
 +                    ),
 +                    "remove this",
 +                    String::new(),
 +                    Applicability::MachineApplicable,
 +                );
 +            } else {
 +                span_lint_and_sugg(
 +                    cx,
 +                    TO_STRING_IN_FORMAT_ARGS,
 +                    value.span,
 +                    &format!(
 +                        "`to_string` applied to a type that implements `Display` in `{name}!` args"
 +                    ),
 +                    "use this",
 +                    format!(
 +                        "{}{:*>n_needed_derefs$}{receiver_snippet}",
 +                        if needs_ref { "&" } else { "" },
 +                        ""
 +                    ),
 +                    Applicability::MachineApplicable,
 +                );
 +            }
 +        }
 +    }
 +}
 +
 +/// 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>)
 +where
 +    I: Iterator<Item = &'tcx Adjustment<'tcx>>,
 +{
 +    let mut n_total = 0;
 +    let mut n_needed = 0;
 +    loop {
 +        if let Some(Adjustment { kind: Adjust::Deref(overloaded_deref), target }) = iter.next() {
 +            n_total += 1;
 +            if overloaded_deref.is_some() {
 +                n_needed = n_total;
 +            }
 +            ty = *target;
 +        } else {
 +            return (n_needed, ty);
 +        }
 +    }
 +}
index dd0ce1da65759396a431f269f6da3e1536cd39a5,0000000000000000000000000000000000000000..5a63c290a315fccffd9fbcad2071c7280cbc5e19
mode 100644,000000..100644
--- /dev/null
@@@ -1,1024 -1,0 +1,1024 @@@
-     /// `format!("{x} {} {y}", 1, z + 2)`.
 +#![allow(clippy::similar_names)] // `expr` and `expn`
 +
 +use crate::is_path_diagnostic_item;
 +use crate::source::snippet_opt;
 +use crate::visitors::{for_each_expr, Descend};
 +
 +use arrayvec::ArrayVec;
 +use itertools::{izip, Either, Itertools};
 +use rustc_ast::ast::LitKind;
 +use rustc_hir::intravisit::{walk_expr, Visitor};
 +use rustc_hir::{self as hir, Expr, ExprField, 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, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
 +use std::iter::{once, zip};
 +use std::ops::ControlFlow;
 +
 +const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
 +    sym::assert_eq_macro,
 +    sym::assert_macro,
 +    sym::assert_ne_macro,
 +    sym::debug_assert_eq_macro,
 +    sym::debug_assert_macro,
 +    sym::debug_assert_ne_macro,
 +    sym::eprint_macro,
 +    sym::eprintln_macro,
 +    sym::format_args_macro,
 +    sym::format_macro,
 +    sym::print_macro,
 +    sym::println_macro,
 +    sym::std_panic_macro,
 +    sym::write_macro,
 +    sym::writeln_macro,
 +];
 +
 +/// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
 +pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
 +    if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
 +        FORMAT_MACRO_DIAG_ITEMS.contains(&name)
 +    } else {
 +        false
 +    }
 +}
 +
 +/// A macro call, like `vec![1, 2, 3]`.
 +///
 +/// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
 +/// Even better is to check if it is a diagnostic item.
 +///
 +/// This structure is similar to `ExpnData` but it precludes desugaring expansions.
 +#[derive(Debug)]
 +pub struct MacroCall {
 +    /// Macro `DefId`
 +    pub def_id: DefId,
 +    /// Kind of macro
 +    pub kind: MacroKind,
 +    /// The expansion produced by the macro call
 +    pub expn: ExpnId,
 +    /// Span of the macro call site
 +    pub span: Span,
 +}
 +
 +impl MacroCall {
 +    pub fn is_local(&self) -> bool {
 +        span_is_local(self.span)
 +    }
 +}
 +
 +/// Returns an iterator of expansions that created the given span
 +pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
 +    std::iter::from_fn(move || {
 +        let ctxt = span.ctxt();
 +        if ctxt == SyntaxContext::root() {
 +            return None;
 +        }
 +        let expn = ctxt.outer_expn();
 +        let data = expn.expn_data();
 +        span = data.call_site;
 +        Some((expn, data))
 +    })
 +}
 +
 +/// Checks whether the span is from the root expansion or a locally defined macro
 +pub fn span_is_local(span: Span) -> bool {
 +    !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
 +}
 +
 +/// Checks whether the expansion is the root expansion or a locally defined macro
 +pub fn expn_is_local(expn: ExpnId) -> bool {
 +    if expn == ExpnId::root() {
 +        return true;
 +    }
 +    let data = expn.expn_data();
 +    let backtrace = expn_backtrace(data.call_site);
 +    std::iter::once((expn, data))
 +        .chain(backtrace)
 +        .find_map(|(_, data)| data.macro_def_id)
 +        .map_or(true, DefId::is_local)
 +}
 +
 +/// Returns an iterator of macro expansions that created the given span.
 +/// Note that desugaring expansions are skipped.
 +pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
 +    expn_backtrace(span).filter_map(|(expn, data)| match data {
 +        ExpnData {
 +            kind: ExpnKind::Macro(kind, _),
 +            macro_def_id: Some(def_id),
 +            call_site: span,
 +            ..
 +        } => Some(MacroCall {
 +            def_id,
 +            kind,
 +            expn,
 +            span,
 +        }),
 +        _ => None,
 +    })
 +}
 +
 +/// If the macro backtrace of `span` has a macro call at the root expansion
 +/// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
 +pub fn root_macro_call(span: Span) -> Option<MacroCall> {
 +    macro_backtrace(span).last()
 +}
 +
 +/// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
 +/// produced by the macro call, as in [`first_node_in_macro`].
 +pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
 +    if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
 +        return None;
 +    }
 +    root_macro_call(node.span())
 +}
 +
 +/// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
 +/// macro call, as in [`first_node_in_macro`].
 +pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
 +    let span = node.span();
 +    first_node_in_macro(cx, node)
 +        .into_iter()
 +        .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
 +}
 +
 +/// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
 +/// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
 +/// is the outermost node of an entire macro expansion, but there are some caveats noted below.
 +/// This is useful for finding macro calls while visiting the HIR without processing the macro call
 +/// at every node within its expansion.
 +///
 +/// If you already have immediate access to the parent node, it is simpler to
 +/// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
 +///
 +/// If a macro call is in statement position, it expands to one or more statements.
 +/// In that case, each statement *and* their immediate descendants will all yield `Some`
 +/// with the `ExpnId` of the containing block.
 +///
 +/// A node may be the "first node" of multiple macro calls in a macro backtrace.
 +/// The expansion of the outermost macro call site is returned in such cases.
 +pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
 +    // get the macro expansion or return `None` if not found
 +    // `macro_backtrace` importantly ignores desugaring expansions
 +    let expn = macro_backtrace(node.span()).next()?.expn;
 +
 +    // get the parent node, possibly skipping over a statement
 +    // if the parent is not found, it is sensible to return `Some(root)`
 +    let hir = cx.tcx.hir();
 +    let mut parent_iter = hir.parent_iter(node.hir_id());
 +    let (parent_id, _) = match parent_iter.next() {
 +        None => return Some(ExpnId::root()),
 +        Some((_, Node::Stmt(_))) => match parent_iter.next() {
 +            None => return Some(ExpnId::root()),
 +            Some(next) => next,
 +        },
 +        Some(next) => next,
 +    };
 +
 +    // get the macro expansion of the parent node
 +    let parent_span = hir.span(parent_id);
 +    let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
 +        // the parent node is not in a macro
 +        return Some(ExpnId::root());
 +    };
 +
 +    if parent_macro_call.expn.is_descendant_of(expn) {
 +        // `node` is input to a macro call
 +        return None;
 +    }
 +
 +    Some(parent_macro_call.expn)
 +}
 +
 +/* Specific Macro Utils */
 +
 +/// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
 +pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
 +    let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
 +    matches!(
 +        name.as_str(),
 +        "core_panic_macro"
 +            | "std_panic_macro"
 +            | "core_panic_2015_macro"
 +            | "std_panic_2015_macro"
 +            | "core_panic_2021_macro"
 +    )
 +}
 +
 +pub enum PanicExpn<'a> {
 +    /// No arguments - `panic!()`
 +    Empty,
 +    /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
 +    Str(&'a Expr<'a>),
 +    /// A single argument that implements `Display` - `panic!("{}", object)`
 +    Display(&'a Expr<'a>),
 +    /// Anything else - `panic!("error {}: {}", a, b)`
 +    Format(FormatArgsExpn<'a>),
 +}
 +
 +impl<'a> PanicExpn<'a> {
 +    pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
 +        if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
 +            return None;
 +        }
 +        let ExprKind::Call(callee, [arg]) = &expr.kind else { return None };
 +        let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
 +        let result = match path.segments.last().unwrap().ident.as_str() {
 +            "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
 +            "panic" | "panic_str" => Self::Str(arg),
 +            "panic_display" => {
 +                let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
 +                Self::Display(e)
 +            },
 +            "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
 +            _ => return None,
 +        };
 +        Some(result)
 +    }
 +}
 +
 +/// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
 +pub fn find_assert_args<'a>(
 +    cx: &LateContext<'_>,
 +    expr: &'a Expr<'a>,
 +    expn: ExpnId,
 +) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
 +    find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
 +}
 +
 +/// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
 +/// expansion
 +pub fn find_assert_eq_args<'a>(
 +    cx: &LateContext<'_>,
 +    expr: &'a Expr<'a>,
 +    expn: ExpnId,
 +) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
 +    find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
 +}
 +
 +fn find_assert_args_inner<'a, const N: usize>(
 +    cx: &LateContext<'_>,
 +    expr: &'a Expr<'a>,
 +    expn: ExpnId,
 +) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
 +    let macro_id = expn.expn_data().macro_def_id?;
 +    let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
 +        None => (expr, expn),
 +        Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
 +    };
 +    let mut args = ArrayVec::new();
 +    let mut panic_expn = None;
 +    let _: Option<!> = for_each_expr(expr, |e| {
 +        if args.is_full() {
 +            if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
 +                panic_expn = PanicExpn::parse(cx, e);
 +            }
 +            ControlFlow::Continue(Descend::from(panic_expn.is_none()))
 +        } else if is_assert_arg(cx, e, expn) {
 +            args.push(e);
 +            ControlFlow::Continue(Descend::No)
 +        } else {
 +            ControlFlow::Continue(Descend::Yes)
 +        }
 +    });
 +    let args = args.into_inner().ok()?;
 +    // if no `panic!(..)` is found, use `PanicExpn::Empty`
 +    // to indicate that the default assertion message is used
 +    let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
 +    Some((args, panic_expn))
 +}
 +
 +fn find_assert_within_debug_assert<'a>(
 +    cx: &LateContext<'_>,
 +    expr: &'a Expr<'a>,
 +    expn: ExpnId,
 +    assert_name: Symbol,
 +) -> Option<(&'a Expr<'a>, ExpnId)> {
 +    for_each_expr(expr, |e| {
 +        if !e.span.from_expansion() {
 +            return ControlFlow::Continue(Descend::No);
 +        }
 +        let e_expn = e.span.ctxt().outer_expn();
 +        if e_expn == expn {
 +            ControlFlow::Continue(Descend::Yes)
 +        } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
 +            ControlFlow::Break((e, e_expn))
 +        } else {
 +            ControlFlow::Continue(Descend::No)
 +        }
 +    })
 +}
 +
 +fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
 +    if !expr.span.from_expansion() {
 +        return true;
 +    }
 +    let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
 +        if macro_call.expn == assert_expn {
 +            ControlFlow::Break(false)
 +        } else {
 +            match cx.tcx.item_name(macro_call.def_id) {
 +                // `cfg!(debug_assertions)` in `debug_assert!`
 +                sym::cfg => ControlFlow::CONTINUE,
 +                // assert!(other_macro!(..))
 +                _ => ControlFlow::Break(true),
 +            }
 +        }
 +    });
 +    match result {
 +        ControlFlow::Break(is_assert_arg) => is_assert_arg,
 +        ControlFlow::Continue(()) => true,
 +    }
 +}
 +
 +/// The format string doesn't exist in the HIR, so we reassemble it from source code
 +#[derive(Debug)]
 +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 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| match ch {
 +            Ok(ch) => unescaped.push(ch),
 +            Err(e) if !e.is_fatal() => (),
 +            Err(e) => panic!("{e:?}"),
 +        });
 +
 +        let mut parts = Vec::new();
 +        let _: Option<!> = for_each_expr(pieces, |expr| {
 +            if let ExprKind::Lit(lit) = &expr.kind
 +                && let LitKind::Str(symbol, _) = lit.node
 +            {
 +                parts.push(symbol);
 +            }
 +            ControlFlow::Continue(())
 +        });
 +
 +        Some(Self {
 +            span,
 +            snippet,
 +            style,
 +            unescaped,
 +            parts,
 +        })
 +    }
 +}
 +
 +struct FormatArgsValues<'tcx> {
 +    /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
++    /// `format!("{x} {} {}", 1, z + 2)`.
 +    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();
 +        let _: Option<!> = for_each_expr(args, |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);
 +                }
 +                ControlFlow::Continue(Descend::Yes)
 +            } else {
 +                // assume that any expr with a differing span is a value
 +                value_args.push(expr);
 +                ControlFlow::Continue(Descend::No)
 +            }
 +        });
 +
 +        Self {
 +            value_args,
 +            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>,
 +}
 +
 +impl<'tcx> Visitor<'tcx> for ParamPosition {
 +    fn visit_expr_field(&mut self, field: &'tcx ExprField<'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
 +            }
 +        }
 +
 +        match field.ident.name {
 +            sym::position => {
 +                if let ExprKind::Lit(lit) = &field.expr.kind
 +                    && let LitKind::Int(pos, _) = lit.node
 +                {
 +                    self.value = pos as usize;
 +                }
 +            },
 +            sym::precision => {
 +                self.precision = parse_count(field.expr);
 +            },
 +            sym::width => {
 +                self.width = parse_count(field.expr);
 +            },
 +            _ => walk_expr(self, field.expr),
 +        }
 +    }
 +}
 +
 +/// 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> {
 +    if let ExprKind::AddrOf(.., array) = fmt_arg.kind
 +        && let ExprKind::Array(specs) = array.kind
 +    {
 +        Some(specs.iter().map(|spec| {
 +            let mut position = ParamPosition::default();
 +            position.visit_expr(spec);
 +            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,
 +    )
 +}
 +
 +/// 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, 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.
 +///
 +/// ```
 +/// 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 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
 +    /// format!("{}, {  }, {0}, {name}", ...);
 +    ///          ^    ~~    ~    ~~~~
 +    /// ```
 +    pub span: Span,
 +}
 +
 +impl<'tcx> FormatParam<'tcx> {
 +    fn new(
 +        mut kind: FormatParamKind,
 +        usage: FormatParamUsage,
 +        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,
 +            usage,
 +            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(
 +        usage: FormatParamUsage,
 +        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, _) => Self::Param(FormatParam::new(
 +                FormatParamKind::Named(Symbol::intern(name)),
 +                usage,
 +                position?,
 +                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::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(
 +                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,
 +                "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)),
 +        })
 +    }
 +
 +    /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
 +    /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
 +    pub fn is_default(&self) -> bool {
 +        self.r#trait == sym::Display
 +            && self.width.is_implied()
 +            && self.precision.is_implied()
 +            && self.align == Alignment::AlignUnknown
 +            && self.flags == 0
 +    }
 +}
 +
 +/// 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,
 +    /// Spans of the commas between the format string and explicit values, excluding any trailing
 +    /// comma
 +    ///
 +    /// ```ignore
 +    /// format!("..", 1, 2, 3,)
 +    /// //          ^  ^  ^
 +    /// ```
 +    comma_spans: Vec<Span>,
 +    /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
 +    /// `format!("{x} {} {y}", 1, z + 2)`.
 +    explicit_values: Vec<&'tcx Expr<'tcx>>,
 +}
 +
 +impl<'tcx> FormatArgsExpn<'tcx> {
 +    /// Gets the spans of the commas inbetween the format string and explicit args, not including
 +    /// any trailing comma
 +    ///
 +    /// ```ignore
 +    /// format!("{} {}", a, b)
 +    /// //             ^  ^
 +    /// ```
 +    ///
 +    /// Ensures that the format string and values aren't coming from a proc macro that sets the
 +    /// output span to that of its input
 +    fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
 +        // `format!("{} {} {c}", "one", "two", c = "three")`
 +        //                       ^^^^^  ^^^^^      ^^^^^^^
 +        let value_spans = explicit_values
 +            .iter()
 +            .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
 +
 +        // `format!("{} {} {c}", "one", "two", c = "three")`
 +        //                     ^^     ^^     ^^^^^^
 +        let between_spans = once(fmt_span)
 +            .chain(value_spans)
 +            .tuple_windows()
 +            .map(|(start, end)| start.between(end));
 +
 +        let mut comma_spans = Vec::new();
 +        for between_span in between_spans {
 +            let mut offset = 0;
 +            let mut seen_comma = false;
 +
 +            for token in tokenize(&snippet_opt(cx, between_span)?) {
 +                match token.kind {
 +                    TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
 +                    TokenKind::Comma if !seen_comma => {
 +                        seen_comma = true;
 +
 +                        let base = between_span.data();
 +                        comma_spans.push(Span::new(
 +                            base.lo + BytePos(offset),
 +                            base.lo + BytePos(offset + 1),
 +                            base.ctxt,
 +                            base.parent,
 +                        ));
 +                    },
 +                    // named arguments, `start_val, name = end_val`
 +                    //                            ^^^^^^^^^ between_span
 +                    TokenKind::Ident | TokenKind::Eq if seen_comma => {},
 +                    // An unexpected token usually indicates the format string or a value came from a proc macro output
 +                    // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
 +                    // emits a string literal with the span set to that of `"input"`
 +                    _ => return None,
 +                }
 +                offset += token.len;
 +            }
 +
 +            if !seen_comma {
 +                return None;
 +            }
 +        }
 +
 +        Some(comma_spans)
 +    }
 +
 +    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)),
 +                            },
 +                            FormatParamUsage::Argument,
 +                            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<_>>>()?;
 +
 +            let mut explicit_values = values.value_args;
 +            // remove values generated for implicitly captured vars
 +            let len = explicit_values
 +                .iter()
 +                .take_while(|val| !format_string.span.contains(val.span))
 +                .count();
 +            explicit_values.truncate(len);
 +
 +            let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
 +
 +            Some(Self {
 +                format_string,
 +                args,
 +                newline,
 +                comma_spans,
 +                explicit_values,
 +            })
 +        } else {
 +            None
 +        }
 +    }
 +
 +    pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
 +        for_each_expr(expr, |e| {
 +            let e_ctxt = e.span.ctxt();
 +            if e_ctxt == expr.span.ctxt() {
 +                ControlFlow::Continue(Descend::Yes)
 +            } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
 +                if let Some(args) = FormatArgsExpn::parse(cx, e) {
 +                    ControlFlow::Break(args)
 +                } else {
 +                    ControlFlow::Continue(Descend::No)
 +                }
 +            } else {
 +                ControlFlow::Continue(Descend::No)
 +            }
 +        })
 +    }
 +
 +    /// Source callsite span of all inputs
 +    pub fn inputs_span(&self) -> Span {
 +        match *self.explicit_values {
 +            [] => self.format_string.span,
 +            [.., last] => self
 +                .format_string
 +                .span
 +                .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
 +        }
 +    }
 +
 +    /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
 +    ///
 +    /// ```ignore
 +    /// format("{}.{}", 10, 11)
 +    /// //            ^^^^
 +    /// ```
 +    pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
 +        for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
 +            if value.hir_id == value_id {
 +                return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
 +            }
 +        }
 +
 +        None
 +    }
 +
 +    /// 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()
 +    }
 +}
 +
 +/// A node with a `HirId` and a `Span`
 +pub trait HirNode {
 +    fn hir_id(&self) -> HirId;
 +    fn span(&self) -> Span;
 +}
 +
 +macro_rules! impl_hir_node {
 +    ($($t:ident),*) => {
 +        $(impl HirNode for hir::$t<'_> {
 +            fn hir_id(&self) -> HirId {
 +                self.hir_id
 +            }
 +            fn span(&self) -> Span {
 +                self.span
 +            }
 +        })*
 +    };
 +}
 +
 +impl_hir_node!(Expr, Pat);
 +
 +impl HirNode for hir::Item<'_> {
 +    fn hir_id(&self) -> HirId {
 +        self.hir_id()
 +    }
 +
 +    fn span(&self) -> Span {
 +        self.span
 +    }
 +}
index dcf10ed60a259b4b2028bb428ee5e4535e7c7fcf,0000000000000000000000000000000000000000..3ca7a401902530446a86f271af9946a2f8bebb57
mode 100644,000000..100644
--- /dev/null
@@@ -1,164 -1,0 +1,169 @@@
-         "val='{local_i32}'"
 +// aux-build:proc_macro_with_span.rs
 +// run-rustfix
 +#![feature(custom_inner_attributes)]
 +#![warn(clippy::uninlined_format_args)]
 +#![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)]
 +#![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)]
 +
 +extern crate proc_macro_with_span;
 +use proc_macro_with_span::with_span;
 +
 +macro_rules! no_param_str {
 +    () => {
 +        "{}"
 +    };
 +}
 +
 +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!(
-         "{local_i32:width$.prec$} {local_i32:prec$.width$} {width:local_i32$.prec$} {width:prec$.local_i32$} {prec:local_i32$.width$} {prec:width$.local_i32$}",
++        "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!(
-         "{val}",
++        "{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 = {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);
 +
 +    println!(
++        "{}",
++        // comment with a comma , in it
++        val,
 +    );
 +    println!("{val}");
 +
 +    println!(with_span!("{0} {1}" "{1} {0}"), local_i32, local_f64);
 +    println!("{}", with_span!(span val));
 +}
 +
 +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}'");
 +}
index 1b4dada28daca9dda753f9e12cca862ab8bda56a,0000000000000000000000000000000000000000..d1a7749263429d5a7a5c9978e8087a05f5732224
mode 100644,000000..100644
--- /dev/null
@@@ -1,894 -1,0 +1,843 @@@
- error: variables can be used directly in the `format!` string
-   --> $DIR/uninlined_format_args.rs:46: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:41: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:42: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:43: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:44: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:45: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:112: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:51: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:52: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:53: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:54: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:55: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:56: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:57: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:58: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:59: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:60: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:61: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:62: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:63: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:64: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:65: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:66: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:68: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:69: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:70: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:71: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:72: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:73: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:74: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:75: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:76: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:77: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:78: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:79: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:80: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:81: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:82: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:83: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:85: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:86: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:87: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:88: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:89: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:90: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:91: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:92: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:93: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:94: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:95: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:96: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:97: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:98: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:99: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:100: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:101: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:102: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:103: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:104: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:105: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:106: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:107: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:108: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:109: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:110: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:111: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:144:5
-    |
- LL | /     println!(
- LL | |         "{}",
- LL | |         // comment with a comma , in it
- LL | |         val,
- LL | |     );
-    | |_____^
-    |
- help: change this to
-    |
- LL -         "{}",
- LL +         "{val}",
-    |
 +error: variables can be used directly in the `format!` string
 +  --> $DIR/uninlined_format_args.rs:123: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:124: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:125: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:126:20
 +   |
 +LL |     println!("{}", format!("{}", local_i32));
 +   |                    ^^^^^^^^^^^^^^^^^^^^^^^^
 +   |
 +help: change this to
 +   |
 +LL -     println!("{}", format!("{}", local_i32));
 +LL +     println!("{}", format!("{local_i32}"));
 +   |
 +
- error: aborting due to 73 previous errors
 +error: variables can be used directly in the `format!` string
 +  --> $DIR/uninlined_format_args.rs:149:5
 +   |
 +LL |     println!("{}", /* comment with a comma , in it */ val);
 +   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 +   |
 +help: change this to
 +   |
 +LL -     println!("{}", /* comment with a comma , in it */ val);
 +LL +     println!("{val}");
 +   |
 +
 +error: variables can be used directly in the `format!` string
 +  --> $DIR/uninlined_format_args.rs:168:5
 +   |
 +LL |     println!("expand='{}'", local_i32);
 +   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 +   |
 +help: change this to
 +   |
 +LL -     println!("expand='{}'", local_i32);
 +LL +     println!("expand='{local_i32}'");
 +   |
 +
++error: aborting due to 70 previous errors
 +
index cde4e96d668c2c78b5cb79478702db0e66520e46,0000000000000000000000000000000000000000..d29888ac62f6b4d2dc54db0361527c56ac5da524
mode 100644,000000..100644
--- /dev/null
@@@ -1,27 -1,0 +1,30 @@@
 +#![allow(unused_imports)]
 +#![allow(dead_code)]
 +#![warn(clippy::unsafe_removed_from_name)]
 +
 +use std::cell::UnsafeCell as TotallySafeCell;
 +
 +use std::cell::UnsafeCell as TotallySafeCellAgain;
 +
 +// Shouldn't error
 +use std::cell::RefCell as ProbablyNotUnsafe;
 +use std::cell::RefCell as RefCellThatCantBeUnsafe;
 +use std::cell::UnsafeCell as SuperDangerousUnsafeCell;
 +use std::cell::UnsafeCell as Dangerunsafe;
 +use std::cell::UnsafeCell as Bombsawayunsafe;
 +
 +mod mod_with_some_unsafe_things {
 +    pub struct Safe;
 +    pub struct Unsafe;
 +}
 +
 +use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety;
 +
 +// Shouldn't error
 +use mod_with_some_unsafe_things::Safe as IPromiseItsSafeThisTime;
 +use mod_with_some_unsafe_things::Unsafe as SuperUnsafeModThing;
 +
++#[allow(clippy::unsafe_removed_from_name)]
++use mod_with_some_unsafe_things::Unsafe as SuperSafeThing;
++
 +fn main() {}