diff --git a/rust-toolchain b/rust-toolchain index 5dac4fcf280..9dda873e3e8 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-07-19" +channel = "nightly-2026-08-27" components = ["llvm-tools", "rustc-dev"] diff --git a/src/closures.rs b/src/closures.rs index 9bd319a3a5f..551e2e40425 100644 --- a/src/closures.rs +++ b/src/closures.rs @@ -14,7 +14,9 @@ use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, Rew use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::types::rewrite_bound_params; -use crate::utils::{NodeIdExt, last_line_width, left_most_sub_expr, outer_attributes, stmt_expr}; +use crate::utils::{ + NodeIdExt, format_coro, last_line_width, left_most_sub_expr, outer_attributes, stmt_expr, +}; // This module is pretty messy because of the rules around closures and blocks: // FIXME - the below is probably no longer true in full. @@ -30,7 +32,7 @@ pub(crate) fn rewrite_closure( binder: &ast::ClosureBinder, constness: ast::Const, capture: ast::CaptureBy, - coroutine_kind: &Option, + coroutine_marker: &Option, movability: ast::Movability, fn_decl: &ast::FnDecl, body: &ast::Expr, @@ -44,7 +46,7 @@ pub(crate) fn rewrite_closure( binder, constness, capture, - coroutine_kind, + coroutine_marker, movability, fn_decl, body, @@ -257,7 +259,7 @@ fn rewrite_closure_fn_decl( binder: &ast::ClosureBinder, constness: ast::Const, capture: ast::CaptureBy, - coroutine_kind: &Option, + coroutine_marker: &Option, movability: ast::Movability, fn_decl: &ast::FnDecl, body: &ast::Expr, @@ -288,12 +290,7 @@ fn rewrite_closure_fn_decl( } else { "" }; - let coro = match coroutine_kind { - Some(ast::CoroutineKind::Async { .. }) => "async ", - Some(ast::CoroutineKind::Gen { .. }) => "gen ", - Some(ast::CoroutineKind::AsyncGen { .. }) => "async gen ", - None => "", - }; + let coro = coroutine_marker.map_or_default(format_coro); let capture_str = match capture { ast::CaptureBy::Value { .. } => "move ", ast::CaptureBy::Use { .. } => "use ", @@ -370,7 +367,7 @@ pub(crate) fn rewrite_last_closure( ref binder, constness, capture_clause, - ref coroutine_kind, + ref coroutine_marker, movability, ref fn_decl, ref body, @@ -392,7 +389,7 @@ pub(crate) fn rewrite_last_closure( binder, constness, capture_clause, - coroutine_kind, + coroutine_marker, movability, fn_decl, body, diff --git a/src/expr.rs b/src/expr.rs index 0499e2fcac4..cef0bd97d57 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -271,7 +271,7 @@ pub(crate) fn format_expr( &cl.binder, cl.constness, cl.capture_clause, - &cl.coroutine_kind, + &cl.coroutine_marker, cl.movability, &cl.fn_decl, &cl.body, diff --git a/src/items.rs b/src/items.rs index 64348ad174f..37dc231c3c4 100644 --- a/src/items.rs +++ b/src/items.rs @@ -297,7 +297,7 @@ pub(crate) struct FnSig<'a> { decl: &'a ast::FnDecl, generics: &'a ast::Generics, ext: ast::Extern, - coroutine_kind: Cow<'a, Option>, + coroutine_marker: &'a Option, constness: ast::Const, defaultness: ast::Defaultness, safety: ast::Safety, @@ -313,7 +313,7 @@ impl<'a> FnSig<'a> { ) -> FnSig<'a> { FnSig { safety: method_sig.header.safety, - coroutine_kind: Cow::Borrowed(&method_sig.header.coroutine_kind), + coroutine_marker: &method_sig.header.coroutine_marker, constness: method_sig.header.constness, defaultness, ext: method_sig.header.ext, @@ -337,7 +337,7 @@ impl<'a> FnSig<'a> { generics, ext: sig.header.ext, constness: sig.header.constness, - coroutine_kind: Cow::Borrowed(&sig.header.coroutine_kind), + coroutine_marker: &sig.header.coroutine_marker, defaultness, safety: sig.header.safety, visibility: vis, @@ -352,8 +352,8 @@ impl<'a> FnSig<'a> { result.push_str(&*format_visibility(context, self.visibility)); result.push_str(format_defaultness(self.defaultness)); result.push_str(format_constness(self.constness)); - self.coroutine_kind - .map(|coroutine_kind| result.push_str(format_coro(&coroutine_kind))); + self.coroutine_marker + .map(|coroutine_marker| result.push_str(format_coro(coroutine_marker))); result.push_str(format_safety(self.safety)); result.push_str(&format_extern( self.ext, @@ -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, @@ -1887,8 +1887,8 @@ pub(crate) fn rewrite_struct_field_prefix( field: &ast::FieldDef, ) -> RewriteResult { let vis = format_visibility(context, &field.vis); - let mut_restriction = format_mut_restriction(context, &field.mut_restriction); - let safety = format_safety(field.safety); + let mut_restriction = format_mut_restriction(context, field.mut_restriction()); + let safety = format_safety(field.safety()); let type_annotation_spacing = type_annotation_spacing(context.config); Ok(match field.ident { Some(name) => format!( @@ -1917,7 +1917,7 @@ pub(crate) fn rewrite_struct_field( lhs_max_width: usize, ) -> RewriteResult { // FIXME(default_field_values): Implement formatting. - if field.default.is_some() { + if field.default_value().is_some() { return Err(RewriteError::Unknown); } @@ -2010,7 +2010,7 @@ impl<'a> StaticParts<'a> { ), ast::ItemKind::Const(c) => ( Some(c.defaultness), - if c.rhs_kind.is_type_const() { + if c.kind == ast::ConstItemKind::TypeConst { "type const" } else { "const" @@ -2019,7 +2019,7 @@ impl<'a> StaticParts<'a> { c.ident, &c.ty, ast::Mutability::Not, - c.rhs_kind.expr(), + c.body.as_deref(), Some(&c.generics), ), _ => unreachable!(), @@ -2041,7 +2041,7 @@ impl<'a> StaticParts<'a> { pub(crate) fn from_trait_item(ti: &'a ast::AssocItem, ident: Ident) -> Self { let (defaultness, ty, expr_opt, generics, prefix) = match &ti.kind { ast::AssocItemKind::Const(c) => { - let prefix = if c.rhs_kind.is_type_const() { + let prefix = if c.kind == ast::ConstItemKind::TypeConst { "type const" } else { "const" @@ -2049,7 +2049,7 @@ impl<'a> StaticParts<'a> { ( c.defaultness, &c.ty, - c.rhs_kind.expr(), + c.body.as_deref(), Some(&c.generics), prefix, ) @@ -2073,7 +2073,7 @@ impl<'a> StaticParts<'a> { pub(crate) fn from_impl_item(ii: &'a ast::AssocItem, ident: Ident) -> Self { let (defaultness, ty, expr_opt, generics, prefix) = match &ii.kind { ast::AssocItemKind::Const(c) => { - let prefix = if c.rhs_kind.is_type_const() { + let prefix = if c.kind == ast::ConstItemKind::TypeConst { "type const" } else { "const" @@ -2081,7 +2081,7 @@ impl<'a> StaticParts<'a> { ( c.defaultness, &c.ty, - c.rhs_kind.expr(), + c.body.as_deref(), Some(&c.generics), prefix, ) diff --git a/src/lib.rs b/src/lib.rs index 9e0ec01e7d0..285fa26d00a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate rustc_ast_pretty; extern crate rustc_data_structures; extern crate rustc_errors; extern crate rustc_expand; +extern crate rustc_feature; extern crate rustc_parse; extern crate rustc_session; extern crate rustc_span; diff --git a/src/macros.rs b/src/macros.rs index e7277a9d26d..8bfd99f2f7c 100644 --- a/src/macros.rs +++ b/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::{ @@ -26,8 +27,10 @@ use crate::config::StyleEdition; use crate::config::lists::*; use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::header::{HeaderPart, format_header}; +use crate::is_nightly_channel; 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 +248,26 @@ fn rewrite_macro_inner( } } + if is_nightly_channel!() && 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 +1553,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/matches.rs b/src/matches.rs index 50c0db8ac06..4e82df98de4 100644 --- a/src/matches.rs +++ b/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/modules.rs b/src/modules.rs index 099a6442821..99e72dcebd8 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -16,7 +16,7 @@ use crate::parse::parser::{ Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError, }; use crate::parse::session::ParseSess; -use crate::utils::{contains_skip, mk_sp}; +use crate::utils::{contains_custom_attributes, contains_skip, mk_sp}; mod visitor; @@ -167,8 +167,11 @@ impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> { Ok(()) } - fn visit_cfg_match(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> { - let mut visitor = visitor::CfgMatchVisitor::new(self.psess); + fn visit_cfg_select( + &mut self, + item: Cow<'ast, ast::Item>, + ) -> Result<(), ModuleResolutionError> { + let mut visitor = visitor::CfgSelectVisitor::new(self.psess); visitor.visit_item(&item); for module_item in visitor.mods() { if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = module_item.item.kind { @@ -197,8 +200,8 @@ impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> { continue; } - if is_cfg_match(&item) { - self.visit_cfg_match(Cow::Owned(*item))?; + if is_cfg_select(&item) { + self.visit_cfg_select(Cow::Owned(*item))?; continue; } @@ -228,8 +231,8 @@ impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> { self.visit_cfg_if(Cow::Borrowed(item))?; } - if is_cfg_match(item) { - self.visit_cfg_match(Cow::Borrowed(item))?; + if is_cfg_select(item) { + self.visit_cfg_select(Cow::Borrowed(item))?; } if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = item.kind { @@ -472,6 +475,16 @@ impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> { } Err(e) => match e { ModError::FileNotFound(_, default_path, _secondary_path) => { + if contains_custom_attributes(attrs) { + // It's possible that at least one of the attributes is a custom proc macro + // that takes the module tokens as an input. It's hard to know for sure + // since rustfmt only operates on the AST pre-expansion. In this case we'll + // be overly permissive and just ignore the file not found error so rustfmt + // can still try formatting the input. + tracing::warn!("Couldn't find file for mod {};`", mod_name.to_string()); + return Ok(None); + } + Err(ModuleResolutionError { module: mod_name.to_string(), kind: ModuleResolutionErrorKind::NotFound { file: default_path }, @@ -605,11 +618,11 @@ fn is_cfg_if(item: &ast::Item) -> bool { } } -fn is_cfg_match(item: &ast::Item) -> bool { +fn is_cfg_select(item: &ast::Item) -> bool { match item.kind { ast::ItemKind::MacCall(ref mac) => { if let Some(last_segment) = mac.path.segments.last() { - if last_segment.ident.name == Symbol::intern("cfg_match") { + if last_segment.ident.name == Symbol::intern("cfg_select") { return true; } } diff --git a/src/modules/visitor.rs b/src/modules/visitor.rs index d302a9ede6c..886128763c8 100644 --- a/src/modules/visitor.rs +++ b/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_match::parse_cfg_match; +use crate::parse::macros::cfg_select::parse_items_from_cfg_select; use crate::parse::session::ParseSess; pub(crate) struct ModItem { @@ -72,15 +72,15 @@ impl<'a, 'ast: 'a> CfgIfVisitor<'a> { } } -/// Traverse `cfg_match!` macro and fetch modules. -pub(crate) struct CfgMatchVisitor<'a> { +/// Traverse `cfg_select!` macro and fetch modules. +pub(crate) struct CfgSelectVisitor<'a> { psess: &'a ParseSess, mods: Vec, } -impl<'a> CfgMatchVisitor<'a> { - pub(crate) fn new(psess: &'a ParseSess) -> CfgMatchVisitor<'a> { - CfgMatchVisitor { +impl<'a> CfgSelectVisitor<'a> { + pub(crate) fn new(psess: &'a ParseSess) -> CfgSelectVisitor<'a> { + CfgSelectVisitor { mods: vec![], psess, } @@ -91,7 +91,7 @@ impl<'a> CfgMatchVisitor<'a> { } } -impl<'a, 'ast: 'a> Visitor<'ast> for CfgMatchVisitor<'a> { +impl<'a, 'ast: 'a> Visitor<'ast> for CfgSelectVisitor<'a> { fn visit_mac_call(&mut self, mac: &'ast ast::MacCall) { match self.visit_mac_inner(mac) { Ok(()) => (), @@ -100,30 +100,30 @@ impl<'a, 'ast: 'a> Visitor<'ast> for CfgMatchVisitor<'a> { } } -impl<'a, 'ast: 'a> CfgMatchVisitor<'a> { +impl<'a, 'ast: 'a> CfgSelectVisitor<'a> { fn visit_mac_inner(&mut self, mac: &'ast ast::MacCall) -> Result<(), &'static str> { // Support both: // ``` - // std::cfg_match! {..} - // core::cfg_match! {..} + // std::cfg_select! {..} + // core::cfg_select! {..} // ``` // And: // ``` - // use std::cfg_match; - // cfg_match! {..} + // use std::cfg_select; + // cfg_select! {..} // ``` match mac.path.segments.last() { Some(last_segment) => { - if last_segment.ident.name != Symbol::intern("cfg_match") { - return Err("Expected cfg_match"); + if last_segment.ident.name != Symbol::intern("cfg_select") { + return Err("Expected cfg_select"); } } None => { - return Err("Expected cfg_match"); + return Err("Expected cfg_select"); } }; - let items = parse_cfg_match(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/parse/macros/cfg_match.rs b/src/parse/macros/cfg_match.rs deleted file mode 100644 index 476289b08b7..00000000000 --- a/src/parse/macros/cfg_match.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::panic::{AssertUnwindSafe, catch_unwind}; - -use rustc_ast::ast; -use rustc_ast::token::TokenKind; -use rustc_parse::exp; -use rustc_parse::parser::{AllowConstBlockItems, ForceCollect}; - -use crate::parse::macros::build_stream_parser; -use crate::parse::session::ParseSess; - -pub(crate) fn parse_cfg_match<'a>( - psess: &'a ParseSess, - mac: &'a ast::MacCall, -) -> Result, &'static str> { - match catch_unwind(AssertUnwindSafe(|| parse_cfg_match_inner(psess, mac))) { - Ok(Ok(items)) => Ok(items), - Ok(err @ Err(_)) => err, - Err(..) => Err("failed to parse cfg_match!"), - } -} - -fn parse_cfg_match_inner<'a>( - psess: &'a ParseSess, - mac: &'a ast::MacCall, -) -> Result, &'static str> { - let ts = mac.args.tokens.clone(); - let mut parser = build_stream_parser(psess.inner(), ts); - - if parser.token == TokenKind::OpenBrace { - return Err("Expression position cfg_match! not yet supported"); - } - - let mut items = vec![]; - - while parser.token.kind != TokenKind::Eof { - if !parser.eat_keyword(exp!(Underscore)) { - parser.parse_attr_item(ForceCollect::No).map_err(|e| { - e.cancel(); - "Failed to parse attr item" - })?; - } - - if !parser.eat(exp!(FatArrow)) { - return Err("Expected a fat arrow"); - } - - if !parser.eat(exp!(OpenBrace)) { - return Err("Expected an opening brace"); - } - - while parser.token != TokenKind::CloseBrace && parser.token.kind != TokenKind::Eof { - let item = match parser - .parse_item(ForceCollect::No, AllowConstBlockItems::DoesNotMatter) - { - Ok(Some(item_ptr)) => *item_ptr, - Ok(None) => continue, - Err(err) => { - err.cancel(); - parser.psess.dcx().reset_err_count(); - return Err( - "Expected item inside cfg_match block, but failed to parse it as an item", - ); - } - }; - if let ast::ItemKind::Mod(..) = item.kind { - items.push(item); - } - } - - if !parser.eat(exp!(CloseBrace)) { - return Err("Expected a closing brace"); - } - - if parser.eat(exp!(Eof)) { - break; - } - } - - Ok(items) -} diff --git a/src/parse/macros/cfg_select.rs b/src/parse/macros/cfg_select.rs new file mode 100644 index 00000000000..7127445a189 --- /dev/null +++ b/src/parse/macros/cfg_select.rs @@ -0,0 +1,201 @@ +//! 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; +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_items_from_cfg_select<'a>( + psess: &'a ParseSess, + mac: &'a ast::MacCall, +) -> Result, &'static str> { + 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_items_from_cfg_select_inner<'a>( + psess: &'a ParseSess, + mac: &'a ast::MacCall, +) -> Result, &'static str> { + let ts = mac.args.tokens.clone(); + let mut parser = build_stream_parser(psess.inner(), ts); + + if parser.token == TokenKind::OpenBrace { + return Err("Expression position cfg_select! not yet supported"); + } + + let mut items = vec![]; + + while parser.token.kind != TokenKind::Eof { + if !parser.eat_keyword(exp!(Underscore)) { + parser.parse_attr_item(ForceCollect::No).map_err(|e| { + e.cancel(); + "Failed to parse attr item" + })?; + } + + if !parser.eat(exp!(FatArrow)) { + return Err("Expected a fat arrow"); + } + + if !parser.eat(exp!(OpenBrace)) { + return Err("Expected an opening brace"); + } + + while parser.token != TokenKind::CloseBrace && parser.token.kind != TokenKind::Eof { + let item = match parser + .parse_item(ForceCollect::No, AllowConstBlockItems::DoesNotMatter) + { + Ok(Some(item_ptr)) => *item_ptr, + Ok(None) => continue, + Err(err) => { + err.cancel(); + parser.psess.dcx().reset_err_count(); + return Err( + "Expected item inside cfg_select block, but failed to parse it as an item", + ); + } + }; + if let ast::ItemKind::Mod(..) = item.kind { + items.push(item); + } + } + + if !parser.eat(exp!(CloseBrace)) { + return Err("Expected a closing brace"); + } + + if parser.eat(exp!(Eof)) { + break; + } + } + + 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/parse/macros/mod.rs b/src/parse/macros/mod.rs index cfbd44c3344..9afeed3c3ee 100644 --- a/src/parse/macros/mod.rs +++ b/src/parse/macros/mod.rs @@ -10,7 +10,7 @@ use crate::macros::MacroArg; use crate::rewrite::RewriteContext; pub(crate) mod cfg_if; -pub(crate) mod cfg_match; +pub(crate) mod cfg_select; pub(crate) mod lazy_static; fn build_stream_parser<'a>(psess: &'a ParseSess, tokens: TokenStream) -> Parser<'a> { diff --git a/src/patterns.rs b/src/patterns.rs index bbc8cca197b..dd0c09b6e96 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -70,10 +70,9 @@ fn is_short_pattern_inner(context: &RewriteContext<'_>, pat: &ast::Pat) -> bool ast::PatKind::TupleStruct(_, ref path, ref subpats) => { path.segments.len() <= 1 && subpats.len() <= 1 } - ast::PatKind::Box(ref p) - | PatKind::Deref(ref p) - | ast::PatKind::Ref(ref p, _, _) - | ast::PatKind::Paren(ref p) => is_short_pattern_inner(context, &*p), + PatKind::Deref(ref p) | ast::PatKind::Ref(ref p, _, _) | ast::PatKind::Paren(ref p) => { + is_short_pattern_inner(context, &*p) + } PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(context, p)), } } @@ -114,7 +113,6 @@ impl Rewrite for Pat { .ends_with_newline(false); write_list(&items, &fmt) } - PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape), PatKind::Ident(BindingMode(by_ref, mutability), ident, ref sub_pat) => { let mut_prefix = format_mutability(mutability).trim(); @@ -535,7 +533,7 @@ pub(crate) fn can_be_overflowed_pat( | ast::PatKind::Tuple(..) | ast::PatKind::Struct(..) | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1, - ast::PatKind::Ref(ref p, _, _) | ast::PatKind::Box(ref p) => { + ast::PatKind::Ref(ref p, _, _) => { can_be_overflowed_pat(context, &TuplePatField::Pat(p), len) } ast::PatKind::Expr(ref expr) => can_be_overflowed_expr(context, expr, len), diff --git a/src/test/mod.rs b/src/test/mod.rs index eb935274155..213c43d8887 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -42,8 +42,8 @@ const FILE_SKIP_LIST: &[&str] = &[ "issue-3253/foo.rs", "issue-3253/bar.rs", "issue-3253/paths", - // This directory is directly tested by format_files_find_new_files_via_cfg_match - "cfg_match", + // This directory is directly tested by format_files_find_new_files_via_cfg_select + "cfg_select", // These files and directory are a part of modules defined inside `cfg_attr(..)`. "cfg_mod/dir", "cfg_mod/bar.rs", @@ -567,15 +567,15 @@ fn format_files_find_new_files_via_cfg_if() { } #[test] -fn format_files_find_new_files_via_cfg_match() { +fn format_files_find_new_files_via_cfg_select() { init_log(); run_test_with(&TestSetting::default(), || { - // We load these two files into the same session to test cfg_match! + // We load these two files into the same session to test cfg_select! // transparent mod discovery, and to ensure that it does not suffer // from a similar issue as cfg_if! support did with issue-4656. let files = vec![ - Path::new("tests/source/cfg_match/lib2.rs"), - Path::new("tests/source/cfg_match/lib.rs"), + Path::new("tests/source/cfg_select/lib2.rs"), + Path::new("tests/source/cfg_select/lib.rs"), ]; let config = Config::default(); @@ -800,6 +800,15 @@ fn check_files(files: Vec, opt_config: &Option) -> (Vec Config { }; for (key, val) in &sig_comments { - if key != "target" && key != "config" && key != "unstable" { + if key != "target" && key != "config" && key != "unstable" && key != "stable" { config.override_value(key, val); } } diff --git a/src/types.rs b/src/types.rs index c0cc9adc78f..b8e072c7c69 100644 --- a/src/types.rs +++ b/src/types.rs @@ -313,19 +313,14 @@ fn rewrite_segment( Ok(result) } -fn format_function_type<'a, I>( - inputs: I, +fn format_function_type( + inputs: &[ast::Param], output: &FnRetTy, variadic: bool, span: Span, context: &RewriteContext<'_>, shape: Shape, -) -> RewriteResult -where - I: ExactSizeIterator, - ::Item: Deref, - ::Target: Rewrite + Spanned + 'a, -{ +) -> RewriteResult { debug!("format_function_type {:#?}", shape); let ty_shape = match context.config.indent_style() { @@ -381,7 +376,7 @@ where } else { let items = itemize_list( context.snippet_provider, - inputs, + inputs.iter(), ")", ",", |arg| arg.span().lo(), @@ -563,14 +558,9 @@ fn rewrite_generic_args( overflow::rewrite_with_angle_brackets(context, "", args.iter(), shape, span) } } - ast::GenericArgs::Parenthesized(ref data) => format_function_type( - data.inputs.iter().map(|x| &**x), - &data.output, - false, - data.span, - context, - shape, - ), + ast::GenericArgs::Parenthesized(ref data) => { + format_function_type(&data.inputs, &data.output, false, data.span, context, shape) + } ast::GenericArgs::ParenthesizedElided(..) => Ok("(..)".to_owned()), } } @@ -1129,7 +1119,7 @@ fn rewrite_fn_ptr( }; let rewrite = format_function_type( - fn_ptr.decl.inputs.iter(), + &fn_ptr.decl.inputs, &fn_ptr.decl.output, fn_ptr.decl.c_variadic(), fn_ptr.decl_span, diff --git a/src/utils.rs b/src/utils.rs index 15a4fce9348..2936025d2f9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use rustc_ast::ast::{ NodeId, Path, RestrictionKind, Visibility, VisibilityKind, }; use rustc_ast_pretty::pprust; +use rustc_feature::is_builtin_attr_name; use rustc_span::{BytePos, LocalExpnId, Span, Symbol, SyntaxContext, sym, symbol}; use unicode_width::UnicodeWidthStr; @@ -116,11 +117,11 @@ fn format_restriction( } #[inline] -pub(crate) fn format_coro(coroutine_kind: &ast::CoroutineKind) -> &'static str { - match coroutine_kind { - ast::CoroutineKind::Async { .. } => "async ", - ast::CoroutineKind::Gen { .. } => "gen ", - ast::CoroutineKind::AsyncGen { .. } => "async gen ", +pub(crate) fn format_coro(coroutine_marker: ast::CoroutineMarker) -> &'static str { + match coroutine_marker.kind { + ast::CoroutineKind::Async => "async ", + ast::CoroutineKind::Gen => "gen ", + ast::CoroutineKind::AsyncGen => "async gen ", } } @@ -327,6 +328,13 @@ pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool { .any(|a| a.meta().map_or(false, |a| is_skip(&a))) } +#[inline] +pub(crate) fn contains_custom_attributes(attrs: &[Attribute]) -> bool { + attrs + .iter() + .any(|a| a.name().is_some_and(|name| !is_builtin_attr_name(name))) +} + #[inline] pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool { // Never try to insert semicolons on expressions when we're inside diff --git a/src/visitor.rs b/src/visitor.rs index 55f9a4d8c8b..b3ba0f2df2c 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -631,6 +631,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { // For now, leave the contents of the Span unformatted. self.push_rewrite(item.span, None) } + ast::ItemKind::TestBinderConstraints(..) => self.push_rewrite(item.span, None), }; } self.skip_context = skip_context_saved; diff --git a/tests/source/cfg_select.rs b/tests/source/cfg_select.rs new file mode 100644 index 00000000000..26f276edeef --- /dev/null +++ b/tests/source/cfg_select.rs @@ -0,0 +1,1116 @@ +// rustfmt-unstable: true +// 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/tests/source/cfg_match/format_me_please_1.rs b/tests/source/cfg_select/format_me_please_1.rs similarity index 100% rename from tests/source/cfg_match/format_me_please_1.rs rename to tests/source/cfg_select/format_me_please_1.rs diff --git a/tests/source/cfg_match/format_me_please_2.rs b/tests/source/cfg_select/format_me_please_2.rs similarity index 100% rename from tests/source/cfg_match/format_me_please_2.rs rename to tests/source/cfg_select/format_me_please_2.rs diff --git a/tests/source/cfg_match/format_me_please_3.rs b/tests/source/cfg_select/format_me_please_3.rs similarity index 100% rename from tests/source/cfg_match/format_me_please_3.rs rename to tests/source/cfg_select/format_me_please_3.rs diff --git a/tests/source/cfg_match/format_me_please_4.rs b/tests/source/cfg_select/format_me_please_4.rs similarity index 100% rename from tests/source/cfg_match/format_me_please_4.rs rename to tests/source/cfg_select/format_me_please_4.rs diff --git a/tests/source/cfg_match/lib.rs b/tests/source/cfg_select/lib.rs similarity index 71% rename from tests/source/cfg_match/lib.rs rename to tests/source/cfg_select/lib.rs index 2f0accac7d7..62fb6dfbe9e 100644 --- a/tests/source/cfg_match/lib.rs +++ b/tests/source/cfg_select/lib.rs @@ -1,13 +1,11 @@ -#![feature(cfg_match)] - -std::cfg_match! { +cfg_select! { test => { mod format_me_please_1; } target_family = "unix" => { mod format_me_please_2; } - cfg(target_pointer_width = "32") => { + target_pointer_width = "32" => { mod format_me_please_3; } _ => { diff --git a/tests/source/cfg_match/lib2.rs b/tests/source/cfg_select/lib2.rs similarity index 100% rename from tests/source/cfg_match/lib2.rs rename to tests/source/cfg_select/lib2.rs diff --git a/tests/source/cfg_select_stable.rs b/tests/source/cfg_select_stable.rs new file mode 100644 index 00000000000..7f37a155cfc --- /dev/null +++ b/tests/source/cfg_select_stable.rs @@ -0,0 +1,10 @@ +// rustfmt-stable: true + +// While we gate the `cfg_select!` formatting behind the `is_nightly_channel!()` check +// this test helps ensure that we don't start formatting `cfg_select!` on the `stable` +// or `beta` release channels. It is intentionally formatted incorrectly. As soon as the +// `is_nightly_channel!()` gate is removed this will start formatting and we can remove this test. +cfg_select! ( + unix => 1, + windows => 1, +); diff --git a/tests/target/cfg_select.rs b/tests/target/cfg_select.rs new file mode 100644 index 00000000000..fa70ea80dad --- /dev/null +++ b/tests/target/cfg_select.rs @@ -0,0 +1,1105 @@ +// rustfmt-unstable: true +// 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 +} diff --git a/tests/target/cfg_match/format_me_please_1.rs b/tests/target/cfg_select/format_me_please_1.rs similarity index 100% rename from tests/target/cfg_match/format_me_please_1.rs rename to tests/target/cfg_select/format_me_please_1.rs diff --git a/tests/target/cfg_match/format_me_please_2.rs b/tests/target/cfg_select/format_me_please_2.rs similarity index 100% rename from tests/target/cfg_match/format_me_please_2.rs rename to tests/target/cfg_select/format_me_please_2.rs diff --git a/tests/target/cfg_match/format_me_please_3.rs b/tests/target/cfg_select/format_me_please_3.rs similarity index 100% rename from tests/target/cfg_match/format_me_please_3.rs rename to tests/target/cfg_select/format_me_please_3.rs diff --git a/tests/target/cfg_match/format_me_please_4.rs b/tests/target/cfg_select/format_me_please_4.rs similarity index 100% rename from tests/target/cfg_match/format_me_please_4.rs rename to tests/target/cfg_select/format_me_please_4.rs diff --git a/tests/target/cfg_match/lib.rs b/tests/target/cfg_select/lib.rs similarity index 71% rename from tests/target/cfg_match/lib.rs rename to tests/target/cfg_select/lib.rs index 2f0accac7d7..62fb6dfbe9e 100644 --- a/tests/target/cfg_match/lib.rs +++ b/tests/target/cfg_select/lib.rs @@ -1,13 +1,11 @@ -#![feature(cfg_match)] - -std::cfg_match! { +cfg_select! { test => { mod format_me_please_1; } target_family = "unix" => { mod format_me_please_2; } - cfg(target_pointer_width = "32") => { + target_pointer_width = "32" => { mod format_me_please_3; } _ => { diff --git a/tests/target/cfg_match/lib2.rs b/tests/target/cfg_select/lib2.rs similarity index 100% rename from tests/target/cfg_match/lib2.rs rename to tests/target/cfg_select/lib2.rs diff --git a/tests/target/cfg_select_stable.rs b/tests/target/cfg_select_stable.rs new file mode 100644 index 00000000000..7f37a155cfc --- /dev/null +++ b/tests/target/cfg_select_stable.rs @@ -0,0 +1,10 @@ +// rustfmt-stable: true + +// While we gate the `cfg_select!` formatting behind the `is_nightly_channel!()` check +// this test helps ensure that we don't start formatting `cfg_select!` on the `stable` +// or `beta` release channels. It is intentionally formatted incorrectly. As soon as the +// `is_nightly_channel!()` gate is removed this will start formatting and we can remove this test. +cfg_select! ( + unix => 1, + windows => 1, +); diff --git a/tests/target/issue_6959.rs b/tests/target/issue_6959.rs new file mode 100644 index 00000000000..2194ba32853 --- /dev/null +++ b/tests/target/issue_6959.rs @@ -0,0 +1,2 @@ +#[my_macro] +mod foo; diff --git a/tests/target/named-fn-trait-parameters.rs b/tests/target/named-fn-trait-parameters.rs new file mode 100644 index 00000000000..36621da2019 --- /dev/null +++ b/tests/target/named-fn-trait-parameters.rs @@ -0,0 +1,15 @@ +fn allowed( + data: &str, + f1: impl Fn(msg: String), + f2: impl Fn(_: String), + f3: impl Fn(String, msg: String), + f4: impl Fn(msg: String, String), + fg: F, +) where + F: Fn(msg: String), +{ +} + +my_macro!(f(x: &str)); +my_macro!(f(x: &str) -> ()); +my_macro!(g(n: i32, m: usize) -> usize);