From 385dbfe9345d5ce7003a49fa9618b5eb2c8eff61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Wed, 24 Jun 2026 18:59:31 +0200 Subject: [PATCH 01/17] rustfmt: Discover modules via `cfg_select!` --- src/modules.rs | 19 ++++++----- src/modules/visitor.rs | 32 +++++++++---------- .../macros/{cfg_match.rs => cfg_select.rs} | 12 +++---- src/parse/macros/mod.rs | 2 +- src/test/mod.rs | 12 +++---- .../format_me_please_1.rs | 0 .../format_me_please_2.rs | 0 .../format_me_please_3.rs | 0 .../format_me_please_4.rs | 0 tests/source/{cfg_match => cfg_select}/lib.rs | 6 ++-- .../source/{cfg_match => cfg_select}/lib2.rs | 0 .../format_me_please_1.rs | 0 .../format_me_please_2.rs | 0 .../format_me_please_3.rs | 0 .../format_me_please_4.rs | 0 tests/target/{cfg_match => cfg_select}/lib.rs | 6 ++-- .../target/{cfg_match => cfg_select}/lib2.rs | 0 17 files changed, 44 insertions(+), 45 deletions(-) rename src/parse/macros/{cfg_match.rs => cfg_select.rs} (84%) rename tests/source/{cfg_match => cfg_select}/format_me_please_1.rs (100%) rename tests/source/{cfg_match => cfg_select}/format_me_please_2.rs (100%) rename tests/source/{cfg_match => cfg_select}/format_me_please_3.rs (100%) rename tests/source/{cfg_match => cfg_select}/format_me_please_4.rs (100%) rename tests/source/{cfg_match => cfg_select}/lib.rs (71%) rename tests/source/{cfg_match => cfg_select}/lib2.rs (100%) rename tests/target/{cfg_match => cfg_select}/format_me_please_1.rs (100%) rename tests/target/{cfg_match => cfg_select}/format_me_please_2.rs (100%) rename tests/target/{cfg_match => cfg_select}/format_me_please_3.rs (100%) rename tests/target/{cfg_match => cfg_select}/format_me_please_4.rs (100%) rename tests/target/{cfg_match => cfg_select}/lib.rs (71%) rename tests/target/{cfg_match => cfg_select}/lib2.rs (100%) diff --git a/src/modules.rs b/src/modules.rs index 099a6442821..89f3e71f9eb 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -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 { @@ -605,11 +608,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..485f44a936b 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_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_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_select.rs similarity index 84% rename from src/parse/macros/cfg_match.rs rename to src/parse/macros/cfg_select.rs index 476289b08b7..040447ff189 100644 --- a/src/parse/macros/cfg_match.rs +++ b/src/parse/macros/cfg_select.rs @@ -8,18 +8,18 @@ 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>( +pub(crate) fn parse_cfg_select<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { - match catch_unwind(AssertUnwindSafe(|| parse_cfg_match_inner(psess, mac))) { + match catch_unwind(AssertUnwindSafe(|| parse_cfg_select_inner(psess, mac))) { Ok(Ok(items)) => Ok(items), Ok(err @ Err(_)) => err, - Err(..) => Err("failed to parse cfg_match!"), + Err(..) => Err("failed to parse cfg_select!"), } } -fn parse_cfg_match_inner<'a>( +fn parse_cfg_select_inner<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { @@ -27,7 +27,7 @@ fn parse_cfg_match_inner<'a>( let mut parser = build_stream_parser(psess.inner(), ts); if parser.token == TokenKind::OpenBrace { - return Err("Expression position cfg_match! not yet supported"); + return Err("Expression position cfg_select! not yet supported"); } let mut items = vec![]; @@ -58,7 +58,7 @@ fn parse_cfg_match_inner<'a>( err.cancel(); parser.psess.dcx().reset_err_count(); return Err( - "Expected item inside cfg_match block, but failed to parse it as an item", + "Expected item inside cfg_select block, but failed to parse it as an item", ); } }; diff --git a/src/parse/macros/mod.rs b/src/parse/macros/mod.rs index 00e0f6f58bd..3d32821ce08 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/test/mod.rs b/src/test/mod.rs index 4eded7c49eb..ff5ed15fce7 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", @@ -471,15 +471,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(); 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/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 From 2aeef85e35db885ddde42bd63a52185e7a094854 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 21 Jul 2026 21:17:31 +0800 Subject: [PATCH 02/17] Merge commit 'd427a7c1adc8afd86a7ebf3daec769a434cb2307' into rustfmt-subtree-update --- .github/FUNDING.yml | 2 + .github/ISSUE_TEMPLATE/bug.md | 71 +++++ .github/ISSUE_TEMPLATE/feature-request.md | 33 ++ .github/ISSUE_TEMPLATE/ice.md | 77 +++++ .github/ISSUE_TEMPLATE/regression.md | 66 ++++ .github/workflows/check_diff.yml | 5 +- .github/workflows/integration.yml | 8 +- .github/workflows/linux.yml | 7 +- .github/workflows/mac.yml | 7 +- .github/workflows/rustdoc_check.yml | 5 +- .github/workflows/upload-assets.yml | 2 +- .github/workflows/windows.yml | 7 +- .gitignore | 1 + CHANGELOG.md | 32 ++ Cargo.lock | 301 ++++++------------ Cargo.toml | 9 +- Configurations.md | 17 +- Contributing.md | 49 ++- Subtree sync procedure.md | 64 ++-- check_diff/src/lib.rs | 4 +- check_diff/src/main.rs | 4 + ci/Cargo.lock | 7 + ci/Cargo.toml | 7 + ci/build_and_test.bat | 25 -- ci/build_and_test.sh | 29 -- ci/integration.sh | 121 ------- ci/src/build_and_test.rs | 49 +++ ci/src/common.rs | 87 +++++ ci/src/integration.rs | 157 +++++++++ ci/src/main.rs | 21 ++ docs/index.html | 15 + rust-toolchain | 2 +- src/cargo-fmt/main.rs | 35 +- src/cargo-fmt/test/message_format.rs | 30 +- src/comment.rs | 8 + src/config/mod.rs | 7 +- src/config/options.rs | 26 ++ src/expr.rs | 80 +---- src/formatting.rs | 9 +- src/header.rs | 106 ++++++ src/items.rs | 32 +- src/lib.rs | 2 + src/macros.rs | 20 +- src/missed_spans.rs | 30 +- src/patterns.rs | 79 +---- src/range.rs | 78 +++++ src/spanned.rs | 7 - src/string.rs | 11 +- src/test/mod.rs | 52 +++ src/types.rs | 14 +- src/utils.rs | 9 + src/visitor.rs | 24 +- .../default-to-max.rs | 71 +++++ .../max-to-default.rs | 68 ++++ .../float_literal_trailing_zero/always.rs | 4 + .../if-no-postfix.rs | 4 + .../float_literal_trailing_zero/never.rs | 4 + .../hex_literal_case/hex_literal_lower.rs | 9 + .../hex_literal_case/hex_literal_preserve.rs | 9 + .../hex_literal_case/hex_literal_upper.rs | 9 + .../configs/spaces_around_ranges/false.rs | 11 + .../configs/spaces_around_ranges/true.rs | 10 + tests/source/hex_literal_lower.rs | 5 - tests/source/hex_literal_upper.rs | 5 - tests/source/issue-5136-1.rs | 7 + tests/source/issue-5136-2.rs | 5 + tests/source/issue-5136-3.rs | 6 + tests/source/issue-5136-4.rs | 4 + tests/source/issue-5136-5.rs | 6 + tests/source/issue-6825.rs | 7 + tests/source/issue-6863/empty-stmt.rs | 7 + tests/source/issue-6863/fn-stmts.rs | 7 + tests/source/issue_6831_style_edition_2021.rs | 46 +++ tests/source/issue_6831_style_edition_2024.rs | 46 +++ tests/source/issue_6831_style_edition_2027.rs | 44 +++ .../reorder_modules/{abcd => abcde}/mod.rs | 0 .../disabled_style_edition_2024.rs | 4 +- .../disabled_style_edition_2027.rs | 4 +- .../enabled_style_edition_2015.rs | 4 +- .../enabled_style_edition_2024.rs | 4 +- .../enabled_style_edition_2027.rs | 4 +- .../reorder_modules/{zyxw => zyxwv}/mod.rs | 0 .../reorder_modules_2027/abcde}/mod.rs | 0 .../reorder_modules_2027/zyxwv}/mod.rs | 0 tests/source/string_lit_unicode_ws.rs | 5 + tests/source/super_let.rs | 7 + .../default-to-max.rs | 73 +++++ .../max-to-default.rs | 71 +++++ .../float_literal_trailing_zero/always.rs | 6 + .../always_spaces_around_ranges_true.rs | 55 ++++ .../if-no-postfix.rs | 6 + ...if-no-postfix_spaces_around_ranges_true.rs | 52 +++ .../float_literal_trailing_zero/never.rs | 4 + .../never_spaces_around_ranges_true.rs | 51 +++ .../preserve_spaces_around_ranges_true.rs | 44 +++ .../hex_literal_case/hex_literal_lower.rs | 9 + .../hex_literal_case/hex_literal_preserve.rs | 9 + .../hex_literal_case/hex_literal_upper.rs | 9 + .../configs/spaces_around_ranges/false.rs | 10 + .../configs/spaces_around_ranges/true.rs | 12 + tests/target/hex_literal_lower.rs | 5 - tests/target/hex_literal_preserve.rs | 5 - tests/target/hex_literal_upper.rs | 5 - tests/target/issue-5136-1.rs | 7 + tests/target/issue-5136-2.rs | 5 + tests/target/issue-5136-3.rs | 6 + tests/target/issue-5136-4.rs | 4 + tests/target/issue-5136-5.rs | 6 + tests/target/issue-6825.rs | 6 + tests/target/issue-6863/empty-stmt.rs | 7 + tests/target/issue-6863/fn-stmts.rs | 7 + tests/target/issue_6831_style_edition_2021.rs | 43 +++ tests/target/issue_6831_style_edition_2024.rs | 43 +++ tests/target/issue_6831_style_edition_2027.rs | 45 +++ tests/target/issue_6869.rs | 8 + tests/target/keywords.rs | 26 ++ tests/target/reorder_modules/abcde/mod.rs | 1 + .../disabled_style_edition_2024.rs | 4 +- .../disabled_style_edition_2027.rs | 4 +- .../enabled_style_edition_2015.rs | 4 +- .../enabled_style_edition_2024.rs | 4 +- .../enabled_style_edition_2027.rs | 4 +- tests/target/reorder_modules/zyxwv/mod.rs | 1 + .../target/reorder_modules_2027/abcde/mod.rs | 1 + .../target/reorder_modules_2027/zyxwv/mod.rs | 1 + tests/target/string_lit_unicode_ws.rs | 5 + tests/target/super_let.rs | 4 + triagebot.toml | 25 +- 128 files changed, 2392 insertions(+), 681 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/ice.md create mode 100644 .github/ISSUE_TEMPLATE/regression.md create mode 100644 ci/Cargo.lock create mode 100644 ci/Cargo.toml delete mode 100755 ci/build_and_test.bat delete mode 100755 ci/build_and_test.sh delete mode 100755 ci/integration.sh create mode 100644 ci/src/build_and_test.rs create mode 100644 ci/src/common.rs create mode 100644 ci/src/integration.rs create mode 100644 ci/src/main.rs create mode 100644 src/header.rs create mode 100644 src/range.rs create mode 100644 tests/source/configs/doc_comment_code_block_small_heuristics/default-to-max.rs create mode 100644 tests/source/configs/doc_comment_code_block_small_heuristics/max-to-default.rs create mode 100644 tests/source/configs/hex_literal_case/hex_literal_lower.rs create mode 100644 tests/source/configs/hex_literal_case/hex_literal_preserve.rs create mode 100644 tests/source/configs/hex_literal_case/hex_literal_upper.rs delete mode 100644 tests/source/hex_literal_lower.rs delete mode 100644 tests/source/hex_literal_upper.rs create mode 100644 tests/source/issue-5136-1.rs create mode 100644 tests/source/issue-5136-2.rs create mode 100644 tests/source/issue-5136-3.rs create mode 100644 tests/source/issue-5136-4.rs create mode 100644 tests/source/issue-5136-5.rs create mode 100644 tests/source/issue-6825.rs create mode 100644 tests/source/issue-6863/empty-stmt.rs create mode 100644 tests/source/issue-6863/fn-stmts.rs create mode 100644 tests/source/issue_6831_style_edition_2021.rs create mode 100644 tests/source/issue_6831_style_edition_2024.rs create mode 100644 tests/source/issue_6831_style_edition_2027.rs rename tests/source/reorder_modules/{abcd => abcde}/mod.rs (100%) rename tests/source/reorder_modules/{zyxw => zyxwv}/mod.rs (100%) rename tests/{target/reorder_modules/abcd => source/reorder_modules_2027/abcde}/mod.rs (100%) rename tests/{target/reorder_modules/zyxw => source/reorder_modules_2027/zyxwv}/mod.rs (100%) create mode 100644 tests/source/string_lit_unicode_ws.rs create mode 100644 tests/source/super_let.rs create mode 100644 tests/target/configs/doc_comment_code_block_small_heuristics/default-to-max.rs create mode 100644 tests/target/configs/doc_comment_code_block_small_heuristics/max-to-default.rs create mode 100644 tests/target/configs/float_literal_trailing_zero/always_spaces_around_ranges_true.rs create mode 100644 tests/target/configs/float_literal_trailing_zero/if-no-postfix_spaces_around_ranges_true.rs create mode 100644 tests/target/configs/float_literal_trailing_zero/never_spaces_around_ranges_true.rs create mode 100644 tests/target/configs/float_literal_trailing_zero/preserve_spaces_around_ranges_true.rs create mode 100644 tests/target/configs/hex_literal_case/hex_literal_lower.rs create mode 100644 tests/target/configs/hex_literal_case/hex_literal_preserve.rs create mode 100644 tests/target/configs/hex_literal_case/hex_literal_upper.rs delete mode 100644 tests/target/hex_literal_lower.rs delete mode 100644 tests/target/hex_literal_preserve.rs delete mode 100644 tests/target/hex_literal_upper.rs create mode 100644 tests/target/issue-5136-1.rs create mode 100644 tests/target/issue-5136-2.rs create mode 100644 tests/target/issue-5136-3.rs create mode 100644 tests/target/issue-5136-4.rs create mode 100644 tests/target/issue-5136-5.rs create mode 100644 tests/target/issue-6825.rs create mode 100644 tests/target/issue-6863/empty-stmt.rs create mode 100644 tests/target/issue-6863/fn-stmts.rs create mode 100644 tests/target/issue_6831_style_edition_2021.rs create mode 100644 tests/target/issue_6831_style_edition_2024.rs create mode 100644 tests/target/issue_6831_style_edition_2027.rs create mode 100644 tests/target/issue_6869.rs create mode 100644 tests/target/keywords.rs create mode 100644 tests/target/reorder_modules/abcde/mod.rs create mode 100644 tests/target/reorder_modules/zyxwv/mod.rs create mode 100644 tests/target/reorder_modules_2027/abcde/mod.rs create mode 100644 tests/target/reorder_modules_2027/zyxwv/mod.rs create mode 100644 tests/target/string_lit_unicode_ws.rs create mode 100644 tests/target/super_let.rs diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..1d270e78949 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: rustfoundation +custom: ["rust-lang.org/funding"] diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 00000000000..646e06bc7e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,71 @@ +--- +name: General rustfmt bug Report +about: Create a general bug report for rustfmt. Prefer more specialized issue templates if applicable. +labels: C-bug +--- + + +## Summary + + + +I tried to format this code: + +```rust + +``` + +### Expected behavior + +I expected to see this happen: *explanation* + +### Actual behavior + +Instead, this happened: *explanation* + + +## Configuration + + + +`rustfmt` cli options used (if applicable): + +```bash +$ +``` + +`rustfmt` configuration file (e.g. `rustfmt.toml`, if applicable): + +```md + +``` + + +## Reproduction Steps + + + +1. ... + + +## Meta + + +`rustfmt --version`: +``` + +``` diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000000..3b203c2aaf2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,33 @@ +--- +name: Feature Request +about: Create a feature request for rustfmt. +labels: C-feature-request +--- + + +## Feature Request + +### Summary + + + +### Motivation + + + +### Related configuration options + + diff --git a/.github/ISSUE_TEMPLATE/ice.md b/.github/ISSUE_TEMPLATE/ice.md new file mode 100644 index 00000000000..5be67ba1350 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ice.md @@ -0,0 +1,77 @@ +--- +name: rustfmt Internal Compiler Error (ICE) +about: Create a report for an internal compiler error in rustfmt. +labels: C-bug, I-ICE +title: "[ICE]: " +--- + + +## Code + +```Rust + +``` + + +## Configuration + + + +`rustfmt` cli options used (if applicable): + +```bash +$ +``` + +`rustfmt` configuration file (e.g. `rustfmt.toml`, if applicable): + +```md + +``` + + +## Reproduction Steps + + + +1. ... + + +## Meta + + +`rustfmt --version`: +``` + +``` + + +## Error output + +``` + +``` + + +
Backtrace +

+ +``` + +``` + +

+
diff --git a/.github/ISSUE_TEMPLATE/regression.md b/.github/ISSUE_TEMPLATE/regression.md new file mode 100644 index 00000000000..f2b61949380 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/regression.md @@ -0,0 +1,66 @@ +--- +name: Regression +about: Report something that unexpectedly changed between rustfmt versions. +labels: C-bug, regression-untriaged +--- + + +## Summary + + + +I tried to format this code: + +```rust + +``` + +### Expected behavior + +I expected to see this happen: *explanation* + +### Actual behavior + +Instead, this happened: *explanation* + + +## Meta + +### Version it worked on + + + +It most recently worked on: + +### Version with regression + + + +`rustc --version --verbose`: +``` + +``` + + diff --git a/.github/workflows/check_diff.yml b/.github/workflows/check_diff.yml index 58425fa0c86..41b138acb63 100644 --- a/.github/workflows/check_diff.yml +++ b/.github/workflows/check_diff.yml @@ -33,13 +33,16 @@ on: description: 'Optional comma separated list of rustfmt config options to pass when running the feature branch' required: false +permissions: + contents: read + jobs: diff_check: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build check_diff binary working-directory: ./check_diff diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index c1b04721cb1..9ef809bc5cc 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -5,6 +5,9 @@ on: - main pull_request: +permissions: + contents: read + jobs: integration-tests: runs-on: ubuntu-latest @@ -64,7 +67,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Run build - name: install rustup @@ -74,7 +77,6 @@ jobs: - name: run integration tests env: - INTEGRATION: ${{ matrix.integration }} TARGET: x86_64-unknown-linux-gnu - run: ./ci/integration.sh + run: cargo run --manifest-path ci/Cargo.toml integration ${{ matrix.integration }} continue-on-error: ${{ matrix.allow-failure == true }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c8cbf00804b..77ab026f1d4 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -5,6 +5,9 @@ on: - main pull_request: +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest @@ -26,7 +29,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Run build - name: install rustup @@ -39,4 +42,4 @@ jobs: env: RUSTFLAGS: -D warnings CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true - run: ./ci/build_and_test.sh + run: cargo run --manifest-path ci/Cargo.toml build-and-test diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 0838800dff0..28c218b729b 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -5,6 +5,9 @@ on: - main pull_request: +permissions: + contents: read + jobs: test: # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources @@ -22,7 +25,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Run build - name: install rustup @@ -35,4 +38,4 @@ jobs: env: RUSTFLAGS: -D warnings CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true - run: ./ci/build_and_test.sh + run: cargo run --manifest-path ci/Cargo.toml build-and-test diff --git a/.github/workflows/rustdoc_check.yml b/.github/workflows/rustdoc_check.yml index c92732366ed..430185e3105 100644 --- a/.github/workflows/rustdoc_check.yml +++ b/.github/workflows/rustdoc_check.yml @@ -5,13 +5,16 @@ on: - main pull_request: +permissions: + contents: read + jobs: rustdoc_check: runs-on: ubuntu-latest name: rustdoc check steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: install rustup run: | diff --git a/.github/workflows/upload-assets.yml b/.github/workflows/upload-assets.yml index 7a639b469e8..49c9172cb46 100644 --- a/.github/workflows/upload-assets.yml +++ b/.github/workflows/upload-assets.yml @@ -31,7 +31,7 @@ jobs: target: x86_64-pc-windows-msvc runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Run build - name: install rustup diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index af47fddcf59..435132106e0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -5,6 +5,9 @@ on: - main pull_request: +permissions: + contents: read + jobs: test: runs-on: windows-latest @@ -33,7 +36,7 @@ jobs: - name: disable git eol translation run: git config --global core.autocrlf false - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Run build - name: Install Rustup using win.rustup.rs @@ -62,4 +65,4 @@ jobs: env: RUSTFLAGS: -D warnings CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true - run: ci\build_and_test.bat + run: cargo run --manifest-path ci/Cargo.toml build-and-test diff --git a/.gitignore b/.gitignore index 71cf88f79e6..cff6f0d046d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # will have compiled files and executables /target tests/cargo-fmt/**/target +/ci/target # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0470d214f..6b6d9a3b8dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,38 @@ ## [Unreleased] +## [1.10.0] 2026-07-21 + +### Fixed +- Prevent ranges from getting incorrectly collapsed in patterns that lead to invalid code [#6871](https://github.com/rust-lang/rustfmt/pull/6871). Issue: [#6869](https://github.com/rust-lang/rustfmt/issues/6869). +- Don't format statements outside of selected `--file-lines` range [#6867](https://github.com/rust-lang/rustfmt/pull/6867). Issue: [#6863](https://github.com/rust-lang/rustfmt/issues/6863). +- Respect `--file-lines` when making various whitespace related changes [#6841](https://github.com/rust-lang/rustfmt/pull/6841). Issue: [#5136](https://github.com/rust-lang/rustfmt/issues/5136). +- Fix formatting of commented single function parameter [#6840](https://github.com/rust-lang/rustfmt/pull/6840). Issue: [#6825](https://github.com/rust-lang/rustfmt/issues/6825). +- (Style Edition 2027) Fix formatting of long return types in functions exceeding max width [#6835](https://github.com/rust-lang/rustfmt/pull/6835). Issue: [#6831](https://github.com/rust-lang/rustfmt/issues/6831). +- Update `style_edition` configuration option docs to reflect that the option is stable, and that the option value `"2027"` is still unstable [#6936](https://github.com/rust-lang/rustfmt/pull/6936). +- Prevent panic when rewriting associated item delegations `#![feature(fn_delegation)]` [rust-lang/rust#154454](https://github.com/rust-lang/rust/pull/154454). Issue: [#6513](https://github.com/rust-lang/rustfmt/issues/6513). +- Fix pattern types formatting (`#![feature(pattern_types)`) [rust-lang/rust#156016](https://github.com/rust-lang/rust/pull/156016). + +### Changed +- Stabilize `hex_literal_case` [#6935](https://github.com/rust-lang/rustfmt/pull/6935). This configuration option controls the case of the letters in hexadecimal literal values. +- Improve error message for nightly-only '--message-format' arguments [#6780](https://github.com/rust-lang/rustfmt/pull/6780). +- Improve formatting of comments within item headers [#6457](https://github.com/rust-lang/rustfmt/pull/6457). We now preserve original whitespace and comments in the header snippets instead of realigning or reformatting them. +- Highlight config documentation version [#6931](https://github.com/rust-lang/rustfmt/pull/6931). +- Format try blocks more similarly to ordinary blocks (`#![feature(try_blocks)]`, `#![feature(try_blocks_heterogeneous)]`) [rust-lang/rust#153445](https://github.com/rust-lang/rust/pull/153445). Issue: [#6799](https://github.com/rust-lang/rustfmt/issues/6799). + +### Added +- Add `doc_comment_code_block_small_heuristics` unstable option (Tracking Issue [#6942](https://github.com/rust-lang/rustfmt/issues/6942)) to override `use_small_heuristics` in doc comment code blocks [#6616](https://github.com/rust-lang/rustfmt/pull/6616). +- Implement initial formatting for `#![feature(super_let)]` [#6952](https://github.com/rust-lang/rustfmt/pull/6952). +- Implement initial formatting for Field Representing Types (FRTs) as part of `#![feature(field_projections)]` [rust-lang/rust#152730](https://github.com/rust-lang/rust/pull/152730). +- Implement initial formatting for `#![feature(impl_restriction)]` [rust-lang/rust#152943](https://github.com/rust-lang/rust/pull/152943). + +### Misc +- Bump `clap-cargo` to 0.18.3 and `cargo_metadata` to 0.23 [#6873](https://github.com/rust-lang/rustfmt/pull/6873). +- Update dependencies to remove `windows-targets` dependency [#6895](https://github.com/rust-lang/rustfmt/pull/6895). +- Bump `annotate-snippets` to 0.11.5 [#6903](https://github.com/rust-lang/rustfmt/pull/6903). +- Bump `itertools` to 0.15 [#6955](https://github.com/rust-lang/rustfmt/pull/6955). + + ## [1.9.0] 2026-02-26 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 64b4aaef4d3..d95fbec8cb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,33 +13,34 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e35ed54e5ea7997c14ed4c70ba043478db1112e98263b3b035907aa197d991" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -52,21 +53,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "once_cell_polyfill", + "windows-sys", ] [[package]] @@ -98,34 +100,35 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "camino" -version = "1.0.7" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3132262930b0522068049f5870a856ab8affc80c70d08b6ecb785771a6fc23" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "cargo-platform" -version = "0.1.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" dependencies = [ "serde", + "serde_core", ] [[package]] name = "cargo_metadata" -version = "0.18.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", - "thiserror 1.0.40", + "thiserror 2.0.18", ] [[package]] @@ -136,9 +139,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.2" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -146,9 +149,9 @@ dependencies = [ [[package]] name = "clap-cargo" -version = "0.12.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383f21342a464d4af96e9a4cad22a0b4f2880d4a5b3bbf5c9654dd1d9a224ee4" +checksum = "936551935c8258754bb8216aec040957d261f977303754b9bf1a213518388006" dependencies = [ "anstyle", "clap", @@ -156,9 +159,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -168,9 +171,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -180,9 +183,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" @@ -224,7 +227,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -246,7 +249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -267,7 +270,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -314,9 +317,9 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "ignore" @@ -346,11 +349,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ "either", ] @@ -429,6 +438,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "option-ext" version = "0.2.0" @@ -519,7 +534,7 @@ dependencies = [ [[package]] name = "rustfmt-nightly" -version = "1.9.0" +version = "1.10.0" dependencies = [ "annotate-snippets", "anyhow", @@ -545,7 +560,7 @@ dependencies = [ "tracing-subscriber", "unicode-properties", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -558,15 +573,9 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys", ] -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - [[package]] name = "same-file" version = "1.0.6" @@ -578,27 +587,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", + "serde_core", ] [[package]] name = "serde" -version = "1.0.196" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -607,13 +627,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -642,9 +664,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -667,16 +689,16 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "term" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43bddab41f8626c7bdaab872bbba75f8df5847b516d77c569c746e2ae5eb746" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -841,11 +863,17 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" @@ -912,150 +940,19 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-sys" -version = "0.48.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" version = "0.7.13" @@ -1067,3 +964,9 @@ name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 6ac140f2e09..eedd83cad34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [package] - name = "rustfmt-nightly" -version = "1.9.0" +version = "1.10.0" description = "Tool to find and fix Rust formatting issues" repository = "https://github.com/rust-lang/rustfmt" readme = "README.md" @@ -39,14 +38,14 @@ generic-simd = [] annotate-snippets = { version = "0.11" } anyhow = "1.0" bytecount = "0.6.9" -cargo_metadata = "0.18" +cargo_metadata = "0.23" clap = { version = "4.4.2", features = ["derive"] } -clap-cargo = "0.12.0" +clap-cargo = "0.18.3" diff = "0.1" dirs = "6.0" getopts = "0.2" ignore = "0.4" -itertools = "0.12" +itertools = "0.15" regex = "1.7" serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0" diff --git a/Configurations.md b/Configurations.md index 45a8b1eba87..976c2904894 100644 --- a/Configurations.md +++ b/Configurations.md @@ -1051,6 +1051,14 @@ Max width for code snippets included in doc comments. Only used if [`format_code - **Possible values**: any nonnegative integer that is less than or equal to the value specified for [`max_width`](#max_width) - **Stable**: No (tracking issue: [#5359](https://github.com/rust-lang/rustfmt/issues/5359)) +## `doc_comment_code_block_small_heuristics` + +Value for [`use_small_heuristics`](#use_small_heuristics) for use in code blocks in doc comments. Only used if [`format_code_in_doc_comments`](#format_code_in_doc_comments) is true. + +- **Default value**: `"Default"` +- **Possible values**: `"Default"`, `"Off"`, `"Max"` +- **Stable**: No (tracking issue: [#6942](https://github.com/rust-lang/rustfmt/issues/6942)) + ## `format_generated_files` Format generated files. A file is considered generated if any of the first several lines contain a `@generated` comment marker. The number of lines to check is configured by `generated_marker_line_search_limit`. @@ -1257,7 +1265,7 @@ Control the case of the letters in hexadecimal literal values - **Default value**: `Preserve` - **Possible values**: `Preserve`, `Upper`, `Lower` -- **Stable**: No (tracking issue: [#5081](https://github.com/rust-lang/rustfmt/issues/5081)) +- **Stable**: Yes ## `float_literal_trailing_zero` @@ -2059,6 +2067,7 @@ This option is deprecated. Use `imports_granularity = "Crate"` instead. - **Default value**: `false` - **Possible values**: `true`, `false` +- **Stable**: No (tracking issue: [#3362](https://github.com/rust-lang/rustfmt/issues/3362)) #### `false` (default): @@ -2840,8 +2849,10 @@ See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuri Controls the edition of the [Rust Style Guide] to use for formatting ([RFC 3338]) - **Default value**: `"2015"` -- **Possible values**: `"2015"`, `"2018"`, `"2021"`, `"2024"` (unstable variant) -- **Stable**: No +- **Possible values**: + - Stable values: `"2015"`, `"2018"`, `"2021"`, `"2024"` + - Unstable values: `"2027"` +- **Stable**: Yes This option is inferred from the [`edition`](#edition) if not specified. diff --git a/Contributing.md b/Contributing.md index 62029a71003..996a30c3a5b 100644 --- a/Contributing.md +++ b/Contributing.md @@ -23,7 +23,14 @@ to create regressions. Any tests you can add are very much appreciated. The tests can be run with `cargo test`. This does a number of things: * runs the unit tests for a number of internal functions; * makes sure that rustfmt run on every file in `./tests/source/` is equal to its - associated file in `./tests/target/`; + associated file in `./tests/target/`; this catches + * unexpected formatting differences from changes to rustfmt + * non-idempotency in formatting even when the file copy in `target/` is + already in the canonical expected format. That is, if `source_start` is + the starting formatting and `source_canonical` is the expected canonical + formatting, catch cases where there is a converging sequence + `source_start -> source_1 -> ... -> source_canonical` that takes multiple + rustfmt runs. * runs idempotence tests on the files in `./tests/target/`. These files should not be changed by rustfmt; * checks that rustfmt's code is not changed by running on itself. This ensures @@ -109,6 +116,46 @@ If you want to test modified `cargo-fmt`, or run `rustfmt` on the whole project RUSTFMT="./target/debug/rustfmt" cargo run --bin cargo-fmt -- --manifest-path path/to/project/you/want2test/Cargo.toml ``` +#### Running a binary directly + +You may want to run one of the built binaries directly, for example to connect +it to a debugger. Since `rustfmt` uses `rustc_driver` it needs to be linked +against the version of that library for the current toolchain, without +configuring anything you are likely to run into errors like: + +``` +./target/debug/rustfmt: error while loading shared libraries: librustc_driver-63b8deb6c23747dd.so: cannot open shared object file: No such file or directory +``` + +This library will be in the sysroot of the current toolchain, which will be +printed by `rustc --print sysroot`, so we'll need to include that in the +system's dynamic library search path. On GNU/Linux this can be done by setting +the `LD_LIBRARY_PATH` variable, e.g. using Bash: + +``` +LD_LIBRARY_PATH="$(rustc --print sysroot)/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" ./target/debug/rustfmt +``` + +On MacOS there is the `DYLD_LIBRARY_PATH` variable, e.g. using Bash: + +``` +DYLD_LIBRARY_PATH="$(rustc --print sysroot)/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" ./target/debug/rustfmt +``` + +And under Windows the `PATH` environment variable, e.g. using Bash: + +``` +PATH="$(rustc --print sysroot)/bin${PATH:+:${PATH}}" +``` + +Continuing the GNU/Linux example, you can invoke a debugger, e.g. `rust-gdb`, +like: + +``` +LD_LIBRARY_PATH="$(rustc --print sysroot)/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" rust-gdb --args ./target/debug/rustfmt --check some_file.rs + +``` + ### Gate formatting changes A change that introduces a different code-formatting must be gated on the diff --git a/Subtree sync procedure.md b/Subtree sync procedure.md index ae3586a1c3b..444246117d4 100644 --- a/Subtree sync procedure.md +++ b/Subtree sync procedure.md @@ -121,34 +121,29 @@ chore: bump rustfmt toolchain to nightly-$LATEST_NIGHTLY_DATE Bumping the toolchain version as part of a git subtree push. -current toolchain (nightly-$CURRENT_NIGHTLY_DATE): - - $CURRENT_NIGHTLY_VERSION-nightly ($CURRENT_NIGHTLY_HASH $CURRENT_NIGHTLY_DATE) +Before: -latest toolchain (nightly-$LATEST_NIGHTLY_DATE): - - $LATEST_NIGHTLY_VERSION-nightly ($LATEST_NIGHTLY_HASH $LATEST_NIGHTLY_DATE) +``` +$CURRENT_NIGHTLY_VERSION-nightly ($CURRENT_NIGHTLY_HASH $CURRENT_NIGHTLY_DATE) ``` -Substituting the placeholders with the right information. +After: -> [!TIP] -> -> Example bump commit message: -> -> ```text -> chore: bump rustfmt toolchain to nightly-2025-10-07 -> -> Bumping the toolchain version as part of a git subtree push. -> -> current toolchain (nightly-2025-04-02): - 1.88.0-nightly (e2014e876 2025-04-01) -> -> latest toolchain (nightly-2025-10-07): - 1.92.0-nightly (f6aa851db 2025-10-07) -> ``` +``` +$LATEST_NIGHTLY_VERSION-nightly ($LATEST_NIGHTLY_HASH $LATEST_NIGHTLY_DATE) +``` + +Substituting the placeholders with the right information. ### 5. Open a PR against `rustfmt` And wait for the sync PR to be merged. The `rustfmt` maintainers will run Diff Check against the PR -to catch any unexpected formatting changes. Once Diff Check failures are investigated and are -resolved, the PR can then be merged. +to catch any unexpected formatting changes. + +- Maintainers should trigger Diff-Check for the combinations of Edition {2021, 2024} x Style + Edition {2021, 2024}. + +Once Diff Check failures are investigated and are resolved, the PR can then be merged. For the PR: @@ -157,29 +152,14 @@ For the PR: - Include a copy of the bump commit message in the PR description for quick reference. Feel free to include additional notes that might be helpful for the maintainers when reviewing. -> [!TIP] -> -> Example subtree-push PR title and description: -> -> **PR title**: `subtree-push nightly-2025-10-07` -> -> **PR description**: -> -> ```text -> Bumping the toolchain version as part of a git subtree push. -> -> current toolchain (nightly-2025-04-02): -> - 1.88.0-nightly (e2014e876 2025-04-01) -> -> latest toolchain (nightly-2025-10-07): -> - 1.92.0-nightly (f6aa851db 2025-10-07) -> ``` +**Make sure to minimize the time between the subtree-push direction and the subtree-pull direction +to avoid unnecessary complications.** -> [!WARNING] -> -> Make sure to immediately follow-up with a subtree-pull direction, syncing `rustfmt` to -> `rust-lang/rust`. We need the {subtree-push, subtree-pull} directions to be performed in -> lock-step, to minimize any changes in between that makes the logistics more complex. +### 5. (Where applicable) Update changelog and bump rustfmt version number + +Where applicable, we may need to update the CHANGELOG entries with merged PRs (both in `rustfmt` +repository and also in the `rust-lang/rust` `rustfmt` subtree that was included in the subtree-push +merge), and then bump rustfmt version number. ## Subtree pull direction: syncing from `rustfmt` to `rust-lang/rust` diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index 7c047bb0850..d6bb7678ea0 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -50,7 +50,7 @@ impl FromStr for Edition { #[derive(Debug, Clone, Copy)] pub enum StyleEdition { - // rustfmt style_edition 2021. Also equivaluent to 2015 and 2018. + // rustfmt style_edition 2021. Also equivalent to 2015 and 2018. Edition2021, // rustfmt style_edition 2024 Edition2024, @@ -82,7 +82,7 @@ impl FromStr for StyleEdition { pub enum FormatCodeError { // IO Error when running code formatter Io(std::io::Error), - /// An error occured that prevents code formatting. For example, a parse error. + /// An error occurred that prevents code formatting. For example, a parse error. CodeNotFormatted(Vec), } diff --git a/check_diff/src/main.rs b/check_diff/src/main.rs index 60aee6ee2cb..bffe090f7ac 100644 --- a/check_diff/src/main.rs +++ b/check_diff/src/main.rs @@ -37,6 +37,10 @@ const REPOS: &[&str] = &[ "https://github.com/serde-rs/serde.git", "https://github.com/SergioBenitez/Rocket.git", "https://github.com/Stebalien/tempfile.git", + // Unicode / international text coverage (see rustfmt#5884) + "https://github.com/unicode-rs/unicode-width.git", + "https://github.com/unicode-rs/unicode-segmentation.git", + "https://github.com/unicode-rs/unicode-normalization.git", ]; /// Inputs for the check_diff script diff --git a/ci/Cargo.lock b/ci/Cargo.lock new file mode 100644 index 00000000000..7c7807f6f37 --- /dev/null +++ b/ci/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ci-integration" +version = "0.0.1" diff --git a/ci/Cargo.toml b/ci/Cargo.toml new file mode 100644 index 00000000000..74312216d4e --- /dev/null +++ b/ci/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ci-integration" +version = "0.0.1" +edition = "2024" +publish = false + +[workspace] diff --git a/ci/build_and_test.bat b/ci/build_and_test.bat deleted file mode 100755 index b6b5ca21364..00000000000 --- a/ci/build_and_test.bat +++ /dev/null @@ -1,25 +0,0 @@ -set "RUSTFLAGS=-D warnings" -set "RUSTFMT_CI=1" - -:: Print version information -rustc -Vv || exit /b 1 -cargo -V || exit /b 1 - -:: Build and test main crate -if "%CFG_RELEASE_CHANNEL%"=="nightly" ( - cargo build --locked --all-features || exit /b 1 -) else ( - cargo build --locked || exit /b 1 -) -cargo test || exit /b 1 - -:: Build and test config_proc_macro -cd config_proc_macro || exit /b 1 -cargo build --locked || exit /b 1 -cargo test || exit /b 1 - -:: Build and test check_diff -cd .. -cd check_diff || exit /b 1 -cargo build --locked || exit /b 1 -cargo test || exit /b 1 diff --git a/ci/build_and_test.sh b/ci/build_and_test.sh deleted file mode 100755 index dd9a0c0fd9b..00000000000 --- a/ci/build_and_test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -export RUSTFLAGS="-D warnings" -export RUSTFMT_CI=1 - -# Print version information -rustc -Vv -cargo -V - -# Build and test main crate -if [ "$CFG_RELEASE_CHANNEL" == "nightly" ]; then - cargo build --locked --all-features -else - cargo build --locked -fi -cargo test - -# Build and test config_proc_macro -cd config_proc_macro -cargo build --locked -cargo test - -# Build and test check_diff -cd .. -cd check_diff -cargo build --locked -cargo test diff --git a/ci/integration.sh b/ci/integration.sh deleted file mode 100755 index ea96e4be130..00000000000 --- a/ci/integration.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -: ${INTEGRATION?"The INTEGRATION environment variable must be set."} - -# FIXME: this means we can get a stale cargo-fmt from a previous run. -# -# `which rustfmt` fails if rustfmt is not found. Since we don't install -# `rustfmt` via `rustup`, this is the case unless we manually install it. Once -# that happens, `cargo install --force` will be called, which installs -# `rustfmt`, `cargo-fmt`, etc to `~/.cargo/bin`. This directory is cached by -# travis (see `.travis.yml`'s "cache" key), such that build-bots that arrive -# here after the first installation will find `rustfmt` and won't need to build -# it again. -# -#which cargo-fmt || cargo install --force -CFG_RELEASE=nightly CFG_RELEASE_CHANNEL=nightly cargo install --path . --force --locked - -echo "Integration tests for: ${INTEGRATION}" -cargo fmt -- --version - -# Checks that: -# -# * `cargo fmt --all` succeeds without any warnings or errors -# * `cargo fmt --all -- --check` after formatting returns success -# * `cargo test --all` still passes (formatting did not break the build) -function check_fmt_with_all_tests { - check_fmt_base "--all" - return $? -} - -# Checks that: -# -# * `cargo fmt --all` succeeds without any warnings or errors -# * `cargo fmt --all -- --check` after formatting returns success -# * `cargo test --lib` still passes (formatting did not break the build) -function check_fmt_with_lib_tests { - check_fmt_base "--lib" - return $? -} - -function check_fmt_base { - local test_args="$1" - local build=$(cargo test $test_args 2>&1) - if [[ "$build" =~ "build failed" ]] || [[ "$build" =~ "test result: FAILED." ]]; then - return 0 - fi - touch rustfmt.toml - cargo fmt --all -v |& tee rustfmt_output - if [[ ${PIPESTATUS[0]} != 0 ]]; then - cat rustfmt_output - return 1 - fi - cat rustfmt_output - ! cat rustfmt_output | grep -q "internal error" - if [[ $? != 0 ]]; then - return 1 - fi - ! cat rustfmt_output | grep -q "warning" - if [[ $? != 0 ]]; then - return 1 - fi - ! cat rustfmt_output | grep -q "Warning" - if [[ $? != 0 ]]; then - return 1 - fi - cargo fmt --all -- --check |& tee rustfmt_check_output - if [[ ${PIPESTATUS[0]} != 0 ]]; then - cat rustfmt_check_output - return 1 - fi - cargo test $test_args - if [[ $? != 0 ]]; then - return $? - fi -} - -function show_head { - local head=$(git rev-parse HEAD) - echo "Head commit of ${INTEGRATION}: $head" -} - -case ${INTEGRATION} in - cargo) - git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git - cd ${INTEGRATION} - show_head - export CFG_DISABLE_CROSS_TESTS=1 - check_fmt_with_all_tests - cd - - ;; - crater) - git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git - cd ${INTEGRATION} - show_head - check_fmt_with_lib_tests - cd - - ;; - bitflags) - git clone --depth=1 https://github.com/bitflags/${INTEGRATION}.git - cd ${INTEGRATION} - show_head - check_fmt_with_all_tests - cd - - ;; - tempdir) - git clone --depth=1 https://github.com/rust-lang-deprecated/${INTEGRATION}.git - cd ${INTEGRATION} - show_head - check_fmt_with_all_tests - cd - - ;; - *) - git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git - cd ${INTEGRATION} - show_head - check_fmt_with_all_tests - cd - - ;; -esac diff --git a/ci/src/build_and_test.rs b/ci/src/build_and_test.rs new file mode 100644 index 00000000000..305c4d5aaa2 --- /dev/null +++ b/ci/src/build_and_test.rs @@ -0,0 +1,49 @@ +use crate::common::run_command_with_env; + +use std::collections::HashMap; + +fn run_tests_in_dir(env: &HashMap<&str, &str>, dir: &str) -> Result<(), String> { + run_command_with_env("cargo", &["build", "--locked"], dir, &env)?; + run_command_with_env("cargo", &["test"], dir, &env) +} + +pub fn runner() -> Result<(), String> { + let Ok(rustflags) = std::env::var("RUSTFLAGS") else { + return Err( + "`RUSTFLAGS` environment variable must be set to run `build-and-test`".to_string(), + ); + }; + if !rustflags.contains("-D warnings") && !rustflags.contains("-Dwarnings") { + return Err( + "`RUSTFLAGS` environment variable must contain `-Dwarnings` to run `build-and-test`" + .to_string(), + ); + } + + let mut env = HashMap::from([("RUSTFLAGS", "-D warnings"), ("RUSTFMT_CI", "1")]); + let value_holder; + if let Ok(cfg_release_channel) = std::env::var("CFG_RELEASE_CHANNEL") { + value_holder = cfg_release_channel; + env.insert("CFG_RELEASE_CHANNEL", value_holder.as_str()); + } + + // Print version information + run_command_with_env("rustc", &["-Vv"], ".", &env)?; + run_command_with_env("cargo", &["-v"], ".", &env)?; + + // Build and test main crate + let options: &[&str] = + if std::env::var("CFG_RELEASE_CHANNEL").is_ok_and(|value| value == "nightly") { + &["build", "--locked", "--all-features"] + } else { + &["build", "--locked"] + }; + run_command_with_env("cargo", options, ".", &env)?; + run_command_with_env("cargo", &["test"], ".", &env)?; + + // Build and test config_proc_macro + run_tests_in_dir(&env, "config_proc_macro")?; + run_tests_in_dir(&env, "check_diff")?; + + Ok(()) +} diff --git a/ci/src/common.rs b/ci/src/common.rs new file mode 100644 index 00000000000..40dc9038a48 --- /dev/null +++ b/ci/src/common.rs @@ -0,0 +1,87 @@ +use std::collections::HashMap; +use std::ffi::OsStr; +use std::path::Path; +use std::process::Command; + +pub fn write_file(file_path: impl AsRef, content: &str) -> Result<(), String> { + std::fs::write(&file_path, content).map_err(|error| { + format!( + "Failed to create empty `{}` file: {error:?}", + file_path.as_ref().display(), + ) + }) +} + +pub fn run_command_with_env( + bin: &str, + args: I, + current_dir: &str, + env: &HashMap<&str, &str>, +) -> Result<(), String> +where + I: IntoIterator, + S: AsRef, +{ + let exit_status = Command::new(bin) + .args(args) + .envs(env) + .current_dir(current_dir) + .spawn() + .map_err(|error| format!("Failed to spawn command `{bin}`: {error:?}"))? + .wait() + .map_err(|error| format!("Failed to wait command `{bin}`: {error:?}"))?; + if exit_status.success() { + Ok(()) + } else { + Err(format!("Command `{bin}` failed")) + } +} + +pub fn run_command(bin: &str, args: I, current_dir: &str) -> Result<(), String> +where + I: IntoIterator, + S: AsRef, +{ + run_command_with_env(bin, args, current_dir, &HashMap::new()) +} + +pub struct CommandOutput { + pub output: String, + pub exited_successfully: bool, +} + +pub fn run_command_with_output_and_env( + bin: &str, + args: I, + current_dir: &str, + env: &HashMap<&str, &str>, +) -> Result +where + I: IntoIterator, + S: AsRef, +{ + let cmd_output = Command::new(bin) + .args(args) + .envs(env) + .current_dir(current_dir) + .output() + .map_err(|error| format!("Failed to spawn command `{bin}`: {error:?}"))?; + let mut output = String::from_utf8_lossy(&cmd_output.stdout).into_owned(); + output.push_str(&String::from_utf8_lossy(&cmd_output.stderr)); + Ok(CommandOutput { + output, + exited_successfully: cmd_output.status.success(), + }) +} + +pub fn run_command_with_output( + bin: &str, + args: I, + current_dir: &str, +) -> Result +where + I: IntoIterator, + S: AsRef, +{ + run_command_with_output_and_env(bin, args, current_dir, &HashMap::new()) +} diff --git a/ci/src/integration.rs b/ci/src/integration.rs new file mode 100644 index 00000000000..27885a66957 --- /dev/null +++ b/ci/src/integration.rs @@ -0,0 +1,157 @@ +use crate::common::{ + run_command, run_command_with_env, run_command_with_output, run_command_with_output_and_env, + write_file, +}; + +use std::collections::HashMap; +use std::path::Path; + +// Checks that: +// +// * `cargo fmt --all` succeeds without any warnings or errors +// * `cargo fmt --all -- --check` after formatting returns success +// * `cargo test --all` still passes (formatting did not break the build) +fn check_fmt_with_all_tests(env: HashMap<&str, &str>, current_dir: &str) -> Result<(), String> { + check_fmt_base("--all", env, current_dir) +} + +// Checks that: +// +// * `cargo fmt --all` succeeds without any warnings or errors +// * `cargo fmt --all -- --check` after formatting returns success +// * `cargo test --lib` still passes (formatting did not break the build) +fn check_fmt_with_lib_tests(env: HashMap<&str, &str>, current_dir: &str) -> Result<(), String> { + check_fmt_base("--lib", env, current_dir) +} + +fn check_fmt_base( + test_args: &str, + env: HashMap<&str, &str>, + current_dir: &str, +) -> Result<(), String> { + fn check_output_does_not_contain(output: &str, needle: &str) -> Result<(), String> { + if output.contains(needle) { + Err(format!("`cargo fmt --all -v` contains `{needle}`")) + } else { + Ok(()) + } + } + + let output = + run_command_with_output_and_env("cargo", &["test", test_args], current_dir, &env)?.output; + if ["build failed", "test result: FAILED."] + .iter() + .any(|needle| output.contains(needle)) + { + println!("`cargo test {test_args}` failed: {output}"); + return Ok(()); + } + + let rustfmt_toml = Path::new(current_dir).join("rustfmt.toml"); + if !rustfmt_toml.is_file() { + write_file(rustfmt_toml, "")?; + } + + let output = + run_command_with_output_and_env("cargo", &["fmt", "--all", "-v"], current_dir, &env)?; + println!("{}", output.output); + + if !output.exited_successfully { + return Err("`cargo fmt --all -v` failed".to_string()); + } + + let output = &output.output; + check_output_does_not_contain(output, "internal error")?; + check_output_does_not_contain(output, "internal compiler error")?; + check_output_does_not_contain(output, "warning")?; + check_output_does_not_contain(output, "Warning")?; + + let output = run_command_with_output_and_env( + "cargo", + &["fmt", "--all", "--", "--check"], + current_dir, + &env, + )?; + + if !output.exited_successfully { + return Err("`cargo fmt --all -- -v --check` failed".to_string()); + } + let output = &output.output; + if let Err(error) = write_file(Path::new(current_dir).join("rustfmt_check_output"), output) { + println!("{output}"); + return Err(error); + } + + // This command allows to ensure that no source file was modified while running the tests. + run_command_with_env("cargo", &["test", test_args], current_dir, &env) +} + +fn show_head(integration: &str) -> Result<(), String> { + let head = run_command_with_output("git", &["rev-parse", "HEAD"], integration)?.output; + println!("Head commit of {integration}: {head}"); + Ok(()) +} + +fn run_test, &str) -> Result<(), String>>( + integration: &str, + git_repository: String, + env: HashMap<&str, &str>, + test_fn: F, +) -> Result<(), String> { + run_command_with_output("git", &["clone", "--depth=1", git_repository.as_str()], ".")?; + show_head(integration)?; + test_fn(env, integration) +} + +pub fn runner(args: &mut impl Iterator) -> Result<(), String> { + let Some(integration) = args.next() else { + return Err("missing command line argument for `integration` checks".to_string()); + }; + + run_command_with_env( + "cargo", + &["install", "--path", ".", "--force", "--locked"], + ".", + &HashMap::from([ + ("CFG_RELEASE", "nightly"), + ("CFG_RELEASE_CHANNEL", "nightly"), + ]), + )?; + + println!("Integration tests for {integration}"); + + run_command("cargo", &["fmt", "--", "--version"], ".")?; + + match integration.as_str() { + "cargo" => run_test( + &integration, + format!("https://github.com/rust-lang/{integration}.git"), + HashMap::from([("CFG_DISABLE_CROSS_TESTS", "1")]), + check_fmt_with_all_tests, + ), + "crater" => run_test( + &integration, + format!("https://github.com/rust-lang/{integration}.git"), + HashMap::new(), + check_fmt_with_lib_tests, + ), + "bitflags" => run_test( + &integration, + format!("https://github.com/bitflags/{integration}.git"), + HashMap::new(), + check_fmt_with_all_tests, + ), + "tempdir" => run_test( + &integration, + format!("https://github.com/rust-lang-deprecated/{integration}.git"), + HashMap::new(), + check_fmt_with_all_tests, + ), + _ => run_test( + &integration, + format!("https://github.com/rust-lang/{integration}.git"), + HashMap::new(), + check_fmt_with_all_tests, + ), + } +} diff --git a/ci/src/main.rs b/ci/src/main.rs new file mode 100644 index 00000000000..4817fa3c49c --- /dev/null +++ b/ci/src/main.rs @@ -0,0 +1,21 @@ +mod build_and_test; +mod common; +mod integration; + +fn main() { + let mut args = std::env::args().skip(1); + if let Err(error) = match args.next().as_deref() { + Some("integration") => integration::runner(&mut args), + Some("build-and-test") => build_and_test::runner(), + Some(arg) => Err(format!( + "Expected `integration` or `build-and-test` as first argument, found {arg:?}" + )), + None => Err( + "Expected `integration` or `build-and-test` as first argument, found nothing" + .to_string(), + ), + } { + eprintln!("{error}"); + std::process::exit(1); + } +} diff --git a/docs/index.html b/docs/index.html index 13399be25ba..76c6ce3f0e5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -58,10 +58,22 @@ .searchCondition { display: flex; flex-wrap: wrap; + position: sticky; + top: 0; + z-index: 1; + padding: 12px 0; + background: #fff; + border-bottom: 1px solid #d1d5da; } .searchCondition > div { margin-right: 30px; } + .version-note { + flex-basis: 100%; + margin-top: 8px; + color: #57606a; + font-size: 0.9em; + } .header-link { position: relative; } @@ -100,6 +112,9 @@ +
+ Configuration options can change between rustfmt versions. Select the version that matches the rustfmt you use. +
diff --git a/rust-toolchain b/rust-toolchain index cad47379b5d..5dac4fcf280 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-02-19" +channel = "nightly-2026-07-19" components = ["llvm-tools", "rustc-dev"] diff --git a/src/cargo-fmt/main.rs b/src/cargo-fmt/main.rs index 9b4adc41a8b..6627ada03fc 100644 --- a/src/cargo-fmt/main.rs +++ b/src/cargo-fmt/main.rs @@ -20,6 +20,19 @@ use clap::{CommandFactory, Parser}; #[cfg(test)] mod cargo_fmt_tests; +const fn is_nightly() -> bool { + match option_env!("CFG_RELEASE_CHANNEL") { + None => true, + Some(c) => matches!(c.as_bytes(), b"nightly" | b"dev"), + } +} + +const MESSAGE_FORMATS: &str = if is_nightly() { + "short|json|human" +} else { + "short|human" +}; + #[derive(Parser)] #[command( disable_version_flag = true, @@ -54,8 +67,11 @@ pub struct Opts { #[arg(long = "manifest-path", value_name = "manifest-path")] manifest_path: Option, - /// Specify message-format: short|json|human - #[arg(long = "message-format", value_name = "message-format")] + #[arg( + long = "message-format", + value_name = "message-format", + help = format!("Specify message-format: {MESSAGE_FORMATS}") + )] message_format: Option, /// Options passed to rustfmt @@ -186,6 +202,11 @@ fn convert_message_format_to_rustfmt_args( Ok(()) } "json" => { + if !is_nightly() { + return Err(String::from( + "--message-format json is only supported in nightly builds", + )); + } if contains_emit_mode { return Err(String::from( "cannot include --emit arg when --message-format is set to json", @@ -202,7 +223,8 @@ fn convert_message_format_to_rustfmt_args( } "human" => Ok(()), _ => Err(format!( - "invalid --message-format value: {message_format}. Allowed values are: short|json|human" + "invalid --message-format value: {message_format}. Allowed values are: \ + {MESSAGE_FORMATS}" )), } } @@ -281,7 +303,7 @@ impl Target { Target { path: canonicalized, - kind: target.kind[0].clone(), + kind: target.kind[0].to_string(), edition: target.edition, } } @@ -444,10 +466,11 @@ fn get_targets_with_hitlist( targets: &mut BTreeSet, ) -> Result<(), io::Error> { let metadata = get_cargo_metadata(manifest_path)?; - let mut workspace_hitlist: BTreeSet<&String> = BTreeSet::from_iter(hitlist); + let mut workspace_hitlist: BTreeSet<&str> = + BTreeSet::from_iter(hitlist.into_iter().map(|s| s.as_str())); for package in metadata.packages { - if workspace_hitlist.remove(&package.name) { + if workspace_hitlist.remove(package.name.as_ref()) { for target in package.targets { targets.insert(Target::from_target(&target)); } diff --git a/src/cargo-fmt/test/message_format.rs b/src/cargo-fmt/test/message_format.rs index bf44924f13c..bf82f1d2759 100644 --- a/src/cargo-fmt/test/message_format.rs +++ b/src/cargo-fmt/test/message_format.rs @@ -1,7 +1,10 @@ use super::*; +use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test}; + +#[nightly_only_test] #[test] -fn invalid_message_format() { +fn invalid_message_format_nightly() { assert_eq!( convert_message_format_to_rustfmt_args("awesome", &mut vec![]), Err(String::from( @@ -10,6 +13,18 @@ fn invalid_message_format() { ); } +#[stable_only_test] +#[test] +fn invalid_message_format_stable() { + assert_eq!( + convert_message_format_to_rustfmt_args("awesome", &mut vec![]), + Err(String::from( + "invalid --message-format value: awesome. Allowed values are: short|human" + )), + ); +} + +#[nightly_only_test] #[test] fn json_message_format_and_check_arg() { let mut args = vec![String::from("--check")]; @@ -21,6 +36,7 @@ fn json_message_format_and_check_arg() { ); } +#[nightly_only_test] #[test] fn json_message_format_and_emit_arg() { let mut args = vec![String::from("--emit"), String::from("checkstyle")]; @@ -32,6 +48,18 @@ fn json_message_format_and_emit_arg() { ); } +#[stable_only_test] +#[test] +fn json_message_format_non_nightly() { + assert_eq!( + convert_message_format_to_rustfmt_args("json", &mut vec![]), + Err(String::from( + "--message-format json is only supported in nightly builds" + )), + ); +} + +#[nightly_only_test] #[test] fn json_message_format() { let mut args = vec![String::from("--edition"), String::from("2018")]; diff --git a/src/comment.rs b/src/comment.rs index 241934a7d3d..05d7310122a 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -764,6 +764,14 @@ impl<'a> CommentRewrite<'a> { .doc_comment_code_block_width() .min(config.max_width()); config.set().max_width(comment_max_width); + if let Some(comment_use_small_heuristics) = config + .doc_comment_code_block_small_heuristics() + .to_heuristics() + { + config + .set() + .use_small_heuristics(comment_use_small_heuristics); + } if let Some(s) = crate::format_code_block(&self.code_block_buffer, &config, false) { diff --git a/src/config/mod.rs b/src/config/mod.rs index 8abb7439257..a3f9842cd4f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -65,6 +65,9 @@ create_config! { doc comments."; doc_comment_code_block_width: DocCommentCodeBlockWidth, false, "Maximum width for code \ snippets in doc comments. No effect unless format_code_in_doc_comments = true"; + doc_comment_code_block_small_heuristics: DocUseSmallHeuristics, false, + "Value for use_small_heuristics for code blocks in doc comments. \ + No effect unless format_code_in_doc_comments = true"; comment_width: CommentWidth, false, "Maximum length of comments. No effect unless wrap_comments = true"; normalize_comments: NormalizeComments, false, "Convert /* */ comments to // comments where \ @@ -78,7 +81,7 @@ create_config! { "Format the bodies of declarative macro definitions"; skip_macro_invocations: SkipMacroInvocations, false, "Skip formatting the bodies of macros invoked with the following names."; - hex_literal_case: HexLiteralCaseConfig, false, "Format hexadecimal integer literals"; + hex_literal_case: HexLiteralCaseConfig, true, "Format hexadecimal integer literals"; float_literal_trailing_zero: FloatLiteralTrailingZeroConfig, false, "Add or remove trailing zero in floating-point literals"; @@ -772,6 +775,7 @@ single_line_let_else_max_width = 50 wrap_comments = false format_code_in_doc_comments = false doc_comment_code_block_width = 100 +doc_comment_code_block_small_heuristics = "Inherit" comment_width = 80 normalize_comments = false normalize_doc_attributes = false @@ -864,6 +868,7 @@ single_line_let_else_max_width = 50 wrap_comments = false format_code_in_doc_comments = false doc_comment_code_block_width = 100 +doc_comment_code_block_small_heuristics = "Inherit" comment_width = 80 normalize_comments = false normalize_doc_attributes = false diff --git a/src/config/options.rs b/src/config/options.rs index 00f9c3f7ec1..3f970ed4bd7 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -95,6 +95,31 @@ pub enum Heuristics { Default, } +#[config_type] +/// Heuristic settings for doc comments. Same as `Heuristics`, but `Inherit` will inherit the value +/// from the top-level configuration. +pub enum DocCodeHeuristics { + /// Inherit from the top-level configuration + Inherit, + /// Turn off any heuristics + Off, + /// Turn on max heuristics + Max, + /// Use scaled values based on the value of `max_width` + Default, +} + +impl DocCodeHeuristics { + pub fn to_heuristics(self) -> Option { + match self { + DocCodeHeuristics::Inherit => None, + DocCodeHeuristics::Off => Some(Heuristics::Off), + DocCodeHeuristics::Max => Some(Heuristics::Max), + DocCodeHeuristics::Default => Some(Heuristics::Default), + } + } +} + impl Density { pub fn to_list_tactic(self, len: usize) -> ListTactic { match self { @@ -620,6 +645,7 @@ config_option_with_style_edition_default!( WrapComments, bool, _ => false; FormatCodeInDocComments, bool, _ => false; DocCommentCodeBlockWidth, usize, _ => 100; + DocUseSmallHeuristics, DocCodeHeuristics, _ => DocCodeHeuristics::Inherit; CommentWidth, usize, _ => 80; NormalizeComments, bool, _ => false; NormalizeDocAttributes, bool, _ => false; diff --git a/src/expr.rs b/src/expr.rs index 5ecb6807856..aec50309943 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -23,6 +23,7 @@ use crate::macros::{MacroPosition, rewrite_macro}; use crate::matches::rewrite_match; use crate::overflow::{self, IntoOverflowableItem, OverflowableItem}; use crate::pairs::{PairParts, rewrite_all_pairs, rewrite_pair}; +use crate::range::rewrite_range; use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult}; use crate::shape::{Indent, Shape}; use crate::source_map::{LineRangeUtils, SpanUtils}; @@ -325,78 +326,13 @@ pub(crate) fn format_expr( shape, SeparatorPlace::Back, ), - ast::ExprKind::Range(ref lhs, ref rhs, limits) => { - let delim = match limits { - ast::RangeLimits::HalfOpen => "..", - ast::RangeLimits::Closed => "..=", - }; - - fn needs_space_before_range(context: &RewriteContext<'_>, lhs: &ast::Expr) -> bool { - match lhs.kind { - ast::ExprKind::Lit(token_lit) => lit_ends_in_dot(&token_lit, context), - ast::ExprKind::Unary(_, ref expr) => needs_space_before_range(context, expr), - ast::ExprKind::Binary(_, _, ref rhs_expr) => { - needs_space_before_range(context, rhs_expr) - } - _ => false, - } - } - - fn needs_space_after_range(rhs: &ast::Expr) -> bool { - // Don't format `.. ..` into `....`, which is invalid. - // - // This check is unnecessary for `lhs`, because a range - // starting from another range needs parentheses as `(x ..) ..` - // (`x .. ..` is a range from `x` to `..`). - matches!(rhs.kind, ast::ExprKind::Range(None, _, _)) - } - - let default_sp_delim = |lhs: Option<&ast::Expr>, rhs: Option<&ast::Expr>| { - let space_if = |b: bool| if b { " " } else { "" }; - - format!( - "{}{}{}", - lhs.map_or("", |lhs| space_if(needs_space_before_range(context, lhs))), - delim, - rhs.map_or("", |rhs| space_if(needs_space_after_range(rhs))), - ) - }; - - match (lhs.as_ref().map(|x| &**x), rhs.as_ref().map(|x| &**x)) { - (Some(lhs), Some(rhs)) => { - let sp_delim = if context.config.spaces_around_ranges() { - format!(" {delim} ") - } else { - default_sp_delim(Some(lhs), Some(rhs)) - }; - rewrite_pair( - &*lhs, - &*rhs, - PairParts::infix(&sp_delim), - context, - shape, - context.config.binop_separator(), - ) - } - (None, Some(rhs)) => { - let sp_delim = if context.config.spaces_around_ranges() { - format!("{delim} ") - } else { - default_sp_delim(None, Some(rhs)) - }; - rewrite_unary_prefix(context, &sp_delim, &*rhs, shape) - } - (Some(lhs), None) => { - let sp_delim = if context.config.spaces_around_ranges() { - format!(" {delim}") - } else { - default_sp_delim(Some(lhs), None) - }; - rewrite_unary_suffix(context, &sp_delim, &*lhs, shape) - } - (None, None) => Ok(delim.to_owned()), - } - } + ast::ExprKind::Range(ref lhs, ref rhs, limits) => rewrite_range( + context, + shape, + lhs.as_deref(), + rhs.as_deref(), + limits.as_str(), + ), // We do not format these expressions yet, but they should still // satisfy our width restrictions. // Style Guide RFC for InlineAsm variant pending diff --git a/src/formatting.rs b/src/formatting.rs index 1e1e329f624..7f2a14f9e31 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -230,7 +230,14 @@ impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> { // For some reason, the source_map does not include terminating // newlines so we must add one on for each file. This is sad. - source_file::append_newline(&mut visitor.buffer); + let num_newlines = count_newlines(&visitor.buffer); + if self + .config + .file_lines() + .contains_line(&path, num_newlines + 1) + { + source_file::append_newline(&mut visitor.buffer); + } format_lines( &mut visitor.buffer, diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 00000000000..5a9d2beb010 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,106 @@ +//! headers are sets of consecutive keywords and tokens, such as +//! `pub const unsafe fn foo` and `pub(crate) unsafe trait Bar`. +//! +//! This module contains general logic for formatting such headers, +//! where they are always placed on a single line except when there +//! are comments between parts of the header. + +use std::borrow::Cow; + +use rustc_ast as ast; +use rustc_span::Span; +use rustc_span::symbol::Ident; +use tracing::debug; + +use crate::comment::{combine_strs_with_missing_comments, contains_comment}; +use crate::rewrite::RewriteContext; +use crate::shape::Shape; +use crate::utils::rewrite_ident; + +pub(crate) fn format_header( + context: &RewriteContext<'_>, + shape: Shape, + parts: Vec>, +) -> String { + debug!(?parts, "format_header"); + let shape = shape.infinite_width(); + + // Empty `HeaderPart`s are ignored. + let mut parts = parts.into_iter().filter(|x| !x.snippet.is_empty()); + let Some(part) = parts.next() else { + return String::new(); + }; + + let mut result = part.snippet.into_owned(); + let mut span = part.span; + + for part in parts { + debug!(?result, "before combine"); + let comments_span = span.between(part.span); + let comments_snippet = context.snippet(comments_span); + result = if contains_comment(comments_snippet) { + // FIXME(fee1-dead): preserve (potentially misaligned) comments instead of reformatting + // them. Revisit this once we have a strategy for properly dealing with them. + format!("{result}{comments_snippet}{}", part.snippet) + } else { + combine_strs_with_missing_comments( + context, + &result, + &part.snippet, + comments_span, + shape, + true, + ) + .unwrap_or_else(|_| format!("{} {}", &result, part.snippet)) + }; + debug!(?result); + span = part.span; + } + + result +} + +#[derive(Debug)] +pub(crate) struct HeaderPart<'a> { + /// snippet of this part without surrounding space + snippet: Cow<'a, str>, + span: Span, +} + +impl<'a> HeaderPart<'a> { + pub(crate) fn new(snippet: impl Into>, span: Span) -> Self { + Self { + snippet: snippet.into(), + span, + } + } + + pub(crate) fn ident(context: &'a RewriteContext<'_>, ident: Ident) -> Self { + Self::new(rewrite_ident(context, ident), ident.span) + } + + pub(crate) fn visibility(context: &RewriteContext<'_>, vis: &ast::Visibility) -> Self { + let snippet = match vis.kind { + ast::VisibilityKind::Public => Cow::from("pub"), + ast::VisibilityKind::Inherited => Cow::from(""), + ast::VisibilityKind::Restricted { ref path, .. } => { + let ast::Path { ref segments, .. } = **path; + let mut segments_iter = + segments.iter().map(|seg| rewrite_ident(context, seg.ident)); + if path.is_global() { + segments_iter + .next() + .expect("Non-global path in pub(restricted)?"); + } + let is_keyword = |s: &str| s == "crate" || s == "self" || s == "super"; + let path = segments_iter.collect::>().join("::"); + let in_str = if is_keyword(&path) { "" } else { "in " }; + + // FIXME(fee1-dead): comments around parens + Cow::from(format!("pub({}{})", in_str, path)) + } + }; + + Self::new(snippet, vis.span) + } +} diff --git a/src/items.rs b/src/items.rs index fa5e23b8d53..6268af93d57 100644 --- a/src/items.rs +++ b/src/items.rs @@ -64,19 +64,17 @@ impl Rewrite for ast::Local { return Err(RewriteError::SkipFormatting); } - // FIXME(super_let): Implement formatting - if self.super_.is_some() { - return Err(RewriteError::SkipFormatting); - } - + let super_ = self.super_.is_some(); + // FIXME: deletes any comments in between super and let + let let_ = if super_ { "super let " } else { "let " }; let attrs_str = self.attrs.rewrite_result(context, shape)?; let mut result = if attrs_str.is_empty() { - "let ".to_owned() + let_.to_owned() } else { combine_strs_with_missing_comments( context, &attrs_str, - "let ", + let_, mk_sp( self.attrs.last().map(|a| a.span.hi()).unwrap(), self.span.lo(), @@ -85,10 +83,9 @@ impl Rewrite for ast::Local { false, )? }; - let let_kw_offset = result.len() - "let ".len(); + let let_kw_offset = result.len() - let_.len(); - // 4 = "let ".len() - let pat_shape = shape.offset_left(4, self.span())?; + let pat_shape = shape.offset_left(let_.len(), self.span())?; // 1 = ; let pat_shape = pat_shape.sub_width(1, self.span())?; let pat_str = self.pat.rewrite_result(context, pat_shape)?; @@ -2588,13 +2585,13 @@ fn rewrite_fn_base( .map_or(false, |last_line| last_line.contains("//")); if context.config.style_edition() >= StyleEdition::Edition2024 { - if closing_paren_overflow_max_width { - result.push(')'); + if params_last_line_contains_comment { result.push_str(&indent.to_string_with_newline(context.config)); + result.push(')'); no_params_and_over_max_width = true; - } else if params_last_line_contains_comment { - result.push_str(&indent.to_string_with_newline(context.config)); + } else if closing_paren_overflow_max_width { result.push(')'); + result.push_str(&indent.to_string_with_newline(context.config)); no_params_and_over_max_width = true; } else { result.push(')'); @@ -2677,7 +2674,12 @@ fn rewrite_fn_base( .unwrap_or(ret_shape) }; - if multi_line_ret_str || ret_should_indent { + let exceeds_max_width = last_line_width(&result) + ret_str_len > context.config.max_width(); + + if multi_line_ret_str + || ret_should_indent + || (context.config.style_edition() >= StyleEdition::Edition2027 && exceeds_max_width) + { // Now that we know the proper indent and width, we need to // re-layout the return type. let ret_str = fd.output.rewrite_result(context, ret_shape)?; diff --git a/src/lib.rs b/src/lib.rs index 942b42ec5f2..5f49bbf0c7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,7 @@ mod emitter; mod expr; mod format_report_formatter; pub(crate) mod formatting; +pub(crate) mod header; mod ignore_path; mod imports; mod items; @@ -84,6 +85,7 @@ mod overflow; mod pairs; mod parse; mod patterns; +mod range; mod release_channel; mod reorder; mod rewrite; diff --git a/src/macros.rs b/src/macros.rs index bd932b8d64d..2a824b4ce30 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -25,6 +25,7 @@ use crate::comment::{ 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::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; use crate::parse::macros::lazy_static::parse_lazy_static; @@ -36,7 +37,7 @@ use crate::shape::{Indent, Shape}; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::utils::{ - NodeIdExt, filtered_str_fits, format_visibility, indent_next_line, is_empty_line, mk_sp, + NodeIdExt, filtered_str_fits, indent_next_line, is_empty_line, mk_sp, remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout, }; use crate::visitor::FmtVisitor; @@ -429,14 +430,21 @@ pub(crate) fn rewrite_macro_def( None => return snippet, }; - let mut result = if def.macro_rules { - String::from("macro_rules!") + let mut header = if def.macro_rules { + let pos = context.snippet_provider.span_after(span, "macro_rules!"); + vec![HeaderPart::new("macro_rules!", span.with_hi(pos))] } else { - format!("{}macro", format_visibility(context, vis)) + let macro_lo = context.snippet_provider.span_before(span, "macro"); + let macro_hi = macro_lo + BytePos("macro".len() as u32); + vec![ + HeaderPart::visibility(context, vis), + HeaderPart::new("macro", mk_sp(macro_lo, macro_hi)), + ] }; - result += " "; - result += rewrite_ident(context, ident); + header.push(HeaderPart::ident(context, ident)); + + let mut result = format_header(context, shape, header); let multi_branch_style = def.macro_rules || parsed_def.branches.len() != 1; diff --git a/src/missed_spans.rs b/src/missed_spans.rs index d394bb40b6d..2654d2464ee 100644 --- a/src/missed_spans.rs +++ b/src/missed_spans.rs @@ -63,7 +63,10 @@ impl<'a> FmtVisitor<'a> { let config = self.config; self.format_missing_inner(end, |this, last_snippet, snippet| { this.push_str(last_snippet.trim_end()); - if last_snippet == snippet && !this.output_at_start() { + if last_snippet == snippet + && !this.output_at_start() + && !out_of_file_lines_range!(this, mk_sp(this.last_pos, end)) + { // No new lines in the snippet. this.push_str("\n"); } @@ -100,7 +103,11 @@ impl<'a> FmtVisitor<'a> { let snippet = self.snippet(span); // Do nothing for spaces in the beginning of the file - if start == BytePos(0) && end.0 as usize == snippet.len() && snippet.trim().is_empty() { + if start == BytePos(0) + && end.0 as usize == snippet.len() + && snippet.trim().is_empty() + && !out_of_file_lines_range!(self, span) + { return; } @@ -357,11 +364,20 @@ impl<'a> FmtVisitor<'a> { } } - let remaining = snippet[status.line_start..subslice.len() + offset].trim(); - if !remaining.is_empty() { - self.push_str(&self.block_indent.to_string(self.config)); - self.push_str(remaining); - status.line_start = subslice.len() + offset; + let mut remaining = &snippet[status.line_start..subslice.len() + offset]; + status.line_start = subslice.len() + offset; + + let skip_this_line = !self + .config + .file_lines() + .contains_line(file_name, status.cur_line); + if !skip_this_line { + remaining = remaining.trim(); + if !remaining.is_empty() { + self.push_str(&self.block_indent.to_string(self.config)); + } } + + self.push_str(remaining); } } diff --git a/src/patterns.rs b/src/patterns.rs index 0a9ff4771b0..2fad1d41ae9 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -1,4 +1,4 @@ -use rustc_ast::ast::{self, BindingMode, ByRef, Pat, PatField, PatKind, RangeEnd, RangeSyntax}; +use rustc_ast::ast::{self, BindingMode, ByRef, Pat, PatField, PatKind}; use rustc_span::{BytePos, Span}; use crate::comment::{FindUncommented, combine_strs_with_missing_comments}; @@ -11,14 +11,15 @@ use crate::lists::{ }; use crate::macros::{MacroPosition, rewrite_macro}; use crate::overflow; -use crate::pairs::{PairParts, rewrite_pair}; +use crate::range::rewrite_range; use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult}; use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::types::{PathContext, rewrite_path}; use crate::utils::{ - format_mutability, format_pinnedness_and_mutability, mk_sp, mk_sp_lo_plus_one, rewrite_ident, + format_mutability, format_pinnedness_and_mutability, format_range_end, mk_sp, + mk_sp_lo_plus_one, rewrite_ident, }; /// Returns `true` if the given pattern is "short". @@ -77,24 +78,6 @@ fn is_short_pattern_inner(context: &RewriteContext<'_>, pat: &ast::Pat) -> bool } } -pub(crate) struct RangeOperand<'a, T> { - pub operand: &'a Option>, - pub span: Span, -} - -impl<'a, T: Rewrite> Rewrite for RangeOperand<'a, T> { - fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { - self.rewrite_result(context, shape).ok() - } - - fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult { - match &self.operand { - None => Ok("".to_owned()), - Some(ref exp) => exp.rewrite_result(context, shape), - } - } -} - impl Rewrite for Pat { fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { self.rewrite_result(context, shape).ok() @@ -291,9 +274,13 @@ impl Rewrite for Pat { } } PatKind::Never => Err(RewriteError::Unknown), - PatKind::Range(ref lhs, ref rhs, ref end_kind) => { - rewrite_range_pat(context, shape, lhs, rhs, end_kind, self.span) - } + PatKind::Range(ref lhs, ref rhs, ref end_kind) => rewrite_range( + context, + shape, + lhs.as_deref(), + rhs.as_deref(), + format_range_end(end_kind.node), + ), PatKind::Ref(ref pat, pinnedness, mutability) => { let (pin_prefix, mut_prefix) = format_pinnedness_and_mutability(pinnedness, mutability); @@ -356,50 +343,6 @@ impl Rewrite for Pat { } } -pub(crate) fn rewrite_range_pat( - context: &RewriteContext<'_>, - shape: Shape, - lhs: &Option>, - rhs: &Option>, - end_kind: &rustc_span::Spanned, - span: Span, -) -> RewriteResult { - let infix = match end_kind.node { - RangeEnd::Included(RangeSyntax::DotDotDot) => "...", - RangeEnd::Included(RangeSyntax::DotDotEq) => "..=", - RangeEnd::Excluded => "..", - }; - let infix = if context.config.spaces_around_ranges() { - let lhs_spacing = match lhs { - None => "", - Some(_) => " ", - }; - let rhs_spacing = match rhs { - None => "", - Some(_) => " ", - }; - format!("{lhs_spacing}{infix}{rhs_spacing}") - } else { - infix.to_owned() - }; - let lspan = span.with_hi(end_kind.span.lo()); - let rspan = span.with_lo(end_kind.span.hi()); - rewrite_pair( - &RangeOperand { - operand: lhs, - span: lspan, - }, - &RangeOperand { - operand: rhs, - span: rspan, - }, - PairParts::infix(&infix), - context, - shape, - SeparatorPlace::Front, - ) -} - fn rewrite_struct_pat( qself: &Option>, path: &ast::Path, diff --git a/src/range.rs b/src/range.rs new file mode 100644 index 00000000000..0cc7c7fef6f --- /dev/null +++ b/src/range.rs @@ -0,0 +1,78 @@ +use crate::expr::{lit_ends_in_dot, rewrite_unary_prefix, rewrite_unary_suffix}; +use crate::pairs::{PairParts, rewrite_pair}; +use crate::rewrite::{RewriteContext, RewriteResult}; +use crate::shape::Shape; + +use rustc_ast::ast; + +fn needs_space_before_range(context: &RewriteContext<'_>, lhs: &ast::Expr) -> bool { + match lhs.kind { + ast::ExprKind::Lit(token_lit) => lit_ends_in_dot(&token_lit, context), + ast::ExprKind::Unary(_, ref expr) => needs_space_before_range(context, expr), + ast::ExprKind::Binary(_, _, ref rhs_expr) => needs_space_before_range(context, rhs_expr), + _ => false, + } +} + +fn needs_space_after_range(rhs: &ast::Expr) -> bool { + // Don't format `.. ..` into `....`, which is invalid. + // + // This check is unnecessary for `lhs`, because a range + // starting from another range needs parentheses as `(x ..) ..` + // (`x .. ..` is a range from `x` to `..`). + matches!(rhs.kind, ast::ExprKind::Range(None, _, _)) +} + +pub(crate) fn rewrite_range( + context: &RewriteContext<'_>, + shape: Shape, + lhs: Option<&ast::Expr>, + rhs: Option<&ast::Expr>, + delim: &str, +) -> RewriteResult { + let default_sp_delim = |lhs: Option<&ast::Expr>, rhs: Option<&ast::Expr>| { + let space_if = |b: bool| if b { " " } else { "" }; + + format!( + "{}{}{}", + lhs.map_or("", |lhs| space_if(needs_space_before_range(context, lhs))), + delim, + rhs.map_or("", |rhs| space_if(needs_space_after_range(rhs))), + ) + }; + + match (lhs, rhs) { + (Some(lhs), Some(rhs)) => { + let sp_delim = if context.config.spaces_around_ranges() { + format!(" {delim} ") + } else { + default_sp_delim(Some(lhs), Some(rhs)) + }; + rewrite_pair( + lhs, + rhs, + PairParts::infix(&sp_delim), + context, + shape, + context.config.binop_separator(), + ) + } + (None, Some(rhs)) => { + let sp_delim = if context.config.spaces_around_ranges() { + format!("{delim} ") + } else { + default_sp_delim(None, Some(rhs)) + }; + rewrite_unary_prefix(context, &sp_delim, rhs, shape) + } + (Some(lhs), None) => { + let sp_delim = if context.config.spaces_around_ranges() { + format!(" {delim}") + } else { + default_sp_delim(Some(lhs), None) + }; + rewrite_unary_suffix(context, &sp_delim, lhs, shape) + } + (None, None) => Ok(delim.to_owned()), + } +} diff --git a/src/spanned.rs b/src/spanned.rs index 143fb1dea22..90331ce926e 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -4,7 +4,6 @@ use rustc_ast::ast; use rustc_span::Span; use crate::macros::MacroArg; -use crate::patterns::RangeOperand; use crate::utils::{mk_sp, outer_attributes}; /// Spanned returns a span including attributes, if available. @@ -205,9 +204,3 @@ impl Spanned for ast::PreciseCapturingArg { } } } - -impl<'a, T> Spanned for RangeOperand<'a, T> { - fn span(&self) -> Span { - self.span - } -} diff --git a/src/string.rs b/src/string.rs index 3b971188cd5..59c445f904b 100644 --- a/src/string.rs +++ b/src/string.rs @@ -360,7 +360,16 @@ fn is_new_line(grapheme: &str) -> bool { } fn is_whitespace(grapheme: &str) -> bool { - grapheme.chars().all(char::is_whitespace) + // We explicitly match these characters instead of using char::is_whitespace + // because char::is_whitespace uses Unicode White_Space which is broader + // than the Rust language's definition of whitespace. For example it would + // also match \u{A0} (non-breaking space). \x0B (vertical tab) and \x0C + // (form feed) are included here because the Rust language defines them + // as whitespace, but is_ascii_whitespace excludes them. + + grapheme + .chars() + .all(|c| matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0B' | '\x0C')) } fn is_punctuation(grapheme: &str) -> bool { diff --git a/src/test/mod.rs b/src/test/mod.rs index 4eded7c49eb..291ac8fa078 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -165,6 +165,58 @@ fn verify_config_test_names() { } } +// Collects all file and directory paths under `root` (relative to `root`). +fn collect_paths(root: &Path) -> Vec { + let mut paths = Vec::new(); + let mut stack = vec![root.to_path_buf()]; + while let Some(dir) = stack.pop() { + for entry in fs::read_dir(&dir).expect(&format!("couldn't read {}", dir.display())) { + let entry = entry.expect("couldn't get DirEntry"); + let path = entry.path(); + paths.push(path.strip_prefix(root).unwrap().to_path_buf()); + if path.is_dir() { + stack.push(path); + } + } + } + paths +} + +#[test] +fn no_case_insensitive_path_collisions() { + // Ensure no two paths in test directories differ only by case, + // which causes warnings when cloning on case-insensitive filesystems + // (e.g. Windows, macOS). + let test_dirs = [Path::new("tests/source"), Path::new("tests/target")]; + let mut collisions = Vec::new(); + + for root in &test_dirs { + let mut seen: HashMap = HashMap::new(); + for path in collect_paths(root) { + let key = path.to_string_lossy().to_lowercase(); + if let Some(existing) = seen.get(&key) { + if *existing != path { + collisions.push(format!( + "{}/{} collides with {}/{}", + root.display(), + existing.display(), + root.display(), + path.display(), + )); + } + } else { + seen.insert(key, path); + } + } + } + + assert!( + collisions.is_empty(), + "Case-insensitive path collisions found (these cause warnings on Windows/macOS):\n {}", + collisions.join("\n ") + ); +} + // This writes to the terminal using the same approach (via `term::stdout` or // `println!`) that is used by `rustfmt::rustfmt_diff::print_diff`. Writing // using only one or the other will cause the output order to differ when diff --git a/src/types.rs b/src/types.rs index d3a1279cfb3..d6a25e61008 100644 --- a/src/types.rs +++ b/src/types.rs @@ -16,14 +16,14 @@ use crate::lists::{ use crate::macros::{MacroPosition, rewrite_macro}; use crate::overflow; use crate::pairs::{PairParts, rewrite_pair}; -use crate::patterns::rewrite_range_pat; +use crate::range::rewrite_range; use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult}; use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::utils::{ colon_spaces, extra_offset, first_line_width, format_extern, format_mutability, - last_line_extendable, last_line_width, mk_sp, rewrite_ident, + format_range_end, last_line_extendable, last_line_width, mk_sp, rewrite_ident, }; #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -1067,9 +1067,13 @@ impl Rewrite for ast::TyPat { fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult { match self.kind { - ast::TyPatKind::Range(ref lhs, ref rhs, ref end_kind) => { - rewrite_range_pat(context, shape, lhs, rhs, end_kind, self.span) - } + ast::TyPatKind::Range(ref lhs, ref rhs, ref end_kind) => rewrite_range( + context, + shape, + lhs.as_deref().map(|x| x.value.as_ref()), + rhs.as_deref().map(|x| x.value.as_ref()), + format_range_end(end_kind.node), + ), ast::TyPatKind::Or(ref variants) => { let mut first = true; let mut s = String::new(); diff --git a/src/utils.rs b/src/utils.rs index 3e06f3899d1..15a4fce9348 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -179,6 +179,15 @@ pub(crate) fn format_pinnedness_and_mutability( } } +#[inline] +pub(crate) fn format_range_end(end: ast::RangeEnd) -> &'static str { + match end { + ast::RangeEnd::Included(ast::RangeSyntax::DotDotDot) => "...", + ast::RangeEnd::Included(ast::RangeSyntax::DotDotEq) => "..=", + ast::RangeEnd::Excluded => "..", + } +} + #[inline] pub(crate) fn format_extern(ext: ast::Extern, explicit_abi: bool) -> Cow<'static, str> { match ext { diff --git a/src/visitor.rs b/src/visitor.rs index 560d62e9d57..55f9a4d8c8b 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -118,6 +118,14 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { fn visit_stmt(&mut self, stmt: &Stmt<'_>, include_empty_semi: bool) { debug!("visit_stmt: {}", self.psess.span_to_debug_info(stmt.span())); + // Preserve original source snippet if the statement isn't in the selected file lines. + if out_of_file_lines_range!(self, stmt.span()) { + let stmt_span = source!(self, stmt.span()); + self.push_str(self.snippet(mk_sp(self.last_pos, stmt_span.hi()))); + self.last_pos = stmt_span.hi(); + return; + } + if stmt.is_empty() { // If the statement is empty, just skip over it. Before that, make sure any comment // snippet preceding the semicolon is picked up. @@ -899,8 +907,12 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { return false; } - let rewrite = attrs.rewrite(&self.get_context(), self.shape()); let span = mk_sp(attrs[0].span.lo(), attrs[attrs.len() - 1].span.hi()); + if out_of_file_lines_range!(self, span) { + return false; + } + + let rewrite = attrs.rewrite(&self.get_context(), self.shape()); self.push_rewrite(span, rewrite); false @@ -1032,12 +1044,16 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { .snippet_provider .opt_span_after(self.next_span(end_pos), "\n") { + let span = self.next_span(pos); if let Some(snippet) = self.opt_snippet(self.next_span(pos)) { - if snippet.trim().is_empty() { - self.last_pos = pos; - } else { + if !snippet.trim().is_empty() { + return; + } + + if out_of_file_lines_range!(self, span) { return; } + self.last_pos = pos; } } } diff --git a/tests/source/configs/doc_comment_code_block_small_heuristics/default-to-max.rs b/tests/source/configs/doc_comment_code_block_small_heuristics/default-to-max.rs new file mode 100644 index 00000000000..dcaa89367f8 --- /dev/null +++ b/tests/source/configs/doc_comment_code_block_small_heuristics/default-to-max.rs @@ -0,0 +1,71 @@ +// rustfmt-format_code_in_doc_comments: true +// rustfmt-use_small_heuristics: Default +// rustfmt-doc_comment_code_block_small_heuristics: Max + +/// Start of a doc comment. +/// +/// ``` +/// enum Lorem { +/// Ipsum, +/// Dolor(bool), +/// Sit { amet: Consectetur, adipiscing: Elit }, +/// } +/// +/// fn main() { +/// lorem( +/// "lorem", +/// "ipsum", +/// "dolor", +/// "sit", +/// "amet", +/// "consectetur", +/// "adipiscing", +/// ); +/// +/// let lorem = Lorem { +/// ipsum: dolor, +/// sit: amet, +/// }; +/// +/// let lorem = if ipsum { dolor } else { sit }; +/// } +/// +/// fn format_let_else() { +/// let Some(a) = opt else {}; +/// +/// let Some(b) = opt else { return }; +/// +/// let Some(c) = opt else { return }; +/// +/// let Some(d) = some_very_very_very_very_long_name else { +/// return; +/// }; +/// } +/// ``` +/// +/// End of a doc comment. +struct S; + +enum Lorem { + Ipsum, + Dolor(bool), + Sit { amet: Consectetur, adipiscing: Elit }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { ipsum: dolor, sit: amet }; + + let lorem = if ipsum { dolor } else { sit }; +} + +fn format_let_else() { + let Some(a) = opt else {}; + + let Some(b) = opt else { return }; + + let Some(c) = opt else { return }; + + let Some(d) = some_very_very_very_very_long_name else { return }; +} diff --git a/tests/source/configs/doc_comment_code_block_small_heuristics/max-to-default.rs b/tests/source/configs/doc_comment_code_block_small_heuristics/max-to-default.rs new file mode 100644 index 00000000000..b9aa3211aaf --- /dev/null +++ b/tests/source/configs/doc_comment_code_block_small_heuristics/max-to-default.rs @@ -0,0 +1,68 @@ +// rustfmt-format_code_in_doc_comments: true +// rustfmt-use_small_heuristics: Max +// rustfmt-doc_comment_code_block_small_heuristics: Default + +/// Start of a doc comment. +/// +/// ``` +/// enum Lorem { +/// Ipsum, +/// Dolor(bool), +/// Sit { amet: Consectetur, adipiscing: Elit }, +/// } +/// +/// fn main() { +/// lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); +/// +/// let lorem = Lorem { ipsum: dolor, sit: amet }; +/// +/// let lorem = if ipsum { dolor } else { sit }; +/// } +/// +/// fn format_let_else() { +/// let Some(a) = opt else {}; +/// +/// let Some(b) = opt else { return }; +/// +/// let Some(c) = opt else { return }; +/// +/// let Some(d) = some_very_very_very_very_long_name else { return }; +/// } +/// ``` +/// +/// End of a doc comment. +struct S; + +enum Lorem { + Ipsum, + Dolor(bool), + Sit { + amet: Consectetur, + adipiscing: Elit, + }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; + + let lorem = if ipsum { + dolor + } else { + sit + }; +} + +fn format_let_else() { + let Some(a) = opt else {}; + + let Some(b) = opt else { return }; + + let Some(c) = opt else { return }; + + let Some(d) = some_very_very_very_very_long_name else { return }; +} diff --git a/tests/source/configs/float_literal_trailing_zero/always.rs b/tests/source/configs/float_literal_trailing_zero/always.rs index 47b0443137a..8ac9737648f 100644 --- a/tests/source/configs/float_literal_trailing_zero/always.rs +++ b/tests/source/configs/float_literal_trailing_zero/always.rs @@ -1,4 +1,5 @@ // rustfmt-float_literal_trailing_zero: Always +// spaces_around_ranges: false is implied since it's the default fn float_literals() { let a = 0.; @@ -43,3 +44,6 @@ fn line_wrapping() { ]; println!("This is floaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat {}", 10e3); } + +fn pattern_in_function_parameters_exactly_max_width_before_zero___(2. ..10.: std::ops::Range) { +} diff --git a/tests/source/configs/float_literal_trailing_zero/if-no-postfix.rs b/tests/source/configs/float_literal_trailing_zero/if-no-postfix.rs index 45e0b87bbae..4889203b616 100644 --- a/tests/source/configs/float_literal_trailing_zero/if-no-postfix.rs +++ b/tests/source/configs/float_literal_trailing_zero/if-no-postfix.rs @@ -1,4 +1,5 @@ // rustfmt-float_literal_trailing_zero: IfNoPostfix +// spaces_around_ranges: false is implied since it's the default fn float_literals() { let a = 0.; @@ -46,3 +47,6 @@ fn line_wrapping() { 10.0e3 ); } + +fn pattern_in_function_parameters_exactly_max_width_before_zero___(2. ..10.: std::ops::Range) { +} diff --git a/tests/source/configs/float_literal_trailing_zero/never.rs b/tests/source/configs/float_literal_trailing_zero/never.rs index 2fe5fe2f438..f07bc4db20f 100644 --- a/tests/source/configs/float_literal_trailing_zero/never.rs +++ b/tests/source/configs/float_literal_trailing_zero/never.rs @@ -1,4 +1,5 @@ // rustfmt-float_literal_trailing_zero: Never +// spaces_around_ranges: false is implied since it's the default fn float_literals() { let a = 0.; @@ -47,3 +48,6 @@ fn line_wrapping() { 10.0e3 ); } + +fn pattern_in_function_parameters_exactly_max_width_before_zero___(2.0..10.: std::ops::Range) { +} diff --git a/tests/source/configs/hex_literal_case/hex_literal_lower.rs b/tests/source/configs/hex_literal_case/hex_literal_lower.rs new file mode 100644 index 00000000000..de2c9f9d9dc --- /dev/null +++ b/tests/source/configs/hex_literal_case/hex_literal_lower.rs @@ -0,0 +1,9 @@ +// rustfmt-hex_literal_case: Lower +fn main() { + let h1 = 0xCAFE_5EA7; + let h2 = 0xCAFE_F00Du32; + let h3 = -0xCAFE_5EA7; + let h4 = -0xCAFE_F00Di32; + let h5 = 0xABcD07_i32; + let h6 = -0xABcD07_i32; +} diff --git a/tests/source/configs/hex_literal_case/hex_literal_preserve.rs b/tests/source/configs/hex_literal_case/hex_literal_preserve.rs new file mode 100644 index 00000000000..876592a4f64 --- /dev/null +++ b/tests/source/configs/hex_literal_case/hex_literal_preserve.rs @@ -0,0 +1,9 @@ +// rustfmt-hex_literal_case: Preserve +fn main() { + let h1 = 0xcAfE_5Ea7; + let h2 = 0xCaFe_F00du32; + let h3 = -0xcAfE_5Ea7; + let h4 = -0xCaFe_F00di32; + let h5 = 0xAbcd07_i32; + let h6 = -0xAbcd07_i32; +} diff --git a/tests/source/configs/hex_literal_case/hex_literal_upper.rs b/tests/source/configs/hex_literal_case/hex_literal_upper.rs new file mode 100644 index 00000000000..d3fbe367185 --- /dev/null +++ b/tests/source/configs/hex_literal_case/hex_literal_upper.rs @@ -0,0 +1,9 @@ +// rustfmt-hex_literal_case: Upper +fn main() { + let h1 = 0xCaFE_5ea7; + let h2 = 0xCAFE_F00Du32; + let h3 = -0xCaFE_5ea7; + let h4 = -0xCAFE_F00Di32; + let h5 = 0xAbcd07_i32; + let h6 = -0xAbcd07_i32; +} diff --git a/tests/source/configs/spaces_around_ranges/false.rs b/tests/source/configs/spaces_around_ranges/false.rs index 1878c68a5a0..b478dade316 100644 --- a/tests/source/configs/spaces_around_ranges/false.rs +++ b/tests/source/configs/spaces_around_ranges/false.rs @@ -7,16 +7,19 @@ fn main() { match lorem { 1 .. 5 => foo(), + 1. .. 5. => (), _ => bar, } match lorem { 1 ..= 5 => foo(), + 1. ..= 5. => (), _ => bar, } match lorem { 1 ... 5 => foo(), + 1. ... 0.5 => foo(), _ => bar, } } @@ -25,10 +28,18 @@ fn half_open() { match [5 .. 4, 99 .. 105, 43 .. 44] { [_, 99 .., _] => {} [_, .. 105, _] => {} + [_, 1. .., _] => {} _ => {} }; if let ..= 5 = 0 {} if let .. 5 = 0 {} + // For now `.. .5` fails parsing with `float literals must have an integer part` + if let .. 0.5 = 0 {} if let 5 .. = 0 {} + if let 5. .. = 0 {} +} + +fn pattern_in_function_parameters_exactly_max_width_before_space__(2. .. 10.: std::ops::Range) { + } diff --git a/tests/source/configs/spaces_around_ranges/true.rs b/tests/source/configs/spaces_around_ranges/true.rs index 0eadfb28515..fe324ee3ac9 100644 --- a/tests/source/configs/spaces_around_ranges/true.rs +++ b/tests/source/configs/spaces_around_ranges/true.rs @@ -7,16 +7,19 @@ fn main() { match lorem { 1..5 => foo(), + 1. ..5. => (), _ => bar, } match lorem { 1..=5 => foo(), + 1. ..=5. => (), _ => bar, } match lorem { 1...5 => foo(), + 1. ... 0.5 => foo(), _ => bar, } } @@ -25,10 +28,17 @@ fn half_open() { match [5..4, 99..105, 43..44] { [_, 99.., _] => {} [_, ..105, _] => {} + [_, 1. .., _] => {} _ => {} }; if let ..=5 = 0 {} if let ..5 = 0 {} + // For now `.. .5` fails parsing with `float literals must have an integer part` + if let .. 0.5 = 0 {} if let 5.. = 0 {} + if let 5. .. = 0 {} +} + +fn pattern_in_function_parameters_exactly_max_width_before_space__(2. ..10.: std::ops::Range) { } diff --git a/tests/source/hex_literal_lower.rs b/tests/source/hex_literal_lower.rs deleted file mode 100644 index ce307b3aa52..00000000000 --- a/tests/source/hex_literal_lower.rs +++ /dev/null @@ -1,5 +0,0 @@ -// rustfmt-hex_literal_case: Lower -fn main() { - let h1 = 0xCAFE_5EA7; - let h2 = 0xCAFE_F00Du32; -} diff --git a/tests/source/hex_literal_upper.rs b/tests/source/hex_literal_upper.rs deleted file mode 100644 index b1092ad71ba..00000000000 --- a/tests/source/hex_literal_upper.rs +++ /dev/null @@ -1,5 +0,0 @@ -// rustfmt-hex_literal_case: Upper -fn main() { - let h1 = 0xCaFE_5ea7; - let h2 = 0xCAFE_F00Du32; -} diff --git a/tests/source/issue-5136-1.rs b/tests/source/issue-5136-1.rs new file mode 100644 index 00000000000..75231555232 --- /dev/null +++ b/tests/source/issue-5136-1.rs @@ -0,0 +1,7 @@ + + +// Test that newlines at the top of this file are preserved when they're not +// in the --file-lines range. + +// This should prevent rustfmt from many any formatting changes at all: +// rustfmt-file_lines: [] diff --git a/tests/source/issue-5136-2.rs b/tests/source/issue-5136-2.rs new file mode 100644 index 00000000000..fe3cfcf37b8 --- /dev/null +++ b/tests/source/issue-5136-2.rs @@ -0,0 +1,5 @@ + use std; +// Test that whitespace at beginning of file is preserved when not in +// --file-lines range. +// This should prevent rustfmt from making any formatting changes at all: +// rustfmt-file_lines: [] diff --git a/tests/source/issue-5136-3.rs b/tests/source/issue-5136-3.rs new file mode 100644 index 00000000000..c152181bc2f --- /dev/null +++ b/tests/source/issue-5136-3.rs @@ -0,0 +1,6 @@ +// rustfmt-file_lines: [] +// Test that a missing newline at the end of the file is preserved when the last +// line is not in the --file-lines range. +// Important: When editing this file, make sure not to add a newline at the end +// of the last line. +use std; \ No newline at end of file diff --git a/tests/source/issue-5136-4.rs b/tests/source/issue-5136-4.rs new file mode 100644 index 00000000000..005167dff63 --- /dev/null +++ b/tests/source/issue-5136-4.rs @@ -0,0 +1,4 @@ +// rustfmt-file_lines: [] +// Test that a missing space at the end of a doc comment is preserved when the +// line is not in the --file-lines range. +//! diff --git a/tests/source/issue-5136-5.rs b/tests/source/issue-5136-5.rs new file mode 100644 index 00000000000..0f836aa989f --- /dev/null +++ b/tests/source/issue-5136-5.rs @@ -0,0 +1,6 @@ +// rustfmt-file_lines: [] +// Test that the space before the comment is not removed if the line is not +// contained in `--file-lines`. +// Note: It's important for the bug to repro that there is no newline at the +// end of the comment +fn f(){} // what \ No newline at end of file diff --git a/tests/source/issue-6825.rs b/tests/source/issue-6825.rs new file mode 100644 index 00000000000..67677f14fdb --- /dev/null +++ b/tests/source/issue-6825.rs @@ -0,0 +1,7 @@ +// rustfmt-edition: 2024 +// rustfmt-style_edition: 2024 +pub async fn foo( + // OriginalUri(original_uri): OriginalUri, +) -> Option>>> { + None +} diff --git a/tests/source/issue-6863/empty-stmt.rs b/tests/source/issue-6863/empty-stmt.rs new file mode 100644 index 00000000000..050fbb9c525 --- /dev/null +++ b/tests/source/issue-6863/empty-stmt.rs @@ -0,0 +1,7 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-6863/empty-stmt.rs","range":[5,5]}] + +fn main() { +; +println!("b"); +; +} diff --git a/tests/source/issue-6863/fn-stmts.rs b/tests/source/issue-6863/fn-stmts.rs new file mode 100644 index 00000000000..e677f842bd5 --- /dev/null +++ b/tests/source/issue-6863/fn-stmts.rs @@ -0,0 +1,7 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-6863/fn-stmts.rs","range":[5,5]}] + +fn main() { +println!("a"); +println!("b"); +println!("c"); +} diff --git a/tests/source/issue_6831_style_edition_2021.rs b/tests/source/issue_6831_style_edition_2021.rs new file mode 100644 index 00000000000..0778433bf1c --- /dev/null +++ b/tests/source/issue_6831_style_edition_2021.rs @@ -0,0 +1,46 @@ +// rustfmt-style_edition: 2021 +// rustfmt-max_width: 100 + +impl + IntoMessage< + capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + >, + > for T +{ + // Incorrectly places the return type on a single line + fn into_msg( + self, + ) -> capnp::message::TypedReader< + capnp::message::Builder, T::Capnp + > + { + todo!() + } +} + +impl Foo for T { + fn into_msg_return_type_single_line_99( + self, + ) -> some::long::path::to::GenericType, some::Type____> + { + todo!() + } + + fn into_msg_return_type_single_line_100( + self, + ) -> some::long::path::to::GenericType, some::Type_____> + { + todo!() + } + + // Incorrectly places the return type on a single line + fn into_msg_return_type_single_line_101( + self, + ) -> some::long::path::to::GenericType, some::Type______> + { + todo!() + } +} + diff --git a/tests/source/issue_6831_style_edition_2024.rs b/tests/source/issue_6831_style_edition_2024.rs new file mode 100644 index 00000000000..94e309a191b --- /dev/null +++ b/tests/source/issue_6831_style_edition_2024.rs @@ -0,0 +1,46 @@ +// rustfmt-style_edition: 2024 +// rustfmt-max_width: 100 + +impl + IntoMessage< + capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + >, + > for T +{ + // Incorrectly places the return type on a single line + fn into_msg( + self, + ) -> capnp::message::TypedReader< + capnp::message::Builder, T::Capnp + > + { + todo!() + } +} + +impl Foo for T { + fn into_msg_return_type_single_line_99( + self, + ) -> some::long::path::to::GenericType, some::Type____> + { + todo!() + } + + fn into_msg_return_type_single_line_100( + self, + ) -> some::long::path::to::GenericType, some::Type_____> + { + todo!() + } + + // Incorrectly places the return type on a single line + fn into_msg_return_type_single_line_101( + self, + ) -> some::long::path::to::GenericType, some::Type______> + { + todo!() + } +} + diff --git a/tests/source/issue_6831_style_edition_2027.rs b/tests/source/issue_6831_style_edition_2027.rs new file mode 100644 index 00000000000..20516d56d68 --- /dev/null +++ b/tests/source/issue_6831_style_edition_2027.rs @@ -0,0 +1,44 @@ +// rustfmt-style_edition: 2027 +// rustfmt-max_width: 100 + +impl + IntoMessage< + capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + >, + > for T +{ + fn into_msg( + self, + ) -> capnp::message::TypedReader< + capnp::message::Builder, T::Capnp + > + { + todo!() + } +} + +impl Foo for T { + fn into_msg_return_type_single_line_99( + self, + ) -> some::long::path::to::GenericType, some::Type____> + { + todo!() + } + + fn into_msg_return_type_single_line_100( + self, + ) -> some::long::path::to::GenericType, some::Type_____> + { + todo!() + } + + + fn into_msg_return_type_single_line_101( + self, + ) -> some::long::path::to::GenericType, some::Type______> + { + todo!() + } +} diff --git a/tests/source/reorder_modules/abcd/mod.rs b/tests/source/reorder_modules/abcde/mod.rs similarity index 100% rename from tests/source/reorder_modules/abcd/mod.rs rename to tests/source/reorder_modules/abcde/mod.rs diff --git a/tests/source/reorder_modules/disabled_style_edition_2024.rs b/tests/source/reorder_modules/disabled_style_edition_2024.rs index d97f9a6da74..0c59d4739fc 100644 --- a/tests/source/reorder_modules/disabled_style_edition_2024.rs +++ b/tests/source/reorder_modules/disabled_style_edition_2024.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/source/reorder_modules/disabled_style_edition_2027.rs b/tests/source/reorder_modules/disabled_style_edition_2027.rs index f5f0cb6357f..4695b06c77b 100644 --- a/tests/source/reorder_modules/disabled_style_edition_2027.rs +++ b/tests/source/reorder_modules/disabled_style_edition_2027.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/source/reorder_modules/enabled_style_edition_2015.rs b/tests/source/reorder_modules/enabled_style_edition_2015.rs index 0243a1da849..f2ceee8d68e 100644 --- a/tests/source/reorder_modules/enabled_style_edition_2015.rs +++ b/tests/source/reorder_modules/enabled_style_edition_2015.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/source/reorder_modules/enabled_style_edition_2024.rs b/tests/source/reorder_modules/enabled_style_edition_2024.rs index 6a9a5c8d607..bfb6c157bd2 100644 --- a/tests/source/reorder_modules/enabled_style_edition_2024.rs +++ b/tests/source/reorder_modules/enabled_style_edition_2024.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/source/reorder_modules/enabled_style_edition_2027.rs b/tests/source/reorder_modules/enabled_style_edition_2027.rs index 46f6abe9312..6a81e9af742 100644 --- a/tests/source/reorder_modules/enabled_style_edition_2027.rs +++ b/tests/source/reorder_modules/enabled_style_edition_2027.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/source/reorder_modules/zyxw/mod.rs b/tests/source/reorder_modules/zyxwv/mod.rs similarity index 100% rename from tests/source/reorder_modules/zyxw/mod.rs rename to tests/source/reorder_modules/zyxwv/mod.rs diff --git a/tests/target/reorder_modules/abcd/mod.rs b/tests/source/reorder_modules_2027/abcde/mod.rs similarity index 100% rename from tests/target/reorder_modules/abcd/mod.rs rename to tests/source/reorder_modules_2027/abcde/mod.rs diff --git a/tests/target/reorder_modules/zyxw/mod.rs b/tests/source/reorder_modules_2027/zyxwv/mod.rs similarity index 100% rename from tests/target/reorder_modules/zyxw/mod.rs rename to tests/source/reorder_modules_2027/zyxwv/mod.rs diff --git a/tests/source/string_lit_unicode_ws.rs b/tests/source/string_lit_unicode_ws.rs new file mode 100644 index 00000000000..f944711e14f --- /dev/null +++ b/tests/source/string_lit_unicode_ws.rs @@ -0,0 +1,5 @@ +// Test Unicode whitespace characters in string literal line continuation +fn main() { + let str = "hello \ + world"; +} diff --git a/tests/source/super_let.rs b/tests/source/super_let.rs new file mode 100644 index 00000000000..e471a198260 --- /dev/null +++ b/tests/source/super_let.rs @@ -0,0 +1,7 @@ +#![feature(super_let)] +fn main() { + super let x =( + &1, + + ) else { 3}; +} diff --git a/tests/target/configs/doc_comment_code_block_small_heuristics/default-to-max.rs b/tests/target/configs/doc_comment_code_block_small_heuristics/default-to-max.rs new file mode 100644 index 00000000000..a4ec39c63c4 --- /dev/null +++ b/tests/target/configs/doc_comment_code_block_small_heuristics/default-to-max.rs @@ -0,0 +1,73 @@ +// rustfmt-format_code_in_doc_comments: true +// rustfmt-use_small_heuristics: Default +// rustfmt-doc_comment_code_block_small_heuristics: Max + +/// Start of a doc comment. +/// +/// ``` +/// enum Lorem { +/// Ipsum, +/// Dolor(bool), +/// Sit { amet: Consectetur, adipiscing: Elit }, +/// } +/// +/// fn main() { +/// lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); +/// +/// let lorem = Lorem { ipsum: dolor, sit: amet }; +/// +/// let lorem = if ipsum { dolor } else { sit }; +/// } +/// +/// fn format_let_else() { +/// let Some(a) = opt else {}; +/// +/// let Some(b) = opt else { return }; +/// +/// let Some(c) = opt else { return }; +/// +/// let Some(d) = some_very_very_very_very_long_name else { +/// return; +/// }; +/// } +/// ``` +/// +/// End of a doc comment. +struct S; + +enum Lorem { + Ipsum, + Dolor(bool), + Sit { amet: Consectetur, adipiscing: Elit }, +} + +fn main() { + lorem( + "lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipiscing", + ); + + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; + + let lorem = if ipsum { dolor } else { sit }; +} + +fn format_let_else() { + let Some(a) = opt else {}; + + let Some(b) = opt else { return }; + + let Some(c) = opt else { return }; + + let Some(d) = some_very_very_very_very_long_name else { + return; + }; +} diff --git a/tests/target/configs/doc_comment_code_block_small_heuristics/max-to-default.rs b/tests/target/configs/doc_comment_code_block_small_heuristics/max-to-default.rs new file mode 100644 index 00000000000..b18878ce8e8 --- /dev/null +++ b/tests/target/configs/doc_comment_code_block_small_heuristics/max-to-default.rs @@ -0,0 +1,71 @@ +// rustfmt-format_code_in_doc_comments: true +// rustfmt-use_small_heuristics: Max +// rustfmt-doc_comment_code_block_small_heuristics: Default + +/// Start of a doc comment. +/// +/// ``` +/// enum Lorem { +/// Ipsum, +/// Dolor(bool), +/// Sit { amet: Consectetur, adipiscing: Elit }, +/// } +/// +/// fn main() { +/// lorem( +/// "lorem", +/// "ipsum", +/// "dolor", +/// "sit", +/// "amet", +/// "consectetur", +/// "adipiscing", +/// ); +/// +/// let lorem = Lorem { +/// ipsum: dolor, +/// sit: amet, +/// }; +/// +/// let lorem = if ipsum { dolor } else { sit }; +/// } +/// +/// fn format_let_else() { +/// let Some(a) = opt else {}; +/// +/// let Some(b) = opt else { return }; +/// +/// let Some(c) = opt else { return }; +/// +/// let Some(d) = some_very_very_very_very_long_name else { +/// return; +/// }; +/// } +/// ``` +/// +/// End of a doc comment. +struct S; + +enum Lorem { + Ipsum, + Dolor(bool), + Sit { amet: Consectetur, adipiscing: Elit }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { ipsum: dolor, sit: amet }; + + let lorem = if ipsum { dolor } else { sit }; +} + +fn format_let_else() { + let Some(a) = opt else {}; + + let Some(b) = opt else { return }; + + let Some(c) = opt else { return }; + + let Some(d) = some_very_very_very_very_long_name else { return }; +} diff --git a/tests/target/configs/float_literal_trailing_zero/always.rs b/tests/target/configs/float_literal_trailing_zero/always.rs index e6d643ad43f..2d64ec87d50 100644 --- a/tests/target/configs/float_literal_trailing_zero/always.rs +++ b/tests/target/configs/float_literal_trailing_zero/always.rs @@ -1,4 +1,5 @@ // rustfmt-float_literal_trailing_zero: Always +// spaces_around_ranges: false is implied since it's the default fn float_literals() { let a = 0.0; @@ -47,3 +48,8 @@ fn line_wrapping() { 10.0e3 ); } + +fn pattern_in_function_parameters_exactly_max_width_before_zero___( + 2.0..10.0: std::ops::Range, +) { +} diff --git a/tests/target/configs/float_literal_trailing_zero/always_spaces_around_ranges_true.rs b/tests/target/configs/float_literal_trailing_zero/always_spaces_around_ranges_true.rs new file mode 100644 index 00000000000..087e4956b67 --- /dev/null +++ b/tests/target/configs/float_literal_trailing_zero/always_spaces_around_ranges_true.rs @@ -0,0 +1,55 @@ +// rustfmt-float_literal_trailing_zero: Always +// rustfmt-spaces_around_ranges: true + +fn float_literals() { + let a = 0.0; + let b = 0.0; + let c = 100.0; + let d = 100.0; + let e = 5.0e3; + let f = 5.0e3; + let g = 5.0e+3; + let h = 5.0e+3; + let i = 5.0e-3; + let j = 5.0e-3; + let k = 5.0E3; + let l = 5.0E3; + let m = 7.0f32; + let n = 7.0f32; + let o = 9.0e3f32; + let p = 9.0e3f32; + let q = 1000.00; + let r = 1_000_.0; + let s = 1_000_.000_000; +} + +fn range_bounds() { + if (1.0 .. 2.0).contains(&1.0) {} + if (1.1 .. 2.2).contains(&1.1) {} + if (1.0e1 .. 2.0e1).contains(&1.0e1) {} + let _binop_range = 3.0 / 2.0 .. 4.0; +} + +fn method_calls() { + let x = 1.0.neg(); + let y = 2.3.neg(); + let z = (4.0).neg(); + let u = 5.0f32.neg(); + let v = -6.0.neg(); +} + +fn line_wrapping() { + let array = [ + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 17.0, 18.0, + ]; + println!( + "This is floaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat {}", + 10.0e3 + ); +} + +fn pattern_in_function_parameters_exactly_max_width_before_zero___( + 2.0 .. 10.0: std::ops::Range, +) { +} diff --git a/tests/target/configs/float_literal_trailing_zero/if-no-postfix.rs b/tests/target/configs/float_literal_trailing_zero/if-no-postfix.rs index d81f5c07ccf..f4a36e9e345 100644 --- a/tests/target/configs/float_literal_trailing_zero/if-no-postfix.rs +++ b/tests/target/configs/float_literal_trailing_zero/if-no-postfix.rs @@ -1,4 +1,5 @@ // rustfmt-float_literal_trailing_zero: IfNoPostfix +// spaces_around_ranges: false is implied since it's the default fn float_literals() { let a = 0.0; @@ -44,3 +45,8 @@ fn line_wrapping() { ]; println!("This is floaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat {}", 10e3); } + +fn pattern_in_function_parameters_exactly_max_width_before_zero___( + 2.0..10.0: std::ops::Range, +) { +} diff --git a/tests/target/configs/float_literal_trailing_zero/if-no-postfix_spaces_around_ranges_true.rs b/tests/target/configs/float_literal_trailing_zero/if-no-postfix_spaces_around_ranges_true.rs new file mode 100644 index 00000000000..b073c34fb8e --- /dev/null +++ b/tests/target/configs/float_literal_trailing_zero/if-no-postfix_spaces_around_ranges_true.rs @@ -0,0 +1,52 @@ +// rustfmt-float_literal_trailing_zero: IfNoPostfix +// rustfmt-spaces_around_ranges: true + +fn float_literals() { + let a = 0.0; + let b = 0.0; + let c = 100.0; + let d = 100.0; + let e = 5e3; + let f = 5e3; + let g = 5e+3; + let h = 5e+3; + let i = 5e-3; + let j = 5e-3; + let k = 5E3; + let l = 5E3; + let m = 7f32; + let n = 7f32; + let o = 9e3f32; + let p = 9e3f32; + let q = 1000.00; + let r = 1_000_.0; + let s = 1_000_.000_000; +} + +fn range_bounds() { + if (1.0 .. 2.0).contains(&1.0) {} + if (1.1 .. 2.2).contains(&1.1) {} + if (1e1 .. 2e1).contains(&1e1) {} + let _binop_range = 3.0 / 2.0 .. 4.0; +} + +fn method_calls() { + let x = 1.0.neg(); + let y = 2.3.neg(); + let z = (4.0).neg(); + let u = 5f32.neg(); + let v = -6.0.neg(); +} + +fn line_wrapping() { + let array = [ + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 17.0, 18.0, + ]; + println!("This is floaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat {}", 10e3); +} + +fn pattern_in_function_parameters_exactly_max_width_before_zero___( + 2.0 .. 10.0: std::ops::Range, +) { +} diff --git a/tests/target/configs/float_literal_trailing_zero/never.rs b/tests/target/configs/float_literal_trailing_zero/never.rs index 6391890e535..a0640a615cb 100644 --- a/tests/target/configs/float_literal_trailing_zero/never.rs +++ b/tests/target/configs/float_literal_trailing_zero/never.rs @@ -1,4 +1,5 @@ // rustfmt-float_literal_trailing_zero: Never +// spaces_around_ranges: false is implied since it's the default fn float_literals() { let a = 0.; @@ -43,3 +44,6 @@ fn line_wrapping() { ]; println!("This is floaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat {}", 10e3); } + +fn pattern_in_function_parameters_exactly_max_width_before_zero___(2. ..10.: std::ops::Range) { +} diff --git a/tests/target/configs/float_literal_trailing_zero/never_spaces_around_ranges_true.rs b/tests/target/configs/float_literal_trailing_zero/never_spaces_around_ranges_true.rs new file mode 100644 index 00000000000..939e2069e9c --- /dev/null +++ b/tests/target/configs/float_literal_trailing_zero/never_spaces_around_ranges_true.rs @@ -0,0 +1,51 @@ +// rustfmt-float_literal_trailing_zero: Never +// rustfmt-spaces_around_ranges: true + +fn float_literals() { + let a = 0.; + let b = 0.; + let c = 100.; + let d = 100.; + let e = 5e3; + let f = 5e3; + let g = 5e+3; + let h = 5e+3; + let i = 5e-3; + let j = 5e-3; + let k = 5E3; + let l = 5E3; + let m = 7f32; + let n = 7f32; + let o = 9e3f32; + let p = 9e3f32; + let q = 1000.; + let r = 1_000_.; + let s = 1_000_.; +} + +fn range_bounds() { + if (1. .. 2.).contains(&1.) {} + if (1.1 .. 2.2).contains(&1.1) {} + if (1e1 .. 2e1).contains(&1e1) {} + let _binop_range = 3. / 2. .. 4.; +} + +fn method_calls() { + let x = (1.).neg(); + let y = 2.3.neg(); + let z = (4.).neg(); + let u = 5f32.neg(); + let v = -(6.).neg(); +} + +fn line_wrapping() { + let array = [ + 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., + ]; + println!("This is floaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat {}", 10e3); +} + +fn pattern_in_function_parameters_exactly_max_width_before_zero___( + 2. .. 10.: std::ops::Range, +) { +} diff --git a/tests/target/configs/float_literal_trailing_zero/preserve_spaces_around_ranges_true.rs b/tests/target/configs/float_literal_trailing_zero/preserve_spaces_around_ranges_true.rs new file mode 100644 index 00000000000..36371bddb2e --- /dev/null +++ b/tests/target/configs/float_literal_trailing_zero/preserve_spaces_around_ranges_true.rs @@ -0,0 +1,44 @@ +// rustfmt-float_literal_trailing_zero: Preserve +// rustfmt-spaces_around_ranges: true + +fn float_literals() { + let a = 0.; + let b = 0.0; + let c = 100.; + let d = 100.0; + let e = 5e3; + let f = 5.0e3; + let g = 5e+3; + let h = 5.0e+3; + let i = 5e-3; + let j = 5.0e-3; + let k = 5E3; + let l = 5.0E3; + let m = 7f32; + let n = 7.0f32; + let o = 9e3f32; + let p = 9.0e3f32; + let q = 1000.00; + let r = 1_000_.; + let s = 1_000_.000_000; +} + +fn range_bounds() { + if (1. .. 2.0).contains(&1.0) {} + if (1.1 .. 2.2).contains(&1.1) {} + if (1.0e1 .. 2.0e1).contains(&1.0e1) {} + let _binop_range = 3.0 / 2.0 .. 4.0; +} + +fn method_calls() { + let x = 1.0.neg(); + let y = 2.3.neg(); + let z = (4.).neg(); + let u = 5.0f32.neg(); + let v = -6.0.neg(); +} + +fn pattern_in_function_parameters_exactly_max_width_before_zero___( + 2. .. 10.0: std::ops::Range, +) { +} diff --git a/tests/target/configs/hex_literal_case/hex_literal_lower.rs b/tests/target/configs/hex_literal_case/hex_literal_lower.rs new file mode 100644 index 00000000000..d1d284c6df0 --- /dev/null +++ b/tests/target/configs/hex_literal_case/hex_literal_lower.rs @@ -0,0 +1,9 @@ +// rustfmt-hex_literal_case: Lower +fn main() { + let h1 = 0xcafe_5ea7; + let h2 = 0xcafe_f00du32; + let h3 = -0xcafe_5ea7; + let h4 = -0xcafe_f00di32; + let h5 = 0xabcd07_i32; + let h6 = -0xabcd07_i32; +} diff --git a/tests/target/configs/hex_literal_case/hex_literal_preserve.rs b/tests/target/configs/hex_literal_case/hex_literal_preserve.rs new file mode 100644 index 00000000000..876592a4f64 --- /dev/null +++ b/tests/target/configs/hex_literal_case/hex_literal_preserve.rs @@ -0,0 +1,9 @@ +// rustfmt-hex_literal_case: Preserve +fn main() { + let h1 = 0xcAfE_5Ea7; + let h2 = 0xCaFe_F00du32; + let h3 = -0xcAfE_5Ea7; + let h4 = -0xCaFe_F00di32; + let h5 = 0xAbcd07_i32; + let h6 = -0xAbcd07_i32; +} diff --git a/tests/target/configs/hex_literal_case/hex_literal_upper.rs b/tests/target/configs/hex_literal_case/hex_literal_upper.rs new file mode 100644 index 00000000000..4336453abae --- /dev/null +++ b/tests/target/configs/hex_literal_case/hex_literal_upper.rs @@ -0,0 +1,9 @@ +// rustfmt-hex_literal_case: Upper +fn main() { + let h1 = 0xCAFE_5EA7; + let h2 = 0xCAFE_F00Du32; + let h3 = -0xCAFE_5EA7; + let h4 = -0xCAFE_F00Di32; + let h5 = 0xABCD07_i32; + let h6 = -0xABCD07_i32; +} diff --git a/tests/target/configs/spaces_around_ranges/false.rs b/tests/target/configs/spaces_around_ranges/false.rs index 72b1be4804c..9b4ae1e7a8d 100644 --- a/tests/target/configs/spaces_around_ranges/false.rs +++ b/tests/target/configs/spaces_around_ranges/false.rs @@ -7,16 +7,19 @@ fn main() { match lorem { 1..5 => foo(), + 1. ..5. => (), _ => bar, } match lorem { 1..=5 => foo(), + 1. ..=5. => (), _ => bar, } match lorem { 1...5 => foo(), + 1. ...0.5 => foo(), _ => bar, } } @@ -25,10 +28,17 @@ fn half_open() { match [5..4, 99..105, 43..44] { [_, 99.., _] => {} [_, ..105, _] => {} + [_, 1. .., _] => {} _ => {} }; if let ..=5 = 0 {} if let ..5 = 0 {} + // For now `.. .5` fails parsing with `float literals must have an integer part` + if let ..0.5 = 0 {} if let 5.. = 0 {} + if let 5. .. = 0 {} +} + +fn pattern_in_function_parameters_exactly_max_width_before_space__(2. ..10.: std::ops::Range) { } diff --git a/tests/target/configs/spaces_around_ranges/true.rs b/tests/target/configs/spaces_around_ranges/true.rs index c56fdbb02b6..d450e7b87ee 100644 --- a/tests/target/configs/spaces_around_ranges/true.rs +++ b/tests/target/configs/spaces_around_ranges/true.rs @@ -7,16 +7,19 @@ fn main() { match lorem { 1 .. 5 => foo(), + 1. .. 5. => (), _ => bar, } match lorem { 1 ..= 5 => foo(), + 1. ..= 5. => (), _ => bar, } match lorem { 1 ... 5 => foo(), + 1. ... 0.5 => foo(), _ => bar, } } @@ -25,10 +28,19 @@ fn half_open() { match [5 .. 4, 99 .. 105, 43 .. 44] { [_, 99 .., _] => {} [_, .. 105, _] => {} + [_, 1. .., _] => {} _ => {} }; if let ..= 5 = 0 {} if let .. 5 = 0 {} + // For now `.. .5` fails parsing with `float literals must have an integer part` + if let .. 0.5 = 0 {} if let 5 .. = 0 {} + if let 5. .. = 0 {} +} + +fn pattern_in_function_parameters_exactly_max_width_before_space__( + 2. .. 10.: std::ops::Range, +) { } diff --git a/tests/target/hex_literal_lower.rs b/tests/target/hex_literal_lower.rs deleted file mode 100644 index 5c27fded167..00000000000 --- a/tests/target/hex_literal_lower.rs +++ /dev/null @@ -1,5 +0,0 @@ -// rustfmt-hex_literal_case: Lower -fn main() { - let h1 = 0xcafe_5ea7; - let h2 = 0xcafe_f00du32; -} diff --git a/tests/target/hex_literal_preserve.rs b/tests/target/hex_literal_preserve.rs deleted file mode 100644 index e8774d0bb24..00000000000 --- a/tests/target/hex_literal_preserve.rs +++ /dev/null @@ -1,5 +0,0 @@ -// rustfmt-hex_literal_case: Preserve -fn main() { - let h1 = 0xcAfE_5Ea7; - let h2 = 0xCaFe_F00du32; -} diff --git a/tests/target/hex_literal_upper.rs b/tests/target/hex_literal_upper.rs deleted file mode 100644 index 48bb93d2c1c..00000000000 --- a/tests/target/hex_literal_upper.rs +++ /dev/null @@ -1,5 +0,0 @@ -// rustfmt-hex_literal_case: Upper -fn main() { - let h1 = 0xCAFE_5EA7; - let h2 = 0xCAFE_F00Du32; -} diff --git a/tests/target/issue-5136-1.rs b/tests/target/issue-5136-1.rs new file mode 100644 index 00000000000..75231555232 --- /dev/null +++ b/tests/target/issue-5136-1.rs @@ -0,0 +1,7 @@ + + +// Test that newlines at the top of this file are preserved when they're not +// in the --file-lines range. + +// This should prevent rustfmt from many any formatting changes at all: +// rustfmt-file_lines: [] diff --git a/tests/target/issue-5136-2.rs b/tests/target/issue-5136-2.rs new file mode 100644 index 00000000000..fe3cfcf37b8 --- /dev/null +++ b/tests/target/issue-5136-2.rs @@ -0,0 +1,5 @@ + use std; +// Test that whitespace at beginning of file is preserved when not in +// --file-lines range. +// This should prevent rustfmt from making any formatting changes at all: +// rustfmt-file_lines: [] diff --git a/tests/target/issue-5136-3.rs b/tests/target/issue-5136-3.rs new file mode 100644 index 00000000000..c152181bc2f --- /dev/null +++ b/tests/target/issue-5136-3.rs @@ -0,0 +1,6 @@ +// rustfmt-file_lines: [] +// Test that a missing newline at the end of the file is preserved when the last +// line is not in the --file-lines range. +// Important: When editing this file, make sure not to add a newline at the end +// of the last line. +use std; \ No newline at end of file diff --git a/tests/target/issue-5136-4.rs b/tests/target/issue-5136-4.rs new file mode 100644 index 00000000000..005167dff63 --- /dev/null +++ b/tests/target/issue-5136-4.rs @@ -0,0 +1,4 @@ +// rustfmt-file_lines: [] +// Test that a missing space at the end of a doc comment is preserved when the +// line is not in the --file-lines range. +//! diff --git a/tests/target/issue-5136-5.rs b/tests/target/issue-5136-5.rs new file mode 100644 index 00000000000..0f836aa989f --- /dev/null +++ b/tests/target/issue-5136-5.rs @@ -0,0 +1,6 @@ +// rustfmt-file_lines: [] +// Test that the space before the comment is not removed if the line is not +// contained in `--file-lines`. +// Note: It's important for the bug to repro that there is no newline at the +// end of the comment +fn f(){} // what \ No newline at end of file diff --git a/tests/target/issue-6825.rs b/tests/target/issue-6825.rs new file mode 100644 index 00000000000..6c9ac60dddb --- /dev/null +++ b/tests/target/issue-6825.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2024 +// rustfmt-style_edition: 2024 +pub async fn foo(// OriginalUri(original_uri): OriginalUri, +) -> Option>>> { + None +} diff --git a/tests/target/issue-6863/empty-stmt.rs b/tests/target/issue-6863/empty-stmt.rs new file mode 100644 index 00000000000..b67340f5bc0 --- /dev/null +++ b/tests/target/issue-6863/empty-stmt.rs @@ -0,0 +1,7 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-6863/empty-stmt.rs","range":[5,5]}] + +fn main() { +; + println!("b"); +; +} diff --git a/tests/target/issue-6863/fn-stmts.rs b/tests/target/issue-6863/fn-stmts.rs new file mode 100644 index 00000000000..82ed6dc37c8 --- /dev/null +++ b/tests/target/issue-6863/fn-stmts.rs @@ -0,0 +1,7 @@ +// rustfmt-file_lines: [{"file":"tests/source/issue-6863/fn-stmts.rs","range":[5,5]}] + +fn main() { +println!("a"); + println!("b"); +println!("c"); +} diff --git a/tests/target/issue_6831_style_edition_2021.rs b/tests/target/issue_6831_style_edition_2021.rs new file mode 100644 index 00000000000..ce248f83572 --- /dev/null +++ b/tests/target/issue_6831_style_edition_2021.rs @@ -0,0 +1,43 @@ +// rustfmt-style_edition: 2021 +// rustfmt-max_width: 100 + +impl + IntoMessage< + capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + >, + > for T +{ + // Incorrectly places the return type on a single line + fn into_msg( + self, + ) -> capnp::message::TypedReader, T::Capnp> + { + todo!() + } +} + +impl Foo for T { + fn into_msg_return_type_single_line_99( + self, + ) -> some::long::path::to::GenericType, some::Type____> + { + todo!() + } + + fn into_msg_return_type_single_line_100( + self, + ) -> some::long::path::to::GenericType, some::Type_____> + { + todo!() + } + + // Incorrectly places the return type on a single line + fn into_msg_return_type_single_line_101( + self, + ) -> some::long::path::to::GenericType, some::Type______> + { + todo!() + } +} diff --git a/tests/target/issue_6831_style_edition_2024.rs b/tests/target/issue_6831_style_edition_2024.rs new file mode 100644 index 00000000000..5fdb66edc89 --- /dev/null +++ b/tests/target/issue_6831_style_edition_2024.rs @@ -0,0 +1,43 @@ +// rustfmt-style_edition: 2024 +// rustfmt-max_width: 100 + +impl + IntoMessage< + capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + >, + > for T +{ + // Incorrectly places the return type on a single line + fn into_msg( + self, + ) -> capnp::message::TypedReader, T::Capnp> + { + todo!() + } +} + +impl Foo for T { + fn into_msg_return_type_single_line_99( + self, + ) -> some::long::path::to::GenericType, some::Type____> + { + todo!() + } + + fn into_msg_return_type_single_line_100( + self, + ) -> some::long::path::to::GenericType, some::Type_____> + { + todo!() + } + + // Incorrectly places the return type on a single line + fn into_msg_return_type_single_line_101( + self, + ) -> some::long::path::to::GenericType, some::Type______> + { + todo!() + } +} diff --git a/tests/target/issue_6831_style_edition_2027.rs b/tests/target/issue_6831_style_edition_2027.rs new file mode 100644 index 00000000000..e0afea04b37 --- /dev/null +++ b/tests/target/issue_6831_style_edition_2027.rs @@ -0,0 +1,45 @@ +// rustfmt-style_edition: 2027 +// rustfmt-max_width: 100 + +impl + IntoMessage< + capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + >, + > for T +{ + fn into_msg( + self, + ) -> capnp::message::TypedReader< + capnp::message::Builder, + T::Capnp, + > { + todo!() + } +} + +impl Foo for T { + fn into_msg_return_type_single_line_99( + self, + ) -> some::long::path::to::GenericType, some::Type____> + { + todo!() + } + + fn into_msg_return_type_single_line_100( + self, + ) -> some::long::path::to::GenericType, some::Type_____> + { + todo!() + } + + fn into_msg_return_type_single_line_101( + self, + ) -> some::long::path::to::GenericType< + long::path::to::GenericType, + some::Type______, + > { + todo!() + } +} diff --git a/tests/target/issue_6869.rs b/tests/target/issue_6869.rs new file mode 100644 index 00000000000..1f4b1df7fbd --- /dev/null +++ b/tests/target/issue_6869.rs @@ -0,0 +1,8 @@ +fn main() { + let x = 0.5; + + match x { + 1. .. => println!("{x} >= 1"), + _ => println!("{x} < 1"), + } +} diff --git a/tests/target/keywords.rs b/tests/target/keywords.rs new file mode 100644 index 00000000000..eeac0f48d5a --- /dev/null +++ b/tests/target/keywords.rs @@ -0,0 +1,26 @@ +pub // a +macro // b +hi( + // c +) { + // d +} + +macro_rules! // a +my_macro { + () => {}; +} + +// == comments don't get reformatted == +macro_rules!// a + // b + // c + // d +my_macro { + () => {}; +} + +macro_rules! /* a block comment */ +my_macro { + () => {}; +} diff --git a/tests/target/reorder_modules/abcde/mod.rs b/tests/target/reorder_modules/abcde/mod.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/target/reorder_modules/abcde/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/target/reorder_modules/disabled_style_edition_2024.rs b/tests/target/reorder_modules/disabled_style_edition_2024.rs index d97f9a6da74..0c59d4739fc 100644 --- a/tests/target/reorder_modules/disabled_style_edition_2024.rs +++ b/tests/target/reorder_modules/disabled_style_edition_2024.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/target/reorder_modules/disabled_style_edition_2027.rs b/tests/target/reorder_modules/disabled_style_edition_2027.rs index f5f0cb6357f..4695b06c77b 100644 --- a/tests/target/reorder_modules/disabled_style_edition_2027.rs +++ b/tests/target/reorder_modules/disabled_style_edition_2027.rs @@ -5,7 +5,7 @@ mod x86; mod v0s; mod v001; mod x87; -mod zyxw; +mod zyxwv; mod A2; mod ZYXW; mod w5s009t; @@ -36,7 +36,7 @@ mod _abcd; mod ABCD; mod Z_YXW; mod u64; -mod abcd; +mod abcde; mod ZYXW_; mod u16; mod uz; diff --git a/tests/target/reorder_modules/enabled_style_edition_2015.rs b/tests/target/reorder_modules/enabled_style_edition_2015.rs index b3831df6d86..a61f576eccf 100644 --- a/tests/target/reorder_modules/enabled_style_edition_2015.rs +++ b/tests/target/reorder_modules/enabled_style_edition_2015.rs @@ -11,7 +11,7 @@ mod Z_YXW; mod _ZYXW; mod _abcd; mod a1; -mod abcd; +mod abcde; mod u128; mod u16; mod u256; @@ -44,4 +44,4 @@ mod x86_128; mod x86_32; mod x86_64; mod x87; -mod zyxw; +mod zyxwv; diff --git a/tests/target/reorder_modules/enabled_style_edition_2024.rs b/tests/target/reorder_modules/enabled_style_edition_2024.rs index addc555aa0e..852e40f213d 100644 --- a/tests/target/reorder_modules/enabled_style_edition_2024.rs +++ b/tests/target/reorder_modules/enabled_style_edition_2024.rs @@ -11,7 +11,7 @@ mod Z_YXW; mod _ZYXW; mod _abcd; mod a1; -mod abcd; +mod abcde; mod u128; mod u16; mod u256; @@ -44,4 +44,4 @@ mod x86_128; mod x86_32; mod x86_64; mod x87; -mod zyxw; +mod zyxwv; diff --git a/tests/target/reorder_modules/enabled_style_edition_2027.rs b/tests/target/reorder_modules/enabled_style_edition_2027.rs index 44acabd75f5..bcca31f4023 100644 --- a/tests/target/reorder_modules/enabled_style_edition_2027.rs +++ b/tests/target/reorder_modules/enabled_style_edition_2027.rs @@ -11,7 +11,7 @@ mod ZY_XW; mod ZYXW; mod ZYXW_; mod a1; -mod abcd; +mod abcde; mod u_zzz; mod u8; mod u16; @@ -44,4 +44,4 @@ mod x86_32; mod x86_64; mod x86_128; mod x87; -mod zyxw; +mod zyxwv; diff --git a/tests/target/reorder_modules/zyxwv/mod.rs b/tests/target/reorder_modules/zyxwv/mod.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/target/reorder_modules/zyxwv/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/target/reorder_modules_2027/abcde/mod.rs b/tests/target/reorder_modules_2027/abcde/mod.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/target/reorder_modules_2027/abcde/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/target/reorder_modules_2027/zyxwv/mod.rs b/tests/target/reorder_modules_2027/zyxwv/mod.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/target/reorder_modules_2027/zyxwv/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/target/string_lit_unicode_ws.rs b/tests/target/string_lit_unicode_ws.rs new file mode 100644 index 00000000000..f944711e14f --- /dev/null +++ b/tests/target/string_lit_unicode_ws.rs @@ -0,0 +1,5 @@ +// Test Unicode whitespace characters in string literal line continuation +fn main() { + let str = "hello \ + world"; +} diff --git a/tests/target/super_let.rs b/tests/target/super_let.rs new file mode 100644 index 00000000000..d049df855f8 --- /dev/null +++ b/tests/target/super_let.rs @@ -0,0 +1,4 @@ +#![feature(super_let)] +fn main() { + super let x = (&1,) else { 3 }; +} diff --git a/triagebot.toml b/triagebot.toml index 8bb264576a8..3463476c285 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -7,9 +7,17 @@ [relabel] allow-unauthenticated = [ - "needs-triage", - "S-*", + "A-*", + "B-*", "C-*", + "E-*", + "F-*", + "I-*", + "S-*", + "SO-*", + "UO-*", + "needs-triage", + "regression-*", ] # ------------------------------------------------------------------------------ @@ -27,6 +35,19 @@ exclude_labels = [ [autolabel."release-notes"] pr_merged = true +# Prioritization of regression triaging. +[autolabel."I-prioritize"] +trigger_labels = [ + "regression-from-stable-to-beta", + "regression-from-stable-to-nightly", + "regression-from-stable-to-stable", + "regression-untriaged", +] +exclude_labels = [ + "P-*", + "requires-nightly", +] + [autolabel."A-CI"] trigger_files = [ ".github/workflows", From a8307d5477eb36fa0349f55643e4242829d08cc2 Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:22:45 +0200 Subject: [PATCH 03/17] unify the AST repr of type const and const RHS --- src/items.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/items.rs b/src/items.rs index 6268af93d57..b6abb097e33 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2008,7 +2008,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" @@ -2017,7 +2017,7 @@ impl<'a> StaticParts<'a> { c.ident, &c.ty, ast::Mutability::Not, - c.rhs_kind.expr(), + c.body.as_deref(), Some(&c.generics), ), _ => unreachable!(), @@ -2039,7 +2039,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" @@ -2047,7 +2047,7 @@ impl<'a> StaticParts<'a> { ( c.defaultness, &c.ty, - c.rhs_kind.expr(), + c.body.as_deref(), Some(&c.generics), prefix, ) @@ -2071,7 +2071,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" @@ -2079,7 +2079,7 @@ impl<'a> StaticParts<'a> { ( c.defaultness, &c.ty, - c.rhs_kind.expr(), + c.body.as_deref(), Some(&c.generics), prefix, ) From 0c393d1724b8f60424233d474f2ba4dd95075cb3 Mon Sep 17 00:00:00 2001 From: panstromek Date: Tue, 16 Jun 2026 20:21:19 +0200 Subject: [PATCH 04/17] Make FieldDef smaller --- src/items.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/items.rs b/src/items.rs index b6abb097e33..407c4aa30ee 100644 --- a/src/items.rs +++ b/src/items.rs @@ -1885,8 +1885,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!( @@ -1915,7 +1915,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); } From 9f6060ff9eb87c451344c132f9167a03acd2e04d Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Wed, 22 Jul 2026 14:58:16 -0400 Subject: [PATCH 05/17] rustfmt fix: ignore file not found errors for external mods with custom outer attributes 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. Fixes rustfmt issue 6959 --- src/lib.rs | 1 + src/modules.rs | 12 +++++++++++- src/utils.rs | 8 ++++++++ tests/target/issue_6959.rs | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/target/issue_6959.rs diff --git a/src/lib.rs b/src/lib.rs index 5f49bbf0c7e..65c83a612bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,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/modules.rs b/src/modules.rs index 099a6442821..baa1a86ad25 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; @@ -472,6 +472,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 }, diff --git a/src/utils.rs b/src/utils.rs index 15a4fce9348..131455fc0ff 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; @@ -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/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; From 9b49f7195aed3d609ca306c84d8c7241ea5226d4 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 21 Mar 2026 23:42:38 -0400 Subject: [PATCH 06/17] feat: parse `cfg_select!` within rustfmt `cfg_select!` parsing needs to be implemented in rustfmt right now because there's no good way to call `rustc_attr_parsing::parse_cfg_select`. --- src/modules/visitor.rs | 4 +- src/parse/macros/cfg_select.rs | 112 +++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/modules/visitor.rs b/src/modules/visitor.rs index 485f44a936b..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_select::parse_cfg_select; +use crate::parse::macros::cfg_select::parse_items_from_cfg_select; use crate::parse::session::ParseSess; pub(crate) struct ModItem { @@ -123,7 +123,7 @@ impl<'a, 'ast: 'a> CfgSelectVisitor<'a> { } }; - let items = parse_cfg_select(self.psess, mac)?; + let items = parse_items_from_cfg_select(self.psess, mac)?; self.mods .append(&mut items.into_iter().map(|item| ModItem { item }).collect()); diff --git a/src/parse/macros/cfg_select.rs b/src/parse/macros/cfg_select.rs index 040447ff189..d6337b71775 100644 --- a/src/parse/macros/cfg_select.rs +++ b/src/parse/macros/cfg_select.rs @@ -1,25 +1,32 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use rustc_ast::ast; -use rustc_ast::token::TokenKind; +use rustc_ast::token; +use rustc_ast::token::{Token, TokenKind}; +use rustc_ast::tokenstream::TokenStream; use rustc_parse::exp; use rustc_parse::parser::{AllowConstBlockItems, ForceCollect}; +use rustc_span::Span; +use tracing::debug; use crate::parse::macros::build_stream_parser; use crate::parse::session::ParseSess; +use crate::spanned::Spanned; -pub(crate) fn parse_cfg_select<'a>( +pub(crate) fn parse_items_from_cfg_select<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { - match catch_unwind(AssertUnwindSafe(|| parse_cfg_select_inner(psess, mac))) { + match catch_unwind(AssertUnwindSafe(|| { + parse_items_from_cfg_select_inner(psess, mac) + })) { Ok(Ok(items)) => Ok(items), Ok(err @ Err(_)) => err, Err(..) => Err("failed to parse cfg_select!"), } } -fn parse_cfg_select_inner<'a>( +fn parse_items_from_cfg_select_inner<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { @@ -78,3 +85,100 @@ fn parse_cfg_select_inner<'a>( Ok(items) } + +pub(crate) enum CfgSelectFormatPredicate { + Cfg(ast::MetaItemInner), + 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, + } + } +} + +pub(crate) struct CfgSelectArm { + pub(crate) predicate: CfgSelectFormatPredicate, + pub(crate) arrow: Token, + pub(crate) expr: Box, + 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(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(_) = parser.expect(exp!(FatArrow)) { + 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) +} From f6deac594856c9e670e28f53e675c4deef513964 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 21 Mar 2026 23:46:47 -0400 Subject: [PATCH 07/17] chore: make `rewrite_match_body` `pub(crate)` within rustfmt The plan is to leverage `rewrite_match_body` to help with `cfg_select!` formatting. --- src/matches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From b764bd3fb1a7ca437046a2fbf67137b3d72d1169 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sun, 22 Mar 2026 00:15:52 -0400 Subject: [PATCH 08/17] feat: implement `cfg_select!` formatting in rustfmt --- src/items.rs | 2 +- src/macros.rs | 126 ++++- tests/source/cfg_select.rs | 962 ++++++++++++++++++++++++++++++++ tests/target/cfg_select.rs | 1061 ++++++++++++++++++++++++++++++++++++ 4 files changed, 2149 insertions(+), 2 deletions(-) create mode 100644 tests/source/cfg_select.rs create mode 100644 tests/target/cfg_select.rs diff --git a/src/items.rs b/src/items.rs index 407c4aa30ee..5619d948fde 100644 --- a/src/items.rs +++ b/src/items.rs @@ -1525,7 +1525,7 @@ fn get_bytepos_after_visibility(vis: &ast::Visibility, default_span: Span) -> By // Format tuple or struct without any fields. We need to make sure that the comments // inside the delimiters are preserved. -fn format_empty_struct_or_tuple( +pub(crate) fn format_empty_struct_or_tuple( context: &RewriteContext<'_>, span: Span, offset: Indent, diff --git a/src/macros.rs b/src/macros.rs index 2a824b4ce30..cfbbe383af8 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::{ @@ -28,6 +29,7 @@ use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::header::{HeaderPart, format_header}; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; +use crate::parse::macros::cfg_select::{CfgSelectFormatPredicate, parse_cfg_select}; use crate::parse::macros::lazy_static::parse_lazy_static; use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args}; use crate::rewrite::{ @@ -245,6 +247,20 @@ fn rewrite_macro_inner( } } + if macro_name.ends_with("cfg_select!") { + match format_cfg_select(¯o_name, style, 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, @@ -1530,3 +1546,111 @@ fn rewrite_macro_with_items( result.push_str(trailing_semicolon); Ok(result) } + +fn format_cfg_select( + name: &str, + delim_token: Delimiter, + context: &RewriteContext<'_>, + shape: Shape, + ts: TokenStream, + span: Span, +) -> 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(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(); + + 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/tests/source/cfg_select.rs b/tests/source/cfg_select.rs new file mode 100644 index 00000000000..7f1e945ba0e --- /dev/null +++ b/tests/source/cfg_select.rs @@ -0,0 +1,962 @@ +// 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 + */ +} + + +// 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") + } +} + + +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; + } +} diff --git a/tests/target/cfg_select.rs b/tests/target/cfg_select.rs new file mode 100644 index 00000000000..ee12530e26b --- /dev/null +++ b/tests/target/cfg_select.rs @@ -0,0 +1,1061 @@ +// 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 + */ +} + +// 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") + } +} + +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; + } +} From 7005bdf4ae312d29b9cac721e966beba2615c702 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sun, 22 Mar 2026 14:52:09 -0400 Subject: [PATCH 09/17] test: Add more `cfg_select!` test cases based on the PR review feedback --- tests/source/cfg_select.rs | 141 +++++++++++++++++++++++++++++++++++ tests/target/cfg_select.rs | 149 +++++++++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+) diff --git a/tests/source/cfg_select.rs b/tests/source/cfg_select.rs index 7f1e945ba0e..1a520b1e129 100644 --- a/tests/source/cfg_select.rs +++ b/tests/source/cfg_select.rs @@ -38,6 +38,11 @@ core::cfg_select! { /* inline comment */ core::cfg_select! { /* inline comment * multi-line */ +} +cfg_select! { // followed by multiple whitespace lines in source + + + } @@ -51,6 +56,11 @@ core::cfg_select! ( /* inline comment */ core::cfg_select!( /* inline comment * multi-line */ +); +cfg_select! ( // followed by multiple whitespace lines in source + + + ); @@ -65,6 +75,12 @@ core::cfg_select![ /* inline comment * multi-line */ ]; +cfg_select! [ // followed by multiple whitespace lines in source + + + +]; + // Original `{}` delimiters @@ -960,3 +976,128 @@ cfg_select! { 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, + } + } + ] +} diff --git a/tests/target/cfg_select.rs b/tests/target/cfg_select.rs index ee12530e26b..149aa4f62f0 100644 --- a/tests/target/cfg_select.rs +++ b/tests/target/cfg_select.rs @@ -27,6 +27,9 @@ core::cfg_select! { * multi-line */ } +cfg_select! { + // followed by multiple whitespace lines in source +} // Original `()` delimiters cfg_select!(/* inline comment */); @@ -37,6 +40,9 @@ core::cfg_select!( * multi-line */ ); +cfg_select!( + // followed by multiple whitespace lines in source +); // Original `[]` delimiters cfg_select![/* inline comment */]; @@ -47,6 +53,9 @@ core::cfg_select![ * multi-line */ ]; +cfg_select![ + // followed by multiple whitespace lines in source +]; // Original `{}` delimiters cfg_select! { @@ -1059,3 +1068,143 @@ cfg_select! { 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, + } + } + ], +} From a750a23c3663c6af95d35af60c25c986f74b2879 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 00:13:47 -0400 Subject: [PATCH 10/17] docs: Add doc comments based on PR feedback --- src/parse/macros/cfg_select.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/parse/macros/cfg_select.rs b/src/parse/macros/cfg_select.rs index d6337b71775..8d60402e060 100644 --- a/src/parse/macros/cfg_select.rs +++ b/src/parse/macros/cfg_select.rs @@ -1,3 +1,7 @@ +//! 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; @@ -86,8 +90,11 @@ fn parse_items_from_cfg_select_inner<'a>( Ok(items) } +/// LHS predicate of a `cfg_select!` arm. pub(crate) enum CfgSelectFormatPredicate { + /// Example: the `unix` in `unix => {}`. Notably, outer or inner attributes are not permitted. Cfg(ast::MetaItemInner), + /// `_` in `_ => {}`. Wildcard(Span), } @@ -100,10 +107,16 @@ impl Spanned for CfgSelectFormatPredicate { } } +/// 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, } From 869e53ca6f82b91ecad22b95fc0f7245ee0e0937 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 00:24:55 -0400 Subject: [PATCH 11/17] feat: flatten `cfg_select!` arms if they're a single expression --- src/macros.rs | 9 +- tests/target/cfg_select.rs | 244 ++++++++++--------------------------- 2 files changed, 73 insertions(+), 180 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index cfbbe383af8..2553ee19bbc 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1598,6 +1598,13 @@ fn format_cfg_select( 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. + let rewrite_context = context.clone(); + rewrite_context.leave_macro(); + let items = itemize_list( context.snippet_provider, arms.iter(), @@ -1614,7 +1621,7 @@ fn format_cfg_select( }; crate::matches::rewrite_match_body( - context, + &rewrite_context, &arm.expr, &predicate_str, nested_shape, diff --git a/tests/target/cfg_select.rs b/tests/target/cfg_select.rs index 149aa4f62f0..eca6cca2d93 100644 --- a/tests/target/cfg_select.rs +++ b/tests/target/cfg_select.rs @@ -126,76 +126,40 @@ core::cfg_select![ fn expression_position() { // cfg_select arms with block println!(cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(std::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(core::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + 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" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(std::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(core::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); // cfg_select arms without block @@ -219,32 +183,20 @@ fn expression_position() { // cfg_select arms with and without blocks println!(cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } + unix => "unix", + windows => "windows", _ => "not windows or unix", }); println!(std::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } + unix => "unix", + windows => "windows", _ => "not windows or unix", }); println!(core::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } + unix => "unix", + windows => "windows", _ => "not windows or unix", }); } @@ -307,15 +259,9 @@ core::cfg_select! { // 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 + windows => "windows", + unix => "unix", + _ => "none", // FIXME. Prevent wrapping back up to the next line } cfg_select! { @@ -592,76 +538,40 @@ mod nested { fn expression_position() { // cfg_select arms with block println!(cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(std::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(core::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + 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" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(std::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); println!(core::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } - _ => { - "not " + "windows" + "or" + "unix" - } + unix => "unix", + windows => "windows", + _ => "not " + "windows" + "or" + "unix", }); // cfg_select arms without block @@ -685,32 +595,20 @@ mod nested { // cfg_select arms with and without blocks println!(cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } + unix => "unix", + windows => "windows", _ => "not windows or unix", }); println!(std::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } + unix => "unix", + windows => "windows", _ => "not windows or unix", }); println!(core::cfg_select! { - unix => { - "unix" - } - windows => { - "windows" - } + unix => "unix", + windows => "windows", _ => "not windows or unix", }); } @@ -773,15 +671,9 @@ mod nested { // 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 + windows => "windows", + unix => "unix", + _ => "none", // FIXME. Prevent wrapping back up to the next line } cfg_select! { @@ -995,9 +887,7 @@ pub const fn midpoint(self, other: f32) -> f32 { all(target_arch = "arm", target_feature = "vfp2"), target_arch = "wasm32", target_arch = "wasm64", - ) => { - ((self as f64 + other as f64) / 2.0) as f32 - } + ) => ((self as f64 + other as f64) / 2.0) as f32, _ => { const HI: f32 = f32::MAX / 2.; @@ -1074,31 +964,29 @@ fn arm_rhs_expr_3() -> i32 { cfg_select! { any(true) => 1, any(false) => 2, - any(true) => { - 42 - } - any(true) => { - 42 - } + any(true) => 42, + any(true) => 42, any(false) => -1 as i32, any(true) => 2 + 2, any(false) => "", - any(true) => + any(true) => { if true { 42 } else { 84 - }, - any(false) => + } + } + any(false) => { if true { 42 } else { 84 - }, + } + } any(true) => return 42, any(false) => loop {}, any(true) => (1, 2), - any(false) => (1, 2,), + any(false) => (1, 2), any(true) => todo!(), any(false) => println!("hello"), } @@ -1118,9 +1006,7 @@ fn expand_to_statements() -> i32 { } type ExpandToType = cfg_select! { - unix => { - u32 - } + unix => u32, _ => i32, }; From f31fc439db26151a147dd6f976433f0b3f8b0a0b Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 10:03:17 -0400 Subject: [PATCH 12/17] fix: rename `parse_cfg_select` -> `parse_cfg_select_arms` Apply feedback from PR review. --- src/macros.rs | 4 ++-- src/parse/macros/cfg_select.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 2553ee19bbc..63e2bd58e55 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -29,7 +29,7 @@ use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::header::{HeaderPart, format_header}; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; -use crate::parse::macros::cfg_select::{CfgSelectFormatPredicate, parse_cfg_select}; +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::{ @@ -1572,7 +1572,7 @@ fn format_cfg_select( }; let arms = - parse_cfg_select(context.psess, ts).macro_error(MacroErrorKind::ParseFailure, span)?; + 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); diff --git a/src/parse/macros/cfg_select.rs b/src/parse/macros/cfg_select.rs index 8d60402e060..881df585c35 100644 --- a/src/parse/macros/cfg_select.rs +++ b/src/parse/macros/cfg_select.rs @@ -151,7 +151,10 @@ impl std::fmt::Debug for CfgSelectArm { // 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(psess: &ParseSess, ts: TokenStream) -> Option> { +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); From 987e6b480735d00c7412ce47afa0e00f424e41ee Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 10:16:13 -0400 Subject: [PATCH 13/17] refactor: reorder `format_cfg_select` arguments Per the PR review I'm making `context: &RewriteContext<'_>` the first argument. Also moved the `shape` and `span` to follow the `context`. --- src/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 63e2bd58e55..8564d351489 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -248,7 +248,7 @@ fn rewrite_macro_inner( } if macro_name.ends_with("cfg_select!") { - match format_cfg_select(¯o_name, style, context, shape, ts.clone(), mac.span()) { + 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 @@ -1548,12 +1548,12 @@ fn rewrite_macro_with_items( } fn format_cfg_select( - name: &str, - delim_token: Delimiter, context: &RewriteContext<'_>, shape: Shape, - ts: TokenStream, 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); From d9ad5c7fded082a4ad228e76e5d19314aca136a3 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 10:21:29 -0400 Subject: [PATCH 14/17] fix: make sure we cancel diagnostic errors when parsing `cfg_select!` arms --- src/parse/macros/cfg_select.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parse/macros/cfg_select.rs b/src/parse/macros/cfg_select.rs index 881df585c35..7127445a189 100644 --- a/src/parse/macros/cfg_select.rs +++ b/src/parse/macros/cfg_select.rs @@ -169,7 +169,8 @@ pub(crate) fn parse_cfg_select_arms( CfgSelectFormatPredicate::Cfg(meta_item) }; - if let Err(_) = parser.expect(exp!(FatArrow)) { + if let Err(e) = parser.expect(exp!(FatArrow)) { + e.cancel(); debug!("Expected to find a `=>` after cfg_selec! predicate."); return None; }; From 02a4cf013ccc1a8620ddc70285877d472d16a8da Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 11:25:20 -0400 Subject: [PATCH 15/17] fix: No need to clone before calling `context.leave_macro` Because `inside_macro` is a `Rc>` cloning the entire context doesn't actually isolate the `inside_macro` state. However, I've added a `debug_assert!` to make sure that we only ever call `context.leave_macro` when we're on a code path that immediately returns from `rewrite_macro_inner` so that we don't unexpectedly impact default macro handling where we need to be more cautious about adding or removing tokens. --- src/macros.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 8564d351489..527140eca3a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -261,6 +261,12 @@ fn rewrite_macro_inner( } } + // 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, @@ -1602,8 +1608,7 @@ fn format_cfg_select( // 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. - let rewrite_context = context.clone(); - rewrite_context.leave_macro(); + context.leave_macro(); let items = itemize_list( context.snippet_provider, @@ -1621,7 +1626,7 @@ fn format_cfg_select( }; crate::matches::rewrite_match_body( - &rewrite_context, + context, &arm.expr, &predicate_str, nested_shape, From 85341730dd8ffaf8c780ac1f2dc4b7e000ad502b Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 30 Jul 2026 12:04:41 -0400 Subject: [PATCH 16/17] test: Add test case where `cfg_select!` falls back to default macro handling --- tests/source/cfg_select.rs | 12 ++++++++++++ tests/target/cfg_select.rs | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/source/cfg_select.rs b/tests/source/cfg_select.rs index 1a520b1e129..794967afeb2 100644 --- a/tests/source/cfg_select.rs +++ b/tests/source/cfg_select.rs @@ -1101,3 +1101,15 @@ std :: cfg_select! { } ] } + +// 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_select.rs b/tests/target/cfg_select.rs index eca6cca2d93..fa242c7d206 100644 --- a/tests/target/cfg_select.rs +++ b/tests/target/cfg_select.rs @@ -1094,3 +1094,11 @@ std::cfg_select! { } ], } + +// 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 +} From 6097a911bfa03da823152706865173083616200a Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Tue, 11 Aug 2026 00:44:46 -0400 Subject: [PATCH 17/17] chore: bump rustfmt toolchain to nightly-2026-08-11 Bumping the toolchain version as part of a git subtree push. Before: ``` 1.99.0-nightly (9f36de775 2026-07-19) ``` After: ``` 1.99.0-nightly (12c36e253 2026-08-10) ``` --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 5dac4fcf280..5e20e013aef 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-07-19" +channel = "nightly-2026-08-11" components = ["llvm-tools", "rustc-dev"]