rustfmt: Format cfg_select! rust-lang/rust#154202
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-contributorsexpanded to 6 candidates- Random selection from
fee1-dead,jieyouxu
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 | ]; |
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.
Suggestion: could you leave this comment in the test too? (um, slightly funny for rustfmt tests because well, comments can be formatting-meaningful themselves)
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 | } |
A pre-existing issue with trailing comments in rustfmt. I wanted to explicitly capture the current behavior in a test case.
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 | } |
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.
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 | } |
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?
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.
src/tools/rustfmt/tests/source/cfg_select.rs · resolved
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:
rust/tests/ui/macros/cfg_select.rs
Lines 64 to 132 in 562dee4
Thanks for pointing that out. I'll add some extra test cases!
Just added some extra test cases
Thanks! The impl broadly looks good to me, just some minor nits and a few test coverage discussions.
@rustbot author
src/tools/rustfmt/src/parse/macros/cfg_select.rs
Question: just to check my understanding,
cfg_select!parsing needs to be implemented in rustfmt right now
because there's no good way to callrustc_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; |
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 | } |
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 | } |
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 | } |
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!") { |
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"); |
Nit (typo): cfg_select!
src/tools/rustfmt/tests/source/cfg_select.rs
| 57 | // Original `{}` delimiters |
|
| 58 | cfg_select! { // opening brace comment |
|
| 59 | |
|
| 60 | } |
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
}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 | } |
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
Suggestion: there's two cases of additional test coverage that we may want to add:
- 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? - Nested
cfg_select!. For better or worse, you can also writecfg_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, |
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.
src/tools/rustfmt/src/parse/macros/cfg_select.rs
Backlinks / context for myself (and future travellers):
Metadata
- Library feature:
cfg_select - std docs: https://doc.rust-lang.org/nightly/std/macro.cfg_select.html
- Tracking issue: #115585
- Style team discussion: rust-lang/style-team#201
- Reference update PR: rust-lang/reference#2103
History
Initial style team discussions
- The arms must be wrapped in braces, so
rustfmtwill have to ensure to not remove those.
Follow-up: unbraced expressions are permitted
PR: #145233
View all comments
tracking issue: #115585
Implementing
cfg_select!formatting here inrust-lang/rustso 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: