--- /dev/null
- | "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)
+}
--- /dev/null
- 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);
+ }
+ }
+}
--- /dev/null
- /// `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
+ }
+}
--- /dev/null
- "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}'");
+}
--- /dev/null
- 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
+
--- /dev/null
+#![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() {}