diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 407c4aa30ee41..5619d948fde97 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -1525,7 +1525,7 @@ fn get_bytepos_after_visibility(vis: &ast::Visibility, default_span: Span) -> By // Format tuple or struct without any fields. We need to make sure that the comments // inside the delimiters are preserved. -fn format_empty_struct_or_tuple( +pub(crate) fn format_empty_struct_or_tuple( context: &RewriteContext<'_>, span: Span, offset: Indent, diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 2a824b4ce30e7..527140eca3a18 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -9,6 +9,7 @@ // List-like invocations with parentheses will be formatted as function calls, // and those with brackets will be formatted as array literals. +use std::borrow::Cow; use std::collections::HashMap; use std::panic::{AssertUnwindSafe, catch_unwind}; @@ -16,7 +17,7 @@ use rustc_ast::ast; use rustc_ast::token::{Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{TokenStream, TokenStreamIter, TokenTree}; use rustc_ast_pretty::pprust; -use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol}; +use rustc_span::{BytePos, DUMMY_SP, Ident, Pos, Span, Symbol}; use tracing::debug; use crate::comment::{ @@ -28,6 +29,7 @@ use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::header::{HeaderPart, format_header}; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; +use crate::parse::macros::cfg_select::{CfgSelectFormatPredicate, parse_cfg_select_arms}; use crate::parse::macros::lazy_static::parse_lazy_static; use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args}; use crate::rewrite::{ @@ -245,6 +247,26 @@ fn rewrite_macro_inner( } } + if macro_name.ends_with("cfg_select!") { + match format_cfg_select(context, shape, mac.span(), ¯o_name, style, ts.clone()) { + Ok(rw) => return Ok(rw), + Err(err) => match err { + // We will move on to parsing macro args just like other macros + // if we could not parse cfg_select! with known syntax + RewriteError::MacroFailure { kind, span: _ } + if kind == MacroErrorKind::ParseFailure => {} + // If formatting fails even though parsing succeeds, return the err early + other => return Err(other), + }, + } + } + + // If we're falling through to default macro handling check that the context is correct + debug_assert!( + context.inside_macro(), + "expect `context.inside_macro() == true`" + ); + let ParsedMacroArgs { args: arg_vec, vec_with_semi, @@ -1530,3 +1552,117 @@ fn rewrite_macro_with_items( result.push_str(trailing_semicolon); Ok(result) } + +fn format_cfg_select( + context: &RewriteContext<'_>, + shape: Shape, + span: Span, + name: &str, + delim_token: Delimiter, + ts: TokenStream, +) -> RewriteResult { + let mut rewrite = String::with_capacity((span.hi() - span.lo()).to_usize() * 2); + rewrite.push_str(name); + + let (opening_delim, closing_delim) = match delim_token { + Delimiter::Brace => ("{", "}"), + Delimiter::Bracket => ("[", "]"), + Delimiter::Parenthesis => ("(", ")"), + Delimiter::Invisible(_) => { + unreachable!("cfg_select! macro will always have outer delimiters"); + } + }; + + if matches!(delim_token, Delimiter::Brace) { + rewrite.push(' '); + }; + + let arms = + parse_cfg_select_arms(context.psess, ts).macro_error(MacroErrorKind::ParseFailure, span)?; + + if arms.is_empty() { + let lo = context.snippet_provider.span_after(span, opening_delim); + let hi = context.snippet_provider.span_before(span, closing_delim); + + // NOTE(ytmimi) reusing `format_empty_struct_or_tuple` since + // it handles proper indentation and recovering comments + crate::items::format_empty_struct_or_tuple( + context, + mk_sp(lo, hi), + shape.indent, + &mut rewrite, + opening_delim, + closing_delim, + ); + return Ok(rewrite); + } else { + rewrite.push_str(opening_delim); + } + + let nested_shape = shape.block_indent(context.config.tab_spaces()); + rewrite.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + + let last_arm = arms.last(); + + // We have to fib a little here and update the context to remove the `inside_macro` state. + // The code that flattens match arms will refuse to do so if it's inside a macro. Mostly + // this is done to prevent rustfmt from removing tokens in the context of a macro, but in + // this case it should be fine since we know that each `cfg_select!` arm must be a valid expr. + context.leave_macro(); + + let items = itemize_list( + context.snippet_provider, + arms.iter(), + closing_delim, + "}", + |arm| arm.span().lo(), + |arm| arm.span().hi(), + |arm| { + let predicate_str = match &arm.predicate { + CfgSelectFormatPredicate::Wildcard(_t) => Cow::Borrowed("_"), + CfgSelectFormatPredicate::Cfg(meta_item_inner) => { + Cow::Owned(meta_item_inner.rewrite_result(context, nested_shape)?) + } + }; + + crate::matches::rewrite_match_body( + context, + &arm.expr, + &predicate_str, + nested_shape, + false, + arm.arrow.span, + last_arm.is_some_and(|la| la == arm), + ) + }, + // Start Span after the opening delimiter. For example, + // ``` + // cfg_select! { + // ^ start here + // } + // ``` + context.snippet_provider.span_after(span, opening_delim), + // End on closing delimiter. For example, + // ``` + // cfg_select! { + // } + // ^ end here + // ``` + span.hi(), + false, + ); + let arms_vec: Vec<_> = items.collect(); + + // We will add/remove commas inside `arm.rewrite()`, and hence no separator here. + let fmt = ListFormatting::new(nested_shape, context.config) + .separator("") + .align_comments(false) + .preserve_newline(true); + + rewrite.push_str(&write_list(&arms_vec, &fmt)?); + rewrite.push('\n'); + rewrite.push_str(&shape.indent.to_string(context.config)); + rewrite.push_str(closing_delim); + + Ok(rewrite) +} diff --git a/src/tools/rustfmt/src/matches.rs b/src/tools/rustfmt/src/matches.rs index 50c0db8ac06e5..4e82df98de453 100644 --- a/src/tools/rustfmt/src/matches.rs +++ b/src/tools/rustfmt/src/matches.rs @@ -394,7 +394,7 @@ fn flatten_arm_body<'a>( } } -fn rewrite_match_body( +pub(crate) fn rewrite_match_body( context: &RewriteContext<'_>, body: &Box, pats_str: &str, diff --git a/src/tools/rustfmt/src/modules/visitor.rs b/src/tools/rustfmt/src/modules/visitor.rs index 485f44a936bd8..886128763c87a 100644 --- a/src/tools/rustfmt/src/modules/visitor.rs +++ b/src/tools/rustfmt/src/modules/visitor.rs @@ -5,7 +5,7 @@ use tracing::debug; use crate::attr::MetaVisitor; use crate::parse::macros::cfg_if::parse_cfg_if; -use crate::parse::macros::cfg_select::parse_cfg_select; +use crate::parse::macros::cfg_select::parse_items_from_cfg_select; use crate::parse::session::ParseSess; pub(crate) struct ModItem { @@ -123,7 +123,7 @@ impl<'a, 'ast: 'a> CfgSelectVisitor<'a> { } }; - let items = parse_cfg_select(self.psess, mac)?; + let items = parse_items_from_cfg_select(self.psess, mac)?; self.mods .append(&mut items.into_iter().map(|item| ModItem { item }).collect()); diff --git a/src/tools/rustfmt/src/parse/macros/cfg_select.rs b/src/tools/rustfmt/src/parse/macros/cfg_select.rs index 040447ff1898f..7127445a18984 100644 --- a/src/tools/rustfmt/src/parse/macros/cfg_select.rs +++ b/src/tools/rustfmt/src/parse/macros/cfg_select.rs @@ -1,25 +1,36 @@ +//! See [`cfg_select!` reference]( +//! https://doc.rust-lang.org/nightly/reference/conditional-compilation.html#the-cfg_select-macro +//! ) for grammar. + use std::panic::{AssertUnwindSafe, catch_unwind}; use rustc_ast::ast; -use rustc_ast::token::TokenKind; +use rustc_ast::token; +use rustc_ast::token::{Token, TokenKind}; +use rustc_ast::tokenstream::TokenStream; use rustc_parse::exp; use rustc_parse::parser::{AllowConstBlockItems, ForceCollect}; +use rustc_span::Span; +use tracing::debug; use crate::parse::macros::build_stream_parser; use crate::parse::session::ParseSess; +use crate::spanned::Spanned; -pub(crate) fn parse_cfg_select<'a>( +pub(crate) fn parse_items_from_cfg_select<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { - match catch_unwind(AssertUnwindSafe(|| parse_cfg_select_inner(psess, mac))) { + match catch_unwind(AssertUnwindSafe(|| { + parse_items_from_cfg_select_inner(psess, mac) + })) { Ok(Ok(items)) => Ok(items), Ok(err @ Err(_)) => err, Err(..) => Err("failed to parse cfg_select!"), } } -fn parse_cfg_select_inner<'a>( +fn parse_items_from_cfg_select_inner<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { @@ -78,3 +89,113 @@ fn parse_cfg_select_inner<'a>( Ok(items) } + +/// 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), +} + +impl Spanned for CfgSelectFormatPredicate { + fn span(&self) -> rustc_span::Span { + match self { + Self::Cfg(meta_item_inner) => meta_item_inner.span(), + Self::Wildcard(span) => *span, + } + } +} + +/// 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, + /// `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, +} + +impl PartialEq for &CfgSelectArm { + fn eq(&self, other: &Self) -> bool { + // consider the arms equal if they have the same span + self.span() == other.span() + } +} + +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() + }) + } +} + +impl std::fmt::Debug for CfgSelectArm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.predicate { + CfgSelectFormatPredicate::Cfg(cfg_entry) => cfg_entry.fmt(f)?, + CfgSelectFormatPredicate::Wildcard(t) => t.fmt(f)?, + }; + write!(f, "=> {:?}", self.expr) + } +} + +// FIXME(ytmimi) would be nice if rustfmt didn't need to implement parsing logic on its own +// and could instead just call rustc_attr_parsing::parse_cfg_select, but this is fine for now. +pub(crate) fn parse_cfg_select_arms( + psess: &ParseSess, + ts: TokenStream, +) -> Option> { + let mut cfg_select_predicates = vec![]; + let mut parser = build_stream_parser(psess.inner(), ts); + + while parser.token != token::Eof { + let predicate = if parser.eat_keyword(exp!(Underscore)) { + CfgSelectFormatPredicate::Wildcard(parser.prev_token.span) + } else { + let Ok(meta_item) = parser.parse_meta_item_inner().map_err(|e| e.cancel()) else { + debug!("Failed to parse cfg entry in cfg_select! predicate"); + return None; + }; + CfgSelectFormatPredicate::Cfg(meta_item) + }; + + if let Err(e) = parser.expect(exp!(FatArrow)) { + e.cancel(); + debug!("Expected to find a `=>` after cfg_selec! predicate."); + return None; + }; + + let arrow = parser.prev_token; + + let Ok(expr) = parser.parse_expr().map_err(|e| e.cancel()) else { + debug!("Couldn't parse cfg_select! arm body after `=>`."); + return None; + }; + + let trailing_comma = if parser.eat(exp!(Comma)) { + Some(parser.prev_token.span) + } else { + None + }; + + let arm = CfgSelectArm { + predicate, + arrow, + expr, + trailing_comma, + }; + + cfg_select_predicates.push(arm); + } + Some(cfg_select_predicates) +} diff --git a/src/tools/rustfmt/tests/source/cfg_select.rs b/src/tools/rustfmt/tests/source/cfg_select.rs new file mode 100644 index 0000000000000..794967afeb26b --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select.rs @@ -0,0 +1,1115 @@ +// rustfmt-style_edition: 2024 +// rustfmt-skip_children: true + +// empty cfg_select! +// Original `{}` delimiters +cfg_select! { +} +std::cfg_select! { +} +core::cfg_select! { +} + +// empty with other delimiters +// Original `()` delimiters +cfg_select! ( +); +std::cfg_select! ( +); +core::cfg_select! ( +); + +// Original `[]` delimiters +cfg_select! [ +]; +std::cfg_select! [ +]; +core::cfg_select! [ +]; + + +// Original `{}` delimiters +cfg_select! { /* inline comment */ +} +std::cfg_select! { /* inline comment */ +} +core::cfg_select! { /* inline comment */ +} +core::cfg_select! { /* inline comment + * multi-line + */ +} +cfg_select! { // followed by multiple whitespace lines in source + + + +} + + +// Original `()` delimiters +cfg_select! ( /* inline comment */ +); +std::cfg_select! ( /* inline comment */ +); +core::cfg_select! ( /* inline comment */ +); +core::cfg_select!( /* inline comment + * multi-line + */ +); +cfg_select! ( // followed by multiple whitespace lines in source + + + +); + + +// Original `[]` delimiters +cfg_select! [ /* inline comment */ +]; +std::cfg_select! [ /* inline comment */ +]; +core::cfg_select! [ /* inline comment */ +]; +core::cfg_select![ /* inline comment + * multi-line + */ +]; +cfg_select! [ // followed by multiple whitespace lines in source + + + +]; + + + +// 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 + +); + +// Original `[]` delimiters +cfg_select! [ // opening brace comment + +]; +std::cfg_select! [ // opening brace comment + +]; +core::cfg_select! [ // opening brace comment + +]; + + +// Original `{}` delimiters +cfg_select! { + // nested inner comment +} +std::cfg_select! { + // nested inner comment +} +core::cfg_select! { + // nested inner comment +} + +// Original `()` delimiters +cfg_select! ( + // nested inner comment +); +std::cfg_select! ( + // nested inner comment +); +core::cfg_select! ( + // nested inner comment +); + +// Original `[]` delimiters +cfg_select! [ + // nested inner comment +]; +std::cfg_select! [ + // nested inner comment +]; +core::cfg_select! [ + // nested inner comment +]; + + +fn expression_position() { + // cfg_select arms with block + println!(cfg_select! { + unix => { "unix" } + windows => { "windows" } + _ => { "not " + "windows" + "or" + "unix" } + }); + + println!(std::cfg_select! { + unix => { "unix" } + windows => { "windows" } + _ => { "not " + "windows" + "or" + "unix" } + }); + + println!(core::cfg_select! { + unix => { "unix" } + windows => { "windows" } + _ => { "not " + "windows" + "or" + "unix" } + }); + + // cfg_select arms with block and trailing commas + println!(cfg_select! { + unix => { "unix" }, + windows => { "windows" }, + _ => { "not " + "windows" + "or" + "unix" }, + }); + + println!(std::cfg_select! { + unix => { "unix" }, + windows => { "windows" }, + _ => { "not " + "windows" + "or" + "unix" }, + }); + + println!(core::cfg_select! { + unix => { "unix" }, + windows => { "windows" }, + _ => { "not " + "windows" + "or" + "unix" }, + }); + + // cfg_select arms without block + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + // cfg_select arms with and without blocks + println!(cfg_select! { + unix => {"unix"} + windows => {"windows"}, + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => {"unix"} + windows => {"windows"}, + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => {"unix"} + windows => {"windows"}, + _ => "not windows or unix", + }); +} + +// user specified newlines between arms are preserved +core::cfg_select! { + windows => {} + + + unix => { } + + + _ => {} +} + +core::cfg_select! ( + windows => {} + + + unix => { } + + + _ => {} +); + +core::cfg_select! [ + windows => {} + + + unix => { } + + + _ => {} +]; + + +// Leading comments are also preserved +core::cfg_select! { + // windows-Pre Comment + + windows => {} + // windows-Post comment + + + // unix-Pre Comment + + unix => { } + // unix-Post comment + + + // wildcard Comment + + _ => {} + // wildcard-Post comment +} + +// trailing comments work +cfg_select! { + windows => {} + // windows-Post comment + + + unix => { } + // unix-Post comment + + + + _ => {} + // wildcard-Post comment +} + +core::cfg_select! { + windows => {}// windows-Post comment + + + unix => { }// unix-Post comment + + _ => {}// wildcard-Post comment +} + +// 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 +} + + +// comments within the predicate are fine with style_edition=2024+ +cfg_select! { + any(true, /* comment */ + true, true, // true, + true, ) + // comment before arrow + => {} + + not(false // comment + ) => { + + } + + any(false // comment + ) => "any" +} + +// comments before and after the `=>` get dropped right now +cfg_select! { + any(true, + true, true, + true, ) + // commetn before arrow + => {} + + not(false + ) => /* comment before opening brace */ { + + } + + any(false + ) => // comment before brace + "any" +} + + +// A bunch of mixed predicates +cfg_select! { + // When all predicates are simple uses mixed list formatting + any(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true) => {} + + // more "complex" predicates will wrap using vertical formatting + all(target_arch = "x86_64", true, target_endian = "little", debug_assertions, panic = "unwind", target_env = "gnu") => {} + all(any(target_arch = "x86_64", true, target_endian = "little"), debug_assertions, panic = "unwind", all(target_env = "gnu", true)) => {} + + // nested "complex" predicates + all(target_arch = "x86_64", true, target_endian = "little", debug_assertions, panic = "unwind", target_env = "gnu", not(all(target_arch = "x86_64", true, target_endian = "little", debug_assertions, panic = "unwind", target_env = "gnu"))) => {} + + any(true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true) => {} + all( + any(target_arch = "x86_64", true, target_endian = "little"), debug_assertions, + panic = "unwind", all(target_env = "gnu", true) + ) => {} + + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + // This line is under 80 characters, no reason to break. + any(feature = "acdefg", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is under 80 characters, but the line as a whole is over 80 characters. + any(feature = "acdefgh123", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is over 80 characters. + not(any(feature = "acdefgh1234", true, true, true, true, true, true, true, true)) => { + compile_error!("foo") + } + // make sure that #![feature(cfg_version)] works + version("1.44.0") => { + } + + _ => {} +} + +// 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; + } +} + +std::cfg_select! { + target_arch = "aarch64" => { + use std::sync::OnceCell; + + fn foo() { + return 3; + } + + } + _ => { + compile_error!("mal", "formed") + } + false => { + compile_error!("also", "mal", "formed") + } +} + + +mod nested { + +// empty cfg_select! +// Original `{}` delimiters +cfg_select! { +} +std::cfg_select! { +} +core::cfg_select! { +} + +// empty with other delimiters +// Original `()` delimiters +cfg_select! ( +); +std::cfg_select! ( +); +core::cfg_select! ( +); + +// Original `[]` delimiters +cfg_select! [ +]; +std::cfg_select! [ +]; +core::cfg_select! [ +]; + + +// Original `{}` delimiters +cfg_select! { /* inline comment */ +} +std::cfg_select! { /* inline comment */ +} +core::cfg_select! { /* inline comment */ +} +core::cfg_select! { /* inline comment + * multi-line + */ +} + + +// Original `()` delimiters +cfg_select! ( /* inline comment */ +); +std::cfg_select! ( /* inline comment */ +); +core::cfg_select! ( /* inline comment */ +); +core::cfg_select! ( /* inline comment + * multi-line + */ +); + + +// Original `[]` delimiters +cfg_select! [ /* inline comment */ +]; +std::cfg_select! [ /* inline comment */ +]; +core::cfg_select! [ /* inline comment */ +]; +core::cfg_select! [ /* inline comment + * multi-line + */ +]; + + +// 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 + +); + +// Original `[]` delimiters +cfg_select! [ // opening brace comment + +]; +std::cfg_select! [ // opening brace comment + +]; +core::cfg_select! [ // opening brace comment + +]; + + +// Original `{}` delimiters +cfg_select! { + // nested inner comment +} +std::cfg_select! { + // nested inner comment +} +core::cfg_select! { + // nested inner comment +} + +// Original `()` delimiters +cfg_select! ( + // nested inner comment +); +std::cfg_select! ( + // nested inner comment +); +core::cfg_select! ( + // nested inner comment +); + +// Original `[]` delimiters +cfg_select! [ + // nested inner comment +]; +std::cfg_select! [ + // nested inner comment +]; +core::cfg_select! [ + // nested inner comment +]; + + +fn expression_position() { + // cfg_select arms with block + println!(cfg_select! { + unix => { "unix" } + windows => { "windows" } + _ => { "not " + "windows" + "or" + "unix" } + }); + + println!(std::cfg_select! { + unix => { "unix" } + windows => { "windows" } + _ => { "not " + "windows" + "or" + "unix" } + }); + + println!(core::cfg_select! { + unix => { "unix" } + windows => { "windows" } + _ => { "not " + "windows" + "or" + "unix" } + }); + + // cfg_select arms with block and trailing commas + println!(cfg_select! { + unix => { "unix" }, + windows => { "windows" }, + _ => { "not " + "windows" + "or" + "unix" }, + }); + + println!(std::cfg_select! { + unix => { "unix" }, + windows => { "windows" }, + _ => { "not " + "windows" + "or" + "unix" }, + }); + + println!(core::cfg_select! { + unix => { "unix" }, + windows => { "windows" }, + _ => { "not " + "windows" + "or" + "unix" }, + }); + + // cfg_select arms without block + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + // cfg_select arms with and without blocks + println!(cfg_select! { + unix => {"unix"} + windows => {"windows"}, + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => {"unix"} + windows => {"windows"}, + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => {"unix"} + windows => {"windows"}, + _ => "not windows or unix", + }); +} + +// user specified newlines between arms are preserved +core::cfg_select! { + windows => {} + + + unix => { } + + + _ => {} +} + +core::cfg_select! ( + windows => {} + + + unix => { } + + + _ => {} +); + +core::cfg_select! [ + windows => {} + + + unix => { } + + + _ => {} +]; + + +// Leading comments are also preserved +core::cfg_select! { + // windows-Pre Comment + + windows => {} + // windows-Post comment + + + // unix-Pre Comment + + unix => { } + // unix-Post comment + + + // wildcard Comment + + _ => {} + // wildcard-Post comment +} + +// trailing comments work +cfg_select! { + windows => {} + // windows-Post comment + + + unix => { } + // unix-Post comment + + + + _ => {} + // wildcard-Post comment +} + +core::cfg_select! { + windows => {}// windows-Post comment + + + unix => { }// unix-Post comment + + _ => {}// wildcard-Post comment +} + +// 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 +} + + +// comments within the predicate are fine with style_edition=2024+ +cfg_select! { + any(true, /* comment */ + true, true, // true, + true, ) + // comment before arrow + => {} + + not(false // comment + ) => { + + } + + any(false // comment + ) => "any" +} + +// comments before and after the `=>` get dropped right now +cfg_select! { + any(true, + true, true, + true, ) + // commetn before arrow + => {} + + not(false + ) => /* comment before opening brace */ { + + } + + any(false + ) => // comment before brace + "any" +} + + +// A bunch of mixed predicates +cfg_select! { + // When all predicates are simple uses mixed list formatting + any(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true) => {} + + // more "complex" predicates will wrap using vertical formatting + all(target_arch = "x86_64", true, target_endian = "little", debug_assertions, panic = "unwind", target_env = "gnu") => {} + all(any(target_arch = "x86_64", true, target_endian = "little"), debug_assertions, panic = "unwind", all(target_env = "gnu", true)) => {} + + // nested "complex" predicates + all(target_arch = "x86_64", true, target_endian = "little", debug_assertions, panic = "unwind", target_env = "gnu", not(all(target_arch = "x86_64", true, target_endian = "little", debug_assertions, panic = "unwind", target_env = "gnu"))) => {} + + any(true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true) => {} + all( + any(target_arch = "x86_64", true, target_endian = "little"), debug_assertions, + panic = "unwind", all(target_env = "gnu", true) + ) => {} + + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + // This line is under 80 characters, no reason to break. + any(feature = "acdefg", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is under 80 characters, but the line as a whole is over 80 characters. + any(feature = "acdefgh123", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is over 80 characters. + not(any(feature = "acdefgh1234", true, true, true, true, true, true, true, true)) => { + compile_error!("foo") + } + // make sure that #![feature(cfg_version)] works + version("1.44.0") => { + } + + _ => {} +} + +// 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; + } +} + +std::cfg_select! { + target_arch = "aarch64" => { + use std::sync::OnceCell; + + fn foo() { + return 3; + } + + } + _ => { + compile_error!("mal", "formed") + } + false => { + compile_error!("also", "mal", "formed") + } +} + +} + + +// Some examples I pulled from rust-lang/rust +#[cfg(target_env = "musl")] +cfg_select! { + all(feature = "llvm-libunwind", feature = "system-llvm-libunwind") => { + compile_error!("`llvm-libunwind` and `system-llvm-libunwind` cannot be enabled at the same time"); + } + feature = "llvm-libunwind" => { + #[link(name = "unwind", kind = "static", modifiers = "-bundle")] + unsafe extern "C" {} + } + feature = "system-llvm-libunwind" => { + #[link(name = "unwind", kind = "static", modifiers = "-bundle", cfg(target_feature = "crt-static"))] + #[link(name = "unwind", cfg(not(target_feature = "crt-static")))] + unsafe extern "C" {} + } + _ => { + #[link(name = "unwind", kind = "static", modifiers = "-bundle", cfg(target_feature = "crt-static"))] + #[link(name = "gcc_s", cfg(all(not(target_feature = "crt-static"), not(target_arch = "hexagon"))))] + unsafe extern "C" {} + } +} + +pub const fn midpoint(self, other: f32) -> f32 { + cfg_select! { + // Allow faster implementation that have known good 64-bit float + // implementations. Falling back to the branchy code on targets that don't + // have 64-bit hardware floats or buggy implementations. + // https://github.com/rust-lang/rust/pull/121062#issuecomment-2123408114 + any( + target_arch = "x86_64", + target_arch = "aarch64", + all(any(target_arch = "riscv32", target_arch = "riscv64"), target_feature = "d"), + all(target_arch = "loongarch64", target_feature = "d"), + all(target_arch = "arm", target_feature = "vfp2"), + target_arch = "wasm32", + target_arch = "wasm64", + ) => { + ((self as f64 + other as f64) / 2.0) as f32 + } + _ => { + const HI: f32 = f32::MAX / 2.; + + let (a, b) = (self, other); + let abs_a = a.abs(); + let abs_b = b.abs(); + + if abs_a <= HI && abs_b <= HI { + // Overflow is impossible + (a + b) / 2. + } else { + (a / 2.) + (b / 2.) + } + } + } +} + +mod c_int_definition { + crate::cfg_select! { + any(target_arch = "avr", target_arch = "msp430") => { + pub(super) type c_int = i16; + pub(super) type c_uint = u16; + } + _ => { + pub(super) type c_int = i32; + pub(super) type c_uint = u32; + } + } +} + +cfg_select! { + any( + target_family = "unix", + target_os = "wasi", + target_os = "teeos", + target_os = "trusty", + ) => { + mod unix; + } + target_os = "windows" => { + mod windows; + } + target_os = "hermit" => { + mod hermit; + } + target_os = "motor" => { + mod motor; + } + all(target_vendor = "fortanix", target_env = "sgx") => { + mod sgx; + } + target_os = "solid_asp3" => { + mod solid; + } + target_os = "uefi" => { + mod uefi; + } + target_os = "vexos" => { + mod vexos; + } + target_family = "wasm" => { + mod wasm; + } + target_os = "xous" => { + mod xous; + } + target_os = "zkvm" => { + mod zkvm; + } +} + +// Other rust-lang/rust ui tests to cover other expansion sites +fn arm_rhs_expr_3() -> i32 { + cfg_select! { + any(true) => 1, + any(false) => 2, + any(true) => { 42 } + any(true) => { 42 }, + any(false) => -1 as i32, + any(true) => 2 + 2, + any(false) => "", + any(true) => if true { 42 } else { 84 } + any(false) => if true { 42 } else { 84 }, + any(true) => return 42, + any(false) => loop {} + any(true) => (1, 2), + any(false) => (1, 2,), + any(true) => todo!(), + any(false) => println!("hello"), + } +} + +fn expand_to_statements() -> i32 { + cfg_select! { + false => { + let b = 2; + b + 1 + } + true => { + let a = 1; + a + 1 + } + } +} + +type ExpandToType = cfg_select! { + unix => { u32 }, + _ => i32, +}; + +fn expand_to_pattern(x: Option) -> 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; + } + } +} + +// Nested cfg_select! +std :: cfg_select! { + + _ => core :: cfg_select! [ + _ => { + // I don't know why you would write a nested cfg_select!, + // but you can, so... 🤷 + 1 + 1 + } + + _ => { + // some coverage for inline comment handling, which currently + // prevents formatting to prevent comment loss. + cfg_select! /* 1 */ { + unix => Some(n), + _ => None, + } + } + ] +} + +// Doesn't parse as expected so this is handled by the default macro handling +cfg_select! ( + A + B + C +); +cfg_select! [ + A + B + C +]; +// rustfmt doesn't format macros with brace delimiters +cfg_select! { + A + B + C +} diff --git a/src/tools/rustfmt/tests/target/cfg_select.rs b/src/tools/rustfmt/tests/target/cfg_select.rs new file mode 100644 index 0000000000000..fa242c7d2062f --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select.rs @@ -0,0 +1,1104 @@ +// rustfmt-style_edition: 2024 +// rustfmt-skip_children: true + +// empty cfg_select! +// Original `{}` delimiters +cfg_select! {} +std::cfg_select! {} +core::cfg_select! {} + +// empty with other delimiters +// Original `()` delimiters +cfg_select!(); +std::cfg_select!(); +core::cfg_select!(); + +// Original `[]` delimiters +cfg_select![]; +std::cfg_select![]; +core::cfg_select![]; + +// Original `{}` delimiters +cfg_select! {/* inline comment */} +std::cfg_select! {/* inline comment */} +core::cfg_select! {/* inline comment */} +core::cfg_select! { + /* inline comment + * multi-line + */ +} +cfg_select! { + // followed by multiple whitespace lines in source +} + +// Original `()` delimiters +cfg_select!(/* inline comment */); +std::cfg_select!(/* inline comment */); +core::cfg_select!(/* inline comment */); +core::cfg_select!( + /* inline comment + * multi-line + */ +); +cfg_select!( + // followed by multiple whitespace lines in source +); + +// Original `[]` delimiters +cfg_select![/* inline comment */]; +std::cfg_select![/* inline comment */]; +core::cfg_select![/* inline comment */]; +core::cfg_select![ + /* inline comment + * multi-line + */ +]; +cfg_select![ + // followed by multiple whitespace lines in source +]; + +// 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 +); + +// Original `[]` delimiters +cfg_select![ + // opening brace comment +]; +std::cfg_select![ + // opening brace comment +]; +core::cfg_select![ + // opening brace comment +]; + +// Original `{}` delimiters +cfg_select! { + // nested inner comment +} +std::cfg_select! { + // nested inner comment +} +core::cfg_select! { + // nested inner comment +} + +// Original `()` delimiters +cfg_select!( + // nested inner comment +); +std::cfg_select!( + // nested inner comment +); +core::cfg_select!( + // nested inner comment +); + +// Original `[]` delimiters +cfg_select![ + // nested inner comment +]; +std::cfg_select![ + // nested inner comment +]; +core::cfg_select![ + // nested inner comment +]; + +fn expression_position() { + // cfg_select arms with block + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + // cfg_select arms with block and trailing commas + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + // cfg_select arms without block + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + // cfg_select arms with and without blocks + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); +} + +// user specified newlines between arms are preserved +core::cfg_select! { + windows => {} + + unix => {} + + _ => {} +} + +core::cfg_select!( + windows => {} + + unix => {} + + _ => {} +); + +core::cfg_select![ + windows => {} + + unix => {} + + _ => {} +]; + +// Leading comments are also preserved +core::cfg_select! { + // windows-Pre Comment + windows => {} + // windows-Post comment + + // unix-Pre Comment + unix => {} + // unix-Post comment + + // wildcard Comment + _ => {} // wildcard-Post comment +} + +// trailing comments work +cfg_select! { + windows => {} + // windows-Post comment + unix => {} + // unix-Post comment + _ => {} // wildcard-Post comment +} + +core::cfg_select! { + windows => {} // windows-Post comment + + unix => {} // unix-Post comment + + _ => {} // wildcard-Post comment +} + +// 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 +} + +// 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", +} + +// comments before and after the `=>` get dropped right now +cfg_select! { + any(true, true, true, true,) => {} + + not(false) => {} + + any(false) => "any", +} + +// A bunch of mixed predicates +cfg_select! { + // When all predicates are simple uses mixed list formatting + any( + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true + ) => {} + + // more "complex" predicates will wrap using vertical formatting + all( + target_arch = "x86_64", + true, + target_endian = "little", + debug_assertions, + panic = "unwind", + target_env = "gnu" + ) => {} + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + // nested "complex" predicates + all( + target_arch = "x86_64", + true, + target_endian = "little", + debug_assertions, + panic = "unwind", + target_env = "gnu", + not(all( + target_arch = "x86_64", + true, + target_endian = "little", + debug_assertions, + panic = "unwind", + target_env = "gnu" + )) + ) => {} + + any( + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true + ) => {} + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + // This line is under 80 characters, no reason to break. + any(feature = "acdefg", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is under 80 characters, but the line as a whole is over 80 characters. + any(feature = "acdefgh123", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is over 80 characters. + not(any( + feature = "acdefgh1234", + true, + true, + true, + true, + true, + true, + true, + true + )) => { + compile_error!("foo") + } + // make sure that #![feature(cfg_version)] works + version("1.44.0") => {} + + _ => {} +} + +// 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; + } +} + +std::cfg_select! { + target_arch = "aarch64" => { + use std::sync::OnceCell; + + fn foo() { + return 3; + } + } + _ => { + compile_error!("mal", "formed") + } + false => { + compile_error!("also", "mal", "formed") + } +} + +mod nested { + + // empty cfg_select! + // Original `{}` delimiters + cfg_select! {} + std::cfg_select! {} + core::cfg_select! {} + + // empty with other delimiters + // Original `()` delimiters + cfg_select!(); + std::cfg_select!(); + core::cfg_select!(); + + // Original `[]` delimiters + cfg_select![]; + std::cfg_select![]; + core::cfg_select![]; + + // Original `{}` delimiters + cfg_select! {/* inline comment */} + std::cfg_select! {/* inline comment */} + core::cfg_select! {/* inline comment */} + core::cfg_select! { + /* inline comment + * multi-line + */ + } + + // Original `()` delimiters + cfg_select!(/* inline comment */); + std::cfg_select!(/* inline comment */); + core::cfg_select!(/* inline comment */); + core::cfg_select!( + /* inline comment + * multi-line + */ + ); + + // Original `[]` delimiters + cfg_select![/* inline comment */]; + std::cfg_select![/* inline comment */]; + core::cfg_select![/* inline comment */]; + core::cfg_select![ + /* inline comment + * multi-line + */ + ]; + + // 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 + ); + + // Original `[]` delimiters + cfg_select![ + // opening brace comment + ]; + std::cfg_select![ + // opening brace comment + ]; + core::cfg_select![ + // opening brace comment + ]; + + // Original `{}` delimiters + cfg_select! { + // nested inner comment + } + std::cfg_select! { + // nested inner comment + } + core::cfg_select! { + // nested inner comment + } + + // Original `()` delimiters + cfg_select!( + // nested inner comment + ); + std::cfg_select!( + // nested inner comment + ); + core::cfg_select!( + // nested inner comment + ); + + // Original `[]` delimiters + cfg_select![ + // nested inner comment + ]; + std::cfg_select![ + // nested inner comment + ]; + core::cfg_select![ + // nested inner comment + ]; + + fn expression_position() { + // cfg_select arms with block + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + // cfg_select arms with block and trailing commas + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", + }); + + // cfg_select arms without block + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + // cfg_select arms with and without blocks + println!(cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(std::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + + println!(core::cfg_select! { + unix => "unix", + windows => "windows", + _ => "not windows or unix", + }); + } + + // user specified newlines between arms are preserved + core::cfg_select! { + windows => {} + + unix => {} + + _ => {} + } + + core::cfg_select!( + windows => {} + + unix => {} + + _ => {} + ); + + core::cfg_select![ + windows => {} + + unix => {} + + _ => {} + ]; + + // Leading comments are also preserved + core::cfg_select! { + // windows-Pre Comment + windows => {} + // windows-Post comment + + // unix-Pre Comment + unix => {} + // unix-Post comment + + // wildcard Comment + _ => {} // wildcard-Post comment + } + + // trailing comments work + cfg_select! { + windows => {} + // windows-Post comment + unix => {} + // unix-Post comment + _ => {} // wildcard-Post comment + } + + core::cfg_select! { + windows => {} // windows-Post comment + + unix => {} // unix-Post comment + + _ => {} // wildcard-Post comment + } + + // 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 + } + + // 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", + } + + // comments before and after the `=>` get dropped right now + cfg_select! { + any(true, true, true, true,) => {} + + not(false) => {} + + any(false) => "any", + } + + // A bunch of mixed predicates + cfg_select! { + // When all predicates are simple uses mixed list formatting + any( + true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true + ) => {} + + // more "complex" predicates will wrap using vertical formatting + all( + target_arch = "x86_64", + true, + target_endian = "little", + debug_assertions, + panic = "unwind", + target_env = "gnu" + ) => {} + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + // nested "complex" predicates + all( + target_arch = "x86_64", + true, + target_endian = "little", + debug_assertions, + panic = "unwind", + target_env = "gnu", + not(all( + target_arch = "x86_64", + true, + target_endian = "little", + debug_assertions, + panic = "unwind", + target_env = "gnu" + )) + ) => {} + + any( + true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true + ) => {} + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + all( + any(target_arch = "x86_64", true, target_endian = "little"), + debug_assertions, + panic = "unwind", + all(target_env = "gnu", true) + ) => {} + + // This line is under 80 characters, no reason to break. + any(feature = "acdefg", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is under 80 characters, but the line as a whole is over 80 characters. + any(feature = "acdefgh123", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + // The cfg is over 80 characters. + not(any( + feature = "acdefgh1234", + true, + true, + true, + true, + true, + true, + true, + true + )) => { + compile_error!("foo") + } + // make sure that #![feature(cfg_version)] works + version("1.44.0") => {} + + _ => {} + } + + // 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; + } + } + + std::cfg_select! { + target_arch = "aarch64" => { + use std::sync::OnceCell; + + fn foo() { + return 3; + } + } + _ => { + compile_error!("mal", "formed") + } + false => { + compile_error!("also", "mal", "formed") + } + } +} + +// Some examples I pulled from rust-lang/rust +#[cfg(target_env = "musl")] +cfg_select! { + all(feature = "llvm-libunwind", feature = "system-llvm-libunwind") => { + compile_error!( + "`llvm-libunwind` and `system-llvm-libunwind` cannot be enabled at the same time" + ); + } + feature = "llvm-libunwind" => { + #[link(name = "unwind", kind = "static", modifiers = "-bundle")] + unsafe extern "C" {} + } + feature = "system-llvm-libunwind" => { + #[link( + name = "unwind", + kind = "static", + modifiers = "-bundle", + cfg(target_feature = "crt-static") + )] + #[link(name = "unwind", cfg(not(target_feature = "crt-static")))] + unsafe extern "C" {} + } + _ => { + #[link( + name = "unwind", + kind = "static", + modifiers = "-bundle", + cfg(target_feature = "crt-static") + )] + #[link( + name = "gcc_s", + cfg(all(not(target_feature = "crt-static"), not(target_arch = "hexagon"))) + )] + unsafe extern "C" {} + } +} + +pub const fn midpoint(self, other: f32) -> f32 { + cfg_select! { + // Allow faster implementation that have known good 64-bit float + // implementations. Falling back to the branchy code on targets that don't + // have 64-bit hardware floats or buggy implementations. + // https://github.com/rust-lang/rust/pull/121062#issuecomment-2123408114 + any( + target_arch = "x86_64", + target_arch = "aarch64", + all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_feature = "d" + ), + all(target_arch = "loongarch64", target_feature = "d"), + all(target_arch = "arm", target_feature = "vfp2"), + target_arch = "wasm32", + target_arch = "wasm64", + ) => ((self as f64 + other as f64) / 2.0) as f32, + _ => { + const HI: f32 = f32::MAX / 2.; + + let (a, b) = (self, other); + let abs_a = a.abs(); + let abs_b = b.abs(); + + if abs_a <= HI && abs_b <= HI { + // Overflow is impossible + (a + b) / 2. + } else { + (a / 2.) + (b / 2.) + } + } + } +} + +mod c_int_definition { + crate::cfg_select! { + any(target_arch = "avr", target_arch = "msp430") => { + pub(super) type c_int = i16; + pub(super) type c_uint = u16; + } + _ => { + pub(super) type c_int = i32; + pub(super) type c_uint = u32; + } + } +} + +cfg_select! { + any( + target_family = "unix", + target_os = "wasi", + target_os = "teeos", + target_os = "trusty", + ) => { + mod unix; + } + target_os = "windows" => { + mod windows; + } + target_os = "hermit" => { + mod hermit; + } + target_os = "motor" => { + mod motor; + } + all(target_vendor = "fortanix", target_env = "sgx") => { + mod sgx; + } + target_os = "solid_asp3" => { + mod solid; + } + target_os = "uefi" => { + mod uefi; + } + target_os = "vexos" => { + mod vexos; + } + target_family = "wasm" => { + mod wasm; + } + target_os = "xous" => { + mod xous; + } + target_os = "zkvm" => { + mod zkvm; + } +} + +// Other rust-lang/rust ui tests to cover other expansion sites +fn arm_rhs_expr_3() -> i32 { + cfg_select! { + any(true) => 1, + any(false) => 2, + any(true) => 42, + any(true) => 42, + any(false) => -1 as i32, + any(true) => 2 + 2, + any(false) => "", + any(true) => { + if true { + 42 + } else { + 84 + } + } + any(false) => { + if true { + 42 + } else { + 84 + } + } + any(true) => return 42, + any(false) => loop {}, + any(true) => (1, 2), + any(false) => (1, 2), + any(true) => todo!(), + any(false) => println!("hello"), + } +} + +fn expand_to_statements() -> i32 { + cfg_select! { + false => { + let b = 2; + b + 1 + } + true => { + let a = 1; + a + 1 + } + } +} + +type ExpandToType = cfg_select! { + unix => u32, + _ => i32, +}; + +fn expand_to_pattern(x: Option) -> 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; + } + } +} + +// Nested cfg_select! +std::cfg_select! { + _ => core::cfg_select![ + _ => { + // I don't know why you would write a nested cfg_select!, + // but you can, so... 🤷 + 1 + 1 + } + + _ => { + // some coverage for inline comment handling, which currently + // prevents formatting to prevent comment loss. + cfg_select! /* 1 */ { + unix => Some(n), + _ => None, + } + } + ], +} + +// Doesn't parse as expected so this is handled by the default macro handling +cfg_select!(A + B + C); +cfg_select![A + B + C]; +// rustfmt doesn't format macros with brace delimiters +cfg_select! { + A + B + C +}