From 20a023d13f0a2ed5448f1b3b5cc3bfbbbd79e499 Mon Sep 17 00:00:00 2001 From: Jason Owen Date: Sun, 10 May 2026 15:25:32 -0700 Subject: [PATCH 01/97] Include trailing comma in single param vertical fn When formatting a function with a single parameter with `fn_params_layout` set to `Vertical`, the previous tactic would remove any trailing commas before spilling to multiple lines for other reasons, such as long function names or return types. Instead, use the same tactic as `Tall` does, so that the trailing comma is kept if the end result spans multiple lines. `fn_params_layout = "Vertical"` is stable, so this is a breaking change. Guard on the style edition so that it only takes effect starting in the next edition. Issue #6889 `fn_params_layout = "Vertical"` removes single argument's trailing comma --- src/config/options.rs | 10 ++- src/items.rs | 2 +- .../configs/fn_params_layout/vertical.rs | 22 ++++++ .../fn_params_layout/vertical_style_2027.rs | 39 ++++++++++ .../configs/fn_params_layout/vertical.rs | 30 ++++++++ .../fn_params_layout/vertical_style_2027.rs | 73 +++++++++++++++++++ 6 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 tests/source/configs/fn_params_layout/vertical_style_2027.rs create mode 100644 tests/target/configs/fn_params_layout/vertical_style_2027.rs diff --git a/src/config/options.rs b/src/config/options.rs index 00f9c3f7ec1ea..88948756626bf 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -96,11 +96,17 @@ pub enum Heuristics { } impl Density { - pub fn to_list_tactic(self, len: usize) -> ListTactic { + pub fn to_list_tactic(self, style_edition: StyleEdition, len: usize) -> ListTactic { match self { Density::Compressed => ListTactic::Mixed, Density::Tall => ListTactic::HorizontalVertical, - Density::Vertical if len == 1 => ListTactic::Horizontal, + Density::Vertical if len == 1 => { + if style_edition <= StyleEdition::Edition2024 { + ListTactic::Horizontal + } else { + ListTactic::HorizontalVertical + } + } Density::Vertical => ListTactic::Vertical, } } diff --git a/src/items.rs b/src/items.rs index 484c5b50adff9..3fe59105e4b37 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2877,7 +2877,7 @@ fn rewrite_params( context .config .fn_params_layout() - .to_list_tactic(param_items.len()), + .to_list_tactic(context.config.style_edition(), param_items.len()), Separator::Comma, one_line_budget, ); diff --git a/tests/source/configs/fn_params_layout/vertical.rs b/tests/source/configs/fn_params_layout/vertical.rs index 674968023f997..9f4ea419d9cb9 100644 --- a/tests/source/configs/fn_params_layout/vertical.rs +++ b/tests/source/configs/fn_params_layout/vertical.rs @@ -2,6 +2,10 @@ // Function arguments density trait Lorem { + fn lorem(ipsum: Ipsum); + + fn lorem(ipsum: Ipsum) -> Dolor; + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { @@ -13,4 +17,22 @@ trait Lorem { fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit) { // body } + + fn long_param_name(lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod: Tempor); + + fn long_param_type(lorem: IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore, + ); + + fn long_return_type(lorem: Lorem) + -> IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore; + + fn lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt + (lorem: Lorem); + + fn lorem(t: T); + + fn lorem + ( + t: T, + ); } diff --git a/tests/source/configs/fn_params_layout/vertical_style_2027.rs b/tests/source/configs/fn_params_layout/vertical_style_2027.rs new file mode 100644 index 0000000000000..07c6905ed5ea4 --- /dev/null +++ b/tests/source/configs/fn_params_layout/vertical_style_2027.rs @@ -0,0 +1,39 @@ +// rustfmt-style_edition: 2027 +// rustfmt-fn_params_layout: Vertical +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum); + + fn lorem(ipsum: Ipsum) -> Dolor; + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: onsectetur, adipiscing: Adipiscing, elit: Elit) { + // body + } + + fn long_param_name(lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod: Tempor); + + fn long_param_type(lorem: IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore, + ); + + fn long_return_type(lorem: Lorem) + -> IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore; + + fn lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt + (lorem: Lorem); + + fn lorem(t: T); + + fn lorem + ( + t: T, + ); +} diff --git a/tests/target/configs/fn_params_layout/vertical.rs b/tests/target/configs/fn_params_layout/vertical.rs index 7a0e42415f3b9..b1ae5710843fb 100644 --- a/tests/target/configs/fn_params_layout/vertical.rs +++ b/tests/target/configs/fn_params_layout/vertical.rs @@ -2,6 +2,10 @@ // Function arguments density trait Lorem { + fn lorem(ipsum: Ipsum); + + fn lorem(ipsum: Ipsum) -> Dolor; + fn lorem( ipsum: Ipsum, dolor: Dolor, @@ -39,4 +43,30 @@ trait Lorem { ) { // body } + + fn long_param_name( + lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod: Tempor + ); + + fn long_param_type( + lorem: IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore + ); + + fn long_return_type( + lorem: Lorem + ) -> IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore; + + fn lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt( + lorem: Lorem + ); + + fn lorem( + t: T + ); + + fn lorem< + T: IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLaboreEtDolore, + >( + t: T + ); } diff --git a/tests/target/configs/fn_params_layout/vertical_style_2027.rs b/tests/target/configs/fn_params_layout/vertical_style_2027.rs new file mode 100644 index 0000000000000..838940a9689c9 --- /dev/null +++ b/tests/target/configs/fn_params_layout/vertical_style_2027.rs @@ -0,0 +1,73 @@ +// rustfmt-style_edition: 2027 +// rustfmt-fn_params_layout: Vertical +// Function arguments density + +trait Lorem { + fn lorem(ipsum: Ipsum); + + fn lorem(ipsum: Ipsum) -> Dolor; + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: onsectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: onsectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } + + fn long_param_name( + lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod: Tempor, + ); + + fn long_param_type( + lorem: IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore, + ); + + fn long_return_type( + lorem: Lorem, + ) -> IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLabore; + + fn lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt( + lorem: Lorem, + ); + + fn lorem( + t: T, + ); + + fn lorem< + T: IpsumDolorSitAmetConsecteturAdipiscingElitSedDoEiusmodTemporIncididuntUtLaboreEtDolore, + >( + t: T, + ); +} 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 02/97] 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 099a644282102..89f3e71f9eb45 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 d302a9ede6cb7..485f44a936bd8 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 476289b08b72a..040447ff1898f 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 00e0f6f58bd37..3d32821ce08b3 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 4eded7c49eb50..ff5ed15fce743 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 2f0accac7d77a..62fb6dfbe9e38 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 2f0accac7d77a..62fb6dfbe9e38 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 c88bbc8b12d6f266c0d4dec3092020db1fc693c3 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 21 Jul 2026 20:48:08 +0800 Subject: [PATCH 03/97] Fix commit message template markup I can't markdown. --- Subtree sync procedure.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Subtree sync procedure.md b/Subtree sync procedure.md index 444246117d49e..99e01486ed1be 100644 --- a/Subtree sync procedure.md +++ b/Subtree sync procedure.md @@ -116,7 +116,7 @@ commit message template](#rust-toolchain-bump-commit-message-template). #### `rust-toolchain` bump commit message template -```text +````text chore: bump rustfmt toolchain to nightly-$LATEST_NIGHTLY_DATE Bumping the toolchain version as part of a git subtree push. @@ -132,6 +132,7 @@ After: ``` $LATEST_NIGHTLY_VERSION-nightly ($LATEST_NIGHTLY_HASH $LATEST_NIGHTLY_DATE) ``` +```` Substituting the placeholders with the right information. From 2aeef85e35db885ddde42bd63a52185e7a094854 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 21 Jul 2026 21:17:31 +0800 Subject: [PATCH 04/97] 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 0000000000000..1d270e78949f8 --- /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 0000000000000..646e06bc7e9c9 --- /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 0000000000000..3b203c2aaf282 --- /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 0000000000000..5be67ba135072 --- /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 0000000000000..f2b61949380d3 --- /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 58425fa0c86d9..41b138acb63fc 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 c1b04721cb1cd..9ef809bc5ccf7 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 c8cbf00804bc3..77ab026f1d4b8 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 0838800dff06e..28c218b729b22 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 c92732366edd3..430185e3105b5 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 7a639b469e849..49c9172cb46d1 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 af47fddcf59e1..435132106e056 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 71cf88f79e67b..cff6f0d046dd7 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 7f0470d214f3a..6b6d9a3b8dc82 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 64b4aaef4d395..d95fbec8cb7fd 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 6ac140f2e0921..eedd83cad343a 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 45a8b1eba8711..976c290489460 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 62029a7100356..996a30c3a5bc1 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 ae3586a1c3bac..444246117d49e 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 7c047bb085066..d6bb7678ea029 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 60aee6ee2cb2a..bffe090f7ace6 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 0000000000000..7c7807f6f37f2 --- /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 0000000000000..74312216d4ea3 --- /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 b6b5ca2136453..0000000000000 --- 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 dd9a0c0fd9b42..0000000000000 --- 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 ea96e4be1305f..0000000000000 --- 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 0000000000000..305c4d5aaa296 --- /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 0000000000000..40dc9038a48bd --- /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 0000000000000..27885a66957a2 --- /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 0000000000000..4817fa3c49c10 --- /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 13399be25ba88..76c6ce3f0e542 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 cad47379b5d33..5dac4fcf28091 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 9b4adc41a8b0f..6627ada03fc6a 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 bf44924f13c31..bf82f1d275957 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 241934a7d3d0b..05d7310122a4b 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 8abb7439257f2..a3f9842cd4f7f 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 00f9c3f7ec1ea..3f970ed4bd710 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 5ecb68078561b..aec503099432e 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 1e1e329f62484..7f2a14f9e314b 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 0000000000000..5a9d2beb010b4 --- /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 fa5e23b8d5376..6268af93d5707 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 942b42ec5f20c..5f49bbf0c7e31 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 bd932b8d64dac..2a824b4ce30e7 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 d394bb40b6d5a..2654d2464eed3 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 0a9ff4771b0e0..2fad1d41ae9f7 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 0000000000000..0cc7c7fef6fed --- /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 143fb1dea2223..90331ce926e5c 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 3b971188cd5cd..59c445f904b9e 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 4eded7c49eb50..291ac8fa078af 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 d3a1279cfb325..d6a25e61008f0 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 3e06f3899d1b3..15a4fce93482a 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 560d62e9d57d7..55f9a4d8c8b26 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 0000000000000..dcaa89367f8c0 --- /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 0000000000000..b9aa3211aaf13 --- /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 47b0443137a40..8ac9737648f1c 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 45e0b87bbae36..4889203b616a4 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 2fe5fe2f43849..f07bc4db20f30 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 0000000000000..de2c9f9d9dc41 --- /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 0000000000000..876592a4f6403 --- /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 0000000000000..d3fbe3671856e --- /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 1878c68a5a0c4..b478dade3160f 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 0eadfb2851579..fe324ee3ac905 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 ce307b3aa521e..0000000000000 --- 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 b1092ad71ba13..0000000000000 --- 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 0000000000000..75231555232cc --- /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 0000000000000..fe3cfcf37b8c3 --- /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 0000000000000..c152181bc2fcf --- /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 0000000000000..005167dff63ea --- /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 0000000000000..0f836aa989fcc --- /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 0000000000000..67677f14fdb21 --- /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 0000000000000..050fbb9c525c1 --- /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 0000000000000..e677f842bd5e9 --- /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 0000000000000..0778433bf1cb8 --- /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 0000000000000..94e309a191bca --- /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 0000000000000..20516d56d6838 --- /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 d97f9a6da7425..0c59d4739fcd5 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 f5f0cb6357f74..4695b06c77b11 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 0243a1da849a7..f2ceee8d68ef7 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 6a9a5c8d60771..bfb6c157bd2e7 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 46f6abe93120f..6a81e9af7427b 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 0000000000000..f944711e14f39 --- /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 0000000000000..e471a1982600d --- /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 0000000000000..a4ec39c63c4a0 --- /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 0000000000000..b18878ce8e868 --- /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 e6d643ad43fec..2d64ec87d50f9 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 0000000000000..087e4956b67f7 --- /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 d81f5c07ccf3c..f4a36e9e345d6 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 0000000000000..b073c34fb8ea7 --- /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 6391890e535d8..a0640a615cbcf 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 0000000000000..939e2069e9c00 --- /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 0000000000000..36371bddb2ee2 --- /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 0000000000000..d1d284c6df0a4 --- /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 0000000000000..876592a4f6403 --- /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 0000000000000..4336453abae0e --- /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 72b1be4804c64..9b4ae1e7a8d06 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 c56fdbb02b681..d450e7b87ee6d 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 5c27fded16743..0000000000000 --- 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 e8774d0bb24eb..0000000000000 --- 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 48bb93d2c1c08..0000000000000 --- 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 0000000000000..75231555232cc --- /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 0000000000000..fe3cfcf37b8c3 --- /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 0000000000000..c152181bc2fcf --- /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 0000000000000..005167dff63ea --- /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 0000000000000..0f836aa989fcc --- /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 0000000000000..6c9ac60dddb82 --- /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 0000000000000..b67340f5bc0fd --- /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 0000000000000..82ed6dc37c895 --- /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 0000000000000..ce248f8357254 --- /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 0000000000000..5fdb66edc8903 --- /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 0000000000000..e0afea04b37a1 --- /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 0000000000000..1f4b1df7fbdbb --- /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 0000000000000..eeac0f48d5ae9 --- /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 0000000000000..8b137891791fe --- /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 d97f9a6da7425..0c59d4739fcd5 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 f5f0cb6357f74..4695b06c77b11 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 b3831df6d86cd..a61f576eccfe1 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 addc555aa0ed4..852e40f213d7a 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 44acabd75f54b..bcca31f4023a5 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 0000000000000..8b137891791fe --- /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 0000000000000..8b137891791fe --- /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 0000000000000..8b137891791fe --- /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 0000000000000..f944711e14f39 --- /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 0000000000000..d049df855f877 --- /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 8bb264576a8fb..3463476c28588 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 05/97] 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 6268af93d5707..b6abb097e3387 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 cf7dafb4d3a96cd3d537eecf9aaf945b3e2b487c Mon Sep 17 00:00:00 2001 From: clubby789 Date: Fri, 17 Jul 2026 15:43:50 +0100 Subject: [PATCH 06/97] tests: Implement snapshot test suites for formatting errors --- Cargo.lock | 67 +++++++++++ Cargo.toml | 1 + src/test/mod.rs | 40 +++++++ tests/rustfmt/main.rs | 109 +++++++++++++++++- tests/warning/snapshots/deprecated_skip.snap | 10 ++ .../warning/snapshots/invalid_attribute.snap | 10 ++ tests/warning/snapshots/line_overflow.snap | 11 ++ tests/warning/snapshots/lost_comment.snap | 11 ++ tests/warning/snapshots/multiple_errors.snap | 38 ++++++ .../snapshots/trailing_whitespace.snap | 12 ++ tests/warning/source/deprecated_skip.rs | 2 + tests/warning/source/invalid_attribute.rs | 2 + tests/warning/source/line_overflow.rs | 1 + tests/warning/source/lost_comment.rs | 1 + tests/warning/source/multiple_errors.rs | 12 ++ tests/warning/source/trailing_whitespace.rs | 2 + 16 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 tests/warning/snapshots/deprecated_skip.snap create mode 100644 tests/warning/snapshots/invalid_attribute.snap create mode 100644 tests/warning/snapshots/line_overflow.snap create mode 100644 tests/warning/snapshots/lost_comment.snap create mode 100644 tests/warning/snapshots/multiple_errors.snap create mode 100644 tests/warning/snapshots/trailing_whitespace.snap create mode 100644 tests/warning/source/deprecated_skip.rs create mode 100644 tests/warning/source/invalid_attribute.rs create mode 100644 tests/warning/source/line_overflow.rs create mode 100644 tests/warning/source/lost_comment.rs create mode 100644 tests/warning/source/multiple_errors.rs create mode 100644 tests/warning/source/trailing_whitespace.rs diff --git a/Cargo.lock b/Cargo.lock index d95fbec8cb7fd..2002e4860ec81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys", +] + [[package]] name = "crossbeam-utils" version = "0.8.8" @@ -236,6 +247,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" @@ -349,6 +366,20 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "insta" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +dependencies = [ + "console", + "once_cell", + "regex", + "similar", + "strip-ansi-escapes", + "tempfile", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -546,6 +577,7 @@ dependencies = [ "dirs", "getopts", "ignore", + "insta", "itertools", "regex", "rustfmt-config_proc_macro", @@ -656,12 +688,27 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -881,6 +928,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "walkdir" version = "2.3.2" diff --git a/Cargo.toml b/Cargo.toml index eedd83cad343a..e64b977359bb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ semver = "1.0.21" [dev-dependencies] tempfile = "3.23.0" +insta = { version = "1.48.0", features = ["filters"] } # Rustc dependencies are loaded from the sysroot, Cargo doesn't know about them. diff --git a/src/test/mod.rs b/src/test/mod.rs index 291ac8fa078af..6089c9a08eafa 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -249,6 +249,46 @@ fn system_tests() { }); } +// Check formatting-specific warning/error emissions against snapshots. +#[test] +fn warning_tests() { + init_log(); + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let manifest_dir_filter = regex::escape(manifest_dir.to_string_lossy().as_ref()); + let files = get_test_files(Path::new("tests/warning/source"), true); + let mut config = Config::default(); + config.set().error_on_line_overflow(true); + config.set().error_on_unformatted(true); + + for file in &files { + let snapshot_name = file.file_stem().unwrap().to_str().unwrap(); + let (parsing_errors, _, report) = format_file(file, config.clone()); + assert!(!parsing_errors, "{} failed to parse", file.display()); + assert!( + report.has_warnings(), + "{} did not emit a warning or error", + file.display() + ); + let warning = FormatReportFormatterBuilder::new(&report) + .build() + .to_string(); + + insta::with_settings!({ + snapshot_path => manifest_dir.join("tests/warning/snapshots"), + prepend_module_to_snapshot => false, + omit_expression => true, + filters => vec![ + (manifest_dir_filter.as_str(), "$$DIR"), + (r"\r\n", "\n"), + (r"\\", "/"), + ], + strip_ansi_escape_codes => true, + }, { + insta::assert_snapshot!(snapshot_name, warning); + }); + } +} + // Do the same for tests/coverage-source directory. // The only difference is the coverage mode. #[test] diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 4008c8d14357c..9a46544b3670f 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -5,7 +5,7 @@ use std::fs::{File, remove_file}; use std::path::Path; use std::process::Command; -use rustfmt_config_proc_macro::{nightly_only_test, rustfmt_only_ci_test}; +use rustfmt_config_proc_macro::{nightly_only_test, rustfmt_only_ci_test, stable_only_test}; /// Run the rustfmt executable with environment vars set and return its output. fn rustfmt_with_extra( @@ -117,11 +117,116 @@ fn inline_config() { ); } +#[stable_only_test] #[test] fn rustfmt_usage_text() { let args = ["--help"]; let (stdout, _) = rustfmt(&args); - assert!(stdout.contains("Format Rust code\n\nusage: rustfmt [options] ...")); + insta::assert_snapshot!(stdout, @" + Format Rust code + + usage: rustfmt [options] ... + + Options: + --check Run in 'check' mode. Exits with 0 if input is + formatted correctly. Exits with 1 and prints a diff if + formatting is required. + --emit [files|stdout] + What data to emit and how + --backup Backup any modified files. + --config-path [Path for the configuration file] + Recursively searches the given path for the + rustfmt.toml config file. If not found reverts to the + input file path + --edition [2015|2018|2021|2024] + Rust edition to use + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide (unstable). + --color [always|never|auto] + Use colored output (if supported) + --print-config [default|minimal|current] PATH + Dumps a default or minimal config to PATH. A minimal + config is the subset of the current config file used + for formatting the current program. `current` writes + to stdout current config as if formatting the file at + PATH. + -l, --files-with-diff + Prints the names of mismatched files that were + formatted. Prints the names of files that would be + formatted when used with `--check` mode. + --config [key1=val1,key2=val2...] + Set options from command line. These settings take + priority over .rustfmt.toml + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide. + -v, --verbose Print verbose output + -q, --quiet Print less output + -V, --version Show version information + -h, --help [=TOPIC] Show this message or help about a specific topic: + `config` + "); +} + +#[nightly_only_test] +#[test] +fn rustfmt_nightly_usage_text() { + let args = ["--help"]; + let (stdout, _) = rustfmt(&args); + insta::assert_snapshot!(stdout, @" + Format Rust code + + usage: rustfmt [options] ... + + Options: + --check Run in 'check' mode. Exits with 0 if input is + formatted correctly. Exits with 1 and prints a diff if + formatting is required. + --emit [files|stdout|coverage|checkstyle|json] + What data to emit and how + --backup Backup any modified files. + --config-path [Path for the configuration file] + Recursively searches the given path for the + rustfmt.toml config file. If not found reverts to the + input file path + --edition [2015|2018|2021|2024] + Rust edition to use + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide (unstable). + --color [always|never|auto] + Use colored output (if supported) + --print-config [default|minimal|current] PATH + Dumps a default or minimal config to PATH. A minimal + config is the subset of the current config file used + for formatting the current program. `current` writes + to stdout current config as if formatting the file at + PATH. + -l, --files-with-diff + Prints the names of mismatched files that were + formatted. Prints the names of files that would be + formatted when used with `--check` mode. + --config [key1=val1,key2=val2...] + Set options from command line. These settings take + priority over .rustfmt.toml + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide. + --unstable-features + Enables unstable features. Only available on nightly + channel. + --file-lines JSON + Format specified line ranges. Run with + `--help=file-lines` for more detail (unstable). + --error-on-unformatted + Error if unable to get comments or string literals + within max_width, or they are left with trailing + whitespaces (unstable). + --skip-children + Don't reformat child modules (unstable). + -v, --verbose Print verbose output + -q, --quiet Print less output + -V, --version Show version information + -h, --help [=TOPIC] Show this message or help about a specific topic: + `config` or `file-lines` + "); } #[test] diff --git a/tests/warning/snapshots/deprecated_skip.snap b/tests/warning/snapshots/deprecated_skip.snap new file mode 100644 index 0000000000000..bdba05a85a17b --- /dev/null +++ b/tests/warning/snapshots/deprecated_skip.snap @@ -0,0 +1,10 @@ +--- +source: src/test/mod.rs +--- +warning: `rustfmt_skip` is deprecated; use `rustfmt::skip` + --> tests/warning/source/deprecated_skip.rs:1 + | +1 | #[rustfmt_skip] + | + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/invalid_attribute.snap b/tests/warning/snapshots/invalid_attribute.snap new file mode 100644 index 0000000000000..5168fd54054bf --- /dev/null +++ b/tests/warning/snapshots/invalid_attribute.snap @@ -0,0 +1,10 @@ +--- +source: src/test/mod.rs +--- +error: invalid attribute + --> tests/warning/source/invalid_attribute.rs:1 + | +1 | #[rustfmt::invalid] + | + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/line_overflow.snap b/tests/warning/snapshots/line_overflow.snap new file mode 100644 index 0000000000000..beb0257c4aa89 --- /dev/null +++ b/tests/warning/snapshots/line_overflow.snap @@ -0,0 +1,11 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 109) + --> tests/warning/source/line_overflow.rs:1:1:101 + | +1 | fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width( + | ^^^^^^^^^ + | + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/lost_comment.snap b/tests/warning/snapshots/lost_comment.snap new file mode 100644 index 0000000000000..1983f12d73a91 --- /dev/null +++ b/tests/warning/snapshots/lost_comment.snap @@ -0,0 +1,11 @@ +--- +source: src/test/mod.rs +--- +error[internal]: not formatted because a comment would be lost + --> tests/warning/source/lost_comment.rs:1 + | +1 | fn main() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/multiple_errors.snap b/tests/warning/snapshots/multiple_errors.snap new file mode 100644 index 0000000000000..4293505608134 --- /dev/null +++ b/tests/warning/snapshots/multiple_errors.snap @@ -0,0 +1,38 @@ +--- +source: src/test/mod.rs +--- +warning: `rustfmt_skip` is deprecated; use `rustfmt::skip` + --> tests/warning/source/multiple_errors.rs:1 + | +1 | #[rustfmt_skip] + | + +error: invalid attribute + --> tests/warning/source/multiple_errors.rs:4 + | +4 | #[rustfmt::invalid] + | + +error[internal]: not formatted because a comment would be lost + --> tests/warning/source/multiple_errors.rs:9 + | +9 | fn lost_comment() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 109) + --> tests/warning/source/multiple_errors.rs:7:7:101 + | +7 | fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width( + | ^^^^^^^^^ + | + +error[internal]: left behind trailing whitespace + --> tests/warning/source/multiple_errors.rs:15:15:46 + | +15 | /// This doc comment has trailing whitespace. + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 5 errors. diff --git a/tests/warning/snapshots/trailing_whitespace.snap b/tests/warning/snapshots/trailing_whitespace.snap new file mode 100644 index 0000000000000..13ca45ac2cd47 --- /dev/null +++ b/tests/warning/snapshots/trailing_whitespace.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: left behind trailing whitespace + --> tests/warning/source/trailing_whitespace.rs:1:1:46 + | +1 | /// This doc comment has trailing whitespace. + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/deprecated_skip.rs b/tests/warning/source/deprecated_skip.rs new file mode 100644 index 0000000000000..6578f13bc7fb6 --- /dev/null +++ b/tests/warning/source/deprecated_skip.rs @@ -0,0 +1,2 @@ +#[rustfmt_skip] +fn main() {} diff --git a/tests/warning/source/invalid_attribute.rs b/tests/warning/source/invalid_attribute.rs new file mode 100644 index 0000000000000..f3957f4c04240 --- /dev/null +++ b/tests/warning/source/invalid_attribute.rs @@ -0,0 +1,2 @@ +#[rustfmt::invalid] +fn main() {} diff --git a/tests/warning/source/line_overflow.rs b/tests/warning/source/line_overflow.rs new file mode 100644 index 0000000000000..1ce77151c956d --- /dev/null +++ b/tests/warning/source/line_overflow.rs @@ -0,0 +1 @@ +fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width() {} diff --git a/tests/warning/source/lost_comment.rs b/tests/warning/source/lost_comment.rs new file mode 100644 index 0000000000000..d075256c92775 --- /dev/null +++ b/tests/warning/source/lost_comment.rs @@ -0,0 +1 @@ +fn main() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } diff --git a/tests/warning/source/multiple_errors.rs b/tests/warning/source/multiple_errors.rs new file mode 100644 index 0000000000000..02e660d3cb840 --- /dev/null +++ b/tests/warning/source/multiple_errors.rs @@ -0,0 +1,12 @@ +#[rustfmt_skip] +fn deprecated_skip() {} + +#[rustfmt::invalid] +fn invalid_attribute() {} + +fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width() {} + +fn lost_comment() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } + +/// This doc comment has trailing whitespace. +fn trailing_whitespace() {} diff --git a/tests/warning/source/trailing_whitespace.rs b/tests/warning/source/trailing_whitespace.rs new file mode 100644 index 0000000000000..bfa58ac288764 --- /dev/null +++ b/tests/warning/source/trailing_whitespace.rs @@ -0,0 +1,2 @@ +/// This doc comment has trailing whitespace. +fn main() {} From 34ced3e601cfbc2cd93c3f8c7629f039038c4d9b Mon Sep 17 00:00:00 2001 From: clubby789 Date: Wed, 22 Jul 2026 02:59:23 +0100 Subject: [PATCH 07/97] doc: Add instructions for updating snapshot tests --- Contributing.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Contributing.md b/Contributing.md index 996a30c3a5bc1..7b298052ab2a0 100644 --- a/Contributing.md +++ b/Contributing.md @@ -63,6 +63,11 @@ would need a configuration file named `test-indent.toml` in that directory. As a example, the `issue-1111.rs` test file is configured by the file `./tests/config/issue-1111.toml`. +### Updating snapshots + +Some tests that test rustfmt-specific output (e.g. `--help` output and formatting-specific errors) use [insta](https://insta.rs/) to snapshot their output. +To update these tests, install [`cargo-insta`](https://insta.rs/docs/cli/) and run `cargo insta test --review`. + ## Debugging Some `rewrite_*` methods use the `debug!` macro for printing useful information. From aceacbf4edfb6d20618291976b5cddb43ef6d6e1 Mon Sep 17 00:00:00 2001 From: clubby789 Date: Tue, 16 Jun 2026 12:58:58 +0100 Subject: [PATCH 08/97] deps: Update `annotate-snippets` --- Cargo.lock | 9 ++++---- Cargo.toml | 2 +- src/format_report_formatter.rs | 40 +++++++++++++++------------------- src/formatting.rs | 10 +++++---- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2002e4860ec81..775470ce2d2db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,11 +13,12 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.11.5" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" dependencies = [ "anstyle", + "memchr", "unicode-width 0.2.2", ] @@ -449,9 +450,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "nu-ansi-term" diff --git a/Cargo.toml b/Cargo.toml index e64b977359bb4..00f7bb11fd288 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ rustfmt-format-diff = [] generic-simd = [] [dependencies] -annotate-snippets = { version = "0.11" } +annotate-snippets = { version = "0.12" } anyhow = "1.0" bytecount = "0.6.9" cargo_metadata = "0.23" diff --git a/src/format_report_formatter.rs b/src/format_report_formatter.rs index 08889c712a5f9..1dfcc05038374 100644 --- a/src/format_report_formatter.rs +++ b/src/format_report_formatter.rs @@ -1,6 +1,6 @@ use crate::formatting::FormattingError; use crate::{ErrorKind, FormatReport}; -use annotate_snippets::{Annotation, Level, Renderer, Snippet}; +use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Padding, Renderer, Snippet}; use std::fmt::{self, Display}; /// A builder for [`FormatReportFormatter`]. @@ -57,26 +57,23 @@ impl<'a> Display for FormatReportFormatter<'a> { for (file, errors) in errors_by_file { for error in errors { let error_kind = error.kind.to_string(); - let mut message = - error_kind_to_snippet_annotation_level(&error.kind).title(&error_kind); + let mut title = + error_kind_to_snippet_annotation_level(&error.kind).primary_title(error_kind); if error.is_internal() { - message = message.id("internal"); + title = title.id("internal"); } - - let message_suffix = error.msg_suffix(); - if !message_suffix.is_empty() { - message = message.footer(Level::Note.title(&message_suffix)); - } - - let origin = format!("{}:{}", file, error.line); let snippet = Snippet::source(&error.line_buffer) .line_start(error.line) - .origin(&origin) + .path(format!("{file}:{}", error.line)) .fold(false) .annotations(annotation(error)); - message = message.snippet(snippet); - - writeln!(f, "{}\n", renderer.render(message))?; + let mut group = title.element(snippet); + if let Some(message_suffix) = error.msg_suffix() { + group = group.element(Level::NOTE.message(message_suffix)); + } else { + group = group.element(Padding); + } + writeln!(f, "{}\n", renderer.render(&[group]))?; } } @@ -85,10 +82,9 @@ impl<'a> Display for FormatReportFormatter<'a> { "rustfmt has failed to format. See previous {} errors.", self.report.warning_count() ); - let message = Level::Warning.title(&label); - writeln!(f, "{}", renderer.render(message))?; + let group = Group::with_title(Level::WARNING.primary_title(label)); + writeln!(f, "{}\n", renderer.render(&[group]))?; } - Ok(()) } } @@ -98,13 +94,13 @@ fn annotation(error: &FormattingError) -> Option> { let range_end = range_start + range_length; if range_length > 0 { - Some(Level::Error.span(range_start..range_end)) + Some(AnnotationKind::Primary.span(range_start..range_end)) } else { None } } -fn error_kind_to_snippet_annotation_level(error_kind: &ErrorKind) -> Level { +fn error_kind_to_snippet_annotation_level(error_kind: &ErrorKind) -> Level<'_> { match error_kind { ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace @@ -114,7 +110,7 @@ fn error_kind_to_snippet_annotation_level(error_kind: &ErrorKind) -> Level { | ErrorKind::LostComment | ErrorKind::BadAttr | ErrorKind::InvalidGlobPattern(_) - | ErrorKind::VersionMismatch => Level::Error, - ErrorKind::DeprecatedAttr => Level::Warning, + | ErrorKind::VersionMismatch => Level::ERROR, + ErrorKind::DeprecatedAttr => Level::WARNING, } } diff --git a/src/formatting.rs b/src/formatting.rs index 7f2a14f9e314b..98d206fc47d93 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -342,12 +342,14 @@ impl FormattingError { } } - pub(crate) fn msg_suffix(&self) -> &str { + pub(crate) fn msg_suffix(&self) -> Option<&str> { if self.is_comment || self.is_string { - "set `error_on_unformatted = false` to suppress \ - the warning against comments or string literals\n" + Some( + "set `error_on_unformatted = false` to suppress \ + the warning against comments or string literals", + ) } else { - "" + None } } From 0c393d1724b8f60424233d474f2ba4dd95075cb3 Mon Sep 17 00:00:00 2001 From: panstromek Date: Tue, 16 Jun 2026 20:21:19 +0200 Subject: [PATCH 09/97] 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 b6abb097e3387..407c4aa30ee41 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 10/97] 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 5f49bbf0c7e31..65c83a612bf9f 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 099a644282102..baa1a86ad25c8 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 15a4fce93482a..131455fc0ff12 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 0000000000000..2194ba3285353 --- /dev/null +++ b/tests/target/issue_6959.rs @@ -0,0 +1,2 @@ +#[my_macro] +mod foo; From 76d2c6dc0d9ce74345e2486d1f550b6d16f60f0e Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Fri, 24 Jul 2026 09:10:51 +0800 Subject: [PATCH 11/97] fix: preserve attributes for variadic fn parameters (6590) --- src/items.rs | 9 ++++++++- tests/source/issue-6561/trait-fn.rs | 6 ++++++ tests/source/issue-6561/variadic.rs | 5 +++++ tests/source/issue-6607/fn-type.rs | 3 +++ tests/target/issue-6561/trait-fn.rs | 6 ++++++ tests/target/issue-6561/variadic.rs | 5 +++++ tests/target/issue-6607/fn-type.rs | 3 +++ 7 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/source/issue-6561/trait-fn.rs create mode 100644 tests/source/issue-6561/variadic.rs create mode 100644 tests/source/issue-6607/fn-type.rs create mode 100644 tests/target/issue-6561/trait-fn.rs create mode 100644 tests/target/issue-6561/variadic.rs create mode 100644 tests/target/issue-6607/fn-type.rs diff --git a/src/items.rs b/src/items.rs index 6268af93d5707..2c5a4d3dec094 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2361,7 +2361,14 @@ impl Rewrite for ast::Param { Ok(result) } else { - self.ty.rewrite_result(context, shape) + combine_strs_with_missing_comments( + context, + ¶m_attrs_result, + &self.ty.rewrite_result(context, shape)?, + span, + shape, + !has_multiple_attr_lines && !has_doc_comments, + ) } } } diff --git a/tests/source/issue-6561/trait-fn.rs b/tests/source/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..f88c69daddafb --- /dev/null +++ b/tests/source/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} \ No newline at end of file diff --git a/tests/source/issue-6561/variadic.rs b/tests/source/issue-6561/variadic.rs new file mode 100644 index 0000000000000..6108d584d30d8 --- /dev/null +++ b/tests/source/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()]...); +} \ No newline at end of file diff --git a/tests/source/issue-6607/fn-type.rs b/tests/source/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..e85d04d6041e1 --- /dev/null +++ b/tests/source/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} \ No newline at end of file diff --git a/tests/target/issue-6561/trait-fn.rs b/tests/target/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..a30396be56126 --- /dev/null +++ b/tests/target/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} diff --git a/tests/target/issue-6561/variadic.rs b/tests/target/issue-6561/variadic.rs new file mode 100644 index 0000000000000..bf5274f31e1da --- /dev/null +++ b/tests/target/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()] ...); +} diff --git a/tests/target/issue-6607/fn-type.rs b/tests/target/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..7f89d12be8d63 --- /dev/null +++ b/tests/target/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} From e481edb36681a83dd4bb8a6ebb52d9b7193dbc81 Mon Sep 17 00:00:00 2001 From: bendn Date: Fri, 24 Jul 2026 15:26:21 +0700 Subject: [PATCH 12/97] format type consts --- src/items.rs | 30 +++++++++++++++++++++++------- tests/source/generic_static.rs | 5 +++++ tests/target/generic_static.rs | 12 ++++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 tests/source/generic_static.rs create mode 100644 tests/target/generic_static.rs diff --git a/src/items.rs b/src/items.rs index 2c5a4d3dec094..e662113232c0b 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2106,28 +2106,44 @@ fn rewrite_static( static_parts: &StaticParts<'_>, offset: Indent, ) -> Option { - // For now, if this static (or const) has generics, then bail. + // For now, if this static (or const) has a where clause, then bail. if static_parts .generics - .is_some_and(|g| !g.params.is_empty() || !g.where_clause.is_empty()) + .is_some_and(|g| !g.where_clause.is_empty()) { return None; } - + let generics = static_parts + .generics + .and_then(|g| { + format_generics( + context, + &g, + context.config.brace_style(), + BracePos::None, + offset, + // make a span that starts right after `const x` + mk_sp(static_parts.ident.span.hi(), static_parts.ty.span.lo()), + offset.block_indent, + ) + }) + .map_or("".into(), |x| format!("{x}")); let colon = colon_spaces(context.config); let mut prefix = format!( - "{}{}{}{} {}{}{}", + "{}{}{}{} {}{}{}{}", format_visibility(context, static_parts.vis), static_parts.defaultness.map_or("", format_defaultness), format_safety(static_parts.safety), static_parts.prefix, format_mutability(static_parts.mutability), rewrite_ident(context, static_parts.ident), - colon, + generics, + colon ); + // 2 = " =".len() - let ty_shape = - Shape::indented(offset.block_only(), context.config).offset_left_opt(prefix.len() + 2)?; + let ty_shape = Shape::indented(offset.block_only(), context.config) + .offset_left_opt(last_line_width(&prefix) + 2)?; let ty_str = match static_parts.ty.rewrite(context, ty_shape) { Some(ty_str) => ty_str, None => { diff --git a/tests/source/generic_static.rs b/tests/source/generic_static.rs new file mode 100644 index 0000000000000..3702daed00813 --- /dev/null +++ b/tests/source/generic_static.rs @@ -0,0 +1,5 @@ +#![feature(generic_const_items)] +pub const SORT: + &[T]= const { 4}; + +pub const SORT: &[T] = const { 4 }; diff --git a/tests/target/generic_static.rs b/tests/target/generic_static.rs new file mode 100644 index 0000000000000..b8593760ef0e0 --- /dev/null +++ b/tests/target/generic_static.rs @@ -0,0 +1,12 @@ +#![feature(generic_const_items)] +pub const SORT: &[T] = const { 4 }; + +pub const SORT< + AAAAAAAAAAAAAAAAAAAA, + BBBBBBBBBBBBBBBBBBBb, + CCCCCCCCCCCCCCCCCCC, + DDDDDDDDDDDDDDDDDDDDD, + EEEEEEEEEEEE, + FFFFF, + G, +>: &[T] = const { 4 }; From cddcf1ca286ecec761a6cbe9d03e3ecbf1d3fb3d Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:59:51 +0800 Subject: [PATCH 13/97] tests: cover trailing whitespace annotations on multibyte lines --- tests/warning/snapshots/issue_6826.snap | 18 ++++++++++++++++++ tests/warning/source/issue_6826.rs | 5 +++++ 2 files changed, 23 insertions(+) create mode 100644 tests/warning/snapshots/issue_6826.snap create mode 100644 tests/warning/source/issue_6826.rs diff --git a/tests/warning/snapshots/issue_6826.snap b/tests/warning/snapshots/issue_6826.snap new file mode 100644 index 0000000000000..920d1c0296e52 --- /dev/null +++ b/tests/warning/snapshots/issue_6826.snap @@ -0,0 +1,18 @@ +--- +source: src/test/mod.rs +--- +error[internal]: not formatted because a comment would be lost + --> tests/warning/source/issue_6826.rs:2 + | +2 | c.is_ascii_alphanumeric() // 123 + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +error[internal]: left behind trailing whitespace + --> tests/warning/source/issue_6826.rs:3:3:20 + | +3 | || c == '。' + | ^ + | + +warning: rustfmt has failed to format. See previous 2 errors. diff --git a/tests/warning/source/issue_6826.rs b/tests/warning/source/issue_6826.rs new file mode 100644 index 0000000000000..330b42b2bb999 --- /dev/null +++ b/tests/warning/source/issue_6826.rs @@ -0,0 +1,5 @@ +pub fn check(c: char) -> bool { + c.is_ascii_alphanumeric() // 123 + || c == '。' + || c == '、' +} From 502b4f33e2e21335f72bc6021b70af4c4dbb3908 Mon Sep 17 00:00:00 2001 From: WaterWhisperer Date: Sat, 16 May 2026 13:58:36 +0800 Subject: [PATCH 14/97] Fix normalize_doc_attributes configuration docs --- Configurations.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Configurations.md b/Configurations.md index 976c290489460..189389fe143b6 100644 --- a/Configurations.md +++ b/Configurations.md @@ -2150,9 +2150,9 @@ Convert `#![doc]` and `#[doc]` attributes to `//!` and `///` doc comments. #### `false` (default): ```rust -#![doc = "Example documentation"] +#![doc = " Example documentation"] -#[doc = "Example item documentation"] +#[doc = " Example item documentation"] pub enum Bar {} /// Example item documentation @@ -2164,6 +2164,9 @@ pub enum Foo {} ```rust //! Example documentation +/// Example item documentation +pub enum Bar {} + /// Example item documentation pub enum Foo {} ``` From 0675df612e6dd311a52b88e679dee9bd1e3b108e Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 6 Jan 2024 17:54:03 -0500 Subject: [PATCH 15/97] Don't remove inner attributes from `loop`, `for` and `while` expressions Fixes 5973 The [attribute] docs in the rust reference explain that inner attributes are allowed in block expressions. [attribute]: https://doc.rust-lang.org/reference/attributes.html --- src/expr.rs | 52 ++++++++++++++++++++++++++++++++------ tests/target/issue_5973.rs | 19 ++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 tests/target/issue_5973.rs diff --git a/src/expr.rs b/src/expr.rs index aec503099432e..f49829c8f46b4 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -678,6 +678,7 @@ pub(crate) fn rewrite_cond( // Abstraction over control flow expressions #[derive(Debug)] struct ControlFlow<'a> { + inner_attributes: Option>, cond: Option<&'a ast::Expr>, block: &'a ast::Block, else_block: Option<&'a ast::Expr>, @@ -702,6 +703,7 @@ fn extract_pats_and_cond(expr: &ast::Expr) -> (Option<&ast::Pat>, &ast::Expr) { // FIXME: Refactor this. fn to_control_flow(expr: &ast::Expr, expr_type: ExprType) -> Option> { + let inner_attributes = inner_attributes(&expr.attrs); match expr.kind { ast::ExprKind::If(ref cond, ref if_block, ref else_block) => { let (pat, cond) = extract_pats_and_cond(cond); @@ -716,14 +718,30 @@ fn to_control_flow(expr: &ast::Expr, expr_type: ExprType) -> Option Some(ControlFlow::new_for( - &f.pat, &f.iter, &f.body, f.label, expr.span, f.kind, + inner_attributes, + &f.pat, + &f.iter, + &f.body, + f.label, + expr.span, + f.kind, + )), + ast::ExprKind::Loop(ref block, label, _) => Some(ControlFlow::new_loop( + inner_attributes, + block, + label, + expr.span, )), - ast::ExprKind::Loop(ref block, label, _) => { - Some(ControlFlow::new_loop(block, label, expr.span)) - } ast::ExprKind::While(ref cond, ref block, label) => { let (pat, cond) = extract_pats_and_cond(cond); - Some(ControlFlow::new_while(pat, cond, block, label, expr.span)) + Some(ControlFlow::new_while( + inner_attributes, + pat, + cond, + block, + label, + expr.span, + )) } _ => None, } @@ -745,6 +763,7 @@ impl<'a> ControlFlow<'a> { ) -> ControlFlow<'a> { let matcher = choose_matcher(pat); ControlFlow { + inner_attributes: None, cond: Some(cond), block, else_block, @@ -760,8 +779,14 @@ impl<'a> ControlFlow<'a> { } } - fn new_loop(block: &'a ast::Block, label: Option, span: Span) -> ControlFlow<'a> { + fn new_loop( + inner_attributes: Vec, + block: &'a ast::Block, + label: Option, + span: Span, + ) -> ControlFlow<'a> { ControlFlow { + inner_attributes: Some(inner_attributes), cond: None, block, else_block: None, @@ -778,6 +803,7 @@ impl<'a> ControlFlow<'a> { } fn new_while( + inner_attributes: Vec, pat: Option<&'a ast::Pat>, cond: &'a ast::Expr, block: &'a ast::Block, @@ -786,6 +812,7 @@ impl<'a> ControlFlow<'a> { ) -> ControlFlow<'a> { let matcher = choose_matcher(pat); ControlFlow { + inner_attributes: Some(inner_attributes), cond: Some(cond), block, else_block: None, @@ -802,6 +829,7 @@ impl<'a> ControlFlow<'a> { } fn new_for( + inner_attributes: Vec, pat: &'a ast::Pat, cond: &'a ast::Expr, block: &'a ast::Block, @@ -810,6 +838,7 @@ impl<'a> ControlFlow<'a> { kind: ForLoopKind, ) -> ControlFlow<'a> { ControlFlow { + inner_attributes: Some(inner_attributes), cond: Some(cond), block, else_block: None, @@ -1138,8 +1167,15 @@ impl<'a> Rewrite for ControlFlow<'a> { let block_str = { let old_val = context.is_if_else_block.replace(self.else_block.is_some()); let old_is_loop = context.is_loop_block.replace(self.is_loop); - let result = - rewrite_block_with_visitor(context, "", self.block, None, None, block_shape, true); + let result = rewrite_block_with_visitor( + context, + "", + self.block, + self.inner_attributes.as_deref(), + None, + block_shape, + true, + ); context.is_loop_block.replace(old_is_loop); context.is_if_else_block.replace(old_val); result? diff --git a/tests/target/issue_5973.rs b/tests/target/issue_5973.rs new file mode 100644 index 0000000000000..244cf618691c4 --- /dev/null +++ b/tests/target/issue_5973.rs @@ -0,0 +1,19 @@ +fn main() { + while i < days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + i += 1; + } + + for i in 0..days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + i += 1; + } + + loop { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + i += 1; + } +} From 7e56db35cdc441ed32f73400cd2980c0ce2bc0bf Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:48:07 +0800 Subject: [PATCH 16/97] fix: prevent panic on inner attributes in a loop body inside a closure --- src/closures.rs | 9 ++++---- tests/source/issue_6209.rs | 33 ++++++++++++++++++++++++++++ tests/target/issue_6209.rs | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 tests/source/issue_6209.rs create mode 100644 tests/target/issue_6209.rs diff --git a/src/closures.rs b/src/closures.rs index 3b24f70d28d4d..9bd319a3a5fff 100644 --- a/src/closures.rs +++ b/src/closures.rs @@ -14,7 +14,7 @@ use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, Rew use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::types::rewrite_bound_params; -use crate::utils::{NodeIdExt, last_line_width, left_most_sub_expr, stmt_expr}; +use crate::utils::{NodeIdExt, last_line_width, left_most_sub_expr, outer_attributes, stmt_expr}; // This module is pretty messy because of the rules around closures and blocks: // FIXME - the below is probably no longer true in full. @@ -166,6 +166,8 @@ fn rewrite_closure_with_block( return Err(RewriteError::Unknown); } + // `body.attrs` may hold inner attributes from a nested block, e.g. `while cond { #![attr] }`. + let outer_attrs = outer_attributes(&body.attrs); let block = ast::Block { stmts: thin_vec![ast::Stmt { id: ast::NodeId::root(), @@ -174,8 +176,7 @@ fn rewrite_closure_with_block( }], id: ast::NodeId::root(), rules: ast::BlockCheckMode::Default, - span: body - .attrs + span: outer_attrs .first() .map(|attr| attr.span.to(body.span)) .unwrap_or(body.span), @@ -184,7 +185,7 @@ fn rewrite_closure_with_block( context, "", &block, - Some(&body.attrs), + Some(&outer_attrs), None, shape, false, diff --git a/tests/source/issue_6209.rs b/tests/source/issue_6209.rs new file mode 100644 index 0000000000000..78333d1d5cad5 --- /dev/null +++ b/tests/source/issue_6209.rs @@ -0,0 +1,33 @@ +fn while_loop() { + thread::spawn(|| { while i < days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + i += 1; + }}); +} + +fn for_loop() { + thread::spawn(|| { for i in 0..days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + }}); +} + +fn empty_loop_body() { + thread::spawn(|| { while true { + #![allow(clippy::indexing_slicing)] + }}); +} + +fn closure_body_without_block() { + thread::spawn(|| while i < days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + }); +} + +fn outer_attribute_still_formatted() { + thread::spawn(|| { #[allow(clippy::indexing_slicing)] while i < days.len() { + w |= days[i].bit_value(); + }}); +} diff --git a/tests/target/issue_6209.rs b/tests/target/issue_6209.rs new file mode 100644 index 0000000000000..89ef64290d3ce --- /dev/null +++ b/tests/target/issue_6209.rs @@ -0,0 +1,44 @@ +fn while_loop() { + thread::spawn(|| { + while i < days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + i += 1; + } + }); +} + +fn for_loop() { + thread::spawn(|| { + for i in 0..days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + } + }); +} + +fn empty_loop_body() { + thread::spawn(|| { + while true { + #![allow(clippy::indexing_slicing)] + } + }); +} + +fn closure_body_without_block() { + thread::spawn(|| { + while i < days.len() { + #![allow(clippy::indexing_slicing)] + w |= days[i].bit_value(); + } + }); +} + +fn outer_attribute_still_formatted() { + thread::spawn(|| { + #[allow(clippy::indexing_slicing)] + while i < days.len() { + w |= days[i].bit_value(); + } + }); +} From b48a7099b045271d0c91d0e228e78e42022d0e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 27 Jul 2026 20:06:42 +0200 Subject: [PATCH 17/97] Remove parallel limit on integration jobs We now have a much larger enterprise parallel job limit. --- .github/workflows/integration.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 9ef809bc5ccf7..af804af7150f8 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -13,11 +13,6 @@ jobs: runs-on: ubuntu-latest name: ${{ matrix.integration }} strategy: - # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits - # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. - # In order to prevent overusing too much of that 60 limit, we throttle the - # number of rustfmt jobs that will run concurrently. - max-parallel: 4 fail-fast: false matrix: integration: [ From d9753b1daf93bb5d0dea834fbdb6f35e2a91dd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 27 Jul 2026 20:02:47 +0200 Subject: [PATCH 18/97] Merge Linux, Windows and Mac CI into one workflow To make it easier to configure merge queue and reduce duplication. --- .github/workflows/linux.yml | 45 --------------- .github/workflows/mac.yml | 41 -------------- .github/workflows/test.yml | 104 ++++++++++++++++++++++++++++++++++ .github/workflows/windows.yml | 68 ---------------------- 4 files changed, 104 insertions(+), 154 deletions(-) delete mode 100644 .github/workflows/linux.yml delete mode 100644 .github/workflows/mac.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml deleted file mode 100644 index 77ab026f1d4b8..0000000000000 --- a/.github/workflows/linux.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: linux -on: - push: - branches: - - main - pull_request: - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) - env: - CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} - strategy: - # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits - # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. - # In order to prevent overusing too much of that 60 limit, we throttle the - # number of rustfmt jobs that will run concurrently. - max-parallel: 1 - fail-fast: false - matrix: - target: [ - x86_64-unknown-linux-gnu, - ] - cfg_release_channel: [nightly, stable] - - steps: - - name: checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - # Run build - - name: install rustup - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh - sh rustup-init.sh -y --default-toolchain none - rustup target add ${{ matrix.target }} - - - name: Build and Test - env: - RUSTFLAGS: -D warnings - CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true - run: cargo run --manifest-path ci/Cargo.toml build-and-test diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml deleted file mode 100644 index 28c218b729b22..0000000000000 --- a/.github/workflows/mac.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: mac -on: - push: - branches: - - 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 - runs-on: macos-latest - name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) - env: - CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} - strategy: - fail-fast: false - matrix: - target: [ - x86_64-apple-darwin, - ] - cfg_release_channel: [nightly, stable] - - steps: - - name: checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - # Run build - - name: install rustup - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh - sh rustup-init.sh -y --default-toolchain none - rustup target add ${{ matrix.target }} - - - name: Build and Test - env: - RUSTFLAGS: -D warnings - CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true - run: cargo run --manifest-path ci/Cargo.toml build-and-test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000000..49c44a429e748 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,104 @@ +name: test +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ${{ matrix.os }} + name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) + env: + CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} + strategy: + fail-fast: false + matrix: + build: [linux, macos, win32-gnu, win32-msvc, win64-gnu, win64-msvc] + cfg_release_channel: [nightly, stable] + include: + - build: linux + os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - build: macos + os: macos-latest + target: x86_64-apple-darwin + - build: win32-gnu + os: windows-latest + target: i686-pc-windows-gnu + - build: win32-msvc + os: windows-latest + target: i686-pc-windows-msvc + - build: win64-gnu + os: windows-latest + target: x86_64-pc-windows-gnu + - build: win64-msvc + os: windows-latest + target: x86_64-pc-windows-msvc + + steps: + - name: disable git eol translation + if: ${{ matrix.os == 'windows-latest' }} + run: git config --global core.autocrlf false + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rustup using win.rustup.rs + if: ${{ matrix.os == 'windows-latest' }} + run: | + # Disable the download progress bar which can cause perf issues + $ProgressPreference = "SilentlyContinue" + Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe + .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none + del rustup-init.exe + rustup target add ${{ matrix.target }} + shell: powershell + + - name: Add mingw32 to path for i686-gnu + run: | + echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH + if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly' + shell: bash + + - name: Add mingw64 to path for x86_64-gnu + run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH + if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly' + shell: bash + + - name: install rustup + if: ${{ matrix.os != 'windows-latest' }} + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh + sh rustup-init.sh -y --default-toolchain none + rustup target add ${{ matrix.target }} + + # Run build + - name: Build and Test + env: + RUSTFLAGS: -D warnings + CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true + run: cargo run --manifest-path ci/Cargo.toml build-and-test + + test-conclusion: + name: "test conclusion" + needs: + - test + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} + runs-on: ubuntu-latest + steps: + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful (or have been skipped). + jq --exit-status 'all(.result == "success" or .result == "skipped")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index 435132106e056..0000000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: windows -on: - push: - branches: - - main - pull_request: - -permissions: - contents: read - -jobs: - test: - runs-on: windows-latest - name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) - env: - CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} - strategy: - # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits - # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. - # In order to prevent overusing too much of that 60 limit, we throttle the - # number of rustfmt jobs that will run concurrently. - max-parallel: 2 - fail-fast: false - matrix: - target: [ - i686-pc-windows-gnu, - i686-pc-windows-msvc, - x86_64-pc-windows-gnu, - x86_64-pc-windows-msvc, - ] - cfg_release_channel: [nightly, stable] - - steps: - # The Windows runners have autocrlf enabled by default - # which causes failures for some of rustfmt's line-ending sensitive tests - - name: disable git eol translation - run: git config --global core.autocrlf false - - name: checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - # Run build - - name: Install Rustup using win.rustup.rs - run: | - # Disable the download progress bar which can cause perf issues - $ProgressPreference = "SilentlyContinue" - Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe - .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none - del rustup-init.exe - rustup target add ${{ matrix.target }} - shell: powershell - - - name: Add mingw32 to path for i686-gnu - run: | - echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH - if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly' - shell: bash - - - name: Add mingw64 to path for x86_64-gnu - run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH - if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly' - shell: bash - - - name: Build and Test - shell: cmd - env: - RUSTFLAGS: -D warnings - CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true - run: cargo run --manifest-path ci/Cargo.toml build-and-test From 5a16631a3479be1373c12f2f02e62d474beb88d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 27 Jul 2026 21:54:57 +0200 Subject: [PATCH 19/97] Use merge queue --- .github/workflows/integration.yml | 3 --- .github/workflows/rustdoc_check.yml | 4 +--- .github/workflows/test.yml | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index af804af7150f8..d248fc41dab5a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -1,8 +1,5 @@ name: integration on: - push: - branches: - - main pull_request: permissions: diff --git a/.github/workflows/rustdoc_check.yml b/.github/workflows/rustdoc_check.yml index 430185e3105b5..4a22d7801ab05 100644 --- a/.github/workflows/rustdoc_check.yml +++ b/.github/workflows/rustdoc_check.yml @@ -1,8 +1,6 @@ name: rustdoc check on: - push: - branches: - - main + merge_group: pull_request: permissions: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49c44a429e748..7202f03f5511c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,6 @@ name: test on: - push: - branches: - - main + merge_group: pull_request: permissions: From b1d0c144a0131041b401c8aae2d190f077ececa3 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:04:56 +0800 Subject: [PATCH 20/97] tests: cover macro calls following one with invalid syntax --- tests/source/macros.rs | 7 +++++++ tests/target/macros.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/tests/source/macros.rs b/tests/source/macros.rs index 3b286579ca8f1..2bcb8a787a223 100644 --- a/tests/source/macros.rs +++ b/tests/source/macros.rs @@ -484,3 +484,10 @@ f!(match a { // #3583 foo!(|x = y|); + +// #3757 +f!(match a { + _ => ( ), +}); + +g!( 1, 2, 3 ); diff --git a/tests/target/macros.rs b/tests/target/macros.rs index 7b4574349df3e..a8275efbe9935 100644 --- a/tests/target/macros.rs +++ b/tests/target/macros.rs @@ -1056,3 +1056,10 @@ f!(match a { // #3583 foo!(|x = y|); + +// #3757 +f!(match a { + _ => (), +}); + +g!(1, 2, 3); From 3d53170c2255fb337ca64d2d4c8789a7b6b9dd83 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:44:44 +0800 Subject: [PATCH 21/97] tests: cover trailing comments that end in a comma --- tests/source/issue_4037.rs | 24 ++++++++++++++++++++++++ tests/target/issue_4037.rs | 17 +++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/source/issue_4037.rs create mode 100644 tests/target/issue_4037.rs diff --git a/tests/source/issue_4037.rs b/tests/source/issue_4037.rs new file mode 100644 index 0000000000000..56dab88c69485 --- /dev/null +++ b/tests/source/issue_4037.rs @@ -0,0 +1,24 @@ +fn f() { + let x = 0; + match x { + 0 => {} + + + 1 => {} + _ => {} + + + // foo + // bar, + } +} + +fn wont_fmt() -> [(); 1] { + [ + () + + + // + // , + ] +} diff --git a/tests/target/issue_4037.rs b/tests/target/issue_4037.rs new file mode 100644 index 0000000000000..9380399c64f9e --- /dev/null +++ b/tests/target/issue_4037.rs @@ -0,0 +1,17 @@ +fn f() { + let x = 0; + match x { + 0 => {} + + 1 => {} + _ => {} // foo + // bar, + } +} + +fn wont_fmt() -> [(); 1] { + [ + (), // + // , + ] +} From 53bf7737f614389de33cc4f2c31ecd22e5c961cd Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Tue, 28 Jul 2026 11:06:19 -0400 Subject: [PATCH 22/97] fix: prevent panic when whitespace separates `macro_rules` and `!` (6990) Co-authored-by: Yacin Tmimi --- src/macros.rs | 2 +- tests/source/issue-6985.rs | 21 +++++++++++++++++++++ tests/target/issue-6985.rs | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/source/issue-6985.rs create mode 100644 tests/target/issue-6985.rs diff --git a/src/macros.rs b/src/macros.rs index 2a824b4ce30e7..e7277a9d26d83 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -431,7 +431,7 @@ pub(crate) fn rewrite_macro_def( }; let mut header = if def.macro_rules { - let pos = context.snippet_provider.span_after(span, "macro_rules!"); + let pos = context.snippet_provider.span_after(span, "!"); vec![HeaderPart::new("macro_rules!", span.with_hi(pos))] } else { let macro_lo = context.snippet_provider.span_before(span, "macro"); diff --git a/tests/source/issue-6985.rs b/tests/source/issue-6985.rs new file mode 100644 index 0000000000000..73e9fc8de4b32 --- /dev/null +++ b/tests/source/issue-6985.rs @@ -0,0 +1,21 @@ +macro_rules ! say_hello { + () => { + println!("Hello!") + }; +} + +macro_rules /* comment */ ! say_goodbye { + () => { + println!("Goodbye!") + }; +} + +macro_rules // comment +! do_nothing { + () => {}; +} + +fn main() { + say_hello!(); + say_goodbye!() +} diff --git a/tests/target/issue-6985.rs b/tests/target/issue-6985.rs new file mode 100644 index 0000000000000..b799937f6f521 --- /dev/null +++ b/tests/target/issue-6985.rs @@ -0,0 +1,20 @@ +macro_rules! say_hello { + () => { + println!("Hello!") + }; +} + +macro_rules! say_goodbye { + () => { + println!("Goodbye!") + }; +} + +macro_rules! do_nothing { + () => {}; +} + +fn main() { + say_hello!(); + say_goodbye!() +} From 9b49f7195aed3d609ca306c84d8c7241ea5226d4 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 21 Mar 2026 23:42:38 -0400 Subject: [PATCH 23/97] 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 485f44a936bd8..886128763c87a 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 040447ff1898f..d6337b71775cf 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 24/97] 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 50c0db8ac06e5..4e82df98de453 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 25/97] 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 407c4aa30ee41..5619d948fde97 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 2a824b4ce30e7..cfbbe383af8f0 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 0000000000000..7f1e945ba0e32 --- /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 0000000000000..ee12530e26b7e --- /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 26/97] 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 7f1e945ba0e32..1a520b1e1298b 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 ee12530e26b7e..149aa4f62f0b8 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 27/97] 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 d6337b71775cf..8d60402e0608a 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 28/97] 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 cfbbe383af8f0..2553ee19bbcce 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 149aa4f62f0b8..eca6cca2d9336 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 29/97] 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 2553ee19bbcce..63e2bd58e5512 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 8d60402e0608a..881df585c3582 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 30/97] 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 63e2bd58e5512..8564d35148950 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 31/97] 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 881df585c3582..7127445a18984 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 32/97] 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 8564d35148950..527140eca3a18 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 33/97] 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 1a520b1e1298b..794967afeb26b 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 eca6cca2d9336..fa242c7d2062f 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 6f91f6efaf2783c0b405efe02dc8e269c64e7ef2 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:36:06 +0800 Subject: [PATCH 34/97] fix: don't apply field init shorthand inside macro calls --- src/expr.rs | 7 +++++- tests/source/issue-6795.rs | 40 +++++++++++++++++++++++++++++++++ tests/target/issue-6795.rs | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/source/issue-6795.rs create mode 100644 tests/target/issue-6795.rs diff --git a/src/expr.rs b/src/expr.rs index f49829c8f46b4..fc91afb25e362 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1928,8 +1928,13 @@ pub(crate) fn rewrite_field( let expr = field.expr.rewrite_result(context, expr_shape); let is_lit = matches!(field.expr.kind, ast::ExprKind::Lit(_)); match expr { + // A macro can give `Field: value` its own meaning, so shortening `a: a` to `a` may + // change what it expands to. In `winnow::seq!` the result no longer compiles. Ok(ref e) - if !is_lit && e.as_str() == name && context.config.use_field_init_shorthand() => + if !is_lit + && e.as_str() == name + && context.config.use_field_init_shorthand() + && !context.inside_macro() => { Ok(attrs_str + name) } diff --git a/tests/source/issue-6795.rs b/tests/source/issue-6795.rs new file mode 100644 index 0000000000000..d1263ffc5d9ee --- /dev/null +++ b/tests/source/issue-6795.rs @@ -0,0 +1,40 @@ +// rustfmt-use_field_init_shorthand: true + +foo!(Foo { a: a }); + +fn main() { + foo!(Foo { a: a }); + foo![Foo { a: a }]; + foo! { Foo { a: a } } + + seq!(Task { a }); + seq!(Task { a: b }); + seq!(Task { a: 1 }); + seq!(Task { a: a, ..base }); + seq!(Task { #[attr] a: a }); + seq!(Task { a: Inner { b: b } }); + seq!(Task { a: a }.build()); + let _ = Foo { a: seq!(Task { b: b }) }; + + // `vec!` is formatted as an array, so the shorthand still applies. + vec![Foo { a: a }]; + vec![vec![Foo { a: a }]]; + foo!(vec![Foo { a: a }]); + + // std/core prelude macros, where the shorthand would be safe. + assert_eq!(Foo { a: a }, x); + debug_assert_eq!(Foo { a: a }, x); + matches!(v, Foo { a: a }); + assert_matches!(v, Foo { a: a }); + assert!(matches!(v, Foo { a: a })); + + // A shorthand already written by hand is kept. + assert_eq!(Foo { a }, x); + debug_assert_eq!(Foo { a }, x); + matches!(v, Foo { a }); + assert_matches!(v, Foo { a }); + assert!(matches!(v, Foo { a })); + + let _ = Foo { a: a }; + let _ = Foo { a: Inner { b: b } }; +} diff --git a/tests/target/issue-6795.rs b/tests/target/issue-6795.rs new file mode 100644 index 0000000000000..7de2d1140fb5d --- /dev/null +++ b/tests/target/issue-6795.rs @@ -0,0 +1,45 @@ +// rustfmt-use_field_init_shorthand: true + +foo!(Foo { a: a }); + +fn main() { + foo!(Foo { a: a }); + foo![Foo { a: a }]; + foo! { Foo { a: a } } + + seq!(Task { a }); + seq!(Task { a: b }); + seq!(Task { a: 1 }); + seq!(Task { a: a, ..base }); + seq!(Task { + #[attr] + a: a + }); + seq!(Task { a: Inner { b: b } }); + seq!(Task { a: a }.build()); + let _ = Foo { + a: seq!(Task { b: b }), + }; + + // `vec!` is formatted as an array, so the shorthand still applies. + vec![Foo { a }]; + vec![vec![Foo { a }]]; + foo!(vec![Foo { a: a }]); + + // std/core prelude macros, where the shorthand would be safe. + assert_eq!(Foo { a: a }, x); + debug_assert_eq!(Foo { a: a }, x); + matches!(v, Foo { a: a }); + assert_matches!(v, Foo { a: a }); + assert!(matches!(v, Foo { a: a })); + + // A shorthand already written by hand is kept. + assert_eq!(Foo { a }, x); + debug_assert_eq!(Foo { a }, x); + matches!(v, Foo { a }); + assert_matches!(v, Foo { a }); + assert!(matches!(v, Foo { a })); + + let _ = Foo { a }; + let _ = Foo { a: Inner { b } }; +} From 6eeeee302c2b15433d7e7086048a9f369b497ff2 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 23 Jul 2026 21:59:54 -0400 Subject: [PATCH 35/97] refactor: replace references to `feature` with `target` In the context of generating diffs I feel like `target` is a better term to use here instead of `feature`. --- check_diff/src/lib.rs | 72 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index d6bb7678ea029..5ca93d74b205b 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -108,13 +108,12 @@ pub enum CreateDiffError { /// Couldn't create a diff because the rustfmt binary compiled from the `main` branch /// failed to format the input. MainRustfmtFailed(FormatCodeError), - /// Couldn't create a diff because the rustfmt binary compiled from the `feature` branch - /// failed to format the input. - FeatureRustfmtFailed(FormatCodeError), + /// Couldn't create a diff because the target formatter failed to format the input. + TargetFormatterFailed(FormatCodeError), /// Couldn't create a diff because both rustfmt binaries failed to format the input BothRustfmtFailed { src: FormatCodeError, - feature: FormatCodeError, + target: FormatCodeError, }, } @@ -162,25 +161,25 @@ impl From for GitError { pub struct Diff { src_format: String, - feature_format: String, + target: String, } impl Display for Diff { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let patch = diffy::create_patch(self.src_format.as_str(), self.feature_format.as_str()); + let patch = diffy::create_patch(self.src_format.as_str(), self.target.as_str()); write!(f, "{}", patch) } } impl Diff { pub fn is_empty(&self) -> bool { - let patch = diffy::create_patch(self.src_format.as_str(), self.feature_format.as_str()); + let patch = diffy::create_patch(self.src_format.as_str(), self.target.as_str()); patch.hunks().is_empty() } } -pub struct CheckDiffRunners { - feature_runner: F, +pub struct CheckDiffRunners { + target: T, src_runner: S, } @@ -201,43 +200,42 @@ pub struct RustfmtRunner { config: Cow<'static, str>, } -impl CheckDiffRunners { - pub fn new(feature_runner: F, src_runner: S) -> Self { +impl CheckDiffRunners { + pub fn new(target: T, src_runner: S) -> Self { Self { - feature_runner, + target, src_runner, } } } -impl CheckDiffRunners +impl CheckDiffRunners where - F: CodeFormatter, + T: CodeFormatter, S: CodeFormatter, { - /// Creates a diff generated by running the source and feature binaries on the same file path + /// Creates a diff generated by running the source and target formatters on the same file path pub fn create_diff>(&self, path: P) -> Result { let src_format = self.src_runner.format_code_from_path(&path); - let feature_format = self.feature_runner.format_code_from_path(&path); + let target_format = self.target.format_code_from_path(&path); - match (src_format, feature_format) { - (Ok(s), Ok(f)) => Ok(Diff { + match (src_format, target_format) { + (Ok(s), Ok(t)) => Ok(Diff { src_format: s, - feature_format: f, + target: t, }), (Err(error), Ok(_)) => { // main formatting failed. Err(CreateDiffError::MainRustfmtFailed(error)) } (Ok(_), Err(error)) => { - // feature formatting failed - Err(CreateDiffError::FeatureRustfmtFailed(error)) + Err(CreateDiffError::TargetFormatterFailed(error)) } - (Err(src_error), Err(feature_error)) => { - // Both main formatting and feature formatting failed + (Err(src_error), Err(target_error)) => { + // Both main formatting and target formatting failed Err(CreateDiffError::BothRustfmtFailed { src: src_error, - feature: feature_error, + target: target_error, }) } } @@ -645,8 +643,8 @@ pub fn compile_rustfmt>( should_detach, )?; - let feature_runner = build_rustfmt_from_src( - dest.join("feature_rustfmt"), + let target_runner = build_rustfmt_from_src( + dest.join("target_rustfmt"), dest, edition, style_edition, @@ -658,15 +656,15 @@ pub fn compile_rustfmt>( "Runtime dependencies for (main) rustfmt -- {}: {}", dynamic_library_path_env_var, src_runner.dynamic_library_path ); - info!("FEATURE_BIN {}", feature_runner.get_binary_version()?); + info!("TARGET_BIN {}", target_runner.get_binary_version()?); info!( "Runtime dependencies for ({}) rustfmt -- {}: {}", - feature_branch, dynamic_library_path_env_var, feature_runner.dynamic_library_path + feature_branch, dynamic_library_path_env_var, target_runner.dynamic_library_path ); Ok(CheckDiffRunners { src_runner, - feature_runner, + target: target_runner, }) } @@ -792,7 +790,7 @@ pub fn clone_repositories_for_diff_check( map.into_values().collect() } -/// Calculates the number of errors when running the compiled binary and the feature binary on the +/// Calculates the number of errors when running the compiled binary and the target binary on the /// repo specified with the specific configs. pub fn check_diff_for_file<'repo, P: AsRef, F: AsRef>( runners: &CheckDiffRunners, @@ -830,22 +828,22 @@ pub fn check_diff_for_file<'repo, P: AsRef, F: AsRef>( ); Ok(()) } - Err(CreateDiffError::FeatureRustfmtFailed(e)) => { + Err(CreateDiffError::TargetFormatterFailed(e)) => { debug!( - "`feature` rustfmt failed to format {}/{}\n{:?}", + "`target` rustfmt failed to format {}/{}\n{:?}", repo_name, relative_path.display(), e, ); Ok(()) } - Err(CreateDiffError::BothRustfmtFailed { src, feature }) => { + Err(CreateDiffError::BothRustfmtFailed { src, target }) => { debug!( "Both rustfmt binaries failed to format {}/{}\n{:?}\n{:?}", repo_name, relative_path.display(), src, - feature, + target, ); Ok(()) } @@ -861,14 +859,14 @@ pub fn get_repo_name(git_url: &str) -> &str { repo_name } -pub fn check_diff<'repo, P, F, M>( - runners: &CheckDiffRunners, +pub fn check_diff<'repo, P, T, M>( + runners: &CheckDiffRunners, repositories: &'repo [Repository

], worker_threads: std::num::NonZeroU8, ) -> Vec<(Diff, PathBuf, &'repo Repository

)> where P: AsRef + Sync + Send, - F: CodeFormatter + Sync, + T: CodeFormatter + Sync, M: CodeFormatter + Sync, { let (tx, rx) = crossbeam_channel::unbounded(); From 5fa3ecd974ea2b948577fb3d30b05cbf2c5a622b Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 23 Jul 2026 22:11:36 -0400 Subject: [PATCH 36/97] refactor: replace references to `src` and `main` with `source` It's nice that `source` is the same length as `target`. In the future I'd like to be able to compare different rustfmt binaries, not just one compiled from the `main` branch and some `target` branch so using a more generic term like `source` insted `main` makes the most sense to me. --- check_diff/src/lib.rs | 58 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index 5ca93d74b205b..bfb6c09a25fd2 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -105,14 +105,13 @@ impl std::fmt::Debug for FormatCodeError { } pub enum CreateDiffError { - /// Couldn't create a diff because the rustfmt binary compiled from the `main` branch - /// failed to format the input. - MainRustfmtFailed(FormatCodeError), + /// Couldn't create a diff because the source formatter failed to format the input. + SourceFormatterFailed(FormatCodeError), /// Couldn't create a diff because the target formatter failed to format the input. TargetFormatterFailed(FormatCodeError), /// Couldn't create a diff because both rustfmt binaries failed to format the input BothRustfmtFailed { - src: FormatCodeError, + source: FormatCodeError, target: FormatCodeError, }, } @@ -160,27 +159,27 @@ impl From for GitError { } pub struct Diff { - src_format: String, + source: String, target: String, } impl Display for Diff { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let patch = diffy::create_patch(self.src_format.as_str(), self.target.as_str()); + let patch = diffy::create_patch(self.source.as_str(), self.target.as_str()); write!(f, "{}", patch) } } impl Diff { pub fn is_empty(&self) -> bool { - let patch = diffy::create_patch(self.src_format.as_str(), self.target.as_str()); + let patch = diffy::create_patch(self.source.as_str(), self.target.as_str()); patch.hunks().is_empty() } } pub struct CheckDiffRunners { target: T, - src_runner: S, + source: S, } pub trait CodeFormatter { @@ -201,10 +200,10 @@ pub struct RustfmtRunner { } impl CheckDiffRunners { - pub fn new(target: T, src_runner: S) -> Self { + pub fn new(target: T, source: S) -> Self { Self { target, - src_runner, + source, } } } @@ -216,25 +215,24 @@ where { /// Creates a diff generated by running the source and target formatters on the same file path pub fn create_diff>(&self, path: P) -> Result { - let src_format = self.src_runner.format_code_from_path(&path); + let source_format = self.source.format_code_from_path(&path); let target_format = self.target.format_code_from_path(&path); - match (src_format, target_format) { + match (source_format, target_format) { (Ok(s), Ok(t)) => Ok(Diff { - src_format: s, + source: s, target: t, }), (Err(error), Ok(_)) => { - // main formatting failed. - Err(CreateDiffError::MainRustfmtFailed(error)) + Err(CreateDiffError::SourceFormatterFailed(error)) } (Ok(_), Err(error)) => { Err(CreateDiffError::TargetFormatterFailed(error)) } - (Err(src_error), Err(target_error)) => { - // Both main formatting and target formatting failed + (Err(source_error), Err(target_error)) => { + // Both source formatting and target formatting failed Err(CreateDiffError::BothRustfmtFailed { - src: src_error, + source: source_error, target: target_error, }) } @@ -630,8 +628,8 @@ pub fn compile_rustfmt>( let cargo_version = get_cargo_version()?; info!("Compiling with {}", cargo_version); - let src_runner = build_rustfmt_from_src( - dest.join("src_rustfmt"), + let source_runner = build_rustfmt_from_src( + dest.join("source_rustfmt"), dest, edition, style_edition, @@ -650,11 +648,11 @@ pub fn compile_rustfmt>( style_edition, config, )?; - info!("RUSFMT_BIN {}", src_runner.get_binary_version()?); + info!("SOURCE_BIN {}", source_runner.get_binary_version()?); let dynamic_library_path_env_var = dynamic_library_path_env_var_name(); info!( "Runtime dependencies for (main) rustfmt -- {}: {}", - dynamic_library_path_env_var, src_runner.dynamic_library_path + dynamic_library_path_env_var, source_runner.dynamic_library_path ); info!("TARGET_BIN {}", target_runner.get_binary_version()?); info!( @@ -663,7 +661,7 @@ pub fn compile_rustfmt>( ); Ok(CheckDiffRunners { - src_runner, + source: source_runner, target: target_runner, }) } @@ -819,9 +817,9 @@ pub fn check_diff_for_file<'repo, P: AsRef, F: AsRef>( Ok(()) } } - Err(CreateDiffError::MainRustfmtFailed(e)) => { + Err(CreateDiffError::SourceFormatterFailed(e)) => { debug!( - "`main` rustfmt failed to format {}/{}\n{:?}", + "`source` rustfmt failed to format {}/{}\n{:?}", repo_name, relative_path.display(), e, @@ -837,12 +835,12 @@ pub fn check_diff_for_file<'repo, P: AsRef, F: AsRef>( ); Ok(()) } - Err(CreateDiffError::BothRustfmtFailed { src, target }) => { + Err(CreateDiffError::BothRustfmtFailed { source, target }) => { debug!( "Both rustfmt binaries failed to format {}/{}\n{:?}\n{:?}", repo_name, relative_path.display(), - src, + source, target, ); Ok(()) @@ -859,15 +857,15 @@ pub fn get_repo_name(git_url: &str) -> &str { repo_name } -pub fn check_diff<'repo, P, T, M>( - runners: &CheckDiffRunners, +pub fn check_diff<'repo, P, T, S>( + runners: &CheckDiffRunners, repositories: &'repo [Repository

], worker_threads: std::num::NonZeroU8, ) -> Vec<(Diff, PathBuf, &'repo Repository

)> where P: AsRef + Sync + Send, T: CodeFormatter + Sync, - M: CodeFormatter + Sync, + S: CodeFormatter + Sync, { let (tx, rx) = crossbeam_channel::unbounded(); From 2544f3edf41de532750ad08250eb5027a06bd0bb Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 23 Jul 2026 22:48:36 -0400 Subject: [PATCH 37/97] refactor: rename `CheckDiffRunners` to `DiffChecker` Also change the order of fields and generic parameters in the source code so that `source` comes before `target`. It feels more asthetic to me that `source` come before `target`. --- check_diff/src/lib.rs | 30 +++++++++++++++--------------- check_diff/src/main.rs | 4 ++-- check_diff/tests/check_diff.rs | 10 +++++----- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index bfb6c09a25fd2..a7279b7233989 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -177,9 +177,9 @@ impl Diff { } } -pub struct CheckDiffRunners { - target: T, +pub struct DiffChecker { source: S, + target: T, } pub trait CodeFormatter { @@ -199,19 +199,19 @@ pub struct RustfmtRunner { config: Cow<'static, str>, } -impl CheckDiffRunners { - pub fn new(target: T, source: S) -> Self { +impl DiffChecker { + pub fn new(source: S, target: T) -> Self { Self { - target, source, + target, } } } -impl CheckDiffRunners +impl DiffChecker where - T: CodeFormatter, S: CodeFormatter, + T: CodeFormatter, { /// Creates a diff generated by running the source and target formatters on the same file path pub fn create_diff>(&self, path: P) -> Result { @@ -618,7 +618,7 @@ pub fn compile_rustfmt>( style_edition: StyleEdition, commit_hash: Option, config: Option<&[T]>, -) -> Result, CheckDiffError> { +) -> Result, CheckDiffError> { const RUSTFMT_REPO: &str = "https://github.com/rust-lang/rustfmt.git"; clone_git_repo(RUSTFMT_REPO, dest)?; @@ -660,7 +660,7 @@ pub fn compile_rustfmt>( feature_branch, dynamic_library_path_env_var, target_runner.dynamic_library_path ); - Ok(CheckDiffRunners { + Ok(DiffChecker { source: source_runner, target: target_runner, }) @@ -791,7 +791,7 @@ pub fn clone_repositories_for_diff_check( /// Calculates the number of errors when running the compiled binary and the target binary on the /// repo specified with the specific configs. pub fn check_diff_for_file<'repo, P: AsRef, F: AsRef>( - runners: &CheckDiffRunners, + diff_checker: &DiffChecker, repo: &'repo Repository

, file: F, ) -> Result<(), (Diff, F, &'repo Repository

)> { @@ -804,7 +804,7 @@ pub fn check_diff_for_file<'repo, P: AsRef, F: AsRef>( relative_path.display() ); - match runners.create_diff(file.as_ref()) { + match diff_checker.create_diff(file.as_ref()) { Ok(diff) => { if !diff.is_empty() { Err((diff, file, repo)) @@ -857,15 +857,15 @@ pub fn get_repo_name(git_url: &str) -> &str { repo_name } -pub fn check_diff<'repo, P, T, S>( - runners: &CheckDiffRunners, +pub fn check_diff<'repo, P, S, T>( + diff_checker: &DiffChecker, repositories: &'repo [Repository

], worker_threads: std::num::NonZeroU8, ) -> Vec<(Diff, PathBuf, &'repo Repository

)> where P: AsRef + Sync + Send, - T: CodeFormatter + Sync, S: CodeFormatter + Sync, + T: CodeFormatter + Sync, { let (tx, rx) = crossbeam_channel::unbounded(); @@ -896,7 +896,7 @@ where let rx = rx.clone(); s.spawn(move || { while let Ok((file, repo)) = rx.recv() { - if let Err(e) = check_diff_for_file(runners, repo, file) { + if let Err(e) = check_diff_for_file(diff_checker, repo, file) { // Push errors to report on later errors.lock().unwrap().push(e); } diff --git a/check_diff/src/main.rs b/check_diff/src/main.rs index bffe090f7ace6..812179d3417c9 100644 --- a/check_diff/src/main.rs +++ b/check_diff/src/main.rs @@ -87,7 +87,7 @@ fn main() -> Result { args.rustfmt_config.as_deref(), ); - let check_diff_runners = match compilation_result { + let diff_checker = match compilation_result { Ok(runner) => runner, Err(e) => { error!("Failed to compile rustfmt:\n{e:?}"); @@ -99,7 +99,7 @@ fn main() -> Result { let repositories = clone_repositories_for_diff_check(REPOS); info!("Starting the Diff Check"); - let errors = check_diff(&check_diff_runners, &repositories, args.worker_threads); + let errors = check_diff(&diff_checker, &repositories, args.worker_threads); if errors.is_empty() { info!("No diff found 😊"); diff --git a/check_diff/tests/check_diff.rs b/check_diff/tests/check_diff.rs index b1b4001bac178..7dceeccd050e3 100644 --- a/check_diff/tests/check_diff.rs +++ b/check_diff/tests/check_diff.rs @@ -1,5 +1,5 @@ use check_diff::{ - CheckDiffError, CheckDiffRunners, CodeFormatter, FormatCodeError, Repository, + CheckDiffError, DiffChecker, CodeFormatter, FormatCodeError, Repository, RustFmtFileFinder, check_diff, }; use std::fs::File; @@ -67,7 +67,7 @@ fn search_for_files_correctly_nested() -> Result<(), Box> #[test] fn check_diff_test_no_formatting_difference() -> Result<(), CheckDiffError> { - let runners = CheckDiffRunners::new(DoNothingFormatter, DoNothingFormatter); + let diff_checker = DiffChecker::new(DoNothingFormatter, DoNothingFormatter); let dir = Builder::new().tempdir_in("").unwrap(); let file_path = dir.path().join("test.rs"); @@ -76,14 +76,14 @@ fn check_diff_test_no_formatting_difference() -> Result<(), CheckDiffError> { let repos = [repo]; let workers = std::num::NonZeroU8::new(1).unwrap(); - let errors = check_diff(&runners, &repos, workers); + let errors = check_diff(&diff_checker, &repos, workers); assert_eq!(errors.len(), 0); Ok(()) } #[test] fn check_diff_test_formatting_difference() -> Result<(), CheckDiffError> { - let runners = CheckDiffRunners::new(DoNothingFormatter, AddWhiteSpaceFormatter); + let diff_checker = DiffChecker::new(DoNothingFormatter, AddWhiteSpaceFormatter); let dir = Builder::new().tempdir_in("").unwrap(); let file_path = dir.path().join("test.rs"); let _tmp_file = File::create(file_path)?; @@ -91,7 +91,7 @@ fn check_diff_test_formatting_difference() -> Result<(), CheckDiffError> { let repos = [repo]; let workers = std::num::NonZeroU8::new(1).unwrap(); - let errors = check_diff(&runners, &repos, workers); + let errors = check_diff(&diff_checker, &repos, workers); assert_ne!(errors.len(), 0); Ok(()) } From c226ebaef238caae77c730f87430634b29ff75c8 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Fri, 31 Jul 2026 22:18:50 -0400 Subject: [PATCH 38/97] fix formatting --- check_diff/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index a7279b7233989..210b62fe9c2bf 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -201,10 +201,7 @@ pub struct RustfmtRunner { impl DiffChecker { pub fn new(source: S, target: T) -> Self { - Self { - source, - target, - } + Self { source, target } } } @@ -223,12 +220,8 @@ where source: s, target: t, }), - (Err(error), Ok(_)) => { - Err(CreateDiffError::SourceFormatterFailed(error)) - } - (Ok(_), Err(error)) => { - Err(CreateDiffError::TargetFormatterFailed(error)) - } + (Err(error), Ok(_)) => Err(CreateDiffError::SourceFormatterFailed(error)), + (Ok(_), Err(error)) => Err(CreateDiffError::TargetFormatterFailed(error)), (Err(source_error), Err(target_error)) => { // Both source formatting and target formatting failed Err(CreateDiffError::BothRustfmtFailed { From 80cae9809f754de2478be24d3b18c21bd07675cc Mon Sep 17 00:00:00 2001 From: Jan Koscisz Date: Mon, 27 Jul 2026 13:55:49 +0200 Subject: [PATCH 39/97] fix: Fmt struct field patters with cfgs --- src/patterns.rs | 9 ++- tests/source/issue_5982_style_edition_2024.rs | 67 +++++++++++++++++++ tests/source/issue_5982_style_edition_2027.rs | 67 +++++++++++++++++++ tests/target/issue_5982_style_edition_2024.rs | 67 +++++++++++++++++++ tests/target/issue_5982_style_edition_2027.rs | 67 +++++++++++++++++++ 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 tests/source/issue_5982_style_edition_2024.rs create mode 100644 tests/source/issue_5982_style_edition_2027.rs create mode 100644 tests/target/issue_5982_style_edition_2024.rs create mode 100644 tests/target/issue_5982_style_edition_2027.rs diff --git a/src/patterns.rs b/src/patterns.rs index 2fad1d41ae9f7..727517b99df6e 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -468,12 +468,19 @@ impl Rewrite for PatField { self.pat.rewrite_result(context, nested_shape)? ) }; + + let combine_shape = if context.config.style_edition() >= StyleEdition::Edition2027 { + shape + } else { + nested_shape + }; + combine_strs_with_missing_comments( context, &attrs_str, &pat_and_id_str, mk_sp(hi_pos, self.pat.span.lo()), - nested_shape, + combine_shape, false, ) } diff --git a/tests/source/issue_5982_style_edition_2024.rs b/tests/source/issue_5982_style_edition_2024.rs new file mode 100644 index 0000000000000..a3422e6303658 --- /dev/null +++ b/tests/source/issue_5982_style_edition_2024.rs @@ -0,0 +1,67 @@ +// rustfmt-style_edition: 2024 +// See https://github.com/rust-lang/rustfmt/issues/5982 and +// https://github.com/rust-lang/rustfmt/issues/5920 + +// https://github.com/rust-lang/rustfmt/issues/5982 +struct Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: i32, + #[cfg(all())] + something_long_enough_to_wrap_bar: i32, +} + +fn example() { + let Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo, + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// The same, but with leading comments on the fields. +fn example_with_comments() { + let Foo { + // comment on shorthand field + #[cfg(all())] + something_long_enough_to_wrap_foo, + // comment on non-shorthand field + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// https://github.com/rust-lang/rustfmt/issues/5920 +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/source/issue_5982_style_edition_2027.rs b/tests/source/issue_5982_style_edition_2027.rs new file mode 100644 index 0000000000000..8569d1d474fa4 --- /dev/null +++ b/tests/source/issue_5982_style_edition_2027.rs @@ -0,0 +1,67 @@ +// rustfmt-style_edition: 2027 +// See https://github.com/rust-lang/rustfmt/issues/5982 and +// https://github.com/rust-lang/rustfmt/issues/5920 + +// https://github.com/rust-lang/rustfmt/issues/5982 +struct Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: i32, + #[cfg(all())] + something_long_enough_to_wrap_bar: i32, +} + +fn example() { + let Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo, + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// The same, but with leading comments on the fields. +fn example_with_comments() { + let Foo { + // comment on shorthand field + #[cfg(all())] + something_long_enough_to_wrap_foo, + // comment on non-shorthand field + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// https://github.com/rust-lang/rustfmt/issues/5920 +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/target/issue_5982_style_edition_2024.rs b/tests/target/issue_5982_style_edition_2024.rs new file mode 100644 index 0000000000000..9db33d44df1f2 --- /dev/null +++ b/tests/target/issue_5982_style_edition_2024.rs @@ -0,0 +1,67 @@ +// rustfmt-style_edition: 2024 +// See https://github.com/rust-lang/rustfmt/issues/5982 and +// https://github.com/rust-lang/rustfmt/issues/5920 + +// https://github.com/rust-lang/rustfmt/issues/5982 +struct Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: i32, + #[cfg(all())] + something_long_enough_to_wrap_bar: i32, +} + +fn example() { + let Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo, + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// The same, but with leading comments on the fields. +fn example_with_comments() { + let Foo { + // comment on shorthand field + #[cfg(all())] + something_long_enough_to_wrap_foo, + // comment on non-shorthand field + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// https://github.com/rust-lang/rustfmt/issues/5920 +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/target/issue_5982_style_edition_2027.rs b/tests/target/issue_5982_style_edition_2027.rs new file mode 100644 index 0000000000000..8569d1d474fa4 --- /dev/null +++ b/tests/target/issue_5982_style_edition_2027.rs @@ -0,0 +1,67 @@ +// rustfmt-style_edition: 2027 +// See https://github.com/rust-lang/rustfmt/issues/5982 and +// https://github.com/rust-lang/rustfmt/issues/5920 + +// https://github.com/rust-lang/rustfmt/issues/5982 +struct Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: i32, + #[cfg(all())] + something_long_enough_to_wrap_bar: i32, +} + +fn example() { + let Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo, + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// The same, but with leading comments on the fields. +fn example_with_comments() { + let Foo { + // comment on shorthand field + #[cfg(all())] + something_long_enough_to_wrap_foo, + // comment on non-shorthand field + #[cfg(all())] + something_long_enough_to_wrap_bar: bar_var, + } = Foo { + #[cfg(all())] + something_long_enough_to_wrap_foo: 111, + #[cfg(all())] + something_long_enough_to_wrap_bar: 222, + }; +} + +// https://github.com/rust-lang/rustfmt/issues/5920 +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} From fbdafded756094d21e425253ba62bfe8d475620f Mon Sep 17 00:00:00 2001 From: Jan Koscisz Date: Mon, 27 Jul 2026 15:52:04 +0200 Subject: [PATCH 40/97] split tests --- tests/source/issue_5920_style_edition_2024.rs | 25 +++++++++++++++++ tests/source/issue_5920_style_edition_2027.rs | 25 +++++++++++++++++ tests/source/issue_5982_style_edition_2024.rs | 28 ------------------- tests/source/issue_5982_style_edition_2027.rs | 28 ------------------- tests/target/issue_5920_style_edition_2024.rs | 25 +++++++++++++++++ tests/target/issue_5920_style_edition_2027.rs | 25 +++++++++++++++++ tests/target/issue_5982_style_edition_2024.rs | 28 ------------------- tests/target/issue_5982_style_edition_2027.rs | 28 ------------------- 8 files changed, 100 insertions(+), 112 deletions(-) create mode 100644 tests/source/issue_5920_style_edition_2024.rs create mode 100644 tests/source/issue_5920_style_edition_2027.rs create mode 100644 tests/target/issue_5920_style_edition_2024.rs create mode 100644 tests/target/issue_5920_style_edition_2027.rs diff --git a/tests/source/issue_5920_style_edition_2024.rs b/tests/source/issue_5920_style_edition_2024.rs new file mode 100644 index 0000000000000..7002338f9fc50 --- /dev/null +++ b/tests/source/issue_5920_style_edition_2024.rs @@ -0,0 +1,25 @@ +// rustfmt-style_edition: 2024 + +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/source/issue_5920_style_edition_2027.rs b/tests/source/issue_5920_style_edition_2027.rs new file mode 100644 index 0000000000000..9d040a37699e3 --- /dev/null +++ b/tests/source/issue_5920_style_edition_2027.rs @@ -0,0 +1,25 @@ +// rustfmt-style_edition: 2027 + +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/source/issue_5982_style_edition_2024.rs b/tests/source/issue_5982_style_edition_2024.rs index a3422e6303658..01d6a92a4501d 100644 --- a/tests/source/issue_5982_style_edition_2024.rs +++ b/tests/source/issue_5982_style_edition_2024.rs @@ -1,8 +1,5 @@ // rustfmt-style_edition: 2024 -// See https://github.com/rust-lang/rustfmt/issues/5982 and -// https://github.com/rust-lang/rustfmt/issues/5920 -// https://github.com/rust-lang/rustfmt/issues/5982 struct Foo { #[cfg(all())] something_long_enough_to_wrap_foo: i32, @@ -40,28 +37,3 @@ fn example_with_comments() { something_long_enough_to_wrap_bar: 222, }; } - -// https://github.com/rust-lang/rustfmt/issues/5920 -struct Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), -} - -fn main() { - let Demo { - field_name_foo, - #[cfg(feature = "diagnostics")] - field_name_baz: _, - field_name_bar, - field_name_tux, - } = Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), - }; -} diff --git a/tests/source/issue_5982_style_edition_2027.rs b/tests/source/issue_5982_style_edition_2027.rs index 8569d1d474fa4..f622cd89b47e5 100644 --- a/tests/source/issue_5982_style_edition_2027.rs +++ b/tests/source/issue_5982_style_edition_2027.rs @@ -1,8 +1,5 @@ // rustfmt-style_edition: 2027 -// See https://github.com/rust-lang/rustfmt/issues/5982 and -// https://github.com/rust-lang/rustfmt/issues/5920 -// https://github.com/rust-lang/rustfmt/issues/5982 struct Foo { #[cfg(all())] something_long_enough_to_wrap_foo: i32, @@ -40,28 +37,3 @@ fn example_with_comments() { something_long_enough_to_wrap_bar: 222, }; } - -// https://github.com/rust-lang/rustfmt/issues/5920 -struct Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), -} - -fn main() { - let Demo { - field_name_foo, - #[cfg(feature = "diagnostics")] - field_name_baz: _, - field_name_bar, - field_name_tux, - } = Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), - }; -} diff --git a/tests/target/issue_5920_style_edition_2024.rs b/tests/target/issue_5920_style_edition_2024.rs new file mode 100644 index 0000000000000..1ca2b208dd6c1 --- /dev/null +++ b/tests/target/issue_5920_style_edition_2024.rs @@ -0,0 +1,25 @@ +// rustfmt-style_edition: 2024 + +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/target/issue_5920_style_edition_2027.rs b/tests/target/issue_5920_style_edition_2027.rs new file mode 100644 index 0000000000000..9d040a37699e3 --- /dev/null +++ b/tests/target/issue_5920_style_edition_2027.rs @@ -0,0 +1,25 @@ +// rustfmt-style_edition: 2027 + +struct Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), +} + +fn main() { + let Demo { + field_name_foo, + #[cfg(feature = "diagnostics")] + field_name_baz: _, + field_name_bar, + field_name_tux, + } = Demo { + field_name_foo: (), + field_name_bar: (), + #[cfg(feature = "diagnostics")] + field_name_baz: (), + field_name_tux: (), + }; +} diff --git a/tests/target/issue_5982_style_edition_2024.rs b/tests/target/issue_5982_style_edition_2024.rs index 9db33d44df1f2..fe5f4a8987cb1 100644 --- a/tests/target/issue_5982_style_edition_2024.rs +++ b/tests/target/issue_5982_style_edition_2024.rs @@ -1,8 +1,5 @@ // rustfmt-style_edition: 2024 -// See https://github.com/rust-lang/rustfmt/issues/5982 and -// https://github.com/rust-lang/rustfmt/issues/5920 -// https://github.com/rust-lang/rustfmt/issues/5982 struct Foo { #[cfg(all())] something_long_enough_to_wrap_foo: i32, @@ -40,28 +37,3 @@ fn example_with_comments() { something_long_enough_to_wrap_bar: 222, }; } - -// https://github.com/rust-lang/rustfmt/issues/5920 -struct Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), -} - -fn main() { - let Demo { - field_name_foo, - #[cfg(feature = "diagnostics")] - field_name_baz: _, - field_name_bar, - field_name_tux, - } = Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), - }; -} diff --git a/tests/target/issue_5982_style_edition_2027.rs b/tests/target/issue_5982_style_edition_2027.rs index 8569d1d474fa4..f622cd89b47e5 100644 --- a/tests/target/issue_5982_style_edition_2027.rs +++ b/tests/target/issue_5982_style_edition_2027.rs @@ -1,8 +1,5 @@ // rustfmt-style_edition: 2027 -// See https://github.com/rust-lang/rustfmt/issues/5982 and -// https://github.com/rust-lang/rustfmt/issues/5920 -// https://github.com/rust-lang/rustfmt/issues/5982 struct Foo { #[cfg(all())] something_long_enough_to_wrap_foo: i32, @@ -40,28 +37,3 @@ fn example_with_comments() { something_long_enough_to_wrap_bar: 222, }; } - -// https://github.com/rust-lang/rustfmt/issues/5920 -struct Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), -} - -fn main() { - let Demo { - field_name_foo, - #[cfg(feature = "diagnostics")] - field_name_baz: _, - field_name_bar, - field_name_tux, - } = Demo { - field_name_foo: (), - field_name_bar: (), - #[cfg(feature = "diagnostics")] - field_name_baz: (), - field_name_tux: (), - }; -} From 8f2a9c38d963a26972653beac7192be4b8f28c6b Mon Sep 17 00:00:00 2001 From: Do Tuan Anh <96874463+DoTuanAnh2k1@users.noreply.github.com> Date: Sat, 1 Aug 2026 09:24:34 +0700 Subject: [PATCH 41/97] Reject `--config=emit_mode=...` when `--check` is set `rustfmt --check --config emit_mode=files ` bypassed the existing `--emit files` + `--check` incompatibility check and formatted the file in place. Reject an `emit_mode` supplied through `--config` in check mode too, so `--check` stays non-mutating. --- src/bin/main.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/bin/main.rs b/src/bin/main.rs index 54b267bb44580..06ae49ed75610 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -630,6 +630,12 @@ impl GetOptsOptions { options.emit_mode = Some(emit_mode_from_emit_str(emit_str)?); } + if options.check && options.inline_config.contains_key("emit_mode") { + return Err(format_err!( + "Invalid to use `--config=emit_mode=` and `--check`" + )); + } + if let Some(ref edition_str) = matches.opt_str("edition") { options.edition = Some(edition_from_edition_str(edition_str)?); } @@ -826,6 +832,18 @@ mod test { assert_eq!(config.style_edition(), StyleEdition::Edition2024); } + #[test] + fn check_rejects_emit_mode_from_inline_config() { + // Regression for #6999: `--check` is non-mutating, so an `emit_mode` + // supplied through `--config` (which could otherwise write the file in + // place) is rejected, just like `--emit` is. + let opts = make_opts(); + let matches = opts + .parse(["--check", "--config", "emit_mode=Files"]) + .unwrap(); + assert!(GetOptsOptions::from_matches(&matches).is_err()); + } + #[nightly_only_test] #[test] fn version_config_file_sets_style_edition_override_correctly() { From a77fdf4e61b82e63d8d06e5904573855d583c9fb Mon Sep 17 00:00:00 2001 From: Do Tuan Anh <96874463+DoTuanAnh2k1@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:26:23 +0700 Subject: [PATCH 42/97] Also reject `--config=emit_mode` with `--emit`, and test all emit modes --- src/bin/main.rs | 52 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 06ae49ed75610..af2dfdfac5b9d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -630,10 +630,17 @@ impl GetOptsOptions { options.emit_mode = Some(emit_mode_from_emit_str(emit_str)?); } - if options.check && options.inline_config.contains_key("emit_mode") { - return Err(format_err!( - "Invalid to use `--config=emit_mode=` and `--check`" - )); + if options.inline_config.contains_key("emit_mode") { + if options.check { + return Err(format_err!( + "Invalid to use `--config=emit_mode=` and `--check`" + )); + } + if options.emit_mode.is_some() { + return Err(format_err!( + "Invalid to use `--config=emit_mode=` and `--emit`" + )); + } } if let Some(ref edition_str) = matches.opt_str("edition") { @@ -833,15 +840,34 @@ mod test { } #[test] - fn check_rejects_emit_mode_from_inline_config() { - // Regression for #6999: `--check` is non-mutating, so an `emit_mode` - // supplied through `--config` (which could otherwise write the file in - // place) is rejected, just like `--emit` is. - let opts = make_opts(); - let matches = opts - .parse(["--check", "--config", "emit_mode=Files"]) - .unwrap(); - assert!(GetOptsOptions::from_matches(&matches).is_err()); + fn emit_mode_from_inline_config_is_rejected() { + // Regression for #6999. + let emit_modes = [ + "Files", + "Stdout", + "Coverage", + "Checkstyle", + "Json", + "ModifiedLines", + "Diff", + ]; + for mode in emit_modes { + let config = format!("emit_mode={mode}"); + + let matches = make_opts().parse(["--check", "--config", &config]).unwrap(); + assert!( + GetOptsOptions::from_matches(&matches).is_err(), + "`--check` with `--config={config}` should be rejected" + ); + + let matches = make_opts() + .parse(["--emit", "stdout", "--config", &config]) + .unwrap(); + assert!( + GetOptsOptions::from_matches(&matches).is_err(), + "`--emit` with `--config={config}` should be rejected" + ); + } } #[nightly_only_test] From bc1582356cf39ef54f747652669311a50186f083 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Aug 2026 15:06:55 -0400 Subject: [PATCH 43/97] fix: Use `ident` span when recovering comments in non-shorthand `PatField` Fixes 6984 Using the `pat` span wasn't correct because it contained the field identifier, which caused code duplication when recovering comments between the attribute and the pattern struct field. --- src/patterns.rs | 2 +- tests/target/issue_6984_style_edition_2024.rs | 13 +++++++++++++ tests/target/issue_6984_style_edition_2027.rs | 13 +++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/target/issue_6984_style_edition_2024.rs create mode 100644 tests/target/issue_6984_style_edition_2027.rs diff --git a/src/patterns.rs b/src/patterns.rs index 727517b99df6e..bbc8cca197ba4 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -479,7 +479,7 @@ impl Rewrite for PatField { context, &attrs_str, &pat_and_id_str, - mk_sp(hi_pos, self.pat.span.lo()), + mk_sp(hi_pos, self.ident.span.lo()), combine_shape, false, ) diff --git a/tests/target/issue_6984_style_edition_2024.rs b/tests/target/issue_6984_style_edition_2024.rs new file mode 100644 index 0000000000000..001d5cdf14650 --- /dev/null +++ b/tests/target/issue_6984_style_edition_2024.rs @@ -0,0 +1,13 @@ +// rustfmt-style_edition: 2024 + +fn main() { + let Demo { + #[cfg(feature = "diagnostics")] + // comment between attribute and field + field_name_baz: _, + + #[cfg(feature = "diagnostics")] + // comment between attribute and shorthand field + field_name_bar, + } = value; +} diff --git a/tests/target/issue_6984_style_edition_2027.rs b/tests/target/issue_6984_style_edition_2027.rs new file mode 100644 index 0000000000000..eef2abc70cf9a --- /dev/null +++ b/tests/target/issue_6984_style_edition_2027.rs @@ -0,0 +1,13 @@ +// rustfmt-style_edition: 2027 + +fn main() { + let Demo { + #[cfg(feature = "diagnostics")] + // comment between attribute and field + field_name_baz: _, + + #[cfg(feature = "diagnostics")] + // comment between attribute and shorthand field + field_name_bar, + } = value; +} From ad3a162bc751f92be753e6fd5dec4835ee75ab35 Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:34:48 +0300 Subject: [PATCH 44/97] Fix brace placement for multiline control flow --- src/expr.rs | 7 ++++++- tests/source/issue_7003_style_edition_2024.rs | 14 ++++++++++++++ tests/source/issue_7003_style_edition_2027.rs | 14 ++++++++++++++ tests/target/issue_7003_style_edition_2024.rs | 14 ++++++++++++++ tests/target/issue_7003_style_edition_2027.rs | 15 +++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/source/issue_7003_style_edition_2024.rs create mode 100644 tests/source/issue_7003_style_edition_2027.rs create mode 100644 tests/target/issue_7003_style_edition_2024.rs create mode 100644 tests/target/issue_7003_style_edition_2027.rs diff --git a/src/expr.rs b/src/expr.rs index fc91afb25e362..0b1ec0e7d937a 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1009,10 +1009,15 @@ impl<'a> ControlFlow<'a> { .config .max_width() .saturating_sub(constr_shape.used_width() + offset + brace_overhead); + let first_line_indent = if context.config.style_edition() >= StyleEdition::Edition2027 { + shape.indent.width() + } else { + shape.used_width() + }; let force_newline_brace = (pat_expr_string.contains('\n') || pat_expr_string.len() > one_line_budget) && (!last_line_extendable(&pat_expr_string) - || last_line_offsetted(shape.used_width(), &pat_expr_string)); + || last_line_offsetted(first_line_indent, &pat_expr_string)); // Try to format if-else on single line. if self.allow_single_line && context.config.single_line_if_else_max_width() > 0 { diff --git a/tests/source/issue_7003_style_edition_2024.rs b/tests/source/issue_7003_style_edition_2024.rs new file mode 100644 index 0000000000000..f2e21df15dadc --- /dev/null +++ b/tests/source/issue_7003_style_edition_2024.rs @@ -0,0 +1,14 @@ +// rustfmt-style_edition: 2024 + +fn main() { + _ = if let Some(term_node) = sema + .token_ancestors_with_macros(token.clone()) + .find(|node| { + matches!( + node.kind(), + BLOCK_EXPR | ARG_LIST | PAREN_EXPR | ARRAY_EXPR | MATCH_EXPR + ) + }) { + match term_node.kind() {} + }; +} diff --git a/tests/source/issue_7003_style_edition_2027.rs b/tests/source/issue_7003_style_edition_2027.rs new file mode 100644 index 0000000000000..eecbd1f42fcaa --- /dev/null +++ b/tests/source/issue_7003_style_edition_2027.rs @@ -0,0 +1,14 @@ +// rustfmt-style_edition: 2027 + +fn main() { + _ = if let Some(term_node) = sema + .token_ancestors_with_macros(token.clone()) + .find(|node| { + matches!( + node.kind(), + BLOCK_EXPR | ARG_LIST | PAREN_EXPR | ARRAY_EXPR | MATCH_EXPR + ) + }) { + match term_node.kind() {} + }; +} diff --git a/tests/target/issue_7003_style_edition_2024.rs b/tests/target/issue_7003_style_edition_2024.rs new file mode 100644 index 0000000000000..f2e21df15dadc --- /dev/null +++ b/tests/target/issue_7003_style_edition_2024.rs @@ -0,0 +1,14 @@ +// rustfmt-style_edition: 2024 + +fn main() { + _ = if let Some(term_node) = sema + .token_ancestors_with_macros(token.clone()) + .find(|node| { + matches!( + node.kind(), + BLOCK_EXPR | ARG_LIST | PAREN_EXPR | ARRAY_EXPR | MATCH_EXPR + ) + }) { + match term_node.kind() {} + }; +} diff --git a/tests/target/issue_7003_style_edition_2027.rs b/tests/target/issue_7003_style_edition_2027.rs new file mode 100644 index 0000000000000..c32a1278eae4b --- /dev/null +++ b/tests/target/issue_7003_style_edition_2027.rs @@ -0,0 +1,15 @@ +// rustfmt-style_edition: 2027 + +fn main() { + _ = if let Some(term_node) = sema + .token_ancestors_with_macros(token.clone()) + .find(|node| { + matches!( + node.kind(), + BLOCK_EXPR | ARG_LIST | PAREN_EXPR | ARRAY_EXPR | MATCH_EXPR + ) + }) + { + match term_node.kind() {} + }; +} From d93cb69576d81d2b919be0147a7354da1862a34e Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Mon, 3 Aug 2026 10:43:24 -0400 Subject: [PATCH 45/97] cleanup: bump toml to 1.1 I just sent a matching change for clippy, so we may as well get rustfmt caught up as well so we're consistent across the toolchain world. --- Cargo.lock | 38 +++++++++++++++++++------------------- Cargo.toml | 2 +- check_diff/Cargo.lock | 24 ++++++++++++------------ check_diff/Cargo.toml | 2 +- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 775470ce2d2db..89594ecee1672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -673,11 +673,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -800,12 +800,12 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.5" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -815,27 +815,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tracing" @@ -1023,9 +1023,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.13" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "wit-bindgen" diff --git a/Cargo.toml b/Cargo.toml index 00f7bb11fd288..84253d2fb0163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0" term = "1.1" thiserror = "1.0.40" -toml = "0.9.5" +toml = "1.1" tracing = { version = "0.1.37", default-features = false, features = ["std"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } unicode-segmentation = "1.9" diff --git a/check_diff/Cargo.lock b/check_diff/Cargo.lock index 78736479e501a..8093a08326d2c 100644 --- a/check_diff/Cargo.lock +++ b/check_diff/Cargo.lock @@ -456,9 +456,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -519,9 +519,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", @@ -534,27 +534,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tracing" @@ -751,6 +751,6 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" diff --git a/check_diff/Cargo.toml b/check_diff/Cargo.toml index 50e3f0062d832..18a4aa90d6c0d 100644 --- a/check_diff/Cargo.toml +++ b/check_diff/Cargo.toml @@ -14,4 +14,4 @@ walkdir = "2.5.0" diffy = "0.4.0" crossbeam-channel = "0.5.15" ignore = "0.4.25" -toml = "0.9.11" +toml = "1.1" From ae50cf23c03f2b4998045e98c1e9caf5b4c14b71 Mon Sep 17 00:00:00 2001 From: Makai Date: Mon, 10 Aug 2026 14:30:05 +0800 Subject: [PATCH 46/97] Fix funding link --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 1d270e78949f8..dbe0dc229a1a3 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ github: rustfoundation -custom: ["rust-lang.org/funding"] +custom: ["https://rust-lang.org/funding"] From 6d8e9466c22760d1210e0693e7cedf2827417b14 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Mon, 10 Aug 2026 17:29:22 +0100 Subject: [PATCH 47/97] Update CI status badge Since d9753b1daf93bb5d0dea834fbdb6f35e2a91dd91 all the CI jobs have been merged into one, so update the badge accordingly. Fixes: #7018 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77e3335cf2cc4..0c7ba34574d75 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# rustfmt [![linux](https://github.com/rust-lang/rustfmt/actions/workflows/linux.yml/badge.svg?event=push)](https://github.com/rust-lang/rustfmt/actions/workflows/linux.yml) [![mac](https://github.com/rust-lang/rustfmt/actions/workflows/mac.yml/badge.svg?event=push)](https://github.com/rust-lang/rustfmt/actions/workflows/mac.yml) [![windows](https://github.com/rust-lang/rustfmt/actions/workflows/windows.yml/badge.svg?event=push)](https://github.com/rust-lang/rustfmt/actions/workflows/windows.yml) [![crates.io](https://img.shields.io/crates/v/rustfmt-nightly.svg)](https://crates.io/crates/rustfmt-nightly) +# rustfmt [![tests](https://github.com/rust-lang/rustfmt/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-lang/rustfmt/actions/workflows/test.yml?query=branch%3Amain) [![crates.io](https://img.shields.io/crates/v/rustfmt-nightly.svg)](https://crates.io/crates/rustfmt-nightly) A tool for formatting Rust code according to style guidelines. From db534c9bd07255d303ff65a634e40f9b9e3429a9 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Mon, 10 Aug 2026 20:03:15 +0100 Subject: [PATCH 48/97] check-diff: avoid unnecessary deep fetching Since we're only interested in the head of the branch/reference we're fetching. This is the same behaviour as this program program already uses when cloning (in `clone_git_repo`) --- check_diff/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index 210b62fe9c2bf..4d3933257dcc5 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -496,7 +496,7 @@ pub fn git_remote_add(url: &str) -> Result<(), GitError> { pub fn git_fetch(branch_name: &str) -> Result<(), GitError> { let git_cmd = Command::new("git") - .args(["fetch", "feature", branch_name]) + .args(["fetch", "--depth", "1", "feature", branch_name]) .output()?; // if the git command does not return successfully, @@ -613,11 +613,12 @@ pub fn compile_rustfmt>( config: Option<&[T]>, ) -> Result, CheckDiffError> { const RUSTFMT_REPO: &str = "https://github.com/rust-lang/rustfmt.git"; + let checkout_ref = commit_hash.as_ref().unwrap_or(&feature_branch); clone_git_repo(RUSTFMT_REPO, dest)?; change_directory_to_path(dest)?; git_remote_add(remote_repo_url.as_str())?; - git_fetch(feature_branch.as_str())?; + git_fetch(checkout_ref.as_str())?; let cargo_version = get_cargo_version()?; info!("Compiling with {}", cargo_version); @@ -629,10 +630,7 @@ pub fn compile_rustfmt>( config, )?; let should_detach = commit_hash.is_some(); - git_switch( - commit_hash.as_ref().unwrap_or(&feature_branch), - should_detach, - )?; + git_switch(checkout_ref, should_detach)?; let target_runner = build_rustfmt_from_src( dest.join("target_rustfmt"), From cafcd7b09c298e1ef2cfc5a2c440e16596819239 Mon Sep 17 00:00:00 2001 From: Jonathan Amponsah <82057176+mgalore@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:06:31 +0000 Subject: [PATCH 49/97] fix: preserve closure receivers for postfix chains --- src/chains.rs | 20 +++++++++++--------- tests/source/issue-7011.rs | 7 +++++++ tests/target/issue-7011.rs | 7 +++++++ 3 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 tests/source/issue-7011.rs create mode 100644 tests/target/issue-7011.rs diff --git a/src/chains.rs b/src/chains.rs index 90adb67ad4392..4bb2f66d484f7 100644 --- a/src/chains.rs +++ b/src/chains.rs @@ -167,7 +167,7 @@ enum CommentPosition { /// Information about an expression in a chain. struct SubExpr { expr: ast::Expr, - is_method_call_receiver: bool, + is_postfix_receiver: bool, } /// An expression plus trailing `?`s to be formatted together. @@ -226,7 +226,7 @@ impl ChainItemKind { fn from_ast( context: &RewriteContext<'_>, expr: &ast::Expr, - is_method_call_receiver: bool, + is_postfix_receiver: bool, ) -> (ChainItemKind, Span) { let (kind, span) = match expr.kind { ast::ExprKind::MethodCall(ref call) => { @@ -276,7 +276,7 @@ impl ChainItemKind { return ( ChainItemKind::Parent { expr: expr.clone(), - parens: is_method_call_receiver && should_add_parens(expr, context), + parens: is_postfix_receiver && should_add_parens(expr, context), }, expr.span, ); @@ -331,8 +331,7 @@ impl Rewrite for ChainItem { impl ChainItem { fn new(context: &RewriteContext<'_>, expr: &SubExpr, tries: usize) -> ChainItem { - let (kind, span) = - ChainItemKind::from_ast(context, &expr.expr, expr.is_method_call_receiver); + let (kind, span) = ChainItemKind::from_ast(context, &expr.expr, expr.is_postfix_receiver); ChainItem { kind, tries, span } } @@ -503,7 +502,7 @@ impl Chain { fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext<'_>) -> Vec { let mut subexpr_list = vec![SubExpr { expr: expr.clone(), - is_method_call_receiver: false, + is_postfix_receiver: false, }]; while let Some(subexpr) = Self::pop_expr_chain(subexpr_list.last().unwrap(), context) { @@ -519,15 +518,18 @@ impl Chain { match expr.expr.kind { ast::ExprKind::MethodCall(ref call) => Some(SubExpr { expr: Self::convert_try(&call.receiver, context), - is_method_call_receiver: true, + is_postfix_receiver: true, }), ast::ExprKind::Field(ref subexpr, _) - | ast::ExprKind::Try(ref subexpr) | ast::ExprKind::Await(ref subexpr, _) | ast::ExprKind::Use(ref subexpr, _) | ast::ExprKind::Yield(ast::YieldKind::Postfix(ref subexpr)) => Some(SubExpr { expr: Self::convert_try(subexpr, context), - is_method_call_receiver: false, + is_postfix_receiver: true, + }), + ast::ExprKind::Try(ref subexpr) => Some(SubExpr { + expr: Self::convert_try(subexpr, context), + is_postfix_receiver: false, }), _ => None, } diff --git a/tests/source/issue-7011.rs b/tests/source/issue-7011.rs new file mode 100644 index 0000000000000..e83631d9230bf --- /dev/null +++ b/tests/source/issue-7011.rs @@ -0,0 +1,7 @@ +fn main() { + || 1.. .field; + || 1.. ?.field; + || 1.. .await; + || 1.. .use; + || 1.. .yield; +} diff --git a/tests/target/issue-7011.rs b/tests/target/issue-7011.rs new file mode 100644 index 0000000000000..b6f147bd94cdc --- /dev/null +++ b/tests/target/issue-7011.rs @@ -0,0 +1,7 @@ +fn main() { + (|| 1..).field; + || 1..?.field; + (|| 1..).await; + (|| 1..).use; + (|| 1..).yield; +} From 5e85cb1245cbbeacaf29e6c354d87501b893384e Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 29 Mar 2025 02:31:11 -0400 Subject: [PATCH 50/97] make `CargoFmtStrategy` an argument to cargo-fmt test helper function --- src/cargo-fmt/test/targets.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cargo-fmt/test/targets.rs b/src/cargo-fmt/test/targets.rs index 34accb2136a44..2ff5730300d1b 100644 --- a/src/cargo-fmt/test/targets.rs +++ b/src/cargo-fmt/test/targets.rs @@ -14,11 +14,12 @@ mod all_targets { source_root: &str, exp_targets: &[ExpTarget], exp_num_targets: usize, + strategy: CargoFmtStrategy, ) { let root_path = Path::new("tests/cargo-fmt/source").join(source_root); let get_path = |exp: &str| PathBuf::from(&root_path).join(exp).canonicalize().unwrap(); let manifest_path = Path::new(&root_path).join(manifest_suffix); - let targets = get_targets(&CargoFmtStrategy::All, Some(manifest_path.as_path())) + let targets = get_targets(&strategy, Some(manifest_path.as_path())) .expect("Targets should have been loaded"); assert_eq!(targets.len(), exp_num_targets); @@ -58,6 +59,7 @@ mod all_targets { "divergent-crate-dir-names", &exp_targets, 3, + CargoFmtStrategy::All, ); } @@ -113,6 +115,7 @@ mod all_targets { "workspaces/path-dep-above", &exp_targets, 6, + CargoFmtStrategy::All, ); } From b54cf0cac9efd2e6ab3ee6f9c70f8844101d20d9 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 29 Mar 2025 02:40:30 -0400 Subject: [PATCH 51/97] Add a cargo-fmt test case for issue 6517 The test case loads package targets from the root of a workspace. --- src/cargo-fmt/test/targets.rs | 28 +++++++++++++++++++ tests/cargo-fmt/source/issues_6517/Cargo.toml | 2 ++ .../source/issues_6517/inner_bin/Cargo.toml | 6 ++++ .../source/issues_6517/inner_bin/src/main.rs | 3 ++ .../source/issues_6517/inner_lib/Cargo.toml | 6 ++++ .../source/issues_6517/inner_lib/src/lib.rs | 14 ++++++++++ 6 files changed, 59 insertions(+) create mode 100644 tests/cargo-fmt/source/issues_6517/Cargo.toml create mode 100644 tests/cargo-fmt/source/issues_6517/inner_bin/Cargo.toml create mode 100644 tests/cargo-fmt/source/issues_6517/inner_bin/src/main.rs create mode 100644 tests/cargo-fmt/source/issues_6517/inner_lib/Cargo.toml create mode 100644 tests/cargo-fmt/source/issues_6517/inner_lib/src/lib.rs diff --git a/src/cargo-fmt/test/targets.rs b/src/cargo-fmt/test/targets.rs index 2ff5730300d1b..ad2842c91a2e9 100644 --- a/src/cargo-fmt/test/targets.rs +++ b/src/cargo-fmt/test/targets.rs @@ -134,4 +134,32 @@ mod all_targets { assert_correct_targets_loaded("ws/a/d/f/Cargo.toml"); } } + + mod cargo_fmt_strategy_root { + use super::*; + + #[test] + fn assert_correct_targets_loaded_from_root() { + let exp_targets = &[ + ExpTarget { + path: "inner_bin/src/main.rs", + edition: Edition::E2021, + kind: "bin", + }, + ExpTarget { + path: "inner_lib/src/lib.rs", + edition: Edition::E2021, + kind: "lib", + }, + ]; + + super::assert_correct_targets_loaded( + "Cargo.toml", + "issues_6517", + exp_targets, + 2, + CargoFmtStrategy::Root, + ); + } + } } diff --git a/tests/cargo-fmt/source/issues_6517/Cargo.toml b/tests/cargo-fmt/source/issues_6517/Cargo.toml new file mode 100644 index 0000000000000..7667843cf7548 --- /dev/null +++ b/tests/cargo-fmt/source/issues_6517/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["inner_bin", "inner_lib"] diff --git a/tests/cargo-fmt/source/issues_6517/inner_bin/Cargo.toml b/tests/cargo-fmt/source/issues_6517/inner_bin/Cargo.toml new file mode 100644 index 0000000000000..bb6a80cd0ce9b --- /dev/null +++ b/tests/cargo-fmt/source/issues_6517/inner_bin/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "inner_bin" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/cargo-fmt/source/issues_6517/inner_bin/src/main.rs b/tests/cargo-fmt/source/issues_6517/inner_bin/src/main.rs new file mode 100644 index 0000000000000..e7a11a969c037 --- /dev/null +++ b/tests/cargo-fmt/source/issues_6517/inner_bin/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/cargo-fmt/source/issues_6517/inner_lib/Cargo.toml b/tests/cargo-fmt/source/issues_6517/inner_lib/Cargo.toml new file mode 100644 index 0000000000000..c9ddf1793b88f --- /dev/null +++ b/tests/cargo-fmt/source/issues_6517/inner_lib/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "inner_lib" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/cargo-fmt/source/issues_6517/inner_lib/src/lib.rs b/tests/cargo-fmt/source/issues_6517/inner_lib/src/lib.rs new file mode 100644 index 0000000000000..b93cf3ffd9cc9 --- /dev/null +++ b/tests/cargo-fmt/source/issues_6517/inner_lib/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 922774350afebd1992a567eae698920ea6d0808b Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 29 Mar 2025 02:43:46 -0400 Subject: [PATCH 52/97] fix: correctly compare directory paths in `get_targets_root_only` Previously, when running `cargo fmt --manifest-path Cargo.toml` from a cargo workspace's root directory `cargo fmt` would error with a message that read `Failed to find targets`. The issue stemmed from an incorrect comparison between the path to the workspace's root **directory** and the path to the specified `Cargo.toml` **file**. This lead `cargo fmt` to incorrectly determine that the command wasn't being run from the workspace's root. --- src/cargo-fmt/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cargo-fmt/main.rs b/src/cargo-fmt/main.rs index 6627ada03fc6a..0513fd766ddf0 100644 --- a/src/cargo-fmt/main.rs +++ b/src/cargo-fmt/main.rs @@ -387,11 +387,16 @@ fn get_targets_root_only( targets: &mut BTreeSet, ) -> Result<(), io::Error> { let metadata = get_cargo_metadata(manifest_path)?; + // `workspace_root_path` is the path to the workspace's root directory let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?; let (in_workspace_root, current_dir_manifest) = if let Some(target_manifest) = manifest_path { + // `target_manifest` is the canonicalized path to a `Cargo.toml` file + let target_manifest = target_manifest.canonicalize()?; ( - workspace_root_path == target_manifest, - target_manifest.canonicalize()?, + target_manifest + .parent() + .is_some_and(|manifest_dir| workspace_root_path == manifest_dir), + target_manifest, ) } else { let current_dir = env::current_dir()?.canonicalize()?; From 17efa091d365546eab9558cd8e6f44439b35747a Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 11 Aug 2026 20:48:12 +0200 Subject: [PATCH 53/97] Fix errors in tooling --- src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index d6a25e61008f0..33898f49c5b1e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -564,7 +564,7 @@ fn rewrite_generic_args( } } ast::GenericArgs::Parenthesized(ref data) => format_function_type( - data.inputs.iter().map(|x| &**x), + data.inputs.iter().map(|x| &*x.ty), &data.output, false, data.span, From 3c3e9889ab6a9a97c17538ed2335beb7de773bee Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 11 Aug 2026 20:56:04 +0200 Subject: [PATCH 54/97] Improve `rewrite_generic_args` to take pattern into account --- src/types.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/types.rs b/src/types.rs index 33898f49c5b1e..9edfa2cf438fe 100644 --- a/src/types.rs +++ b/src/types.rs @@ -313,19 +313,14 @@ fn rewrite_segment( Ok(result) } -fn format_function_type<'a, I>( - inputs: I, +fn format_function_type( + inputs: &[ast::Param], output: &FnRetTy, variadic: bool, span: Span, context: &RewriteContext<'_>, shape: Shape, -) -> RewriteResult -where - I: ExactSizeIterator, - ::Item: Deref, - ::Target: Rewrite + Spanned + 'a, -{ +) -> RewriteResult { debug!("format_function_type {:#?}", shape); let ty_shape = match context.config.indent_style() { @@ -381,7 +376,7 @@ where } else { let items = itemize_list( context.snippet_provider, - inputs, + inputs.iter(), ")", ",", |arg| arg.span().lo(), @@ -563,14 +558,9 @@ fn rewrite_generic_args( overflow::rewrite_with_angle_brackets(context, "", args.iter(), shape, span) } } - ast::GenericArgs::Parenthesized(ref data) => format_function_type( - data.inputs.iter().map(|x| &*x.ty), - &data.output, - false, - data.span, - context, - shape, - ), + ast::GenericArgs::Parenthesized(ref data) => { + format_function_type(&data.inputs, &data.output, false, data.span, context, shape) + } ast::GenericArgs::ParenthesizedElided(..) => Ok("(..)".to_owned()), } } @@ -1129,7 +1119,7 @@ fn rewrite_fn_ptr( }; let rewrite = format_function_type( - fn_ptr.decl.inputs.iter(), + &fn_ptr.decl.inputs, &fn_ptr.decl.output, fn_ptr.decl.c_variadic(), span, From 7462d133575ec83407a127cd8bb52a66f791e9cf Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 11 Aug 2026 21:12:31 +0200 Subject: [PATCH 55/97] Add rustfmt regression test --- tests/target/named-fn-trait-parameters.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/target/named-fn-trait-parameters.rs diff --git a/tests/target/named-fn-trait-parameters.rs b/tests/target/named-fn-trait-parameters.rs new file mode 100644 index 0000000000000..36621da20192f --- /dev/null +++ b/tests/target/named-fn-trait-parameters.rs @@ -0,0 +1,15 @@ +fn allowed( + data: &str, + f1: impl Fn(msg: String), + f2: impl Fn(_: String), + f3: impl Fn(String, msg: String), + f4: impl Fn(msg: String, String), + fg: F, +) where + F: Fn(msg: String), +{ +} + +my_macro!(f(x: &str)); +my_macro!(f(x: &str) -> ()); +my_macro!(g(n: i32, m: usize) -> usize); From 1568a3111ca920eb69a450a477c63378445934b9 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Wed, 12 Aug 2026 13:33:58 -0400 Subject: [PATCH 56/97] rustfmt: only format `cfg_select!` macros on the `nightly` release channel --- src/macros.rs | 3 ++- tests/source/cfg_select.rs | 1 + tests/target/cfg_select.rs | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 527140eca3a18..e4c05d58004a7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -27,6 +27,7 @@ use crate::config::StyleEdition; use crate::config::lists::*; use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::header::{HeaderPart, format_header}; +use crate::is_nightly_channel; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; use crate::parse::macros::cfg_select::{CfgSelectFormatPredicate, parse_cfg_select_arms}; @@ -247,7 +248,7 @@ fn rewrite_macro_inner( } } - if macro_name.ends_with("cfg_select!") { + if is_nightly_channel!() && macro_name.ends_with("cfg_select!") { match format_cfg_select(context, shape, mac.span(), ¯o_name, style, ts.clone()) { Ok(rw) => return Ok(rw), Err(err) => match err { diff --git a/tests/source/cfg_select.rs b/tests/source/cfg_select.rs index 794967afeb26b..26f276edeef6e 100644 --- a/tests/source/cfg_select.rs +++ b/tests/source/cfg_select.rs @@ -1,3 +1,4 @@ +// rustfmt-unstable: true // rustfmt-style_edition: 2024 // rustfmt-skip_children: true diff --git a/tests/target/cfg_select.rs b/tests/target/cfg_select.rs index fa242c7d2062f..fa70ea80dad38 100644 --- a/tests/target/cfg_select.rs +++ b/tests/target/cfg_select.rs @@ -1,3 +1,4 @@ +// rustfmt-unstable: true // rustfmt-style_edition: 2024 // rustfmt-skip_children: true From 9ebbe582e2fd1818d8e7095f90c5685d41b7388e Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Wed, 12 Aug 2026 13:24:24 -0400 Subject: [PATCH 57/97] rustfmt test: prevent some rustfmt system tests from running on a `nightly` release channel Tests can use the following comment configuration to mark themselves as only running on `stable` / `beta` release channels. ```rust // rustfmt-stable: true ``` > [!NOTE] > Using `stable` here since we already have an `ustable` variant for nightly > only tests and I didn't want to refactor things now, but in the future I think > we should refactor this to be more like: ```rust // rustfmt-cfg_release_channel: {stable|beta|nightly} ``` --- src/test/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/mod.rs b/src/test/mod.rs index 29904c6c7765f..201a581549914 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -754,6 +754,15 @@ fn check_files(files: Vec, opt_config: &Option) -> (Vec Config { }; for (key, val) in &sig_comments { - if key != "target" && key != "config" && key != "unstable" { + if key != "target" && key != "config" && key != "unstable" && key != "stable" { config.override_value(key, val); if config.is_default(key) { warn!("Default value {} used explicitly for {}", val, key); From 9419d209c3136bf46e58ec8d83f78357db856787 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Wed, 12 Aug 2026 13:35:16 -0400 Subject: [PATCH 58/97] rustfmt test: add `stable`-only tests for `cfg_select!` formatting These tests show that `cfg_select!` won't be formatted on `stable` or `beta` release channels. --- tests/source/cfg_select_stable.rs | 10 ++++++++++ tests/target/cfg_select_stable.rs | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/source/cfg_select_stable.rs create mode 100644 tests/target/cfg_select_stable.rs diff --git a/tests/source/cfg_select_stable.rs b/tests/source/cfg_select_stable.rs new file mode 100644 index 0000000000000..7f37a155cfcd0 --- /dev/null +++ b/tests/source/cfg_select_stable.rs @@ -0,0 +1,10 @@ +// rustfmt-stable: true + +// While we gate the `cfg_select!` formatting behind the `is_nightly_channel!()` check +// this test helps ensure that we don't start formatting `cfg_select!` on the `stable` +// or `beta` release channels. It is intentionally formatted incorrectly. As soon as the +// `is_nightly_channel!()` gate is removed this will start formatting and we can remove this test. +cfg_select! ( + unix => 1, + windows => 1, +); diff --git a/tests/target/cfg_select_stable.rs b/tests/target/cfg_select_stable.rs new file mode 100644 index 0000000000000..7f37a155cfcd0 --- /dev/null +++ b/tests/target/cfg_select_stable.rs @@ -0,0 +1,10 @@ +// rustfmt-stable: true + +// While we gate the `cfg_select!` formatting behind the `is_nightly_channel!()` check +// this test helps ensure that we don't start formatting `cfg_select!` on the `stable` +// or `beta` release channels. It is intentionally formatted incorrectly. As soon as the +// `is_nightly_channel!()` gate is removed this will start formatting and we can remove this test. +cfg_select! ( + unix => 1, + windows => 1, +); From 903bc51eeb6df0974245ba8a9d96182d3ae71166 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 14 Aug 2026 13:23:40 +1000 Subject: [PATCH 59/97] clippy: Remove `Cow` from `FnSig::coroutine_kind` A normal reference suffices. --- src/items.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/items.rs b/src/items.rs index 5619d948fde97..a5be241056aab 100644 --- a/src/items.rs +++ b/src/items.rs @@ -297,7 +297,7 @@ pub(crate) struct FnSig<'a> { decl: &'a ast::FnDecl, generics: &'a ast::Generics, ext: ast::Extern, - coroutine_kind: Cow<'a, Option>, + coroutine_kind: &'a Option, constness: ast::Const, defaultness: ast::Defaultness, safety: ast::Safety, @@ -313,7 +313,7 @@ impl<'a> FnSig<'a> { ) -> FnSig<'a> { FnSig { safety: method_sig.header.safety, - coroutine_kind: Cow::Borrowed(&method_sig.header.coroutine_kind), + coroutine_kind: &method_sig.header.coroutine_kind, constness: method_sig.header.constness, defaultness, ext: method_sig.header.ext, @@ -337,7 +337,7 @@ impl<'a> FnSig<'a> { generics, ext: sig.header.ext, constness: sig.header.constness, - coroutine_kind: Cow::Borrowed(&sig.header.coroutine_kind), + coroutine_kind: &sig.header.coroutine_kind, defaultness, safety: sig.header.safety, visibility: vis, From 27762fbfb754b1fb776d4d63b47a9879eebd3a19 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 14 Aug 2026 15:36:31 +1000 Subject: [PATCH 60/97] rustfmt: Avoid some code duplication --- src/closures.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/closures.rs b/src/closures.rs index 3b24f70d28d4d..cc3241b66612f 100644 --- a/src/closures.rs +++ b/src/closures.rs @@ -14,7 +14,7 @@ use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, Rew use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::types::rewrite_bound_params; -use crate::utils::{NodeIdExt, last_line_width, left_most_sub_expr, stmt_expr}; +use crate::utils::{NodeIdExt, format_coro, last_line_width, left_most_sub_expr, stmt_expr}; // This module is pretty messy because of the rules around closures and blocks: // FIXME - the below is probably no longer true in full. @@ -288,9 +288,7 @@ fn rewrite_closure_fn_decl( "" }; let coro = match coroutine_kind { - Some(ast::CoroutineKind::Async { .. }) => "async ", - Some(ast::CoroutineKind::Gen { .. }) => "gen ", - Some(ast::CoroutineKind::AsyncGen { .. }) => "async gen ", + Some(marker) => format_coro(marker), None => "", }; let capture_str = match capture { From 6d6cd71303b4203804f258117b24f7787360787b Mon Sep 17 00:00:00 2001 From: Vetle Rasmussen Date: Fri, 14 Aug 2026 14:22:01 +0200 Subject: [PATCH 61/97] fix: prevent char boundary panics when reporting formatting errors Before this change the highlight range for an error was computed from display widths and treated as byte offsets into the line, so lines with tabs or non-ascii characters could slice mid-character and panic. fixes rust-lang/rustfmt 6850 --- src/format_report_formatter.rs | 12 ++-- src/formatting.rs | 64 ++++++++++--------- src/test/mod.rs | 7 +- tests/warning/snapshots/issue_6850.snap | 12 ++++ .../warning/snapshots/line_overflow_tabs.snap | 12 ++++ ...ine_overflow_with_trailing_whitespace.snap | 20 ++++++ .../trailing_whitespace_overflow.snap | 12 ++++ .../unicode_trailing_whitespace.snap | 12 ++++ tests/warning/source/issue_6850.rs | 4 ++ tests/warning/source/line_overflow_tabs.rs | 4 ++ .../line_overflow_with_trailing_whitespace.rs | 3 + .../source/trailing_whitespace_overflow.rs | 3 + .../source/unicode_trailing_whitespace.rs | 2 + 13 files changed, 126 insertions(+), 41 deletions(-) create mode 100644 tests/warning/snapshots/issue_6850.snap create mode 100644 tests/warning/snapshots/line_overflow_tabs.snap create mode 100644 tests/warning/snapshots/line_overflow_with_trailing_whitespace.snap create mode 100644 tests/warning/snapshots/trailing_whitespace_overflow.snap create mode 100644 tests/warning/snapshots/unicode_trailing_whitespace.snap create mode 100644 tests/warning/source/issue_6850.rs create mode 100644 tests/warning/source/line_overflow_tabs.rs create mode 100644 tests/warning/source/line_overflow_with_trailing_whitespace.rs create mode 100644 tests/warning/source/trailing_whitespace_overflow.rs create mode 100644 tests/warning/source/unicode_trailing_whitespace.rs diff --git a/src/format_report_formatter.rs b/src/format_report_formatter.rs index 1dfcc05038374..dc8d2641d5b2e 100644 --- a/src/format_report_formatter.rs +++ b/src/format_report_formatter.rs @@ -90,14 +90,10 @@ impl<'a> Display for FormatReportFormatter<'a> { } fn annotation(error: &FormattingError) -> Option> { - let (range_start, range_length) = error.format_len(); - let range_end = range_start + range_length; - - if range_length > 0 { - Some(AnnotationKind::Primary.span(range_start..range_end)) - } else { - None - } + error + .highlight + .clone() + .map(|range| AnnotationKind::Primary.span(range)) } fn error_kind_to_snippet_annotation_level(error_kind: &ErrorKind) -> Level<'_> { diff --git a/src/formatting.rs b/src/formatting.rs index 98d206fc47d93..2dadf8e65d297 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::io::{self, Write}; +use std::ops::Range; use std::time::{Duration, Instant}; use rustc_ast::ast; @@ -318,6 +319,8 @@ pub(crate) struct FormattingError { is_comment: bool, is_string: bool, pub(crate) line_buffer: String, + /// The byte range within `line_buffer` that the error should highlight + pub(crate) highlight: Option>, } impl FormattingError { @@ -328,6 +331,7 @@ impl FormattingError { kind, is_string: false, line_buffer: psess.span_to_first_line_string(span), + highlight: None, } } @@ -352,28 +356,6 @@ impl FormattingError { None } } - - // (space, target) - pub(crate) fn format_len(&self) -> (usize, usize) { - match self.kind { - ErrorKind::LineOverflow(found, max) => (max, found - max), - ErrorKind::TrailingWhitespace - | ErrorKind::DeprecatedAttr - | ErrorKind::BadAttr - | ErrorKind::LostComment => { - let trailing_ws_start = self - .line_buffer - .rfind(|c: char| !c.is_whitespace()) - .map(|pos| pos + 1) - .unwrap_or(0); - ( - trailing_ws_start, - self.line_buffer.len() - trailing_ws_start, - ) - } - _ => unreachable!(), - } - } } pub(crate) type FormatErrorMap = HashMap>; @@ -502,7 +484,8 @@ fn format_lines( struct FormatLines<'a> { name: &'a FileName, skipped_range: &'a [(usize, usize)], - last_was_space: bool, + whitespace_start: Option, + overflow_start: Option, line_len: usize, cur_line: usize, newline_count: usize, @@ -522,7 +505,8 @@ impl<'a> FormatLines<'a> { FormatLines { name, skipped_range, - last_was_space: false, + whitespace_start: None, + overflow_start: None, line_len: 0, cur_line: 1, newline_count: 0, @@ -552,7 +536,7 @@ impl<'a> FormatLines<'a> { fn new_line(&mut self, kind: FullCodeCharKind) { if self.format_line { // Check for (and record) trailing whitespace. - if self.last_was_space { + if let Some(whitespace_start) = self.whitespace_start { if self.should_report_error(kind, &ErrorKind::TrailingWhitespace) && !self.is_skipped_line() { @@ -560,9 +544,10 @@ impl<'a> FormatLines<'a> { ErrorKind::TrailingWhitespace, kind.is_comment(), kind.is_string(), + self.line_buffer.trim_end().len()..self.line_buffer.len(), ); } - self.line_len -= 1; + self.line_len = whitespace_start; } // Check for any line width errors we couldn't correct. @@ -572,7 +557,11 @@ impl<'a> FormatLines<'a> { && self.should_report_error(kind, &error_kind) { let is_string = self.current_line_contains_string_literal; - self.push_err(error_kind, kind.is_comment(), is_string); + let overflow_start = self + .overflow_start + .expect("overflow_start is set whenever the line exceeds max_width"); + let highlight = overflow_start..self.line_buffer.trim_end().len(); + self.push_err(error_kind, kind.is_comment(), is_string, highlight); } } @@ -583,32 +572,47 @@ impl<'a> FormatLines<'a> { .file_lines() .contains_line(self.name, self.cur_line); self.newline_count += 1; - self.last_was_space = false; + self.whitespace_start = None; + self.overflow_start = None; self.line_buffer.clear(); self.current_line_contains_string_literal = false; } fn char(&mut self, c: char, kind: FullCodeCharKind) { self.newline_count = 0; + if !c.is_whitespace() { + self.whitespace_start = None; + } else if self.whitespace_start.is_none() { + self.whitespace_start = Some(self.line_len); + } self.line_len += if c == '\t' { self.config.tab_spaces() } else { 1 }; - self.last_was_space = c.is_whitespace(); + if self.line_len > self.config.max_width() && self.overflow_start.is_none() { + self.overflow_start = Some(self.line_buffer.len()); + } self.line_buffer.push(c); if kind.is_string() { self.current_line_contains_string_literal = true; } } - fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) { + fn push_err( + &mut self, + kind: ErrorKind, + is_comment: bool, + is_string: bool, + highlight: Range, + ) { self.errors.push(FormattingError { line: self.cur_line, kind, is_comment, is_string, line_buffer: self.line_buffer.clone(), + highlight: Some(highlight), }); } diff --git a/src/test/mod.rs b/src/test/mod.rs index 6089c9a08eafa..13c9ee25359df 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -256,11 +256,12 @@ fn warning_tests() { let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let manifest_dir_filter = regex::escape(manifest_dir.to_string_lossy().as_ref()); let files = get_test_files(Path::new("tests/warning/source"), true); - let mut config = Config::default(); - config.set().error_on_line_overflow(true); - config.set().error_on_unformatted(true); for file in &files { + let mut config = read_config(file); + config.set().error_on_line_overflow(true); + config.set().error_on_unformatted(true); + let snapshot_name = file.file_stem().unwrap().to_str().unwrap(); let (parsing_errors, _, report) = format_file(file, config.clone()); assert!(!parsing_errors, "{} failed to parse", file.display()); diff --git a/tests/warning/snapshots/issue_6850.snap b/tests/warning/snapshots/issue_6850.snap new file mode 100644 index 0000000000000..f074864857744 --- /dev/null +++ b/tests/warning/snapshots/issue_6850.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 50 (see `max_width` option), found: 53) + --> tests/warning/source/issue_6850.rs:3:3:51 + | +3 | "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃"; + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/line_overflow_tabs.snap b/tests/warning/snapshots/line_overflow_tabs.snap new file mode 100644 index 0000000000000..88cf9af8b2c14 --- /dev/null +++ b/tests/warning/snapshots/line_overflow_tabs.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 50 (see `max_width` option), found: 59) + --> tests/warning/source/line_overflow_tabs.rs:3:3:23 + | +3 | let s = " "; + | ^^^^^^^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/line_overflow_with_trailing_whitespace.snap b/tests/warning/snapshots/line_overflow_with_trailing_whitespace.snap new file mode 100644 index 0000000000000..01f685284e730 --- /dev/null +++ b/tests/warning/snapshots/line_overflow_with_trailing_whitespace.snap @@ -0,0 +1,20 @@ +--- +source: src/test/mod.rs +--- +error[internal]: left behind trailing whitespace + --> tests/warning/source/line_overflow_with_trailing_whitespace.rs:2:2:55 + | +2 | /// This doc comment overflows and has trailing space. + | ^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +error[internal]: line formatted, but exceeded maximum width (maximum: 50 (see `max_width` option), found: 54) + --> tests/warning/source/line_overflow_with_trailing_whitespace.rs:2:2:51 + | +2 | /// This doc comment overflows and has trailing space. + | ^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 2 errors. diff --git a/tests/warning/snapshots/trailing_whitespace_overflow.snap b/tests/warning/snapshots/trailing_whitespace_overflow.snap new file mode 100644 index 0000000000000..6c0a510d1555a --- /dev/null +++ b/tests/warning/snapshots/trailing_whitespace_overflow.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: left behind trailing whitespace + --> tests/warning/source/trailing_whitespace_overflow.rs:2:2:37 + | +2 | /// Overflowing trailing whitespace. + | ^^^^^^^^^^^^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/unicode_trailing_whitespace.snap b/tests/warning/snapshots/unicode_trailing_whitespace.snap new file mode 100644 index 0000000000000..d263a09e5a5a7 --- /dev/null +++ b/tests/warning/snapshots/unicode_trailing_whitespace.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: left behind trailing whitespace + --> tests/warning/source/unicode_trailing_whitespace.rs:1:1:41 + | +1 | /// This doc comment ends in a snowman ☃ + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/issue_6850.rs b/tests/warning/source/issue_6850.rs new file mode 100644 index 0000000000000..1f130a43cbf68 --- /dev/null +++ b/tests/warning/source/issue_6850.rs @@ -0,0 +1,4 @@ +// rustfmt-max_width: 50 +fn main() { + "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃"; +} diff --git a/tests/warning/source/line_overflow_tabs.rs b/tests/warning/source/line_overflow_tabs.rs new file mode 100644 index 0000000000000..e6a2a515ffbda --- /dev/null +++ b/tests/warning/source/line_overflow_tabs.rs @@ -0,0 +1,4 @@ +// rustfmt-max_width: 50 +fn main() { + let s = " "; +} diff --git a/tests/warning/source/line_overflow_with_trailing_whitespace.rs b/tests/warning/source/line_overflow_with_trailing_whitespace.rs new file mode 100644 index 0000000000000..ccb2ece866806 --- /dev/null +++ b/tests/warning/source/line_overflow_with_trailing_whitespace.rs @@ -0,0 +1,3 @@ +// rustfmt-max_width: 50 +/// This doc comment overflows and has trailing space. +fn main() {} diff --git a/tests/warning/source/trailing_whitespace_overflow.rs b/tests/warning/source/trailing_whitespace_overflow.rs new file mode 100644 index 0000000000000..16be61a5e3cbe --- /dev/null +++ b/tests/warning/source/trailing_whitespace_overflow.rs @@ -0,0 +1,3 @@ +// rustfmt-max_width: 50 +/// Overflowing trailing whitespace. +fn main() {} diff --git a/tests/warning/source/unicode_trailing_whitespace.rs b/tests/warning/source/unicode_trailing_whitespace.rs new file mode 100644 index 0000000000000..0ba61f2f3b140 --- /dev/null +++ b/tests/warning/source/unicode_trailing_whitespace.rs @@ -0,0 +1,2 @@ +/// This doc comment ends in a snowman ☃ +fn main() {} From 746fc38b2e1b32b1d6749a027db617535b5cc3b6 Mon Sep 17 00:00:00 2001 From: Will Buckner <1458615+willbuckner@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:26:38 -0600 Subject: [PATCH 62/97] test: add regression test for line overflow panic with `hard_tabs` (6442) The underlying panic was fixed by #7029, which computes annotation ranges in bytes rather than visual columns. Add a snapshot test to cover the original hard tabs case from 6442. --- tests/warning/snapshots/issue_6442.snap | 12 ++++++++++++ tests/warning/source/issue_6442.rs | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 tests/warning/snapshots/issue_6442.snap create mode 100644 tests/warning/source/issue_6442.rs diff --git a/tests/warning/snapshots/issue_6442.snap b/tests/warning/snapshots/issue_6442.snap new file mode 100644 index 0000000000000..75e7888b3ccbd --- /dev/null +++ b/tests/warning/snapshots/issue_6442.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 125) + --> tests/warning/source/issue_6442.rs:4:4:98 + | +4 | // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/issue_6442.rs b/tests/warning/source/issue_6442.rs new file mode 100644 index 0000000000000..f3760c972313c --- /dev/null +++ b/tests/warning/source/issue_6442.rs @@ -0,0 +1,5 @@ +// rustfmt-hard_tabs: true + +fn main() { + // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +} From 9223c55925402c24dc139221534406ffd23eae8e Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 15 Aug 2026 14:49:24 -0400 Subject: [PATCH 63/97] test: Add regression test for rust-lang/rustfmt 6632 --- tests/warning/snapshots/issue_6632.snap | 12 ++++++++++++ tests/warning/source/issue_6632.rs | 14 ++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/warning/snapshots/issue_6632.snap create mode 100644 tests/warning/source/issue_6632.rs diff --git a/tests/warning/snapshots/issue_6632.snap b/tests/warning/snapshots/issue_6632.snap new file mode 100644 index 0000000000000..4933f655bc227 --- /dev/null +++ b/tests/warning/snapshots/issue_6632.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 103) + --> tests/warning/source/issue_6632.rs:8:8:83 + | +8 | Self::$variant(s) => s.service_name(),// BuildService::service_name(s.clone()), + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/issue_6632.rs b/tests/warning/source/issue_6632.rs new file mode 100644 index 0000000000000..e9a5c8a528f85 --- /dev/null +++ b/tests/warning/source/issue_6632.rs @@ -0,0 +1,14 @@ +macro_rules! impl_routes_and_health { + ($($feature:literal, $variant:ident),* $(,)?) => { + impl EitherState { + pub(crate) fn service_name(&self) -> &'static str { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(s) => s.service_name(),// BuildService::service_name(s.clone()), + )* + } + } + } + }; +} From cbbf6e4a43b8aca1c8f444faf36cb8b35e356333 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 15 Aug 2026 14:54:34 -0400 Subject: [PATCH 64/97] test: add regression test for rust-lang/rustfmt 4968 --- tests/warning/snapshots/issue_4968.snap | 12 ++++++++++++ tests/warning/source/issue_4968.rs | 10 ++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/warning/snapshots/issue_4968.snap create mode 100644 tests/warning/source/issue_4968.rs diff --git a/tests/warning/snapshots/issue_4968.snap b/tests/warning/snapshots/issue_4968.snap new file mode 100644 index 0000000000000..8892c58b136ab --- /dev/null +++ b/tests/warning/snapshots/issue_4968.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 40 (see `max_width` option), found: 61) + --> tests/warning/source/issue_4968.rs:7:7:32 + | +7 | println!("0123456789abcdefghijklmnopqrstuvwxyz"); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/issue_4968.rs b/tests/warning/source/issue_4968.rs new file mode 100644 index 0000000000000..3726abc694bae --- /dev/null +++ b/tests/warning/source/issue_4968.rs @@ -0,0 +1,10 @@ +// rustfmt-hard_tabs: true +// rustfmt-max_width: 40 + +fn foo(x: u32) { + if x > 10 { + if x > 20 { + println!("0123456789abcdefghijklmnopqrstuvwxyz"); + } + } +} From 65d8cae98bcfab6d5043c8570c71d4fbf467fbb9 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 15 Aug 2026 15:15:23 -0400 Subject: [PATCH 65/97] test: add regression test for rust-lang/rustfmt 3950 --- tests/warning/snapshots/issue_3950.snap | 12 ++++++++++++ tests/warning/source/issue_3950.rs | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 tests/warning/snapshots/issue_3950.snap create mode 100644 tests/warning/source/issue_3950.rs diff --git a/tests/warning/snapshots/issue_3950.snap b/tests/warning/snapshots/issue_3950.snap new file mode 100644 index 0000000000000..ce2156813cbb3 --- /dev/null +++ b/tests/warning/snapshots/issue_3950.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 106) + --> tests/warning/source/issue_3950.rs:4:4:98 + | +4 | let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. + | ^^^^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/issue_3950.rs b/tests/warning/source/issue_3950.rs new file mode 100644 index 0000000000000..5fa559f5d9872 --- /dev/null +++ b/tests/warning/source/issue_3950.rs @@ -0,0 +1,5 @@ +// rustfmt-hard_tabs: true + +fn lorem_ipsum() { + let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem. +} From f8d67ae3a3005e6f8829ea127dd5c6ad82d94e6a Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 17 Aug 2026 09:29:29 +1000 Subject: [PATCH 66/97] Add splat formatting tests --- tests/target/splat.rs | 111 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 tests/target/splat.rs diff --git a/tests/target/splat.rs b/tests/target/splat.rs new file mode 100644 index 0000000000000..10702d0bdb231 --- /dev/null +++ b/tests/target/splat.rs @@ -0,0 +1,111 @@ +/// Tests that the `#[rustc_splat]` attribute is preserved by rustfmt. +/// This attribute is currently unstable, and its syntax might change in future. +/// It currently uses the default formatting for attributes. + +// These snippets are mainly from rust/tests/ui/splat + +// Rejected by rustc, but still needs to be formatted correctly + +// Can't have rust-call and splat on the same function +trait Trait: Tuple + Sized { + extern "rust-call" fn method(#[rustc_splat] self: Self); +} + +extern "rust-call" fn f(#[rustc_splat] _: ()); + +fn wrong_type(#[rustc_splat] _x: u32) {} + +// Can't have multiple splats in the same function +fn multi_splat_bad(#[rustc_splat] (_a, _b): (u32, i8), #[rustc_splat] (_c, _d): (u32, i8)) {} + +// Multiple splats on the same argument are redundant +fn multisplat_arg_bad( + #[rustc_splat] + #[rustc_splat] + (_a, _b): (u32, i8), +) { +} + +fn multisplat_arg_fn_bad( + #[rustc_splat] + #[rustc_splat] + (_a, _b): (u32, i8), + #[rustc_splat] (_c, _d): (u32, i8), +) { +} + +// Can't have variadic and splat on the same function +unsafe extern "C" fn splat_variadic(#[rustc_splat] (_a, _b): (u32, i8), varargs: ...) {} +unsafe extern "C" fn splat_variadic2(varargs: ..., #[rustc_splat] (_a, _b): (u32, i8)) {} + +// Accepted by rustc +struct Foo; + +impl Foo { + fn method(&self, #[rustc_splat] args: impl MethodArgs) -> String {} + fn tuple_1(#[rustc_splat] (_a,): (u32,)) {} + fn tuple_3(#[rustc_splat] (_a, _b, _c): (u32, i32, i8)) {} +} + +fn generic(#[rustc_splat] a: T) -> String { + String::new() +} + +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (i8, f64, u32) { + (a, b, c) +} + +const X: fn(#[rustc_splat] (f32,)) = None.unwrap(); + +fn main() { + struct Type(T); + + // Rejected by rustc, but still needs to be formatted correctly + // Closures + (|#[rustc_splat] x: i32| {})(1); + + // Function pointer types + + // Rust-call and splat aren't allowed in the same function + let f_: extern "rust-call" fn(#[rustc_splat] ()) = f; + + let wrong_type_: fn(#[rustc_splat] _x: u32) = wrong_type; + + let multi_splat_bad_: fn(#[rustc_splat] (u32, i8), #[rustc_splat] (u32, i8)) = multi_splat_bad; + let multisplat_arg_bad_: fn( + #[rustc_splat] + #[rustc_splat] + (u32, i8), + ) = multisplat_arg_bad; + let multisplat_arg_fn_bad_: fn( + #[rustc_splat] + #[rustc_splat] + (u32, i8), + #[rustc_splat] (u32, i8), + ) = multisplat_arg_fn_bad; + + // Splat and variadic aren't allowed in the same function + let splat_variadic_: unsafe extern "C" fn(#[rustc_splat] (u32, i8), ...) = splat_variadic; + let splat_variadic2_: unsafe extern "C" fn(..., #[rustc_splat] (u32, i8)) = splat_variadic2; + + // Accepted by rustc + // Function pointer types + + // Only one splatted arg + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = + generic as fn(#[rustc_splat] (u32, i8)) -> String; + impl Type {} + + // Leading splatted arg + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) = + &(splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)); + + // Trailing splatted arg + impl Type<*mut fn(u32, i8, #[rustc_splat] (f64,))> {} + + // Middle splatted arg + impl Type<&fn(u32, #[rustc_splat] (i8, f32, usize), f64)> {} + + // Splats within splats + impl Type> {} +} From a8226bbb1a7d3a52b4f3978b747025e564a8af3f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 14 Aug 2026 15:33:12 +1000 Subject: [PATCH 67/97] Overhaul `CoroutineKind` It is an enum with three variant, and all the variants have identical fields, which is silly. This commit does the following. - Renames it as `CoroutineMarker`, because the `Kind` suffix is used for enums. (The existing doc comment already uses the word "marker".) - Renames the existing `GenBlockKind` as `CoroutineKind`, uses it within `CoroutineMarker`, and adds `CoroutineKind::is_gen`. This removes the need for the `span`, `closure_id` and `return_id` methods; direct field access now suffices. --- src/closures.rs | 15 ++++++--------- src/expr.rs | 2 +- src/items.rs | 10 +++++----- src/utils.rs | 10 +++++----- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/closures.rs b/src/closures.rs index cc3241b66612f..e7c04c975a385 100644 --- a/src/closures.rs +++ b/src/closures.rs @@ -30,7 +30,7 @@ pub(crate) fn rewrite_closure( binder: &ast::ClosureBinder, constness: ast::Const, capture: ast::CaptureBy, - coroutine_kind: &Option, + coroutine_marker: &Option, movability: ast::Movability, fn_decl: &ast::FnDecl, body: &ast::Expr, @@ -44,7 +44,7 @@ pub(crate) fn rewrite_closure( binder, constness, capture, - coroutine_kind, + coroutine_marker, movability, fn_decl, body, @@ -256,7 +256,7 @@ fn rewrite_closure_fn_decl( binder: &ast::ClosureBinder, constness: ast::Const, capture: ast::CaptureBy, - coroutine_kind: &Option, + coroutine_marker: &Option, movability: ast::Movability, fn_decl: &ast::FnDecl, body: &ast::Expr, @@ -287,10 +287,7 @@ fn rewrite_closure_fn_decl( } else { "" }; - let coro = match coroutine_kind { - Some(marker) => format_coro(marker), - None => "", - }; + let coro = coroutine_marker.map_or_default(format_coro); let capture_str = match capture { ast::CaptureBy::Value { .. } => "move ", ast::CaptureBy::Use { .. } => "use ", @@ -367,7 +364,7 @@ pub(crate) fn rewrite_last_closure( ref binder, constness, capture_clause, - ref coroutine_kind, + ref coroutine_marker, movability, ref fn_decl, ref body, @@ -389,7 +386,7 @@ pub(crate) fn rewrite_last_closure( binder, constness, capture_clause, - coroutine_kind, + coroutine_marker, movability, fn_decl, body, diff --git a/src/expr.rs b/src/expr.rs index aec503099432e..268f79cd127b4 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -271,7 +271,7 @@ pub(crate) fn format_expr( &cl.binder, cl.constness, cl.capture_clause, - &cl.coroutine_kind, + &cl.coroutine_marker, cl.movability, &cl.fn_decl, &cl.body, diff --git a/src/items.rs b/src/items.rs index a5be241056aab..6a68e2ec8c502 100644 --- a/src/items.rs +++ b/src/items.rs @@ -297,7 +297,7 @@ pub(crate) struct FnSig<'a> { decl: &'a ast::FnDecl, generics: &'a ast::Generics, ext: ast::Extern, - coroutine_kind: &'a Option, + coroutine_marker: &'a Option, constness: ast::Const, defaultness: ast::Defaultness, safety: ast::Safety, @@ -313,7 +313,7 @@ impl<'a> FnSig<'a> { ) -> FnSig<'a> { FnSig { safety: method_sig.header.safety, - coroutine_kind: &method_sig.header.coroutine_kind, + coroutine_marker: &method_sig.header.coroutine_marker, constness: method_sig.header.constness, defaultness, ext: method_sig.header.ext, @@ -337,7 +337,7 @@ impl<'a> FnSig<'a> { generics, ext: sig.header.ext, constness: sig.header.constness, - coroutine_kind: &sig.header.coroutine_kind, + coroutine_marker: &sig.header.coroutine_marker, defaultness, safety: sig.header.safety, visibility: vis, @@ -352,8 +352,8 @@ impl<'a> FnSig<'a> { result.push_str(&*format_visibility(context, self.visibility)); result.push_str(format_defaultness(self.defaultness)); result.push_str(format_constness(self.constness)); - self.coroutine_kind - .map(|coroutine_kind| result.push_str(format_coro(&coroutine_kind))); + self.coroutine_marker + .map(|coroutine_marker| result.push_str(format_coro(coroutine_marker))); result.push_str(format_safety(self.safety)); result.push_str(&format_extern( self.ext, diff --git a/src/utils.rs b/src/utils.rs index 131455fc0ff12..2936025d2f92c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -117,11 +117,11 @@ fn format_restriction( } #[inline] -pub(crate) fn format_coro(coroutine_kind: &ast::CoroutineKind) -> &'static str { - match coroutine_kind { - ast::CoroutineKind::Async { .. } => "async ", - ast::CoroutineKind::Gen { .. } => "gen ", - ast::CoroutineKind::AsyncGen { .. } => "async gen ", +pub(crate) fn format_coro(coroutine_marker: ast::CoroutineMarker) -> &'static str { + match coroutine_marker.kind { + ast::CoroutineKind::Async => "async ", + ast::CoroutineKind::Gen => "gen ", + ast::CoroutineKind::AsyncGen => "async gen ", } } From 890195106881c3377e4ce5cf56fbe1fe55b9eebc Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sun, 15 Feb 2026 13:44:17 +0000 Subject: [PATCH 68/97] Update docs for `Session.format` To stop referencing parameters `out` and `config` that were both moved into the `Session` object with 71d3d04270474ae0afdeeb410fdcc168a54714b7 --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 45aefc2a15851..9e0ec01e7d0b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -462,7 +462,7 @@ impl<'b, T: Write + 'b> Session<'b, T> { } /// The main entry point for Rustfmt. Formats the given input according to the - /// given config. `out` is only necessary if required by the configuration. + /// session's config. pub fn format(&mut self, input: Input) -> Result { self.format_input_inner(input, false) } From 1873b825e5a7138a095563e3c9da49b9d63d0421 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sun, 15 Feb 2026 13:46:12 +0000 Subject: [PATCH 69/97] Move stray `imports_granularity` test Move this to be with all the other tests for this config option --- tests/source/{ => imports}/imports_granularity_one.rs | 0 tests/target/{ => imports}/imports_granularity_one.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/source/{ => imports}/imports_granularity_one.rs (100%) rename tests/target/{ => imports}/imports_granularity_one.rs (100%) diff --git a/tests/source/imports_granularity_one.rs b/tests/source/imports/imports_granularity_one.rs similarity index 100% rename from tests/source/imports_granularity_one.rs rename to tests/source/imports/imports_granularity_one.rs diff --git a/tests/target/imports_granularity_one.rs b/tests/target/imports/imports_granularity_one.rs similarity index 100% rename from tests/target/imports_granularity_one.rs rename to tests/target/imports/imports_granularity_one.rs From 172ecbcab8dbdcf66f1e84c683cfb74773e7123f Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sun, 15 Feb 2026 21:36:12 +0000 Subject: [PATCH 70/97] Silence expected parser errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I find otherwise the test output can be a bit noisy, and in particular the error messages make me think some test somewhere failed when even when it's all passing, for example, one such removed error: error: mismatched closing delimiter: `}` --> tests/parser/unclosed-delims/issue_4466.rs:3:17 | 2 | if true { | - closing delimiter possibly meant for this 3 | println!("answer: {}", a_func(); | ^ unclosed delimiter 4 | } else { | ^ mismatched closing delimiter This also removes some noisy warnings like: warning: whitespace symbol '\u{a0}' is not skipped --> tests/target/string_lit_unicode_ws.rs:3:22 | 3 | let str = "hello \ | ______________________^ 4 | |  world"; | | ^ whitespace symbol '\u{a0}' is not skipped | |_| | --- src/test/mod.rs | 4 +++- src/test/parser.rs | 11 +++++++++-- tests/source/string_lit_unicode_ws.rs | 2 ++ tests/target/string_lit_unicode_ws.rs | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/test/mod.rs b/src/test/mod.rs index 13c9ee25359df..71031341b26ed 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -627,7 +627,9 @@ fn stdin_parser_panic_caught() { // See issue #3239. for text in ["{", "}"].iter().cloned().map(String::from) { let mut buf = vec![]; - let mut session = Session::new(Default::default(), Some(&mut buf)); + let mut config = Config::default(); + config.set().show_parse_errors(false); + let mut session = Session::new(config, Some(&mut buf)); let _ = session.format(Input::Text(text)); assert!(session.has_parsing_errors()); diff --git a/src/test/parser.rs b/src/test/parser.rs index 903d4e4862573..04b4d3b362b0c 100644 --- a/src/test/parser.rs +++ b/src/test/parser.rs @@ -6,13 +6,20 @@ use super::read_config; use crate::modules::{ModuleResolutionError, ModuleResolutionErrorKind}; use crate::{ErrorKind, Input, Session}; +/// Load the config, but hide expected parse errors +fn read_config_hide_parse_errors(filename: &std::path::Path) -> crate::Config { + let mut config = read_config(&filename); + config.set().show_parse_errors(false); + config +} + #[test] fn parser_errors_in_submods_are_surfaced() { // See also https://github.com/rust-lang/rustfmt/issues/4126 let filename = "tests/parser/issue-4126/lib.rs"; let input_file = PathBuf::from(filename); let exp_mod_name = "invalid"; - let config = read_config(&input_file); + let config = read_config_hide_parse_errors(&input_file); let mut session = Session::::new(config, None); if let Err(ErrorKind::ModuleResolutionError(ModuleResolutionError { module, kind })) = session.format(Input::File(filename.into())) @@ -36,7 +43,7 @@ fn parser_errors_in_submods_are_surfaced() { fn assert_parser_error(filename: &str) { let file = PathBuf::from(filename); - let config = read_config(&file); + let config = read_config_hide_parse_errors(&file); let mut session = Session::::new(config, None); let _ = session.format(Input::File(filename.into())).unwrap(); assert!(session.has_parsing_errors()); diff --git a/tests/source/string_lit_unicode_ws.rs b/tests/source/string_lit_unicode_ws.rs index f944711e14f39..fff135fbc4fdf 100644 --- a/tests/source/string_lit_unicode_ws.rs +++ b/tests/source/string_lit_unicode_ws.rs @@ -1,3 +1,5 @@ +// rustfmt-show_parse_errors: false + // Test Unicode whitespace characters in string literal line continuation fn main() { let str = "hello \ diff --git a/tests/target/string_lit_unicode_ws.rs b/tests/target/string_lit_unicode_ws.rs index f944711e14f39..fff135fbc4fdf 100644 --- a/tests/target/string_lit_unicode_ws.rs +++ b/tests/target/string_lit_unicode_ws.rs @@ -1,3 +1,5 @@ +// rustfmt-show_parse_errors: false + // Test Unicode whitespace characters in string literal line continuation fn main() { let str = "hello \ From 148b5a9057a2f25101b4e1465b01e492ad8c7974 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 7 Mar 2026 19:28:02 +0000 Subject: [PATCH 71/97] Remove warning on explicit default config value in tests This was added with f7a25a1177c86541071932a9e578819c7ae33d5a, this was to address an issue with the worthy goal[1]: > It is probably worth auditing the tests and where they are not deliberately testing an option, removing any options they set. However, given the number of warnings this produces: $ cargo test -- --nocapture |& grep -c 'Default value.*used explicitly' 236 I think this approach did not work, one could enforce this approach by changing this to be an error, but I think there are valid cases to use defaults, e.g. a reproduction of an issue relating to line length uses the default `max_width`: it's simplest to add a test verifying the behaviour with an explicit `max_width: 100` rather than adjusting the reproduction. Link: https://github.com/rust-lang/rustfmt/issues/1720 [1] --- src/test/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/mod.rs b/src/test/mod.rs index 71031341b26ed..3353b78b221e3 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -19,7 +19,7 @@ use crate::{ }; use rustfmt_config_proc_macro::nightly_only_test; -use tracing::{debug, warn}; +use tracing::debug; mod configuration_snippet; mod mod_resolver; @@ -869,9 +869,6 @@ fn read_config(filename: &Path) -> Config { for (key, val) in &sig_comments { if key != "target" && key != "config" && key != "unstable" { config.override_value(key, val); - if config.is_default(key) { - warn!("Default value {} used explicitly for {}", val, key); - } } } From bec6bf591f6f59f68b6b202d8576d848333e94df Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Wed, 19 Aug 2026 18:29:40 +0000 Subject: [PATCH 72/97] Uplift rustfmt macro formatting fix --- src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index e4c05d58004a7..8bfd99f2f7c9f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -454,7 +454,7 @@ pub(crate) fn rewrite_macro_def( }; let mut header = if def.macro_rules { - let pos = context.snippet_provider.span_after(span, "macro_rules!"); + let pos = context.snippet_provider.span_after(span, "!"); vec![HeaderPart::new("macro_rules!", span.with_hi(pos))] } else { let macro_lo = context.snippet_provider.span_before(span, "macro"); From a778e81e455bad9c7c0e8ee687554aad34468955 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Fri, 13 Feb 2026 20:26:47 +0000 Subject: [PATCH 73/97] Add clearer test example for issue 5023 --- tests/source/issue-5023.rs | 10 ++++++++++ tests/target/issue-5023.rs | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/source/issue-5023.rs b/tests/source/issue-5023.rs index ae1c723eff76a..2a6f4a90aa980 100644 --- a/tests/source/issue-5023.rs +++ b/tests/source/issue-5023.rs @@ -1,5 +1,15 @@ // rustfmt-wrap_comments: true +// below we try and force a split at a byte in the middle of a multi-byte +// character. The two parts of the first line are constructed such that: +// 1) the entire line is longer than `$comment_width` +// 2) the length of the second part is such that: +// `$comment_width - 3 + $length` is positive _and_ less than the number of +// bytes in the multi-byte char + +// xxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +// 是 + /// A comment to test special unicode characters on boundaries /// 是,是,是,是,是,是,是,是,是,是,是,是 it should break right here this goes to the next line fn main() { diff --git a/tests/target/issue-5023.rs b/tests/target/issue-5023.rs index 4e84c7d98427a..7ff35783b1fe1 100644 --- a/tests/target/issue-5023.rs +++ b/tests/target/issue-5023.rs @@ -1,5 +1,16 @@ // rustfmt-wrap_comments: true +// below we try and force a split at a byte in the middle of a multi-byte +// character. The two parts of the first line are constructed such that: +// 1) the entire line is longer than `$comment_width` +// 2) the length of the second part is such that: +// `$comment_width - 3 + $length` is positive _and_ less than the number of +// bytes in the multi-byte char + +// xxxxxxxxxx +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +// 是 + /// A comment to test special unicode characters on boundaries /// 是,是,是,是,是,是,是,是,是,是,是,是 it should break right here /// this goes to the next line From e325046d3be27586c425d80d6e616554d0b6f52e Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Thu, 12 Feb 2026 21:37:13 +0000 Subject: [PATCH 74/97] Make comment wrapping indent aware When formatting 'missed' spans (I'm still not sure I understand exactly what 'missed' means). Previously, if `comment_width` was less than `max_width - $current_indent` we would format the comment ignoring the length of any indentation, so e.g. the following, with `comment_width=38` would not be formatted fn foo() { // The '.' is at 'comment_width' . A () } This involved rewriting some existing tests, without impacting what they're meant to be asserting on. This emphasises that this patch _is not_ backwards compatible: while I believe the change to be correct (some tests had comments longer than `comment_width` that weren't being formatted). So there's a trade-off to be made between correctness and stability, since `comment_width` is still not stabilised this may be acceptable. Issue: #6801 --- src/missed_spans.rs | 6 +-- tests/source/issue_6801.rs | 52 +++++++++++++++++++ tests/target/issue-5023.rs | 5 +- tests/target/issue_6801.rs | 57 +++++++++++++++++++++ tests/target/trailing_comments/hard_tabs.rs | 8 +-- tests/target/trailing_comments/soft_tabs.rs | 8 +-- tests/target/unicode.rs | 3 +- 7 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 tests/source/issue_6801.rs create mode 100644 tests/target/issue_6801.rs diff --git a/src/missed_spans.rs b/src/missed_spans.rs index 2654d2464eed3..0242094a75269 100644 --- a/src/missed_spans.rs +++ b/src/missed_spans.rs @@ -269,11 +269,7 @@ impl<'a> FmtVisitor<'a> { Indent::from_width(self.config, last_line_width(&self.buffer)) }; - let comment_width = ::std::cmp::min( - self.config.comment_width(), - self.config.max_width() - self.block_indent.width(), - ); - let comment_shape = Shape::legacy(comment_width, comment_indent); + let comment_shape = Shape::indented(comment_indent, self.config).comment(self.config); if on_same_line { match subslice.find('\n') { diff --git a/tests/source/issue_6801.rs b/tests/source/issue_6801.rs new file mode 100644 index 0000000000000..a53c2b305e5ba --- /dev/null +++ b/tests/source/issue_6801.rs @@ -0,0 +1,52 @@ +// rustfmt-wrap_comments: true +// rustfmt-comment_width: 80 +// rustfmt-max_width: 200 + +fn foo() { + // In this line, the next '.' is exactly at'comment_width' . It should break on that dot + + // In this line, 'comment_width' is reached in the middle of ThisVeryLongWord. It should break before that word + + { + { + { + { + // again 'comment_width' is just at the next '.' . Here's some more stuff + fn f1() { + fn f2() { + fn f3() { + fn f4() { + fn f5() { + fn f6() { + fn f7() { + fn f8() { + fn f9() { + fn f10() { + fn f11() { + // again . Deeply nested comment + fn f12() { + fn f13() { + fn f14() { + fn f15() { + // indentation means this comment starts after 'comment_width' + // we don't touch this comment + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/target/issue-5023.rs b/tests/target/issue-5023.rs index 7ff35783b1fe1..aca47d0f4a95c 100644 --- a/tests/target/issue-5023.rs +++ b/tests/target/issue-5023.rs @@ -20,8 +20,9 @@ fn main() { .into_iter() .filter(|(xxx, xxx)| { if let Some(x) = Some(1) { - // xxxxxxxxxxxxxxxxxx, xxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx xxx xxxxxxx, xxxxx xxx - // xxxxxxxxxx. xxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxx xxx xxxxxxx + // xxxxxxxxxxxxxxxxxx, xxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx + // xxx xxxxxxx, xxxxx xxx xxxxxxxxxx. + // xxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxx xxx xxxxxxx // 是sdfadsdfxxxxxxxxx,sdfaxxxxxx_xxxxx_masdfaonxxx, if false { return true; diff --git a/tests/target/issue_6801.rs b/tests/target/issue_6801.rs new file mode 100644 index 0000000000000..46372db44769d --- /dev/null +++ b/tests/target/issue_6801.rs @@ -0,0 +1,57 @@ +// rustfmt-wrap_comments: true +// rustfmt-comment_width: 80 +// rustfmt-max_width: 200 + +fn foo() { + // In this line, the next '.' is exactly at'comment_width' . + // It should break on that dot + + // In this line, 'comment_width' is reached in the middle of + // ThisVeryLongWord. It should break before that word + + { + { + { + { + // again 'comment_width' is just at the next '.' . + // Here's some more stuff + fn f1() { + fn f2() { + fn f3() { + fn f4() { + fn f5() { + fn f6() { + fn f7() { + fn f8() { + fn f9() { + fn f10() { + fn f11() { + // again . + // Deeply nested + // comment + fn f12() { + fn f13() { + fn f14() { + fn f15() { + // indentation means this comment starts after 'comment_width' + // we don't touch this comment + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/target/trailing_comments/hard_tabs.rs b/tests/target/trailing_comments/hard_tabs.rs index e7009ac00c01b..8135b1a8caf7d 100644 --- a/tests/target/trailing_comments/hard_tabs.rs +++ b/tests/target/trailing_comments/hard_tabs.rs @@ -18,10 +18,10 @@ fn lorem_ipsum() { // nunc. Mauris consequat, enim vitae venenatis sollicitudin, dolor orci // bibendum enim, a sagittis nulla nunc quis elit. Phasellus augue. Nunc // suscipit, magna tincidunt lacinia faucibus, lacus tellus ornare purus, a - // pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor faucibus. - // In hac habitasse platea dictumst. Vivamus a orci at nulla tristique - // condimentum. Donec arcu quam, dictum accumsan, convallis accumsan, cursus sit - // amet, ipsum. In pharetra sagittis nunc. + // pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor + // faucibus. In hac habitasse platea dictumst. Vivamus a orci at nulla + // tristique condimentum. Donec arcu quam, dictum accumsan, convallis + // accumsan, cursus sit amet, ipsum. In pharetra sagittis nunc. let b = baz(); let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] diff --git a/tests/target/trailing_comments/soft_tabs.rs b/tests/target/trailing_comments/soft_tabs.rs index 34cfed1a2293b..3e2dbc1bb7d73 100644 --- a/tests/target/trailing_comments/soft_tabs.rs +++ b/tests/target/trailing_comments/soft_tabs.rs @@ -18,10 +18,10 @@ fn foo() { // nunc. Mauris consequat, enim vitae venenatis sollicitudin, dolor orci // bibendum enim, a sagittis nulla nunc quis elit. Phasellus augue. Nunc // suscipit, magna tincidunt lacinia faucibus, lacus tellus ornare purus, a - // pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor faucibus. - // In hac habitasse platea dictumst. Vivamus a orci at nulla tristique - // condimentum. Donec arcu quam, dictum accumsan, convallis accumsan, cursus sit - // amet, ipsum. In pharetra sagittis nunc. + // pulvinar lacus orci eget nibh. Maecenas sed nibh non lacus tempor + // faucibus. In hac habitasse platea dictumst. Vivamus a orci at nulla + // tristique condimentum. Donec arcu quam, dictum accumsan, convallis + // accumsan, cursus sit amet, ipsum. In pharetra sagittis nunc. let b = baz(); let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] diff --git a/tests/target/unicode.rs b/tests/target/unicode.rs index 34a4f46347969..610e482d6b498 100644 --- a/tests/target/unicode.rs +++ b/tests/target/unicode.rs @@ -4,7 +4,8 @@ fn foo() { let s = "this line goes to 100: ͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶͶ"; let s = 42; - // a comment of length 80, with the starting sigil: ҘҘҘҘҘҘҘҘҘҘ ҘҘҘҘҘҘҘҘҘҘҘҘҘҘ + // a comment of length 80, with the starting sigil: ҘҘҘҘҘҘҘҘҘҘ + // ҘҘҘҘҘҘҘҘҘҘҘҘҘҘ let s = 42; } From 1d822d78e18bfd99566fbeeae849b7e611b0712f Mon Sep 17 00:00:00 2001 From: Sandijigs Date: Fri, 21 Aug 2026 13:36:47 +0100 Subject: [PATCH 75/97] Replace archived actions/upload-release-asset with gh CLI --- .github/workflows/upload-assets.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/upload-assets.yml b/.github/workflows/upload-assets.yml index 49c9172cb46d1..a81545052e07e 100644 --- a/.github/workflows/upload-assets.yml +++ b/.github/workflows/upload-assets.yml @@ -68,11 +68,6 @@ jobs: - name: Upload Release Asset if: github.event_name == 'release' - uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ${{ env.ASSET }} - asset_name: ${{ env.ASSET }} - asset_content_type: application/octet-stream + run: gh release upload "${{ github.event.release.tag_name }}" "${{ env.ASSET }}" From e9b0f1ec52a9a90296a58528be27cf60e8af588f Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Fri, 21 Aug 2026 23:19:28 +0000 Subject: [PATCH 76/97] Inconsistent formatting of doc comments in macros --- src/parse/macros/mod.rs | 4 +++- tests/source/issue_7036.rs | 21 +++++++++++++++++++++ tests/target/issue_7036.rs | 21 +++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/source/issue_7036.rs create mode 100644 tests/target/issue_7036.rs diff --git a/src/parse/macros/mod.rs b/src/parse/macros/mod.rs index 00e0f6f58bd37..cfbd44c3344b4 100644 --- a/src/parse/macros/mod.rs +++ b/src/parse/macros/mod.rs @@ -25,7 +25,9 @@ fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { macro_rules! parse_macro_arg { ($macro_arg:ident, $nt_kind:expr, $try_parse:expr, $then:expr) => { let mut cloned_parser = (*parser).clone(); - if Parser::nonterminal_may_begin_with($nt_kind, &cloned_parser.token) { + if Parser::nonterminal_may_begin_with($nt_kind, &cloned_parser.token) + || matches!(cloned_parser.token.kind, TokenKind::DocComment(..)) + { match $try_parse(&mut cloned_parser) { Ok(x) => { if parser.psess.dcx().has_errors().is_some() { diff --git a/tests/source/issue_7036.rs b/tests/source/issue_7036.rs new file mode 100644 index 0000000000000..ece1006a44fc0 --- /dev/null +++ b/tests/source/issue_7036.rs @@ -0,0 +1,21 @@ +// Doc comments inside macro calls should not prevent formatting. + +foo!( + /// Doc + A, + B, +); + +foo!( + #[doc = ""] + /// Doc + A, + B, +); + +foo!( + /// Doc + #[doc = ""] + A, + B, +); diff --git a/tests/target/issue_7036.rs b/tests/target/issue_7036.rs new file mode 100644 index 0000000000000..d37a674c47c48 --- /dev/null +++ b/tests/target/issue_7036.rs @@ -0,0 +1,21 @@ +// Doc comments inside macro calls should not prevent formatting. + +foo!( + /// Doc + A, + B, +); + +foo!( + #[doc = ""] + /// Doc + A, + B, +); + +foo!( + /// Doc + #[doc = ""] + A, + B, +); From 7995b68eb960459117a0da37f328d25359324c1e Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Sat, 22 Aug 2026 09:03:38 +0000 Subject: [PATCH 77/97] more tests added for doc comments --- tests/source/issue_7036.rs | 142 ++++++++++++++++++++++++++++++++++++- tests/target/issue_7036.rs | 126 +++++++++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 6 deletions(-) diff --git a/tests/source/issue_7036.rs b/tests/source/issue_7036.rs index ece1006a44fc0..8bc7cadbbd0bd 100644 --- a/tests/source/issue_7036.rs +++ b/tests/source/issue_7036.rs @@ -1,21 +1,157 @@ // Doc comments inside macro calls should not prevent formatting. +// +// Each comment form below is tested three ways: on its own, after an +// attribute, and before an attribute. foo!( - /// Doc + // Only a comment A, B, ); foo!( #[doc = ""] - /// Doc + // Only a comment A, B, ); foo!( - /// Doc + // Only a comment #[doc = ""] A, B, ); + + +foo!( + /// Outer line doc (exactly 3 slashes) + A, + B, +); + +foo!( + #[doc = ""] + /// Outer line doc (exactly 3 slashes) + A, + B, +); + +foo!( + /// Outer line doc (exactly 3 slashes) + #[doc = ""] + A, + B, +); + + +foo!( + //// Only a comment + A, + B, +); + +foo!( + #[doc = ""] + //// Only a comment + A, + B, +); + +foo!( + //// Only a comment + #[doc = ""] + A, + B, +); + + +foo!( + /* Only a comment */ + A, + B, +); + +foo!( + #[doc = ""] + /* Only a comment */ + A, + B, +); + +foo!( + /* Only a comment */ + #[doc = ""] + A, + B, +); + + +foo!( + /** Outer block doc (exactly 2 asterisks) */ + A, + B, +); + +foo!( + #[doc = ""] + /** Outer block doc (exactly 2 asterisks) */ + A, + B, +); + +foo!( + /** Outer block doc (exactly 2 asterisks) */ + #[doc = ""] + A, + B, +); + + +foo!( + /*** Only a comment */ + A, + B, +); + +foo!( + #[doc = ""] + /*** Only a comment */ + A, + B, +); + +foo!( + /*** Only a comment */ + #[doc = ""] + A, + B, +); + + +// Inner doc comments cannot attach to a macro argument, so these are left +// unformatted on purpose. + +foo!( + //! Inner line doc + A, + B, +); + +foo!( + //!! Still an inner line doc (but with a bang at the beginning) + A, + B, +); + +foo!( + /*! Inner block doc */ + A, + B, +); + +foo!( + /*!! Still an inner block doc (but with a bang at the beginning) */ + A, + B, +); \ No newline at end of file diff --git a/tests/target/issue_7036.rs b/tests/target/issue_7036.rs index d37a674c47c48..294bc77b11d01 100644 --- a/tests/target/issue_7036.rs +++ b/tests/target/issue_7036.rs @@ -1,21 +1,141 @@ // Doc comments inside macro calls should not prevent formatting. +// +// Each comment form below is tested three ways: on its own, after an +// attribute, and before an attribute. foo!( - /// Doc + // Only a comment + A, B, +); + +foo!( + #[doc = ""] + // Only a comment + A, + B, +); + +foo!( + // Only a comment + #[doc = ""] + A, + B, +); + +foo!( + /// Outer line doc (exactly 3 slashes) + A, + B, +); + +foo!( + #[doc = ""] + /// Outer line doc (exactly 3 slashes) + A, + B, +); + +foo!( + /// Outer line doc (exactly 3 slashes) + #[doc = ""] + A, + B, +); + +foo!( + //// Only a comment + A, B, +); + +foo!( + #[doc = ""] + //// Only a comment + A, + B, +); + +foo!( + //// Only a comment + #[doc = ""] + A, + B, +); + +foo!(/* Only a comment */ A, B,); + +foo!( + #[doc = ""] + /* Only a comment */ A, B, ); foo!( + /* Only a comment */ #[doc = ""] - /// Doc A, B, ); foo!( - /// Doc + /** Outer block doc (exactly 2 asterisks) */ + A, + B, +); + +foo!( #[doc = ""] + /** Outer block doc (exactly 2 asterisks) */ A, B, ); + +foo!( + /** Outer block doc (exactly 2 asterisks) */ + #[doc = ""] + A, + B, +); + +foo!(/*** Only a comment */ A, B,); + +foo!( + #[doc = ""] + /*** Only a comment */ + A, + B, +); + +foo!( + /*** Only a comment */ + #[doc = ""] + A, + B, +); + +// Inner doc comments cannot attach to a macro argument, so these are left +// unformatted on purpose. + +foo!( + //! Inner line doc + A, + B, +); + +foo!( + //!! Still an inner line doc (but with a bang at the beginning) + A, + B, +); + +foo!( + /*! Inner block doc */ + A, + B, +); + +foo!( + /*!! Still an inner block doc (but with a bang at the beginning) */ + A, + B, +); From 5fbc3ab12ef18c559131669f94258cb09f9766b3 Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Sat, 22 Aug 2026 12:33:50 +0000 Subject: [PATCH 78/97] ci tests rustfmt, but rustfmt doesn't test ci --- src/test/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/mod.rs b/src/test/mod.rs index 3353b78b221e3..f0fd7e2e0cf25 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -486,7 +486,7 @@ fn self_tests() { files.push(path); } // for crates that need to be included but lies outside src - let external_crates = vec!["check_diff", "config_proc_macro"]; + let external_crates = vec!["check_diff", "config_proc_macro", "ci"]; for external_crate in external_crates { let mut path = PathBuf::from(external_crate); path.push("src"); From 72fa0cf0e6fbfdba2db274364b4b6f7d4567e448 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sun, 23 Aug 2026 16:14:26 +0100 Subject: [PATCH 79/97] Remove repeated `--style-edition` option Due to a quirk of history, there were two version of this flag. The flag used to be nested under `is_nightly` but then separately: * f649c4c1771db3f9a6e4edeabde69978d2c0a340: move it to the main options, with `(unstable)` * 840eb96e1614182cc6fad779c49d5311791debfa: moved it to the main options, without `(unstable)` Those two commits were part of subtree pushes, so the two commits were likely made at roughly the same time on different subtrees. There should be no functional change here, the flags were identical, so no distinction was ever made when matching on them with `matches.opt_str("style-edition")` Fixes: #7046 --- src/bin/main.rs | 6 ------ tests/rustfmt/main.rs | 4 ---- 2 files changed, 10 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index af2dfdfac5b9d..05a22324d0f68 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -135,12 +135,6 @@ fn make_opts() -> Options { "Rust edition to use", "[2015|2018|2021|2024]", ); - opts.optopt( - "", - "style-edition", - "The edition of the Style Guide (unstable).", - "[2015|2018|2021|2024]", - ); opts.optopt( "", "color", diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 9a46544b3670f..ee972d8a6afb7 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -140,8 +140,6 @@ fn rustfmt_usage_text() { input file path --edition [2015|2018|2021|2024] Rust edition to use - --style-edition [2015|2018|2021|2024] - The edition of the Style Guide (unstable). --color [always|never|auto] Use colored output (if supported) --print-config [default|minimal|current] PATH @@ -190,8 +188,6 @@ fn rustfmt_nightly_usage_text() { input file path --edition [2015|2018|2021|2024] Rust edition to use - --style-edition [2015|2018|2021|2024] - The edition of the Style Guide (unstable). --color [always|never|auto] Use colored output (if supported) --print-config [default|minimal|current] PATH From 6c7ecd02e339b79147247c5d5e36df75f959895e Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Mon, 24 Aug 2026 08:54:48 +0200 Subject: [PATCH 80/97] add internal DSL for testing binders --- src/visitor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/visitor.rs b/src/visitor.rs index 55f9a4d8c8b26..b3ba0f2df2cdb 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -631,6 +631,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { // For now, leave the contents of the Span unformatted. self.push_rewrite(item.span, None) } + ast::ItemKind::TestBinderConstraints(..) => self.push_rewrite(item.span, None), }; } self.skip_context = skip_context_saved; From 58b45e59d9f0f980efa75d4495f6820e6bcaee04 Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Mon, 24 Aug 2026 10:02:28 +0000 Subject: [PATCH 81/97] Fix ICE on `for await` loops with separated keyword tokens --- src/expr.rs | 56 ++++++++++++++++++++----- tests/source/issue-7054.rs | 72 +++++++++++++++++++++++++++++++++ tests/target/issue-7054.rs | 83 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 10 deletions(-) create mode 100644 tests/source/issue-7054.rs create mode 100644 tests/target/issue-7054.rs diff --git a/src/expr.rs b/src/expr.rs index fc91afb25e362..4e8041803268d 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -990,8 +990,50 @@ impl<'a> ControlFlow<'a> { }; let label_string = rewrite_label(context, self.label); + + // Do not include the label in the span. + let lo = self + .label + .map_or(self.span.lo(), |label| label.ident.span.hi()); + + // `for await` is spelled with two tokens, and the source is free to + // separate them with any whitespace or comments. Locate each token in + // turn rather than searching for the rendered keyword, and keep + // whatever sits in the gap. + let (keyword, after_kwd) = if self.keyword == "for await" { + let after_for = context + .snippet_provider + .span_after(mk_sp(lo, self.span.hi()), "for"); + let before_await = context + .snippet_provider + .opt_span_before(mk_sp(after_for, self.span.hi()), "await") + .unknown_error()?; + let after_await = context + .snippet_provider + .opt_span_after(mk_sp(after_for, self.span.hi()), "await") + .unknown_error()?; + + // "for" + whatever is in the gap + "await" + let kwd = combine_strs_with_missing_comments( + context, + "for", + "await", + mk_sp(after_for, before_await), + shape, + true, + )?; + (kwd, after_await) + } else { + ( + self.keyword.to_owned(), + context + .snippet_provider + .span_after(mk_sp(lo, self.span.hi()), self.keyword.trim()), + ) + }; + // 1 = space after keyword. - let offset = self.keyword.len() + label_string.len() + 1; + let offset = keyword.len() + label_string.len() + 1; let pat_expr_string = match self.cond { Some(cond) => self.rewrite_pat_expr(context, cond, constr_shape, offset)?, @@ -1032,14 +1074,8 @@ impl<'a> ControlFlow<'a> { }; // `for event in event` - // Do not include label in the span. - let lo = self - .label - .map_or(self.span.lo(), |label| label.ident.span.hi()); let between_kwd_cond = mk_sp( - context - .snippet_provider - .span_after(mk_sp(lo, self.span.hi()), self.keyword.trim()), + after_kwd, if self.pat.is_none() { cond_span.lo() } else if self.matcher.is_empty() { @@ -1070,14 +1106,14 @@ impl<'a> ControlFlow<'a> { last_line_width(&pat_expr_string) } else { // 2 = spaces after keyword and condition. - label_string.len() + self.keyword.len() + pat_expr_string.len() + 2 + label_string.len() + keyword.len() + pat_expr_string.len() + 2 }; Ok(( format!( "{}{}{}{}{}", label_string, - self.keyword, + keyword, between_kwd_cond_comment.as_ref().map_or( if pat_expr_string.is_empty() || pat_expr_string.starts_with('\n') { "" diff --git a/tests/source/issue-7054.rs b/tests/source/issue-7054.rs new file mode 100644 index 0000000000000..5614292d79385 --- /dev/null +++ b/tests/source/issue-7054.rs @@ -0,0 +1,72 @@ +// rustfmt-edition: 2024 + +// `for await` is spelled with two tokens, so the source may separate them with +// arbitrary whitespace or comments. rustfmt must not search for the rendered +// keyword `for await` as a single literal string. + +#![feature(async_iterator, async_for_loop)] + +async fn for_await_canonical(iter: Iter) { + for await i in iter {} +} + +async fn for_await_extra_spaces(iter: Iter) { + for await i in iter {} +} + +async fn for_await_newline(iter: Iter) { + for + await i in iter {} +} + +async fn for_await_comment_between_keywords(iter: Iter) { + for /* between for and await */ await i in iter {} +} + +async fn for_await_comment_after_keyword(iter: Iter) { + for await /* between await and pat */ i in iter {} +} + +async fn for_await_comment_both_gaps(iter: Iter) { + for /* first gap */ await /* second gap */ i in iter {} +} + +async fn for_await_labeled(iter: Iter) { + 'outer: for await i in iter {} +} + +async fn for_await_body(iter: Iter) { + for await i in iter { + do_something(i); + } +} + +// Single-token keywords must be unaffected by the change. + +fn plain_for(iter: Iter) { + for i in iter {} + for /* comment */ i in iter {} + 'outer: for i in iter {} +} + +fn plain_while(cond: bool) { + while cond {} + while /* comment */ cond {} + 'outer: while cond {} +} + +fn plain_while_let(opt: Opt) { + while let Some(x) = opt {} + while /* comment */ let Some(x) = opt {} +} + +fn plain_loop() { + loop {} + 'outer: loop {} +} + +fn plain_if(cond: bool) { + if cond {} + if /* comment */ cond {} + if let Some(x) = opt {} +} diff --git a/tests/target/issue-7054.rs b/tests/target/issue-7054.rs new file mode 100644 index 0000000000000..3245f2e5e79d3 --- /dev/null +++ b/tests/target/issue-7054.rs @@ -0,0 +1,83 @@ +// rustfmt-edition: 2024 + +// `for await` is spelled with two tokens, so the source may separate them with +// arbitrary whitespace or comments. rustfmt must not search for the rendered +// keyword `for await` as a single literal string. + +#![feature(async_iterator, async_for_loop)] + +async fn for_await_canonical(iter: Iter) { + for await i in iter {} +} + +async fn for_await_extra_spaces(iter: Iter) { + for await i in iter {} +} + +async fn for_await_newline(iter: Iter) { + for await i in iter {} +} + +async fn for_await_comment_between_keywords(iter: Iter) { + for /* between for and await */ await i in iter {} +} + +async fn for_await_comment_after_keyword(iter: Iter) { + for await + /* between await and pat */ + i in iter {} +} + +async fn for_await_comment_both_gaps(iter: Iter) { + for /* first gap */ await + /* second gap */ + i in iter {} +} + +async fn for_await_labeled(iter: Iter) { + 'outer: for await i in iter {} +} + +async fn for_await_body(iter: Iter) { + for await i in iter { + do_something(i); + } +} + +// Single-token keywords must be unaffected by the change. + +fn plain_for(iter: Iter) { + for i in iter {} + for + /* comment */ + i in iter {} + 'outer: for i in iter {} +} + +fn plain_while(cond: bool) { + while cond {} + while + /* comment */ + cond {} + 'outer: while cond {} +} + +fn plain_while_let(opt: Opt) { + while let Some(x) = opt {} + while + /* comment */ + let Some(x) = opt {} +} + +fn plain_loop() { + loop {} + 'outer: loop {} +} + +fn plain_if(cond: bool) { + if cond {} + if + /* comment */ + cond {} + if let Some(x) = opt {} +} From 8571bda78886010da12752da9383f551647ac9bd Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Mon, 24 Aug 2026 11:49:22 -0400 Subject: [PATCH 82/97] fix: correct visibility and defaultness order on associated impl type alias Fixes rust-lang/rustfmt 7057 --- src/items.rs | 20 +++++++++++--------- tests/target/issue_7057.rs | 3 +++ 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 tests/target/issue_7057.rs diff --git a/src/items.rs b/src/items.rs index a63fd87448729..64348ad174f73 100644 --- a/src/items.rs +++ b/src/items.rs @@ -1711,13 +1711,13 @@ pub(crate) fn rewrite_type_alias<'a>( match (visitor_kind, &op_ty) { (Item | AssocTraitItem | ForeignItem, Some(op_bounds)) => { let op = OpaqueType { bounds: op_bounds }; - rewrite_ty(rw_info, Some(bounds), Some(&op), rhs_hi, vis) + rewrite_ty(rw_info, Some(bounds), Some(&op), rhs_hi, vis, defaultness) } (Item | AssocTraitItem | ForeignItem, None) => { - rewrite_ty(rw_info, Some(bounds), ty_opt, rhs_hi, vis) + rewrite_ty(rw_info, Some(bounds), ty_opt, rhs_hi, vis, defaultness) } (AssocImplItem, _) => { - let result = if let Some(op_bounds) = op_ty { + if let Some(op_bounds) = op_ty { let op = OpaqueType { bounds: op_bounds }; rewrite_ty( rw_info, @@ -1725,13 +1725,10 @@ pub(crate) fn rewrite_type_alias<'a>( Some(&op), rhs_hi, &DEFAULT_VISIBILITY, + defaultness, ) } else { - rewrite_ty(rw_info, Some(bounds), ty_opt, rhs_hi, vis) - }?; - match defaultness { - ast::Defaultness::Default(..) => Ok(format!("default {result}")), - _ => Ok(result), + rewrite_ty(rw_info, Some(bounds), ty_opt, rhs_hi, vis, defaultness) } } } @@ -1744,10 +1741,15 @@ fn rewrite_ty( // the span of the end of the RHS (or the end of the generics, if there is no RHS) rhs_hi: BytePos, vis: &ast::Visibility, + defaultness: ast::Defaultness, ) -> RewriteResult { let mut result = String::with_capacity(128); let TyAliasRewriteInfo(context, indent, generics, after_where_clause, ident, span) = *rw_info; - result.push_str(&format!("{}type ", format_visibility(context, vis))); + result.push_str(&format!( + "{}{}type ", + format_visibility(context, vis), + format_defaultness(defaultness) + )); let ident_str = rewrite_ident(context, ident); if generics.params.is_empty() { diff --git a/tests/target/issue_7057.rs b/tests/target/issue_7057.rs new file mode 100644 index 0000000000000..102d13cfa9859 --- /dev/null +++ b/tests/target/issue_7057.rs @@ -0,0 +1,3 @@ +impl T for S { + pub default type X; +} From a5add3025a54a9182698751760a8a2e11a905748 Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Mon, 24 Aug 2026 16:35:45 +0000 Subject: [PATCH 83/97] offset calculation changed from keyword's byte length to width of its last new line. Test cases added for multi-line and line comment --- src/expr.rs | 4 +- tests/source/issue-7054.rs | 83 +++++++++++++++++++++++++++++++++++- tests/target/issue-7054.rs | 87 +++++++++++++++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 4 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index 4e8041803268d..2a989ad833a48 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1033,7 +1033,7 @@ impl<'a> ControlFlow<'a> { }; // 1 = space after keyword. - let offset = keyword.len() + label_string.len() + 1; + let offset = last_line_width(&keyword) + label_string.len() + 1; let pat_expr_string = match self.cond { Some(cond) => self.rewrite_pat_expr(context, cond, constr_shape, offset)?, @@ -1106,7 +1106,7 @@ impl<'a> ControlFlow<'a> { last_line_width(&pat_expr_string) } else { // 2 = spaces after keyword and condition. - label_string.len() + keyword.len() + pat_expr_string.len() + 2 + label_string.len() + last_line_width(&keyword) + pat_expr_string.len() + 2 }; Ok(( diff --git a/tests/source/issue-7054.rs b/tests/source/issue-7054.rs index 5614292d79385..3ae28c2132963 100644 --- a/tests/source/issue-7054.rs +++ b/tests/source/issue-7054.rs @@ -1,4 +1,4 @@ -// rustfmt-edition: 2024 +// rustfmt-edition: 2018 // `for await` is spelled with two tokens, so the source may separate them with // arbitrary whitespace or comments. rustfmt must not search for the rendered @@ -31,10 +31,82 @@ async fn for_await_comment_both_gaps(iter: Iter) { for /* first gap */ await /* second gap */ i in iter {} } +async fn for_multi_line_comment_await(iter: Iter) { + for /* some + * multi-line + * comment */ await + i in iter {} +} + +async fn for_comment_await_comment_(iter: Iter) { + for /* some + * multi-line + * comment */ await +/* some + * multi-line + * comment */ + i in iter {} +} + +async fn for_await_comment_with_long_iterator(iter: Iter) { + for /* some + * multi-line + * comment */ await + i in some_really_long_iterator_expression(alpha, beta, gamma, delta) {} +} + +async fn for_await_comment_exceeding_line_budget(iter: Iter) { + for /* some + * multi-line + * comment + * that keeps + * going on + * and on + * past ninety + * six bytes */ + await + i in iter {} +} + +async fn for_await_line_comment_between_keywords(iter: Iter) { + for // between for and await + await i in iter {} +} + +async fn for_await_multiple_line_comments_between_keywords_with_spaces(iter: Iter) { + for // first line + // second line + await i in iter {} +} + +async fn for_await_line_comment_after_keyword(iter: Iter) { + for await // between await and pat + i in iter {} +} + +async fn for_await_line_comments_both_gaps(iter: Iter) { + for // first gap + await // second gap + i in iter {} +} + async fn for_await_labeled(iter: Iter) { 'outer: for await i in iter {} } +async fn for_await_labeled(iter: Iter) { + 'outer: for // line comment + await i in iter {} +} + +async fn for_await_labeled_multi_line_comment(iter: Iter) { + 'outer: for /* some + * multi-line + * comment */ await + i in iter {} +} + + async fn for_await_body(iter: Iter) { for await i in iter { do_something(i); @@ -46,18 +118,24 @@ async fn for_await_body(iter: Iter) { fn plain_for(iter: Iter) { for i in iter {} for /* comment */ i in iter {} + for // line comment + i in iter {} 'outer: for i in iter {} } fn plain_while(cond: bool) { while cond {} while /* comment */ cond {} + while // line comment + cond {} 'outer: while cond {} } fn plain_while_let(opt: Opt) { while let Some(x) = opt {} while /* comment */ let Some(x) = opt {} + while // line comment + let Some(x) = opt {} } fn plain_loop() { @@ -68,5 +146,8 @@ fn plain_loop() { fn plain_if(cond: bool) { if cond {} if /* comment */ cond {} + if // line comment + cond {} if let Some(x) = opt {} } + diff --git a/tests/target/issue-7054.rs b/tests/target/issue-7054.rs index 3245f2e5e79d3..754c0379c7677 100644 --- a/tests/target/issue-7054.rs +++ b/tests/target/issue-7054.rs @@ -1,4 +1,4 @@ -// rustfmt-edition: 2024 +// rustfmt-edition: 2018 // `for await` is spelled with two tokens, so the source may separate them with // arbitrary whitespace or comments. rustfmt must not search for the rendered @@ -34,10 +34,83 @@ async fn for_await_comment_both_gaps(iter: Iter) { i in iter {} } +async fn for_multi_line_comment_await(iter: Iter) { + for /* some + * multi-line + * comment */ + await i in iter {} +} + +async fn for_comment_await_comment_(iter: Iter) { + for /* some + * multi-line + * comment */ + await + /* some + * multi-line + * comment */ + i in iter {} +} + +async fn for_await_comment_with_long_iterator(iter: Iter) { + for /* some + * multi-line + * comment */ + await i in some_really_long_iterator_expression(alpha, beta, gamma, delta) {} +} + +async fn for_await_comment_exceeding_line_budget(iter: Iter) { + for /* some + * multi-line + * comment + * that keeps + * going on + * and on + * past ninety + * six bytes */ + await i in iter {} +} + +async fn for_await_line_comment_between_keywords(iter: Iter) { + for // between for and await + await i in iter {} +} + +async fn for_await_multiple_line_comments_between_keywords_with_spaces(iter: Iter) { + for // first line + // second line + await i in iter {} +} + +async fn for_await_line_comment_after_keyword(iter: Iter) { + for await + // between await and pat + i in iter {} +} + +async fn for_await_line_comments_both_gaps(iter: Iter) { + for // first gap + await + // second gap + i in iter {} +} + async fn for_await_labeled(iter: Iter) { 'outer: for await i in iter {} } +async fn for_await_labeled(iter: Iter) { + 'outer: for // line comment + await i in iter {} +} + +async fn for_await_labeled_multi_line_comment(iter: Iter) { + 'outer: for /* some + * multi-line + * comment */ + await i in iter {} +} + async fn for_await_body(iter: Iter) { for await i in iter { do_something(i); @@ -51,6 +124,9 @@ fn plain_for(iter: Iter) { for /* comment */ i in iter {} + for + // line comment + i in iter {} 'outer: for i in iter {} } @@ -59,6 +135,9 @@ fn plain_while(cond: bool) { while /* comment */ cond {} + while + // line comment + cond {} 'outer: while cond {} } @@ -67,6 +146,9 @@ fn plain_while_let(opt: Opt) { while /* comment */ let Some(x) = opt {} + while + // line comment + let Some(x) = opt {} } fn plain_loop() { @@ -79,5 +161,8 @@ fn plain_if(cond: bool) { if /* comment */ cond {} + if + // line comment + cond {} if let Some(x) = opt {} } From eac232c9f2415dd264bde56554709b64dd018a70 Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Mon, 24 Aug 2026 20:42:35 +0000 Subject: [PATCH 84/97] external-crate/tests directory added in self_tests for format check --- check_diff/tests/check_diff.rs | 4 ++-- src/test/mod.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/check_diff/tests/check_diff.rs b/check_diff/tests/check_diff.rs index 7dceeccd050e3..8e0e9333d7c71 100644 --- a/check_diff/tests/check_diff.rs +++ b/check_diff/tests/check_diff.rs @@ -1,6 +1,6 @@ use check_diff::{ - CheckDiffError, DiffChecker, CodeFormatter, FormatCodeError, Repository, - RustFmtFileFinder, check_diff, + CheckDiffError, CodeFormatter, DiffChecker, FormatCodeError, Repository, RustFmtFileFinder, + check_diff, }; use std::fs::File; use tempfile::Builder; diff --git a/src/test/mod.rs b/src/test/mod.rs index f0fd7e2e0cf25..7320d7e1d7895 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -504,6 +504,9 @@ fn self_tests() { for file in search_files { files.push(file); } + + let mut tests_files = get_test_files(&PathBuf::from(external_crate).join("tests"), false); + files.append(&mut tests_files); } files.push(PathBuf::from("src/lib.rs")); From e445464e2883a760d5370fb8826dab1849108e37 Mon Sep 17 00:00:00 2001 From: AsthaMishra Date: Mon, 24 Aug 2026 23:15:33 +0000 Subject: [PATCH 85/97] enable recursive file search in self-tests for external-crate tests --- src/test/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/mod.rs b/src/test/mod.rs index 7320d7e1d7895..eb9352741555f 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -505,7 +505,7 @@ fn self_tests() { files.push(file); } - let mut tests_files = get_test_files(&PathBuf::from(external_crate).join("tests"), false); + let mut tests_files = get_test_files(&PathBuf::from(external_crate).join("tests"), true); files.append(&mut tests_files); } files.push(PathBuf::from("src/lib.rs")); From 17050c9074c5bce81a13ff04ecd19891d5ba5bf6 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Mon, 24 Aug 2026 19:25:15 -0400 Subject: [PATCH 86/97] fix: correct the span used when rewriting `ast::TyKind::FnPtr` Fixes rust-lang/rustfmt 7062 The span for the entire `ast::Ty` contains the generic params, and if those generics contain a `()`, the comment recovery code will accidentally pick those up. Instead limit the rewrite to just the `decl_span`. --- src/types.rs | 2 +- tests/target/issue_7062.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/target/issue_7062.rs diff --git a/src/types.rs b/src/types.rs index d6a25e61008f0..c0cc9adc78f9a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1132,7 +1132,7 @@ fn rewrite_fn_ptr( fn_ptr.decl.inputs.iter(), &fn_ptr.decl.output, fn_ptr.decl.c_variadic(), - span, + fn_ptr.decl_span, context, func_ty_shape, )?; diff --git a/tests/target/issue_7062.rs b/tests/target/issue_7062.rs new file mode 100644 index 0000000000000..58364d3290598 --- /dev/null +++ b/tests/target/issue_7062.rs @@ -0,0 +1 @@ +type FnNo = for<#[cfg_attr(FALSE, unknown)] 'a> fn(); From e01c77e24d8c009f7c1795783a820957de489a1e Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sat, 28 Jun 2025 23:12:35 +0800 Subject: [PATCH 87/97] Fix --- src/items.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/items.rs b/src/items.rs index 6a68e2ec8c502..a4719334fd5e6 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2361,7 +2361,14 @@ impl Rewrite for ast::Param { Ok(result) } else { - self.ty.rewrite_result(context, shape) + combine_strs_with_missing_comments( + context, + ¶m_attrs_result, + &self.ty.rewrite_result(context, shape)?, + span, + shape, + !has_multiple_attr_lines && !has_doc_comments, + ) } } } From 69af1349b0aa060b99e47ea5f0f809715c6a6ab9 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sat, 28 Jun 2025 23:12:43 +0800 Subject: [PATCH 88/97] Add variadic test --- tests/source/issue-6561/variadic.rs | 5 +++++ tests/target/issue-6561/variadic.rs | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 tests/source/issue-6561/variadic.rs create mode 100644 tests/target/issue-6561/variadic.rs diff --git a/tests/source/issue-6561/variadic.rs b/tests/source/issue-6561/variadic.rs new file mode 100644 index 0000000000000..6108d584d30d8 --- /dev/null +++ b/tests/source/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()]...); +} \ No newline at end of file diff --git a/tests/target/issue-6561/variadic.rs b/tests/target/issue-6561/variadic.rs new file mode 100644 index 0000000000000..bf5274f31e1da --- /dev/null +++ b/tests/target/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()] ...); +} From dbdd05bc81ec68e1945cba822ecbbb60149cb79e Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sat, 28 Jun 2025 23:17:40 +0800 Subject: [PATCH 89/97] Add trait fn tests --- tests/source/issue-6561/trait-fn.rs | 6 ++++++ tests/target/issue-6561/trait-fn.rs | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/source/issue-6561/trait-fn.rs create mode 100644 tests/target/issue-6561/trait-fn.rs diff --git a/tests/source/issue-6561/trait-fn.rs b/tests/source/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..f88c69daddafb --- /dev/null +++ b/tests/source/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} \ No newline at end of file diff --git a/tests/target/issue-6561/trait-fn.rs b/tests/target/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..a30396be56126 --- /dev/null +++ b/tests/target/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} From 86abbb8e87da3f6eebb95ee2097499bdecd848d8 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Mon, 21 Jul 2025 12:10:48 +0800 Subject: [PATCH 90/97] Add test for issue 6607 --- tests/source/issue-6607/fn-type.rs | 3 +++ tests/target/issue-6607/fn-type.rs | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/source/issue-6607/fn-type.rs create mode 100644 tests/target/issue-6607/fn-type.rs diff --git a/tests/source/issue-6607/fn-type.rs b/tests/source/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..e85d04d6041e1 --- /dev/null +++ b/tests/source/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} \ No newline at end of file diff --git a/tests/target/issue-6607/fn-type.rs b/tests/target/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..7f89d12be8d63 --- /dev/null +++ b/tests/target/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} From 4461e6d808046a0fdcc22563cae87fc8e3155771 Mon Sep 17 00:00:00 2001 From: cyrgani Date: Tue, 19 May 2026 09:03:37 +0000 Subject: [PATCH 91/97] remove `box_patterns` feature --- src/patterns.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/patterns.rs b/src/patterns.rs index 2fad1d41ae9f7..62a72c4dc433e 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -70,10 +70,9 @@ fn is_short_pattern_inner(context: &RewriteContext<'_>, pat: &ast::Pat) -> bool ast::PatKind::TupleStruct(_, ref path, ref subpats) => { path.segments.len() <= 1 && subpats.len() <= 1 } - ast::PatKind::Box(ref p) - | PatKind::Deref(ref p) - | ast::PatKind::Ref(ref p, _, _) - | ast::PatKind::Paren(ref p) => is_short_pattern_inner(context, &*p), + PatKind::Deref(ref p) | ast::PatKind::Ref(ref p, _, _) | ast::PatKind::Paren(ref p) => { + is_short_pattern_inner(context, &*p) + } PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(context, p)), } } @@ -114,7 +113,6 @@ impl Rewrite for Pat { .ends_with_newline(false); write_list(&items, &fmt) } - PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape), PatKind::Ident(BindingMode(by_ref, mutability), ident, ref sub_pat) => { let mut_prefix = format_mutability(mutability).trim(); @@ -528,7 +526,7 @@ pub(crate) fn can_be_overflowed_pat( | ast::PatKind::Tuple(..) | ast::PatKind::Struct(..) | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1, - ast::PatKind::Ref(ref p, _, _) | ast::PatKind::Box(ref p) => { + ast::PatKind::Ref(ref p, _, _) => { can_be_overflowed_pat(context, &TuplePatField::Pat(p), len) } ast::PatKind::Expr(ref expr) => can_be_overflowed_expr(context, expr, len), From bcde489b16e8e8b504610c1364dee783e5763eb6 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Wed, 26 Aug 2026 19:56:01 -0400 Subject: [PATCH 92/97] chore: fix lint `cargo::manual_readme` ``` warning: explicit `package.readme` can be inferred --> src/tools/rustfmt/Cargo.toml:6:1 | 6 | readme = "README.md" | ^^^^^^^^^^^^^^^^^^^^ | = note: `cargo::manual_readme` is set to `warn` by default help: consider removing `package.readme` warning: `rustfmt-nightly` (manifest) generated 1 warning ``` See --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 84253d2fb0163..48dfeed93dad9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ name = "rustfmt-nightly" version = "1.10.0" description = "Tool to find and fix Rust formatting issues" repository = "https://github.com/rust-lang/rustfmt" -readme = "README.md" license = "Apache-2.0 OR MIT" build = "build.rs" categories = ["development-tools"] From f533ffb60280151f6c43288b2210f0ae99027c41 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 27 Aug 2026 22:45:28 -0400 Subject: [PATCH 93/97] chore: bump rustfmt toolchain to nightly-2026-08-27 Bumping the toolchain version as part of a git subtree push. Before: ``` 1.99.0-nightly (9f36de775 2026-07-19) ``` After: ``` 1.100.0-nightly (bff8e12ff 2026-08-26) ``` --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 5dac4fcf28091..9dda873e3e868 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-07-19" +channel = "nightly-2026-08-27" components = ["llvm-tools", "rustc-dev"] From 37064d38059353fa6e203335d2d56562cdd34a04 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 27 Aug 2026 23:48:33 -0400 Subject: [PATCH 94/97] feature (Diff Check): add CLI option to configure the release channel Now we can configure which release channel the compiled rustfmt should target. Because `CFG_RELEASE_CHANNEL` wasn't getting set before we'd default to the `nightly` channel. For the most part that's not really an issue, but because we're using the diff check to ensure that we're not changing stable formatting it's more appropriate to default builds to the `stable` release channel. --- check_diff/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++- check_diff/src/main.rs | 6 +++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index 4d3933257dcc5..9578a2e19c0fd 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -79,6 +79,37 @@ impl FromStr for StyleEdition { } } +/// Configure which release channel to use when compiling rustfmt +#[derive(Debug, Clone, Copy)] +pub enum ReleaseChannel { + Stable, + Beta, + Nightly, +} + +impl ReleaseChannel { + fn as_str(&self) -> &str { + match self { + Self::Stable => "stable", + Self::Beta => "beta", + Self::Nightly => "nightly", + } + } +} + +impl FromStr for ReleaseChannel { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "stable" => Ok(Self::Stable), + "beta" => Ok(Self::Beta), + "nightly" => Ok(Self::Nightly), + _ => Err(format!("Invalid release channel {s}")), + } + } +} + pub enum FormatCodeError { // IO Error when running code formatter Io(std::io::Error), @@ -572,13 +603,16 @@ pub fn build_rustfmt_from_src>( edition: Edition, style_edition: StyleEdition, config: Option<&[T]>, + release_channel: ReleaseChannel, ) -> Result { // Because we're building standalone binaries we need to set the dynamic library path // so each rustfmt binary can find it's runtime dependencies. let dynamic_library_path = get_dynamic_library_path(dir)?; + let release_channel = release_channel.as_str(); - info!("Building rustfmt from source"); + info!("Building {} rustfmt from source", release_channel); let Ok(_) = Command::new("cargo") + .env("CFG_RELEASE_CHANNEL", release_channel) .current_dir(dir) .args(["build", "-q", "--release", "--bin", "rustfmt"]) .output() @@ -611,6 +645,7 @@ pub fn compile_rustfmt>( style_edition: StyleEdition, commit_hash: Option, config: Option<&[T]>, + release_channel: ReleaseChannel, ) -> Result, CheckDiffError> { const RUSTFMT_REPO: &str = "https://github.com/rust-lang/rustfmt.git"; let checkout_ref = commit_hash.as_ref().unwrap_or(&feature_branch); @@ -628,6 +663,7 @@ pub fn compile_rustfmt>( edition, style_edition, config, + release_channel, )?; let should_detach = commit_hash.is_some(); git_switch(checkout_ref, should_detach)?; @@ -638,6 +674,7 @@ pub fn compile_rustfmt>( edition, style_edition, config, + release_channel, )?; info!("SOURCE_BIN {}", source_runner.get_binary_version()?); let dynamic_library_path_env_var = dynamic_library_path_env_var_name(); diff --git a/check_diff/src/main.rs b/check_diff/src/main.rs index 812179d3417c9..c48280f254d4b 100644 --- a/check_diff/src/main.rs +++ b/check_diff/src/main.rs @@ -2,7 +2,8 @@ use std::io::Error; use std::process::ExitCode; use check_diff::{ - Edition, StyleEdition, check_diff, clone_repositories_for_diff_check, compile_rustfmt, + Edition, ReleaseChannel, StyleEdition, check_diff, clone_repositories_for_diff_check, + compile_rustfmt, }; use clap::Parser; use tempfile::tempdir; @@ -67,6 +68,8 @@ struct CliInputs { // Choosing 16 as the default since that's a common multiple of available CPU cores. #[arg(short, long, default_value_t = std::num::NonZeroU8::new(16).unwrap())] worker_threads: std::num::NonZeroU8, + #[arg(long, default_value = "stable")] + release_channel: ReleaseChannel, } fn main() -> Result { @@ -85,6 +88,7 @@ fn main() -> Result { args.style_edition, args.commit_hash, args.rustfmt_config.as_deref(), + args.release_channel, ); let diff_checker = match compilation_result { From 7376a6ad5e63aa807834ed02cda138c6ed3c57b1 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 27 Aug 2026 23:55:01 -0400 Subject: [PATCH 95/97] feature (Diff Check): allow user's to set `release_channel` for check_diff.yml The `release_channel` defaults to `stable`, but it might be nice in some cases to test different release channels. --- .github/workflows/check_diff.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/check_diff.yml b/.github/workflows/check_diff.yml index 41b138acb63fc..38aac68b94c72 100644 --- a/.github/workflows/check_diff.yml +++ b/.github/workflows/check_diff.yml @@ -32,6 +32,14 @@ on: rustfmt_configs: description: 'Optional comma separated list of rustfmt config options to pass when running the feature branch' required: false + release_channel: + description: 'Configure which release channel to use when compiling rustfmt' + default: stable + type: choice + options: + - stable + - beta + - nightly permissions: contents: read From d382b9047f89aa0a021d455f6da529277f86ecae Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 28 Aug 2026 19:47:19 +0800 Subject: [PATCH 96/97] [CI] Drop unused `Makefile.toml` This was added in fb517f45c6de7e146a2fe3b654bf051c433b00f8 but later no longer used since 0cff306b61922ed5f460a4f6cbd4134498a3c42f. --- Makefile.toml | 71 --------------------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 Makefile.toml diff --git a/Makefile.toml b/Makefile.toml deleted file mode 100644 index 597dd1205643d..0000000000000 --- a/Makefile.toml +++ /dev/null @@ -1,71 +0,0 @@ -[env] -CFG_RELEASE = { value = "${CARGO_MAKE_RUST_VERSION}", condition = { env_not_set = ["CFG_RELEASE"] } } -CFG_RELEASE_CHANNEL = { value = "${CARGO_MAKE_RUST_CHANNEL}", condition = { env_not_set = ["CFG_RELEASE_CHANNEL"] } } - -[tasks.build-bin] -command = "cargo" -args = [ - "build", - "--bin", - "rustfmt", - "--bin", - "cargo-fmt", -] - -[tasks.build-bins] -command = "cargo" -args = [ - "build", - "--bins", -] - -[tasks.install] -command = "cargo" -args = [ - "install", - "--path", - ".", - "--force", - "--locked", # Respect Cargo.lock -] - -[tasks.release] -command = "cargo" -args = [ - "build", - "--release", -] - -[tasks.test] -command = "cargo" -args = [ - "test", -] - -[tasks.test-all] -dependencies = ["build-bin"] -run_task = { name = ["test", "test-ignored"] } - -[tasks.test-ignored] -command = "cargo" -args = [ - "test", - "--", - "--ignored", -] - -[tasks.b] -alias = "build" - -[tasks.bb] -alias = "build-bin" - -[tasks.bins] -alias = "build-bins" - -[tasks.c] -alias = "check" - -[tasks.t] -alias = "test" - From e3e8a34286c2c9d8766518bee5e7040eabe1c6d1 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 29 Aug 2026 15:35:54 +0800 Subject: [PATCH 97/97] [Deps] Regenerate lockfile --- Cargo.lock | 61 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 696b797e612f9..53bbc356e4c34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2010,6 +2010,20 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "insta" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +dependencies = [ + "console", + "once_cell", + "regex", + "similar", + "strip-ansi-escapes", + "tempfile", +] + [[package]] name = "installer" version = "0.0.0" @@ -5099,7 +5113,7 @@ dependencies = [ name = "rustfmt-nightly" version = "1.10.0" dependencies = [ - "annotate-snippets 0.11.5", + "annotate-snippets 0.12.16", "anyhow", "bytecount", "cargo_metadata 0.23.1", @@ -5109,6 +5123,7 @@ dependencies = [ "dirs", "getopts", "ignore", + "insta", "itertools", "regex", "rustfmt-config_proc_macro", @@ -5118,7 +5133,7 @@ dependencies = [ "tempfile", "term", "thiserror 1.0.69", - "toml 0.9.8", + "toml 1.1.0+spec-1.1.0", "tracing", "tracing-subscriber", "unicode-properties", @@ -5501,6 +5516,15 @@ dependencies = [ "stacker", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -5803,21 +5827,6 @@ dependencies = [ "toml_edit 0.22.27", ] -[[package]] -name = "toml" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned 1.1.0", - "toml_datetime 0.7.3", - "toml_parser", - "toml_writer", - "winnow 0.7.13", -] - [[package]] name = "toml" version = "1.1.0+spec-1.1.0" @@ -5842,15 +5851,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.0+spec-1.1.0" @@ -6292,6 +6292,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + [[package]] name = "wait-timeout" version = "0.2.1"