diff --git a/src/tools/rustfmt/src/attr.rs b/src/tools/rustfmt/src/attr.rs index 381c938ae8062..5fc09873cf4ca 100644 --- a/src/tools/rustfmt/src/attr.rs +++ b/src/tools/rustfmt/src/attr.rs @@ -249,12 +249,7 @@ impl Rewrite for ast::MetaItemInner { } fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult { - match self { - ast::MetaItemInner::MetaItem(ref meta_item) => meta_item.rewrite_result(context, shape), - ast::MetaItemInner::Lit(ref l) => { - rewrite_literal(context, l.as_token_lit(), l.span, shape) - } - } + rewrite_meta_item_inner_result(self, context, shape, false) } } @@ -283,45 +278,97 @@ impl Rewrite for ast::MetaItem { } fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult { - Ok(match self.kind { - ast::MetaItemKind::Word => { - rewrite_path(context, PathContext::Type, &None, &self.path, shape)? - } - ast::MetaItemKind::List(ref list) => { - let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?; - let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span); - overflow::rewrite_with_parens( - context, - &path, - list.iter(), - // 1 = "]" - shape.sub_width(1).max_width_error(shape.width, self.span)?, - self.span, - context.config.attr_fn_like_width(), - Some(if has_trailing_comma { - SeparatorTactic::Always + rewrite_meta_item_result(self, context, shape, false) + } +} + +pub(crate) fn rewrite_meta_item_inner_result( + this: &ast::MetaItemInner, + context: &RewriteContext<'_>, + shape: Shape, + line_break_list: bool, +) -> RewriteResult { + match this { + ast::MetaItemInner::MetaItem(ref meta_item) => { + rewrite_meta_item_result(meta_item, context, shape, line_break_list) + } + ast::MetaItemInner::Lit(ref l) => rewrite_literal(context, l.as_token_lit(), l.span, shape), + } +} + +pub(crate) fn rewrite_meta_item_result( + this: &ast::MetaItem, + context: &RewriteContext<'_>, + shape: Shape, + line_break_list: bool, +) -> RewriteResult { + let path = rewrite_path(context, PathContext::Type, &None, &this.path, shape)?; + + match this.kind { + ast::MetaItemKind::Word => { + // E.g., `#[test]`, which lacks any arguments after `test`. + Ok(path) + } + ast::MetaItemKind::List(ref list) => { + // E.g., `#[derive(..)]`, where the `list` represents the `..`. + + let separator_tactic = if crate::expr::span_ends_with_comma(context, this.span) { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + }; + + overflow::rewrite_with_parens( + context, + &path, + list.iter(), + shape + .sub_width("]".len()) + .max_width_error(shape.width, this.span)?, + this.span, + context.config.attr_fn_like_width(), + Some(separator_tactic), + ) + } + ast::MetaItemKind::NameValue(ref lit) => { + // E.g., `#[feature = "foo"]`, where the `lit` represents the `"foo"`. + + let lit_shape = shape + .shrink_left(path.len() + " = ".len()) + .max_width_error(shape.width, this.span)?; + + // `rewrite_literal` returns `None` when `lit` exceeds max + // width. Since a literal is basically unformattable unless it + // is a string literal (and only if `format_strings` is set), + // we might be better off ignoring the fact that the attribute + // is longer than the max width and continue on formatting. + // See #2479 for example. + + match rewrite_literal(context, lit.as_token_lit(), lit.span, lit_shape) { + Ok(value) => { + // The whole meta item fits on one line. + Ok(format!("{path} = {value}")) + } + Err(_) => { + // `lit` exceeded the maximum width of its Shape. + if line_break_list { + // Insert a line break before the `=`. + let mut result = String::new(); + result.push_str(&path); + let indented = shape.indent.block_indent(context.config); + result.push_str(&indented.to_string_with_newline(context.config)); + result.push_str("= "); + result.push_str(context.snippet(lit.span)); + + Ok(result) } else { - SeparatorTactic::Never - }), - )? - } - ast::MetaItemKind::NameValue(ref lit) => { - let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?; - // 3 = ` = ` - let lit_shape = shape - .shrink_left(path.len() + 3) - .max_width_error(shape.width, self.span)?; - // `rewrite_literal` returns `None` when `lit` exceeds max - // width. Since a literal is basically unformattable unless it - // is a string literal (and only if `format_strings` is set), - // we might be better off ignoring the fact that the attribute - // is longer than the max width and continue on formatting. - // See #2479 for example. - let value = rewrite_literal(context, lit.as_token_lit(), lit.span, lit_shape) - .unwrap_or_else(|_| context.snippet(lit.span).to_owned()); - format!("{path} = {value}") + // Just format on a single line anyway. + let value = context.snippet(lit.span); + Ok(format!("{path} = {value}")) + } + } } - }) + } } } diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 2e7ac90f596c8..c158b1e35e692 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -19,6 +19,8 @@ use rustc_ast_pretty::pprust; use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol}; use tracing::debug; +use crate::Config; +use crate::attr::rewrite_meta_item_inner_result; use crate::comment::{ CharClasses, FindUncommented, FullCodeCharKind, LineClasses, contains_comment, }; @@ -27,6 +29,7 @@ use crate::config::lists::*; use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; +use crate::parse::macros::cfg_select::parse_cfg_select; use crate::parse::macros::lazy_static::parse_lazy_static; use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args}; use crate::rewrite::{ @@ -240,6 +243,20 @@ fn rewrite_macro_inner( } } + if macro_name.ends_with("cfg_select!") { + match format_cfg_select(context, shape, ts.clone(), mac.span()) { + 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), + }, + } + } + let ParsedMacroArgs { args: arg_vec, vec_with_semi, @@ -1288,17 +1305,19 @@ impl MacroBranch { let old_body = context.snippet(self.body).trim(); let has_block_body = old_body.starts_with('{'); - let mut prefix_width = 5; // 5 = " => {" - if context.config.style_edition() >= StyleEdition::Edition2024 { - if has_block_body { - prefix_width = 6; // 6 = " => {{" - } - } + + let prefix = + if context.config.style_edition() >= StyleEdition::Edition2024 && has_block_body { + " => {{" + } else { + " => {" + }; + let mut result = format_macro_args( context, self.args.clone(), shape - .sub_width(prefix_width) + .sub_width(prefix.len()) .max_width_error(shape.width, self.span)?, )?; @@ -1321,10 +1340,63 @@ impl MacroBranch { let (body_str, substs) = replace_names(old_body).macro_error(MacroErrorKind::ReplaceMacroVariable, self.span)?; - let mut config = context.config.clone(); - config.set().show_parse_errors(false); + let mut new_body = + Self::try_format_rhs(&context.config, shape, has_block_body, &body_str, self.span)?; - result += " {"; + // Undo our replacement of macro variables. + // FIXME: this could be *much* more efficient. + for (old, new) in &substs { + if old_body.contains(new) { + debug!("rewrite_macro_def: bailing matching variable: `{}`", new); + return Err(RewriteError::MacroFailure { + kind: MacroErrorKind::ReplaceMacroVariable, + span: self.span, + }); + } + new_body = new_body.replace(new, old); + } + + Self::emit_formatted_body( + &context.config, + &shape, + &mut result, + has_block_body, + &new_body, + ); + + Ok(result) + } + + fn emit_formatted_body( + config: &Config, + shape: &Shape, + result: &mut String, + has_block_body: bool, + body: &str, + ) { + *result += " {"; + + if has_block_body { + *result += body.trim(); + } else if !body.is_empty() { + *result += "\n"; + *result += &body; + *result += &shape.indent.to_string(&config); + } + + *result += "}"; + } + + fn try_format_rhs( + config: &Config, + shape: Shape, + has_block_body: bool, + body_str: &str, + span: Span, + ) -> RewriteResult { + // This is a best-effort, reporting parse errors is not helpful. + let mut config = config.clone(); + config.set().show_parse_errors(false); let body_indent = if has_block_body { shape.indent @@ -1335,33 +1407,34 @@ impl MacroBranch { config.set().max_width(new_width); // First try to format as items, then as statements. - let new_body_snippet = match crate::format_snippet(&body_str, &config, true) { - Some(new_body) => new_body, - None => { - let new_width = new_width + config.tab_spaces(); - config.set().max_width(new_width); - match crate::format_code_block(&body_str, &config, true) { - Some(new_body) => new_body, - None => { - return Err(RewriteError::MacroFailure { - kind: MacroErrorKind::Unknown, - span: self.span, - }); - } - } + let new_body_snippet = 'blk: { + if let Some(new_body) = crate::format_snippet(&body_str, &config, true) { + break 'blk new_body; } + + let new_width = config.max_width() + config.tab_spaces(); + config.set().max_width(new_width); + + if let Some(new_body) = crate::format_code_block(&body_str, &config, true) { + break 'blk new_body; + } + + return Err(RewriteError::MacroFailure { + kind: MacroErrorKind::Unknown, + span, + }); }; if !filtered_str_fits(&new_body_snippet.snippet, config.max_width(), shape) { return Err(RewriteError::ExceedsMaxWidth { configured_width: shape.width, - span: self.span, + span, }); } // Indent the body since it is in a block. let indent_str = body_indent.to_string(&config); - let mut new_body = LineClasses::new(new_body_snippet.snippet.trim_end()) + let new_body = LineClasses::new(new_body_snippet.snippet.trim_end()) .enumerate() .fold( (String::new(), true), @@ -1377,30 +1450,7 @@ impl MacroBranch { ) .0; - // Undo our replacement of macro variables. - // FIXME: this could be *much* more efficient. - for (old, new) in &substs { - if old_body.contains(new) { - debug!("rewrite_macro_def: bailing matching variable: `{}`", new); - return Err(RewriteError::MacroFailure { - kind: MacroErrorKind::ReplaceMacroVariable, - span: self.span, - }); - } - new_body = new_body.replace(new, old); - } - - if has_block_body { - result += new_body.trim(); - } else if !new_body.is_empty() { - result += "\n"; - result += &new_body; - result += &shape.indent.to_string(&config); - } - - result += "}"; - - Ok(result) + Ok(new_body) } } @@ -1464,6 +1514,120 @@ fn format_lazy_static( Ok(result) } +fn format_cfg_select_rule( + rule: &ast::MetaItemInner, + context: &RewriteContext<'_>, + shape: Shape, + span: Span, +) -> RewriteResult { + let mut result = String::with_capacity(128); + + // The cfg plus ` => {` should stay within the line length. + let rule_shape = shape + .sub_width(" => {".len()) + .max_width_error(shape.width, span)?; + + let formatted = rewrite_meta_item_inner_result(rule, context, rule_shape, true)?; + result.push_str(&formatted); + + let is_key_value = match rule { + ast::MetaItemInner::MetaItem(ref meta_item) => { + matches!(meta_item.kind, ast::MetaItemKind::NameValue(_)) + } + _ => false, + }; + + if is_key_value && formatted.contains('\n') { + result.push_str(&shape.indent.to_string_with_newline(context.config)); + result.push_str("=>"); + } else { + result.push_str(" =>"); + } + + Ok(result) +} + +fn format_cfg_select( + context: &RewriteContext<'_>, + shape: Shape, + ts: TokenStream, + span: Span, +) -> RewriteResult { + let mut result = String::with_capacity(1024); + + result.push_str("cfg_select! {"); + + let branches = parse_cfg_select(context, ts).macro_error(MacroErrorKind::ParseFailure, span)?; + + let shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + + result.push_str(&shape.indent.to_string_with_newline(context.config)); + + for (rule, rhs, _) in branches.reachable { + result.push_str(&format_cfg_select_rule(&rule, context, shape, span)?); + result.push_str(&format_cfg_select_rhs(context, shape, rhs)?); + } + + if let Some((_, rhs, _)) = branches.wildcard { + result.push_str("_ =>"); + result.push_str(&format_cfg_select_rhs(context, shape, rhs)?); + } + + for (lhs, rhs, _) in branches.unreachable { + use rustc_parse::parser::cfg_select::CfgSelectPredicate; + + match lhs { + CfgSelectPredicate::Cfg(rule) => { + result.push_str(&format_cfg_select_rule(&rule, context, shape, span)?); + } + CfgSelectPredicate::Wildcard(_) => { + result.push_str("_ =>"); + } + } + + result.push_str(&format_cfg_select_rhs(context, shape, rhs)?); + } + + // Emit the final `}` at the correct indentation level. + result.truncate(result.len() - context.config.tab_spaces()); + result.push('}'); + + Ok(result) +} + +fn format_cfg_select_rhs( + context: &RewriteContext<'_>, + shape: Shape, + ts: TokenStream, +) -> RewriteResult { + let has_block_body = false; + + let span = ts + .iter() + .map(|tt| tt.span()) + .reduce(Span::to) + .unwrap_or_default(); + + let old_body = context.snippet(span).trim(); + let new_body = + MacroBranch::try_format_rhs(&context.config, shape, has_block_body, old_body, span)?; + + let mut result = String::new(); + MacroBranch::emit_formatted_body( + &context.config, + &shape, + &mut result, + has_block_body, + &new_body, + ); + + result.push_str(&shape.indent.to_string_with_newline(context.config)); + + Ok(result) +} + fn rewrite_macro_with_items( context: &RewriteContext<'_>, items: &[MacroArg], diff --git a/src/tools/rustfmt/src/parse/macros/cfg_select.rs b/src/tools/rustfmt/src/parse/macros/cfg_select.rs new file mode 100644 index 0000000000000..e21fe57fff653 --- /dev/null +++ b/src/tools/rustfmt/src/parse/macros/cfg_select.rs @@ -0,0 +1,12 @@ +use rustc_ast::tokenstream::TokenStream; +use rustc_parse::parser::{self, cfg_select::CfgSelectBranches}; + +use crate::rewrite::RewriteContext; + +pub(crate) fn parse_cfg_select( + context: &RewriteContext<'_>, + ts: TokenStream, +) -> Option { + let mut parser = super::build_parser(context, ts); + parser::cfg_select::parse_cfg_select(&mut parser).ok() +} diff --git a/src/tools/rustfmt/src/parse/macros/mod.rs b/src/tools/rustfmt/src/parse/macros/mod.rs index 724d7a09e4afa..baa688e9be500 100644 --- a/src/tools/rustfmt/src/parse/macros/mod.rs +++ b/src/tools/rustfmt/src/parse/macros/mod.rs @@ -11,6 +11,7 @@ use crate::rewrite::RewriteContext; pub(crate) mod asm; pub(crate) mod cfg_if; +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/tools/rustfmt/tests/source/cfg_select.rs b/src/tools/rustfmt/tests/source/cfg_select.rs new file mode 100644 index 0000000000000..4bbaf0f769cb5 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select.rs @@ -0,0 +1,94 @@ +fn print() { + println!(cfg_select! { + unix => { "unix" } + _ => { "not " + "unix" } + }); + + println!(cfg_select! { + unix => { "unix" } + _ => { "not unix" } + }); +} + +std::cfg_select! { + target_arch = "aarch64" => { + use std::sync::OnceCell; + + fn foo() { + return 3; + } + + } + _ => { + compile_error!("mal", "formed") + } + false => { + compile_error!("also", "mal", "formed") + } +} + +core::cfg_select! { + windows => {} + unix => { } + _ => {} +} + +fn nested_blocks() { + println!(cfg_select! { + unix => {{ "unix"} } + _ => { + { { "not " + "unix" + } } } + }); +} + +cfg_select! {} + +cfg_select! { + any(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true) => {} + 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)) => {} + + 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. + any(feature = "acdefgh1234", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + + _ => {} +} + +cfg_select! { + feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" + => { + // abc + } + feature = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" + => { + // abc + } + anything( "some other long long long long long thing long long long long long long long long long long long",) => {} + // abc + } +} 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..d130b65604a5d --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select.rs @@ -0,0 +1,131 @@ +fn print() { + println!(cfg_select! { + unix => { + "unix" + } + _ => { + "not " + "unix" + } + }); + + println!(cfg_select! { + unix => { + "unix" + } + _ => { + "not unix" + } + }); +} + +cfg_select! { + target_arch = "aarch64" => { + use std::sync::OnceCell; + + fn foo() { + return 3; + } + } + _ => { + compile_error!("mal", "formed") + } + false => { + compile_error!("also", "mal", "formed") + } +} + +cfg_select! { + windows => {} + unix => {} + _ => {} +} + +fn nested_blocks() { + println!(cfg_select! { + unix => { + { + "unix" + } + } + _ => { + { + { + "not " + "unix" + } + } + } + }); +} + +cfg_select! {} + +cfg_select! { + any( + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true + ) => {} + 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) + ) => {} + 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) + ) => {} + any(feature = "acdefg", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + any(feature = "acdefgh123", true, true, true, true, true, true, true, true) => { + compile_error!("foo") + } + any( + feature = "acdefgh1234", + true, + true, + true, + true, + true, + true, + true, + true + ) => { + compile_error!("foo") + } + _ => {} +} + +cfg_select! { + feature + = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" + => { + println!(); + } + feature + = "debug-with-rustfmt-long-long-long-long-loooooooonnnnnnnnnnnnnnnggggggffffffffffffffff" + => {} + anything( + "some other long long long long long thing long long long long long long long long long long long", + ) => {} +}