Skip to content

#[clippy::format_args] does not support macros that adapt arguments #16833

@tamird

Description

@tamird

Summary

In Rust-for-Linux we use a procedural macro to apply a rewrite before delegating to core::format_args!; we wrap all arguments in Adapter which allows us to implement Display for types that do not implement it in core e.g. core::ffi::CStr.

It turns out that annotated macros which internally apply this rewrite are somehow prevented from triggering uninlined_format_args. Note that the repro I've provided is not perfect; rewriting to inline format args there would produce broken code, but the actual macro in the kernel is smart enough to rewrite foo!("{i}") into format_args!("{i}", i = Adapter(&i)).

Please note there are good reasons for both decisions mentioned at the top: implementing Display implies being able to round trip through ToString and FromStr, but in the kernel the ergonomics of having Display on CStr are important. Let's refrain from litigating these decisions here.

Lint Name

uninlined_format_args

Reproducer

I tried this code playground:

#![warn(clippy::uninlined_format_args)]

use std::fmt;

struct Adapter<T>(T);

impl<T: fmt::Display> fmt::Display for Adapter<&T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

#[clippy::format_args]
macro_rules! forwarding_format_args {
    ($($args:tt)*) => {
        format_args!($($args)*)
    };
}

#[clippy::format_args]
macro_rules! adapted_format_args {
    ($fmt:literal, $arg:expr) => {
        format_args!($fmt, Adapter(&($arg)))
    };
}

#[clippy::format_args]
macro_rules! forwarding_adapted_format_args {
    ($($args:tt)*) => {
        adapted_format_args!($($args)*)
    };
}

pub fn demo(x: i32) {
    #[expect(clippy::uninlined_format_args)]
    let _ = format_args!("{}", x);
    #[expect(clippy::uninlined_format_args)]
    let _ = forwarding_format_args!("{}", x);

    // Expected: `#[clippy::format_args]` should make this equivalent to a
    // formatting macro invocation and warn that `x` can be inlined.
    //
    // Actual: Clippy does not warn because the macro expansion wraps `x` in
    // `Adapter(&x)` before the eventual `format_args!` invocation.
    #[expect(clippy::uninlined_format_args)]
    let _ = adapted_format_args!("{}", x);
    #[expect(clippy::uninlined_format_args)]
    let _ = forwarding_adapted_format_args!("{}", x);
}

I expected to see this happen:

No warnings.

Instead, this happened:

    Checking playground v0.0.1 (/playground)
warning: this lint expectation is unfulfilled
  --> src/lib.rs:45:14
   |
45 |     #[expect(clippy::uninlined_format_args)]
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unfulfilled_lint_expectations)]` on by default

warning: this lint expectation is unfulfilled
  --> src/lib.rs:47:14
   |
47 |     #[expect(clippy::uninlined_format_args)]
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: `playground` (lib) generated 2 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s

Version


Metadata

Metadata

Assignees

Labels

C-bugCategory: Clippy is not doing the correct thingI-false-negativeIssue: The lint should have been triggered on code, but wasn't

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions