rustfmt: Format cfg_select! rust-lang/rust#154202

Open

11 comments and reviews loaded in 1.77s

ytmimi Avatar
ytmimi on 2026-03-22 05:04:25 UTC · edited
ytmimi Avatar
ytmimi on 2026-03-22 05:04:25 UTC · edited
View on GitHub

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:

rustbot Avatar
rustbot on 2026-03-22 05:04:29 UTC
rustbot Avatar
rustbot on 2026-03-22 05:04:29 UTC
View on GitHub

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

rustbot Avatar
rustbot on 2026-03-22 05:04:31 UTC
rustbot Avatar
rustbot on 2026-03-22 05:04:31 UTC
View on GitHub

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
ytmimi Avatar
ytmimi commented on 2026-03-22 05:07:25 UTC
src/tools/rustfmt/tests/target/cfg_select.rs
503 // Original `()` delimiters
504 cfg_select!( // opening brace comment
505
506 );
507 std::cfg_select!( // opening brace comment
508
509 );
510 core::cfg_select!( // opening brace comment
511
512 );
513
514 // Original `[]` delimiters
515 cfg_select![ // opening brace comment
516
517 ];
518 std::cfg_select![ // opening brace comment
519
520 ];
521 core::cfg_select![ // opening brace comment
522
523 ];
ytmimi Avatar ytmimi on 2026-03-22 05:07:25 UTC
View on GitHub

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.

👍1
jieyouxu Avatar jieyouxu on 2026-03-30 02:53:51 UTC
View on GitHub

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

ytmimi Avatar
ytmimi commented on 2026-03-22 05:08:45 UTC
src/tools/rustfmt/tests/target/cfg_select.rs
286 // trailing comments on the last line are a little buggy and always wrap back up
287 cfg_select! {
288 windows => {
289 "windows"
290 }
291 unix => {
292 "unix"
293 }
294 _ => {
295 "none"
296 } // FIXME. Prevent wrapping back up to the next line
297 }
298
299 cfg_select! {
300 windows => "windows",
301 unix => "unix",
302 _ => "none", // FIXME. Prevent wrapping back up to the next line
303 }
ytmimi Avatar ytmimi on 2026-03-22 05:08:45 UTC
View on GitHub

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

👍1
ytmimi Avatar
ytmimi commented on 2026-03-22 05:10:35 UTC
src/tools/rustfmt/tests/target/cfg_select.rs
419 // Can't format cfg_select! at all with style_edition <= 2021.
420 // Things can be formatted with style_edition >= 2024
421 cfg_select! {
422 feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" =>
423 {
424 // abc
425 println!();
426 }
427 feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" =>
428 {
429 // abc
430 }
431 all(anything(
432 "some other long long long long long thing long long long long long long long long long long long",
433 feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff"
434 )) => {
435 let x = 7;
436 }
437 }
ytmimi Avatar ytmimi on 2026-03-22 05:10:36 UTC
View on GitHub

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.

👍1
ytmimi Avatar
ytmimi commented on 2026-03-22 05:17:06 UTC
src/tools/rustfmt/tests/target/cfg_select.rs
305 // comments within the predicate are fine with style_edition=2024+
306 cfg_select! {
307 any(
308 true, /* comment */
309 true, true, // true,
310 true,
311 ) => {}
312
313 not(
314 false // comment
315 ) => {}
316
317 any(
318 false // comment
319 ) => "any",
320 }
ytmimi Avatar ytmimi on 2026-03-22 05:17:06 UTC · edited
View on GitHub

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?

👍1
jieyouxu Avatar jieyouxu on 2026-03-30 02:48:31 UTC
View on GitHub

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.

folkertdev Avatar
folkertdev commented on 2026-03-22 12:15:01 UTC
folkertdev left a comment · edited
View on GitHub

Extremely happy to see this!

View changes since this review

😄1
src/tools/rustfmt/tests/source/cfg_select.rs · resolved
folkertdev Avatar folkertdev on 2026-03-22 12:14:50 UTC
View on GitHub

For completeness it might be good to test at all expansion sites. e.g. types, patterns and several items in a trait, impl or extern block. We have some basic examples here:

type ExpandToType = cfg_select! {
unix => { u32 },
_ => i32,
};
fn expand_to_pattern(x: Option<i32>) -> bool {
match x {
(cfg_select! {
unix => Some(n),
_ => None,
}) => true,
_ => false,
}
}
cfg_select! {
false => {
fn foo() {}
}
_ => {
fn bar() {}
}
}
struct S;
impl S {
cfg_select! {
false => {
fn foo() {}
}
_ => {
fn bar() {}
}
}
}
trait T {
cfg_select! {
false => {
fn a();
}
_ => {
fn b();
}
}
}
impl T for S {
cfg_select! {
false => {
fn a() {}
},
_ => {
fn b() {}
}
}
}
extern "C" {
cfg_select! {
false => {
fn puts(s: *const i8) -> i32;
}
_ => {
fn printf(fmt: *const i8, ...) -> i32;
}
}
}

ytmimi Avatar ytmimi on 2026-03-22 13:14:42 UTC
View on GitHub

Thanks for pointing that out. I'll add some extra test cases!

ytmimi Avatar ytmimi on 2026-03-22 18:53:36 UTC
View on GitHub

Just added some extra test cases

jieyouxu Avatar
jieyouxu on 2026-03-23 06:10:57 UTC
jieyouxu Avatar
jieyouxu on 2026-03-23 06:10:57 UTC
View on GitHub

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

👍1
jieyouxu Avatar
jieyouxu commented on 2026-03-30 03:04:10 UTC
jieyouxu left a comment · edited
View on GitHub

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

src/tools/rustfmt/src/parse/macros/cfg_select.rs
jieyouxu Avatar jieyouxu on 2026-03-23 14:39:03 UTC
View on GitHub

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?

src/tools/rustfmt/src/parse/macros/cfg_select.rs
1 use rustc_ast::tokenstream::TokenStream;
jieyouxu Avatar jieyouxu on 2026-03-24 01:59:55 UTC
View on GitHub

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.
src/tools/rustfmt/src/parse/macros/cfg_select.rs
13 pub(crate) enum CfgSelectFormatPredicate {
14 Cfg(ast::MetaItemInner),
15 Wildcard(Span),
16 }
jieyouxu Avatar jieyouxu on 2026-03-30 02:20:11 UTC · edited
View on GitHub

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() {}
    }
}
src/tools/rustfmt/src/parse/macros/cfg_select.rs
29 pub(crate) arrow: Token,
30 pub(crate) expr: Box<ast::Expr>,
31 pub(crate) trailing_comma: Option<Span>,
32 }
jieyouxu Avatar jieyouxu on 2026-03-30 02:25:13 UTC
View on GitHub

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>,
}
src/tools/rustfmt/src/parse/macros/cfg_select.rs
41 impl Spanned for CfgSelectArm {
42 fn span(&self) -> Span {
43 self.predicate
44 .span()
45 .with_hi(if let Some(comma) = self.trailing_comma {
46 comma.hi()
47 } else {
48 self.expr.span.hi()
49 })
50 }
51 }
jieyouxu Avatar jieyouxu on 2026-03-30 02:30:28 UTC · edited
View on GitHub

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?

src/tools/rustfmt/src/macros.rs
244 246 }
245 247 }
246 248
249 if macro_name.ends_with("cfg_select!") {
jieyouxu Avatar jieyouxu on 2026-03-30 02:37:03 UTC · edited
View on GitHub

Remark: hm, I think this will work for 99% of use cases, but a user can re-export the std cfg_select! under another name (rustfmt AFAIK cannot use namers information, so I believe this is indeed the upper bound of what rustfmt can and should do), i.e.

// `cfg_select` available from core prelude.
// Re-export that name as another in macro namespace.
use cfg_select as i_want_to_trick_rustfmt;

i_want_to_trick_rustfmt! {
    _ => {
        fn main() {}
    }
}
src/tools/rustfmt/src/macros.rs
1563 Delimiter::Bracket => "[",
1564 Delimiter::Parenthesis => "(",
1565 Delimiter::Invisible(_) => {
1566 unreachable!("cfg_selec! macro will always have outer delimiters");
jieyouxu Avatar jieyouxu on 2026-03-30 02:38:25 UTC
View on GitHub

Nit (typo): cfg_select!

src/tools/rustfmt/tests/source/cfg_select.rs
57 // Original `{}` delimiters
58 cfg_select! { // opening brace comment
59
60 }
jieyouxu Avatar jieyouxu on 2026-03-30 02:45:42 UTC · edited
View on GitHub

Question: could you remind me how multiple (>= 1, >= 2) whitespace lines are intended to be handled? Should we add a test case for e.g.

// Original `{}` delimiters, multiple whitespace lines
cfg_select! { // opening brace comment



}
jieyouxu Avatar jieyouxu on 2026-03-30 03:06:15 UTC · edited
View on GitHub

Question: does/should the above interact with the max whitespace lines config option? (Sorry I don't immediately recall what that one was called.)

src/tools/rustfmt/tests/target/cfg_select.rs
322 // comments before and after the `=>` get dropped right now
323 cfg_select! {
324 any(true, true, true, true,) => {}
325
326 not(false) => {}
327
328 any(false) => "any",
329 }
jieyouxu Avatar jieyouxu on 2026-03-30 02:49:26 UTC
View on GitHub

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

src/tools/rustfmt/tests/source/cfg_select.rs
jieyouxu Avatar jieyouxu on 2026-03-30 02:59:42 UTC · edited
View on GitHub

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 #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... 🤷 
            }
        ]
    }
src/tools/rustfmt/tests/source/cfg_select.rs
980 match x {
981 (cfg_select! {
982 unix => Some(n),
983 _ => None,
jieyouxu Avatar jieyouxu on 2026-03-30 03:01:56 UTC · edited
View on GitHub

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 Avatar
rustbot on 2026-03-30 03:04:15 UTC
rustbot Avatar
rustbot on 2026-03-30 03:04:15 UTC
View on GitHub

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

jieyouxu Avatar
jieyouxu commented on 2026-03-30 08:30:20 UTC
src/tools/rustfmt/src/parse/macros/cfg_select.rs
jieyouxu Avatar jieyouxu on 2026-03-30 08:30:16 UTC
View on GitHub

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