Skip to content

Make format_args!() its own AST node (ast::ExprKind::FormatArgs) #541

Closed
@m-ou-se

Description

@m-ou-se

Proposal

I originally wrote about this idea here: rust-lang/rust#78356 (comment)

Today, format_args!() expands to a (often rather big) call expression of fmt::Arguments::new…(…). Instead, I think it'd be better if it expanded to its own special AST node (e.g. ast::ExprKind::FormatArgs), which is only lowered to the actual fmt::Argument::new call in a later stage when the types and values of the arguments can be inspected (hir? mir?).

This means it'll be much easier to implement optimizations like the ones I described in rust-lang/rust#78356: Flattening and simplifying format_args!(). Not only to make things like format_args!("a {}", "b") compile down to the same code as format_args!("a b") (which const evaluation might also be able to achieve at some point), but also to make format_args!("a {} {}", "b", x) compile down to the same as format_args!("a b {}", x), and also to flatten nested format_args. For example, instead of needing format_args_nl!(…), a format_args!("{}\n", format_args!(…)) could result in the exact same code.

These optimization possibilities remove the need for some bad use cases of concat!() we currently see in the ecosystem. A crate might have a log!() macro that expands to something like write!(log, concat!("info: ", first_arg, "\n"), other_args…), which has the unfortunate consequence that log!(1) works, since concat converts that 1 to "1" implicitly. It also breaks implicit capturing like log!("hello {name}"). The "proper" solution is to instead expand to write!(log, "info: {}\n", format_args!(all_args…)), which, today, is far less efficient. (Which is the reason we have format_args_nl!() today for println.)

Separately from the optimization possibilities, having an intermediate representation of a parsed format_args!() invocation (with all the numeric and named and implicitly captured placeholders 'resolved', after any lints/errors/etc.), makes it much easier to try out different fmt::Arguments implementations. Today, changing the structure/implementation of fmt::Arguments is quite a big task, since it requires changing large parts of the format_args builtin macro. This makes experimenting with new, more efficient, implementations harder than necessary, which is a bit of a blocker for rust-lang/rust#99012.

In addition, it'd also make diagnostics/lints around formatting much easier, since a format_args!() invocation can be recognized as a single node, without having to pattern-match on the exact expansion of that macro. For example, check out Clippy's current code for recognizing format_args!(): https://github.com/rust-lang/rust-clippy/blob/97a0cf2de2367187c1f9eb9d9f6d333cb8bc8b8f/clippy_utils/src/macros.rs#L353-L552. Changing the implementation of fmt::Arguments also involves updating that part of Clippy at the same time, which adds even more resistance to any work on rust-lang/rust#99012.

And as a bonus, -Zunpretty=expanded would be a lot less noisy if every println/format/format_args call didn't result in a huge unreadable expansion. The ast::ExprKind::FormatArgs node would simply be displayed as format_args!(…).

cc @eddyb @oli-obk, I think I discussed this idea with both of you some time last year.

Mentors or Reviewers

If you have a reviewer or mentor in mind for this work, mention then
here. You can put your own name here if you are planning to mentor the
work.

Process

The main points of the Major Change Process are as follows:

  • File an issue describing the proposal.
  • A compiler team member or contributor who is knowledgeable in the area can second by writing @rustbot second.
    • Finding a "second" suffices for internal changes. If however, you are proposing a new public-facing feature, such as a -C flag, then full team check-off is required.
    • Compiler team members can initiate a check-off via @rfcbot fcp merge on either the MCP or the PR.
  • Once an MCP is seconded, the Final Comment Period begins. If no objections are raised after 10 days, the MCP is considered approved.

You can read more about Major Change Proposals on forge.

Comments

This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-compilerAdd this label so rfcbot knows to poll the compiler teammajor-changeA proposal to make a major change to rustcmajor-change-acceptedA major change proposal that was accepted

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions