Skip to content

rustfmt: Format cfg_select!#154202

Open
ytmimi wants to merge 5 commits intorust-lang:mainfrom
ytmimi:format_cfg_select
Open

rustfmt: Format cfg_select!#154202
ytmimi wants to merge 5 commits intorust-lang:mainfrom
ytmimi:format_cfg_select

Conversation

@ytmimi
Copy link
Copy Markdown
Contributor

@ytmimi ytmimi commented Mar 22, 2026

View all comments

tracking issue: #115585

Implementing cfg_select! formatting here in rust-lang/rust so that the feature can get out to nightly quicker.
The previous PR (#144323) is a bit old at this point and I felt like I could simplify the implementation so I've opted to reimplement the formatting instead of building off the previous PR.

I've tried to break the PR up into logical commits and I think this would be best reviewed one commit at a time.


Previous PR:

See also:

ytmimi added 4 commits March 21, 2026 23:42
`cfg_select!` parsing needs to be implemented in rustfmt right now
because there's no good way to call `rustc_attr_parsing::parse_cfg_select`.
The plan is to leverage `rewrite_match_body` to help with `cfg_select!`
formatting.
Since macro calls can use `{}`, `()`, or `[]` delimiters I'm making this
change to handle cases where users write:

```rust
cfg_select!();
cfg_select![];
```
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 22, 2026

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-rustfmt Relevant to the rustfmt team, which will review and decide on the PR/issue. labels Mar 22, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 22, 2026

r? @jieyouxu

rustbot has assigned @jieyouxu.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: rustfmt, rustfmt-contributors
  • rustfmt, rustfmt-contributors expanded to 6 candidates
  • Random selection from fee1-dead, jieyouxu

Comment on lines +503 to +523
// Original `()` delimiters
cfg_select!( // opening brace comment

);
std::cfg_select!( // opening brace comment

);
core::cfg_select!( // opening brace comment

);

// Original `[]` delimiters
cfg_select![ // opening brace comment

];
std::cfg_select![ // opening brace comment

];
core::cfg_select![ // opening brace comment

];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation leaves empty cfg_select! calls written with () or [] delimiters unchanged so that comments aren't accidentally removed. That's why these examples aren't properly indented.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: could you leave this comment in the test too? (um, slightly funny for rustfmt tests because well, comments can be formatting-meaningful themselves)

Comment on lines +286 to +303
// trailing comments on the last line are a little buggy and always wrap back up
cfg_select! {
windows => {
"windows"
}
unix => {
"unix"
}
_ => {
"none"
} // FIXME. Prevent wrapping back up to the next line
}

cfg_select! {
windows => "windows",
unix => "unix",
_ => "none", // FIXME. Prevent wrapping back up to the next line
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A pre-existing issue with trailing comments in rustfmt. I wanted to explicitly capture the current behavior in a test case.

Comment on lines +419 to +437
// Can't format cfg_select! at all with style_edition <= 2021.
// Things can be formatted with style_edition >= 2024
cfg_select! {
feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" =>
{
// abc
println!();
}
feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" =>
{
// abc
}
all(anything(
"some other long long long long long thing long long long long long long long long long long long",
feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff"
)) => {
let x = 7;
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Through some testing I found that in style_edition <= 2021 this example won't be formatted at all because the predicate can't be formatted within max_width, but with style_edition >= 2024 that's not an issue.

Comment on lines +305 to +320
// comments within the predicate are fine with style_edition=2024+
cfg_select! {
any(
true, /* comment */
true, true, // true,
true,
) => {}

not(
false // comment
) => {}

any(
false // comment
) => "any",
}
Copy link
Copy Markdown
Contributor Author

@ytmimi ytmimi Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the best place for a comment to begin with IMO, but I wanted to call out that comments within the predicate aren't correctly handled in style_edition <= 2021. This is how the current example is formatted:

cfg_select! {
    any(
        true, /* comment */
        true, true, // true,
        true,
    ) => {}

    not(false // comment) => {}

    any(false // comment) => "any",
}

Clearly that's wrong and produces invalid code, but fixing this seems like it's outisde the scope of this PR. since it only impacts style_edition <= 2021.

Maybe this is something we can address later?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I think we shouldn't try to bundle that in this PR. We probably should open an issue in rustfmt/r-l/r to track this as a known-bug though.

Copy link
Copy Markdown
Contributor

@folkertdev folkertdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extremely happy to see this!

View changes since this review

@jieyouxu
Copy link
Copy Markdown
Member

(I'll gradually review this; need to build up some background ctx first.)

Copy link
Copy Markdown
Member

@jieyouxu jieyouxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The impl broadly looks good to me, just some minor nits and a few test coverage discussions.
@rustbot author

View changes since this review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: just to check my understanding,

cfg_select! parsing needs to be implemented in rustfmt right now
because there's no good way to call rustc_attr_parsing::parse_cfg_select.

I assume this is because rustfmt can't / don't want to (transitively) depend on things beyond parsing/AST? I.e. rustc_hir?

@@ -0,0 +1,108 @@
use rustc_ast::tokenstream::TokenStream;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: add a backlink to the reference re. cfg_select!:

//! See <https://doc.rust-lang.org/nightly/reference/conditional-compilation.html#the-cfg_select-macro> for grammar.

Comment on lines +13 to +16
pub(crate) enum CfgSelectFormatPredicate {
Cfg(ast::MetaItemInner),
Wildcard(Span),
}
Copy link
Copy Markdown
Member

@jieyouxu jieyouxu Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: maybe leave a quick TL;DR example for this:

/// LHS predicate of a `cfg_select!` arm.
pub(crate) enum CfgSelectFormatPredicate {
    /// Example: the `unix` in `unix => {}`. Notably, outer or inner attributes are not permitted.
    Cfg(ast::MetaItemInner),
    /// `_` in `_ => {}`.
    Wildcard(Span),
}

Re. outer/inner attributes, counter-example:

cfg_select! {
    #[cfg(true)]
    _ => {
        fn main() {}
    }
}

pub(crate) arrow: Token,
pub(crate) expr: Box<ast::Expr>,
pub(crate) trailing_comma: Option<Span>,
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: maybe some comments for "visual" purposes.

/// Each `$predicate => $production` arm in `cfg_select!`.
pub(crate) struct CfgSelectArm {
    /// The `$predicate` part.
    pub(crate) predicate: CfgSelectFormatPredicate,
    /// Span of `=>`.
    pub(crate) arrow: Token,
    /// The RHS `$production` expression.
    pub(crate) expr: Box<ast::Expr>,
    /// `cfg_select!` arms `$production`s can be optionally `,` terminated, like `match` arms. The `,` is not needed when `$production` is itself braced `{}`.
    pub(crate) trailing_comma: Option<Span>,
}

Comment on lines +41 to +51
impl Spanned for CfgSelectArm {
fn span(&self) -> Span {
self.predicate
.span()
.with_hi(if let Some(comma) = self.trailing_comma {
comma.hi()
} else {
self.expr.span.hi()
})
}
}
Copy link
Copy Markdown
Member

@jieyouxu jieyouxu Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: could you double-check if the following cross-crate macro expansion case

// another_crate.rs
macro_rules! garbage {
    () => {
        fn main() {}
    }
}

// main.rs
cfg_select! {
    _ => {
        garbage!();
    }
}

produces a span that can be used for formatting?

Comment on lines +305 to +320
// comments within the predicate are fine with style_edition=2024+
cfg_select! {
any(
true, /* comment */
true, true, // true,
true,
) => {}

not(
false // comment
) => {}

any(
false // comment
) => "any",
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I think we shouldn't try to bundle that in this PR. We probably should open an issue in rustfmt/r-l/r to track this as a known-bug though.

Comment on lines +322 to +329
// comments before and after the `=>` get dropped right now
cfg_select! {
any(true, true, true, true,) => {}

not(false) => {}

any(false) => "any",
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: this should also a "known bug" right? Should we also include a follow-up issue to track this?

Comment on lines +503 to +523
// Original `()` delimiters
cfg_select!( // opening brace comment

);
std::cfg_select!( // opening brace comment

);
core::cfg_select!( // opening brace comment

);

// Original `[]` delimiters
cfg_select![ // opening brace comment

];
std::cfg_select![ // opening brace comment

];
core::cfg_select![ // opening brace comment

];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: could you leave this comment in the test too? (um, slightly funny for rustfmt tests because well, comments can be formatting-meaningful themselves)

Copy link
Copy Markdown
Member

@jieyouxu jieyouxu Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: there's two cases of additional test coverage that we may want to add:

  1. Max-width and when to prefer , versus {}.
    This was originally discussed in cfg_select: Support unbraced expressions #145233 where it was decided that we also permit unbraced RHS productions when the RHS is an expression, that <expr>, versus {...} is picked based on max width IIUC. Should we add a test cases for non-default max-width versus default max-width and its influence on whether , or {} is picked?
  2. Nested cfg_select!. For better or worse, you can also write
    cfg_select! {
        _ => cfg_select! [
            _ => {
                // I don't know why you would write this,
                // but you can, so... 🤷 
            }
        ]
    }

Comment on lines +980 to +983
(cfg_select! {
unix => Some(n),
_ => None,
}) => true,
Copy link
Copy Markdown
Member

@jieyouxu jieyouxu Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: should we add some coverage for how certain inline comments are handled?

    match x {
        (/* 1 */ cfg_select! /* 2 */  {
            unix => Some(n),
            _ => None,
        } /* 3 */) => true,
    }

I'm less fussed about them, since I know certain inline comments gets dropped (long-standing known issues). But might still be good to have coverage to detect changes in case a fix is attempted.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 30, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 30, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backlinks / context for myself (and future travellers):

Metadata

History

Initial style team discussions

  • The arms must be wrapped in braces, so rustfmt will have to ensure to not remove those.

Follow-up: unbraced expressions are permitted

PR: #145233

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-rustfmt Relevant to the rustfmt team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants