From 05a72d20b0c6b68c279f1a0393a23f71c93bbe4c Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Fri, 16 Oct 2026 18:45:19 +0200 Subject: [PATCH 01/46] Rewrite `--print-config` integration tests The main thing here is to be more explicit on assertions: e.g. to answer are we expecting something to be printed to stdout or stderr? I thought about rewriting the existing macro to handle this case too, but couldn't find a good approach for that, and decided that the plain code, while more lines, was more readable. --- src/tools/rustfmt/tests/rustfmt/main.rs | 153 ++++++++++++++---------- 1 file changed, 93 insertions(+), 60 deletions(-) diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index 5fbf64d8862c1..c37951c085982 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -1,6 +1,5 @@ //! Integration tests for rustfmt. -use indoc::panicdoc; use std::env; use std::fs::{File, remove_file}; use std::io::Write; @@ -57,79 +56,113 @@ fn rustfmt(args: &[&str]) -> (String, String) { rustfmt_with_extra(args, None, &[], None) } -macro_rules! assert_that { - ($args:expr, $($check:ident $check_args:tt)&&+) => { - let (stdout, stderr) = rustfmt($args); - if $(!stdout.$check$check_args && !stderr.$check$check_args)||* { - let args = $args; - let expected = stringify!($( $check$check_args )&&*); - panicdoc! {" - Output not expected for rustfmt {args:?} - expected: {expected} - actual stdout: - {stdout} - actual stderr: - {stderr}", - }; - } - }; +#[test] +fn print_config_errors_on_unknown_option() { + let args = &["--print-config", "unknown"]; + + let (stdout, stderr) = rustfmt(args); + + assert_eq!(&stdout, ""); + assert!( + stderr.contains("Unknown print-config option"), + "expected helpful error message, got: {stderr}" + ); } -#[rustfmt_only_ci_test] #[test] -fn print_config() { - assert_that!( - &["--print-config", "unknown"], - starts_with("Unknown print-config option") +fn print_config_default() { + let args = &["--print-config", "default"]; + let (stdout, stderr) = rustfmt(args); + + assert!( + stdout.contains("max_width = 100"), + "expected stdout to contain a default config, got: {stdout}", ); - assert_that!(&["--print-config", "default"], contains("max_width = 100")); - assert_that!(&["--print-config", "minimal"], contains("PATH required")); - assert_that!( - &["--print-config", "minimal", "minimal-config"], - contains("doesn't work with standard input.") + assert_eq!(&stderr, ""); +} + +#[test] +fn print_config_errors_minimal_with_no_path() { + let args = &["--print-config", "minimal"]; + + let (stdout, stderr) = rustfmt(args); + + // NOTE: existing the behaviour, possibly a bug + assert_eq!(&stdout, "\n"); + assert!( + stderr.contains("PATH required"), + "expected helpful error message, got: {stderr}" ); +} - let (stdout, stderr) = rustfmt(&[ - "--print-config", - "minimal", - "minimal-config", - "src/shape.rs", - ]); +#[test] +fn print_config_errors_minimal_without_input_file() { + let args = &["--print-config", "minimal", "minimal.toml"]; + + let (stdout, stderr) = rustfmt(args); + + assert_eq!(&stdout, ""); assert!( - Path::new("minimal-config").exists(), - "stdout:\n{stdout}\nstderr:\n{stderr}" + stderr.contains("doesn't work with standard input"), + "expected helpful error message, got: {stderr}" ); - remove_file("minimal-config").unwrap(); } +// test writes to file within the repo, rust-lang/rust doesn't like that #[rustfmt_only_ci_test] #[test] -fn inline_config() { - // single invocation - assert_that!( - &[ - "--print-config", - "current", - ".", - "--config=color=Never,edition=2018" - ], - contains("color = \"Never\"") && contains("edition = \"2018\"") +fn print_config_writes_to_file() { + let out_file = "minimal.toml"; + let args = &["--print-config", "minimal", out_file, "src/shape.rs"]; + + let (stdout, stderr) = rustfmt(args); + + assert_eq!(stderr, ""); + assert!( + Path::new(out_file).exists(), + "stdout:\n{stdout}\nstderr:\n{stderr}" + ); + remove_file(out_file).unwrap(); +} + +#[test] +fn print_config_merges_override_from_args() { + let args = &[ + "--print-config", + "current", + ".", + "--config=color=Never,edition=2018", + ]; + + let (stdout, stderr) = rustfmt(args); + + assert_eq!(stderr, ""); + assert!( + stdout.contains(r#"color = "Never""#) && stdout.contains(r#"edition = "2018""#), + "expected overrides in stdout, got: {stdout}", ); +} - // multiple overriding invocations - assert_that!( - &[ - "--print-config", - "current", - ".", - "--config", - "color=never,edition=2018", - "--config", - "color=always,format_strings=true" - ], - contains("color = \"Always\"") - && contains("edition = \"2018\"") - && contains("format_strings = true") +#[test] +fn print_config_merges_multiple_overrides_from_args() { + let args = &[ + "--print-config", + "current", + ".", + "--config", + "color=never,edition=2018", + "--config", + "color=always,format_strings=true", + ]; + + let (stdout, stderr) = rustfmt(args); + + assert_eq!(stderr, ""); + assert!( + stdout.contains(r#"color = "Always""#) + && stdout.contains(r#"edition = "2018""#) + && stdout.contains("format_strings = true"), + "expected overrides in stdout, got: {stdout}", ); } From d7ecf4d6fc403c51bc2ff8299def0a0713abae97 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 17 Oct 2026 16:01:39 +0100 Subject: [PATCH 02/46] Update config discovery integration tests Firstly, Simplify the directory structure: we only need a single package here, so drag everything up for that. Secondly, make it more explicit exactly which config we expect to read by setting up configs with different options, rather than just ignoring all formatting. Also add tests for other existing behaviour: searching in `HOME` and `XDG_CONFIG_HOME` when no config is given nor found. --- .../tests/config/issue_4660/Cargo.toml | 8 +- .../issue_4660/configs/rustfmt.hard_tabs.toml | 1 + .../inner_bin/.rustfmt.unstable.toml | 1 - .../config/issue_4660/inner_bin/Cargo.toml | 6 - .../config/issue_4660/inner_bin/src/main.rs | 1 - .../config/issue_4660/inner_lib/Cargo.toml | 6 - .../config/issue_4660/inner_lib/rustfmt.toml | 1 - .../config/issue_4660/inner_lib/src/lib.rs | 1 - .../tests/config/issue_4660/rustfmt.toml | 1 + .../tests/config/issue_4660/src/lib.rs | 2 + .../issue_4660/src/longhand_field_init.rs | 10 ++ .../config/issue_4660/src/with_spaces.rs | 3 + src/tools/rustfmt/tests/rustfmt/main.rs | 114 +++++++++++++++--- 13 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 src/tools/rustfmt/tests/config/issue_4660/configs/rustfmt.hard_tabs.toml delete mode 100644 src/tools/rustfmt/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml delete mode 100644 src/tools/rustfmt/tests/config/issue_4660/inner_bin/Cargo.toml delete mode 100644 src/tools/rustfmt/tests/config/issue_4660/inner_bin/src/main.rs delete mode 100644 src/tools/rustfmt/tests/config/issue_4660/inner_lib/Cargo.toml delete mode 100644 src/tools/rustfmt/tests/config/issue_4660/inner_lib/rustfmt.toml delete mode 100644 src/tools/rustfmt/tests/config/issue_4660/inner_lib/src/lib.rs create mode 100644 src/tools/rustfmt/tests/config/issue_4660/rustfmt.toml create mode 100644 src/tools/rustfmt/tests/config/issue_4660/src/lib.rs create mode 100644 src/tools/rustfmt/tests/config/issue_4660/src/longhand_field_init.rs create mode 100644 src/tools/rustfmt/tests/config/issue_4660/src/with_spaces.rs diff --git a/src/tools/rustfmt/tests/config/issue_4660/Cargo.toml b/src/tools/rustfmt/tests/config/issue_4660/Cargo.toml index c716e9d057548..c9ddf1793b88f 100644 --- a/src/tools/rustfmt/tests/config/issue_4660/Cargo.toml +++ b/src/tools/rustfmt/tests/config/issue_4660/Cargo.toml @@ -1,2 +1,6 @@ -[workspace] -members = ["inner_lib"] +[package] +name = "inner_lib" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/src/tools/rustfmt/tests/config/issue_4660/configs/rustfmt.hard_tabs.toml b/src/tools/rustfmt/tests/config/issue_4660/configs/rustfmt.hard_tabs.toml new file mode 100644 index 0000000000000..218e203215e94 --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue_4660/configs/rustfmt.hard_tabs.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/src/tools/rustfmt/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml b/src/tools/rustfmt/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml deleted file mode 100644 index c7ad93bafe36c..0000000000000 --- a/src/tools/rustfmt/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/src/tools/rustfmt/tests/config/issue_4660/inner_bin/Cargo.toml b/src/tools/rustfmt/tests/config/issue_4660/inner_bin/Cargo.toml deleted file mode 100644 index bb6a80cd0ce9b..0000000000000 --- a/src/tools/rustfmt/tests/config/issue_4660/inner_bin/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "inner_bin" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/src/tools/rustfmt/tests/config/issue_4660/inner_bin/src/main.rs b/src/tools/rustfmt/tests/config/issue_4660/inner_bin/src/main.rs deleted file mode 100644 index 21c34773b8af5..0000000000000 --- a/src/tools/rustfmt/tests/config/issue_4660/inner_bin/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main(){println!("Hello, world!")} diff --git a/src/tools/rustfmt/tests/config/issue_4660/inner_lib/Cargo.toml b/src/tools/rustfmt/tests/config/issue_4660/inner_lib/Cargo.toml deleted file mode 100644 index c9ddf1793b88f..0000000000000 --- a/src/tools/rustfmt/tests/config/issue_4660/inner_lib/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "inner_lib" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/src/tools/rustfmt/tests/config/issue_4660/inner_lib/rustfmt.toml b/src/tools/rustfmt/tests/config/issue_4660/inner_lib/rustfmt.toml deleted file mode 100644 index c7ad93bafe36c..0000000000000 --- a/src/tools/rustfmt/tests/config/issue_4660/inner_lib/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/src/tools/rustfmt/tests/config/issue_4660/inner_lib/src/lib.rs b/src/tools/rustfmt/tests/config/issue_4660/inner_lib/src/lib.rs deleted file mode 100644 index 42375c99d2c97..0000000000000 --- a/src/tools/rustfmt/tests/config/issue_4660/inner_lib/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub fn add(left:u64,right:u64)->u64{left+right} diff --git a/src/tools/rustfmt/tests/config/issue_4660/rustfmt.toml b/src/tools/rustfmt/tests/config/issue_4660/rustfmt.toml new file mode 100644 index 0000000000000..8a1751f67698f --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue_4660/rustfmt.toml @@ -0,0 +1 @@ +use_field_init_shorthand = true diff --git a/src/tools/rustfmt/tests/config/issue_4660/src/lib.rs b/src/tools/rustfmt/tests/config/issue_4660/src/lib.rs new file mode 100644 index 0000000000000..012fb0f850f8c --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue_4660/src/lib.rs @@ -0,0 +1,2 @@ +mod longhand_field_init; +mod with_spaces; diff --git a/src/tools/rustfmt/tests/config/issue_4660/src/longhand_field_init.rs b/src/tools/rustfmt/tests/config/issue_4660/src/longhand_field_init.rs new file mode 100644 index 0000000000000..87ee30033c955 --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue_4660/src/longhand_field_init.rs @@ -0,0 +1,10 @@ +struct Foo { + x: u32, + y: u32, +} +fn foo() -> Foo { + let x = 1; + let y = 2; + + Foo { x: x, y: y } +} diff --git a/src/tools/rustfmt/tests/config/issue_4660/src/with_spaces.rs b/src/tools/rustfmt/tests/config/issue_4660/src/with_spaces.rs new file mode 100644 index 0000000000000..211d2de3c959e --- /dev/null +++ b/src/tools/rustfmt/tests/config/issue_4660/src/with_spaces.rs @@ -0,0 +1,3 @@ +fn f() { + // comment +} diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index c37951c085982..832087289af0f 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -87,8 +87,8 @@ fn print_config_errors_minimal_with_no_path() { let (stdout, stderr) = rustfmt(args); - // NOTE: existing the behaviour, possibly a bug - assert_eq!(&stdout, "\n"); + // NOTE: existing the behaviour: prints an empty line + assert_eq!(stdout.trim(), ""); assert!( stderr.contains("PATH required"), "expected helpful error message, got: {stderr}" @@ -113,7 +113,16 @@ fn print_config_errors_minimal_without_input_file() { #[test] fn print_config_writes_to_file() { let out_file = "minimal.toml"; - let args = &["--print-config", "minimal", out_file, "src/shape.rs"]; + let args = &[ + "--config-path", + Path::new("tests/config/disable_all_formatting.toml") + .to_str() + .unwrap(), + "--print-config", + "minimal", + out_file, + "src/shape.rs", + ]; let (stdout, stderr) = rustfmt(args); @@ -396,30 +405,105 @@ fn rustfmt_error_improvement_regarding_invalid_toml() { #[test] fn config_path_walks_parent_directories_with_dir_name() { - let src_dir = "tests/config/issue_4660/inner_lib/src"; - let src_file = src_dir.to_owned() + "/lib.rs"; - let args = ["--config-path", src_dir, "--check", &src_file]; + let src_dir = Path::new("tests/config/issue_4660/src"); + let src_file = src_dir.join("longhand_field_init.rs"); + let args = [ + "--config-path", + &src_dir.to_str().unwrap(), + "--check", + "--files-with-diff", + &src_file.to_str().unwrap(), + ]; + let (stdout, stderr) = rustfmt(&args); assert_eq!(stderr, ""); - // Due to `disable_all_formatting = true` in `tests/config/issue_4660/inner_lib/rustfmt.toml`, - // the source file should not be modified. - assert_eq!(stdout, ""); + assert_eq!( + stdout, + format!("{}\n", src_file.canonicalize().unwrap().display()), + // this config sets `use_field_init_shorthand = true` + "we should walk up to tests/config/issue_4660/rustfmt.toml" + ); +} + +// on unix [`std::env::home_dir`] reads the `HOME` env var. +#[cfg(unix)] +#[test] +fn config_path_searches_home_dir_when_dir_path_no_resolve() { + // move the src file to a tempdir, since we'll search up from there looking for the config file + // so we need to avoid using a path within this repo + let new_src_dir = tempfile::tempdir().unwrap().keep(); + let src_file = Path::new("tests/config/issue_4660/src/with_spaces.rs"); + let new_src_path = new_src_dir.join("with_spaces.rs"); + let config_file = Path::new("tests/config/issue_4660/configs/rustfmt.hard_tabs.toml"); + let homedir_path = tempfile::tempdir().unwrap().keep(); + + std::fs::copy(&config_file, homedir_path.join("rustfmt.toml")).unwrap(); + std::fs::copy(&src_file, &new_src_path).unwrap(); + + let args = [ + "--check", + "--files-with-diff", + &new_src_path.to_str().unwrap(), + ]; + let env = &[("HOME", homedir_path.to_str().unwrap())]; + + let (stdout, stderr) = rustfmt_with_extra(&args, None, env, None); + + assert_eq!(stderr, ""); + assert_eq!( + stdout, + format!("{}\n", new_src_path.canonicalize().unwrap().display()), + "should search in HOME" + ); +} + +// The behaviour [`dirs::config_dir`] is os-dependent, so pick one and test to that. +#[cfg(target_os = "linux")] +#[test] +fn config_path_searches_config_dir_when_dir_path_no_resolve() { + // move the src file to a tempdir, since we'll search up from there looking for the config file + let new_src_dir = tempfile::tempdir().unwrap().keep(); + let src_file = Path::new("tests/config/issue_4660/src/with_spaces.rs"); + let new_src_path = new_src_dir.join("with_spaces.rs"); + let config_file = Path::new("tests/config/issue_4660/configs/rustfmt.hard_tabs.toml"); + let xdg_config_dir_path = tempfile::tempdir().unwrap().keep(); + let config_path = xdg_config_dir_path.join("rustfmt/rustfmt.toml"); + + std::fs::create_dir(&config_path.parent().unwrap()).unwrap(); + std::fs::copy(&config_file, config_path).unwrap(); + std::fs::copy(&src_file, &new_src_path).unwrap(); + + let args = [ + "--check", + "--files-with-diff", + &new_src_path.to_str().unwrap(), + ]; + let env = &[("XDG_CONFIG_HOME", xdg_config_dir_path.to_str().unwrap())]; + + let (stdout, stderr) = rustfmt_with_extra(&args, None, env, None); + + assert_eq!(stderr, ""); + assert_eq!( + stdout, + format!("{}\n", new_src_path.canonicalize().unwrap().display()), + "should search in XDG_CONFIG_HOME" + ); } #[test] -fn config_path_does_not_walk_parent_directories_with_toml_name() { - let src_dir = "tests/config/issue_4660/inner_bin/src"; - let toml_file = src_dir.to_owned() + "/.rustfmt.unstable.toml"; - let src_file = src_dir.to_owned() + "/main.rs"; - let args = ["--config-path", &toml_file, "--check", &src_file]; +fn config_path_errors_when_invalid_file_given() { + let src_file = "not_a_file.rs"; + let config_file = "also_not_a_file.toml"; + let args = ["--config-path", &config_file, "--check", &src_file]; + let (stdout, stderr) = rustfmt(&args); assert_eq!( stderr, format!( "Error: unable to find a config file for the given path: `{}`\n", - Path::new(&toml_file).display(), + Path::new(&config_file).display(), ) ); assert_eq!(stdout, ""); From a50d9a911ce8f2d1862543cf3244e10d799e6ac8 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 22 Oct 2026 11:15:15 +0800 Subject: [PATCH 03/46] Allow `llm-assisted` label to be set by users without write access --- src/tools/rustfmt/triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rustfmt/triagebot.toml b/src/tools/rustfmt/triagebot.toml index de28ad00a4272..7197066a41a98 100644 --- a/src/tools/rustfmt/triagebot.toml +++ b/src/tools/rustfmt/triagebot.toml @@ -13,6 +13,7 @@ allow-unauthenticated = [ "E-*", "F-*", "I-*", + "llm-assisted", "needs-rfc", "needs-triage", "regression-*", From fad0b13215bb67bb9cd784a1501ba16c18e53069 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 16 Oct 2026 17:54:00 +0800 Subject: [PATCH 04/46] Add test demonstrating current error leak formatting --- .../rustfmt/tests/source/cfg-if-error-count-leak.rs | 10 ++++++++++ .../rustfmt/tests/target/cfg-if-error-count-leak.rs | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs create mode 100644 src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs diff --git a/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs b/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs new file mode 100644 index 0000000000000..e2441228108f3 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs @@ -0,0 +1,10 @@ +cfg_if::cfg_if! { + if #[cfg(unix)] { + fn foo {} + } +} + +fn main() { + foo!(1 + 2); + foo!(1 + 2); +} diff --git a/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs b/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs new file mode 100644 index 0000000000000..c62d3a17c1a09 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs @@ -0,0 +1,10 @@ +cfg_if::cfg_if! { + if #[cfg(unix)] { + fn foo {} + } +} + +fn main() { + foo!(1 + 2); + foo!(1 + 2); +} From b1c0b194703ebd321a769dbf4d8e0c6f7f533b99 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 16 Oct 2026 17:56:32 +0800 Subject: [PATCH 05/46] Properly reset error count for `cfg_if!` --- src/tools/rustfmt/src/parse/macros/cfg_if.rs | 10 +++++++--- .../rustfmt/tests/source/cfg-if-error-count-leak.rs | 2 ++ .../rustfmt/tests/target/cfg-if-error-count-leak.rs | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/tools/rustfmt/src/parse/macros/cfg_if.rs b/src/tools/rustfmt/src/parse/macros/cfg_if.rs index 495f12c8f5d5d..08accda2fbd09 100644 --- a/src/tools/rustfmt/src/parse/macros/cfg_if.rs +++ b/src/tools/rustfmt/src/parse/macros/cfg_if.rs @@ -13,9 +13,13 @@ pub(crate) fn parse_cfg_if<'a>( psess: &'a ParseSess, mac: &'a ast::MacCall, ) -> Result, &'static str> { - match catch_unwind(AssertUnwindSafe(|| parse_cfg_if_inner(psess, mac))) { - Ok(Ok(items)) => Ok(items), - Ok(err @ Err(_)) => err, + let result = catch_unwind(AssertUnwindSafe(|| parse_cfg_if_inner(psess, mac))); + let dcx = psess.inner().dcx(); + let recovered = dcx.has_errors().is_some(); + dcx.reset_err_count(); + match result { + Ok(Ok(_)) if recovered => Err("cfg_if! parsed with recovery"), + Ok(res) => res, Err(..) => Err("failed to parse cfg_if!"), } } diff --git a/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs b/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs index e2441228108f3..d5e289ec95b12 100644 --- a/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs +++ b/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs @@ -1,3 +1,5 @@ +//@ rustfmt-show_parse_errors: false + cfg_if::cfg_if! { if #[cfg(unix)] { fn foo {} diff --git a/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs b/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs index c62d3a17c1a09..48ef7ddc3ba7a 100644 --- a/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs +++ b/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs @@ -1,3 +1,5 @@ +//@ rustfmt-show_parse_errors: false + cfg_if::cfg_if! { if #[cfg(unix)] { fn foo {} @@ -5,6 +7,6 @@ cfg_if::cfg_if! { } fn main() { - foo!(1 + 2); + foo!(1 + 2); foo!(1 + 2); } From ba09f12ddab5c1b58490fc63a7a2ce0a5ce33ce4 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Mon, 19 Oct 2026 20:30:49 +0100 Subject: [PATCH 06/46] Integration tests: also capture exit code So we can assert that `rustfmt` exits how we expect. --- src/tools/rustfmt/tests/rustfmt/main.rs | 53 +++++++++++++------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index 832087289af0f..71f52abe430c1 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -15,7 +15,7 @@ fn rustfmt_with_extra( working_dir: Option<&str>, envs: &[(&str, &str)], input: Option<&str>, -) -> (String, String) { +) -> (String, String, i32) { let rustfmt_exe = env!("CARGO_BIN_EXE_rustfmt"); let bin_dir = Path::new(rustfmt_exe).parent().unwrap(); @@ -47,12 +47,13 @@ fn rustfmt_with_extra( Ok(output) => ( String::from_utf8(output.stdout).expect("utf-8"), String::from_utf8(output.stderr).expect("utf-8"), + output.status.code().expect("should exit with a status"), ), Err(e) => panic!("failed to run `{cmd:?} {args:?}`: {e}"), } } -fn rustfmt(args: &[&str]) -> (String, String) { +fn rustfmt(args: &[&str]) -> (String, String, i32) { rustfmt_with_extra(args, None, &[], None) } @@ -60,7 +61,7 @@ fn rustfmt(args: &[&str]) -> (String, String) { fn print_config_errors_on_unknown_option() { let args = &["--print-config", "unknown"]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); assert_eq!(&stdout, ""); assert!( @@ -72,7 +73,7 @@ fn print_config_errors_on_unknown_option() { #[test] fn print_config_default() { let args = &["--print-config", "default"]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); assert!( stdout.contains("max_width = 100"), @@ -85,7 +86,7 @@ fn print_config_default() { fn print_config_errors_minimal_with_no_path() { let args = &["--print-config", "minimal"]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); // NOTE: existing the behaviour: prints an empty line assert_eq!(stdout.trim(), ""); @@ -99,7 +100,7 @@ fn print_config_errors_minimal_with_no_path() { fn print_config_errors_minimal_without_input_file() { let args = &["--print-config", "minimal", "minimal.toml"]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); assert_eq!(&stdout, ""); assert!( @@ -124,7 +125,7 @@ fn print_config_writes_to_file() { "src/shape.rs", ]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); assert_eq!(stderr, ""); assert!( @@ -143,7 +144,7 @@ fn print_config_merges_override_from_args() { "--config=color=Never,edition=2018", ]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); assert_eq!(stderr, ""); assert!( @@ -164,7 +165,7 @@ fn print_config_merges_multiple_overrides_from_args() { "color=always,format_strings=true", ]; - let (stdout, stderr) = rustfmt(args); + let (stdout, stderr, _) = rustfmt(args); assert_eq!(stderr, ""); assert!( @@ -179,7 +180,7 @@ fn print_config_merges_multiple_overrides_from_args() { #[test] fn rustfmt_usage_text() { let args = ["--help"]; - let (stdout, _) = rustfmt(&args); + let (stdout, _, _) = rustfmt(&args); insta::assert_snapshot!(stdout, @" Format Rust code @@ -227,7 +228,7 @@ fn rustfmt_usage_text() { #[test] fn rustfmt_nightly_usage_text() { let args = ["--help"]; - let (stdout, _) = rustfmt(&args); + let (stdout, _, _) = rustfmt(&args); insta::assert_snapshot!(stdout, @" Format Rust code @@ -295,14 +296,14 @@ fn mod_resolution_error_multiple_candidate_files() { ); let args = ["tests/mod-resolver/issue-5167/src/lib.rs"]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); assert!(stderr.contains(&error_message)) } #[test] fn mod_resolution_error_sibling_module_not_found() { let args = ["tests/mod-resolver/module-not-found/sibling_module/lib.rs"]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); // Module resolution fails because we're unable to find `a.rs` in the same directory as lib.rs assert!(stderr.contains("a.rs does not exist")) } @@ -310,7 +311,7 @@ fn mod_resolution_error_sibling_module_not_found() { #[test] fn mod_resolution_error_relative_module_not_found() { let args = ["tests/mod-resolver/module-not-found/relative_module/lib.rs"]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); // The file `./a.rs` and directory `./a` both exist. // Module resolution fails because we're unable to find `./a/b.rs` #[cfg(not(windows))] @@ -322,7 +323,7 @@ fn mod_resolution_error_relative_module_not_found() { #[test] fn mod_resolution_error_path_attribute_does_not_exist() { let args = ["tests/mod-resolver/module-not-found/bad_path_attribute/lib.rs"]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); // The path attribute points to a file that does not exist assert!(stderr.contains("does_not_exist.rs does not exist")); } @@ -336,7 +337,7 @@ fn rustfmt_emits_error_on_line_overflow_true() { "tests/cargo-fmt/source/issue_3164/src/main.rs", ]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); assert!(stderr.contains( "line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option)" )) @@ -357,7 +358,7 @@ fn dont_emit_ICE() { let panic_re = regex::Regex::new("thread.*panicked").unwrap(); for file in files { let args = [file]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); assert!(!panic_re.is_match(&stderr)); } } @@ -374,7 +375,7 @@ fn rustfmt_emits_error_when_control_brace_style_is_always_next_line() { "tests/target/issue_5912.rs", ]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); assert!(!stderr.contains("error[internal]: left behind trailing whitespace")) } @@ -385,7 +386,7 @@ fn rustfmt_generates_no_error_if_failed_format_code_in_doc_comments() { let file = "tests/target/issue-6109.rs"; let args = ["--config", "format_code_in_doc_comments=true", file]; - let (stdout, stderr) = rustfmt(&args); + let (stdout, stderr, _) = rustfmt(&args); assert!(stderr.is_empty()); assert!(stdout.is_empty()); } @@ -395,7 +396,7 @@ fn rustfmt_error_improvement_regarding_invalid_toml() { // See also https://github.com/rust-lang/rustfmt/issues/6302 let invalid_toml_config = "tests/config/issue-6302.toml"; let args = ["--config-path", invalid_toml_config]; - let (_stdout, stderr) = rustfmt(&args); + let (_stdout, stderr, _) = rustfmt(&args); let toml_path = Path::new(invalid_toml_config).canonicalize().unwrap(); let expected_error_message = format!("The file `{}` failed to parse", toml_path.display()); @@ -415,7 +416,7 @@ fn config_path_walks_parent_directories_with_dir_name() { &src_file.to_str().unwrap(), ]; - let (stdout, stderr) = rustfmt(&args); + let (stdout, stderr, _) = rustfmt(&args); assert_eq!(stderr, ""); assert_eq!( @@ -448,7 +449,7 @@ fn config_path_searches_home_dir_when_dir_path_no_resolve() { ]; let env = &[("HOME", homedir_path.to_str().unwrap())]; - let (stdout, stderr) = rustfmt_with_extra(&args, None, env, None); + let (stdout, stderr, _) = rustfmt_with_extra(&args, None, env, None); assert_eq!(stderr, ""); assert_eq!( @@ -481,7 +482,7 @@ fn config_path_searches_config_dir_when_dir_path_no_resolve() { ]; let env = &[("XDG_CONFIG_HOME", xdg_config_dir_path.to_str().unwrap())]; - let (stdout, stderr) = rustfmt_with_extra(&args, None, env, None); + let (stdout, stderr, _) = rustfmt_with_extra(&args, None, env, None); assert_eq!(stderr, ""); assert_eq!( @@ -497,7 +498,7 @@ fn config_path_errors_when_invalid_file_given() { let config_file = "also_not_a_file.toml"; let args = ["--config-path", &config_file, "--check", &src_file]; - let (stdout, stderr) = rustfmt(&args); + let (stdout, stderr, _) = rustfmt(&args); assert_eq!( stderr, @@ -533,7 +534,7 @@ fn rustfmt_allow_not_a_dir_errors() { let args = [empty_rs.to_str().unwrap()]; let envs = &[("HOME", fake_home_str)]; - let (stdout, stderr) = rustfmt_with_extra(&args, Some(fake_home_str), envs, None); + let (stdout, stderr, _) = rustfmt_with_extra(&args, Some(fake_home_str), envs, None); // Should pass without any errors assert_eq!(stdout, ""); @@ -547,7 +548,7 @@ fn cfg_select_parser_recovery_ice_7087() { // See also https://github.com/rust-lang/rustfmt/issues/7087 let source = "cfg_select! { true => { fn foo {} } }\n"; let panic_re = regex::Regex::new("thread.*panicked").unwrap(); - let (stdout, stderr) = rustfmt_with_extra(&[], None, &[], Some(source)); + let (stdout, stderr, _) = rustfmt_with_extra(&[], None, &[], Some(source)); assert!(!panic_re.is_match(&stderr)); // Invalid arm should cause `cfg_select!` to be re-emitted unmodified. assert_eq!(source, stdout); From 4689345faf4fdf3dc9037111d947cf7b17c181cb Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 23 Oct 2026 22:20:57 +0800 Subject: [PATCH 07/46] test: rename multi-file `cfg_select!` tests subfolder --- src/tools/rustfmt/src/test/mod.rs | 6 +++--- .../format_me_please_1.rs | 0 .../format_me_please_2.rs | 0 .../format_me_please_3.rs | 0 .../format_me_please_4.rs | 0 .../source/{cfg_select => cfg_select_multifile}/lib.rs | 0 .../source/{cfg_select => cfg_select_multifile}/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 .../target/{cfg_select => cfg_select_multifile}/lib.rs | 0 .../target/{cfg_select => cfg_select_multifile}/lib2.rs | 0 13 files changed, 3 insertions(+), 3 deletions(-) rename src/tools/rustfmt/tests/source/{cfg_select => cfg_select_multifile}/format_me_please_1.rs (100%) rename src/tools/rustfmt/tests/source/{cfg_select => cfg_select_multifile}/format_me_please_2.rs (100%) rename src/tools/rustfmt/tests/source/{cfg_select => cfg_select_multifile}/format_me_please_3.rs (100%) rename src/tools/rustfmt/tests/source/{cfg_select => cfg_select_multifile}/format_me_please_4.rs (100%) rename src/tools/rustfmt/tests/source/{cfg_select => cfg_select_multifile}/lib.rs (100%) rename src/tools/rustfmt/tests/source/{cfg_select => cfg_select_multifile}/lib2.rs (100%) rename src/tools/rustfmt/tests/target/{cfg_select => cfg_select_multifile}/format_me_please_1.rs (100%) rename src/tools/rustfmt/tests/target/{cfg_select => cfg_select_multifile}/format_me_please_2.rs (100%) rename src/tools/rustfmt/tests/target/{cfg_select => cfg_select_multifile}/format_me_please_3.rs (100%) rename src/tools/rustfmt/tests/target/{cfg_select => cfg_select_multifile}/format_me_please_4.rs (100%) rename src/tools/rustfmt/tests/target/{cfg_select => cfg_select_multifile}/lib.rs (100%) rename src/tools/rustfmt/tests/target/{cfg_select => cfg_select_multifile}/lib2.rs (100%) diff --git a/src/tools/rustfmt/src/test/mod.rs b/src/tools/rustfmt/src/test/mod.rs index 96b96700a9288..5d5be709e5fb5 100644 --- a/src/tools/rustfmt/src/test/mod.rs +++ b/src/tools/rustfmt/src/test/mod.rs @@ -44,7 +44,7 @@ const FILE_SKIP_LIST: &[&str] = &[ "issue-3253/bar.rs", "issue-3253/paths", // This directory is directly tested by format_files_find_new_files_via_cfg_select - "cfg_select", + "cfg_select_multifile", // These files and directory are a part of modules defined inside `cfg_attr(..)`. "cfg_mod/dir", "cfg_mod/bar.rs", @@ -575,8 +575,8 @@ fn format_files_find_new_files_via_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_select/lib2.rs"), - Path::new("tests/source/cfg_select/lib.rs"), + Path::new("tests/source/cfg_select_multifile/lib2.rs"), + Path::new("tests/source/cfg_select_multifile/lib.rs"), ]; let config = Config::default(); diff --git a/src/tools/rustfmt/tests/source/cfg_select/format_me_please_1.rs b/src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_1.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select/format_me_please_1.rs rename to src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_1.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/format_me_please_2.rs b/src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_2.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select/format_me_please_2.rs rename to src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_2.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/format_me_please_3.rs b/src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_3.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select/format_me_please_3.rs rename to src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_3.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/format_me_please_4.rs b/src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_4.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select/format_me_please_4.rs rename to src/tools/rustfmt/tests/source/cfg_select_multifile/format_me_please_4.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/lib.rs b/src/tools/rustfmt/tests/source/cfg_select_multifile/lib.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select/lib.rs rename to src/tools/rustfmt/tests/source/cfg_select_multifile/lib.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/lib2.rs b/src/tools/rustfmt/tests/source/cfg_select_multifile/lib2.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select/lib2.rs rename to src/tools/rustfmt/tests/source/cfg_select_multifile/lib2.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select/format_me_please_1.rs b/src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_1.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select/format_me_please_1.rs rename to src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_1.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select/format_me_please_2.rs b/src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_2.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select/format_me_please_2.rs rename to src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_2.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select/format_me_please_3.rs b/src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_3.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select/format_me_please_3.rs rename to src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_3.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select/format_me_please_4.rs b/src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_4.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select/format_me_please_4.rs rename to src/tools/rustfmt/tests/target/cfg_select_multifile/format_me_please_4.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select/lib.rs b/src/tools/rustfmt/tests/target/cfg_select_multifile/lib.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select/lib.rs rename to src/tools/rustfmt/tests/target/cfg_select_multifile/lib.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select/lib2.rs b/src/tools/rustfmt/tests/target/cfg_select_multifile/lib2.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select/lib2.rs rename to src/tools/rustfmt/tests/target/cfg_select_multifile/lib2.rs From 49c47fc3a39550c4a24dc67e597a1f507a28575d Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 23 Oct 2026 22:30:29 +0800 Subject: [PATCH 08/46] test: group `cfg_select!` related tests under subfolder --- src/tools/rustfmt/tests/source/{ => cfg_select}/cfg_select.rs | 0 .../tests/source/{ => cfg_select}/cfg_select_edge_cases.rs | 0 .../rustfmt/tests/source/{ => cfg_select}/cfg_select_stable.rs | 0 .../cfg_select_valid_then_invalid_item_error_count_leak.rs} | 0 src/tools/rustfmt/tests/source/{ => cfg_select}/issue_7045.rs | 0 .../rustfmt/tests/source/{ => cfg_select}/issue_7088_stable.rs | 0 .../rustfmt/tests/source/{ => cfg_select}/issue_7088_unstable.rs | 0 src/tools/rustfmt/tests/target/{ => cfg_select}/cfg_select.rs | 0 .../tests/target/{ => cfg_select}/cfg_select_edge_cases.rs | 0 .../rustfmt/tests/target/{ => cfg_select}/cfg_select_stable.rs | 0 .../cfg_select_valid_then_invalid_item_error_count_leak.rs} | 0 src/tools/rustfmt/tests/target/{ => cfg_select}/issue_7045.rs | 0 .../rustfmt/tests/target/{ => cfg_select}/issue_7088_stable.rs | 0 .../rustfmt/tests/target/{ => cfg_select}/issue_7088_unstable.rs | 0 src/tools/rustfmt/tests/target/{ => cfg_select}/issue_7198.rs | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename src/tools/rustfmt/tests/source/{ => cfg_select}/cfg_select.rs (100%) rename src/tools/rustfmt/tests/source/{ => cfg_select}/cfg_select_edge_cases.rs (100%) rename src/tools/rustfmt/tests/source/{ => cfg_select}/cfg_select_stable.rs (100%) rename src/tools/rustfmt/tests/source/{cfg-select-valid-then-invalid-item-error-count-leak.rs => cfg_select/cfg_select_valid_then_invalid_item_error_count_leak.rs} (100%) rename src/tools/rustfmt/tests/source/{ => cfg_select}/issue_7045.rs (100%) rename src/tools/rustfmt/tests/source/{ => cfg_select}/issue_7088_stable.rs (100%) rename src/tools/rustfmt/tests/source/{ => cfg_select}/issue_7088_unstable.rs (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/cfg_select.rs (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/cfg_select_edge_cases.rs (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/cfg_select_stable.rs (100%) rename src/tools/rustfmt/tests/target/{cfg-select-valid-then-invalid-item-error-count-leak.rs => cfg_select/cfg_select_valid_then_invalid_item_error_count_leak.rs} (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/issue_7045.rs (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/issue_7088_stable.rs (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/issue_7088_unstable.rs (100%) rename src/tools/rustfmt/tests/target/{ => cfg_select}/issue_7198.rs (100%) diff --git a/src/tools/rustfmt/tests/source/cfg_select.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select.rs rename to src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select_edge_cases.rs rename to src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select_stable.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_stable.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg_select_stable.rs rename to src/tools/rustfmt/tests/source/cfg_select/cfg_select_stable.rs diff --git a/src/tools/rustfmt/tests/source/cfg-select-valid-then-invalid-item-error-count-leak.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_valid_then_invalid_item_error_count_leak.rs similarity index 100% rename from src/tools/rustfmt/tests/source/cfg-select-valid-then-invalid-item-error-count-leak.rs rename to src/tools/rustfmt/tests/source/cfg_select/cfg_select_valid_then_invalid_item_error_count_leak.rs diff --git a/src/tools/rustfmt/tests/source/issue_7045.rs b/src/tools/rustfmt/tests/source/cfg_select/issue_7045.rs similarity index 100% rename from src/tools/rustfmt/tests/source/issue_7045.rs rename to src/tools/rustfmt/tests/source/cfg_select/issue_7045.rs diff --git a/src/tools/rustfmt/tests/source/issue_7088_stable.rs b/src/tools/rustfmt/tests/source/cfg_select/issue_7088_stable.rs similarity index 100% rename from src/tools/rustfmt/tests/source/issue_7088_stable.rs rename to src/tools/rustfmt/tests/source/cfg_select/issue_7088_stable.rs diff --git a/src/tools/rustfmt/tests/source/issue_7088_unstable.rs b/src/tools/rustfmt/tests/source/cfg_select/issue_7088_unstable.rs similarity index 100% rename from src/tools/rustfmt/tests/source/issue_7088_unstable.rs rename to src/tools/rustfmt/tests/source/cfg_select/issue_7088_unstable.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select.rs rename to src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select_edge_cases.rs rename to src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs diff --git a/src/tools/rustfmt/tests/target/cfg_select_stable.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_stable.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg_select_stable.rs rename to src/tools/rustfmt/tests/target/cfg_select/cfg_select_stable.rs diff --git a/src/tools/rustfmt/tests/target/cfg-select-valid-then-invalid-item-error-count-leak.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_valid_then_invalid_item_error_count_leak.rs similarity index 100% rename from src/tools/rustfmt/tests/target/cfg-select-valid-then-invalid-item-error-count-leak.rs rename to src/tools/rustfmt/tests/target/cfg_select/cfg_select_valid_then_invalid_item_error_count_leak.rs diff --git a/src/tools/rustfmt/tests/target/issue_7045.rs b/src/tools/rustfmt/tests/target/cfg_select/issue_7045.rs similarity index 100% rename from src/tools/rustfmt/tests/target/issue_7045.rs rename to src/tools/rustfmt/tests/target/cfg_select/issue_7045.rs diff --git a/src/tools/rustfmt/tests/target/issue_7088_stable.rs b/src/tools/rustfmt/tests/target/cfg_select/issue_7088_stable.rs similarity index 100% rename from src/tools/rustfmt/tests/target/issue_7088_stable.rs rename to src/tools/rustfmt/tests/target/cfg_select/issue_7088_stable.rs diff --git a/src/tools/rustfmt/tests/target/issue_7088_unstable.rs b/src/tools/rustfmt/tests/target/cfg_select/issue_7088_unstable.rs similarity index 100% rename from src/tools/rustfmt/tests/target/issue_7088_unstable.rs rename to src/tools/rustfmt/tests/target/cfg_select/issue_7088_unstable.rs diff --git a/src/tools/rustfmt/tests/target/issue_7198.rs b/src/tools/rustfmt/tests/target/cfg_select/issue_7198.rs similarity index 100% rename from src/tools/rustfmt/tests/target/issue_7198.rs rename to src/tools/rustfmt/tests/target/cfg_select/issue_7198.rs From 04d74cee3fa5b921bd37540988acf4b159b7790c Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 24 Oct 2026 01:39:11 +0800 Subject: [PATCH 09/46] test: add regression test for miri between-block comment --- .../rustfmt/tests/source/cfg_select/issue_7209.rs | 11 +++++++++++ .../rustfmt/tests/target/cfg_select/issue_7209.rs | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/tools/rustfmt/tests/source/cfg_select/issue_7209.rs create mode 100644 src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/issue_7209.rs b/src/tools/rustfmt/tests/source/cfg_select/issue_7209.rs new file mode 100644 index 0000000000000..4c06baa145027 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select/issue_7209.rs @@ -0,0 +1,11 @@ +//@ rustfmt-unstable: true +//@ rustfmt-edition: 2024 + +fn gettid() -> u64 { + cfg_select! { + any(target_os = "solaris", target_os = "illumos") => { + // On Solaris and Illumos, the `pthread_t` is the OS TID. + unsafe { libc::pthread_self() as u64 } + } + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs b/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs new file mode 100644 index 0000000000000..19d36edf1d2c7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs @@ -0,0 +1,10 @@ +//@ rustfmt-unstable: true +//@ rustfmt-edition: 2024 + +fn gettid() -> u64 { + cfg_select! { + any(target_os = "solaris", target_os = "illumos") => {unsafe { + libc::pthread_self() as u64 + }} + } +} From 44541eb0f2e6d3379166a4ef09065b29c68683f0 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 23 Oct 2026 23:00:41 +0800 Subject: [PATCH 10/46] fix(cfg_select): don't use concise format when in-between comments exist I.e. do not try to use `{{ ... }}` concise double-block formatting if there are comments in-between the outer/inner braces. --- src/tools/rustfmt/src/macros.rs | 2 +- .../rustfmt/src/parse/macros/cfg_select.rs | 61 +++++++++++++++---- .../cfg_select/cfg_select_edge_cases.rs | 6 +- .../tests/target/cfg_select/issue_7209.rs | 7 ++- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 935459ee4670e..46771ceb7a7b2 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -1630,7 +1630,7 @@ fn format_cfg_select( } }; - match arm.double_block_inner() { + match arm.maybe_double_block_concise_formatting(context) { // FIXME(ytmimi) The formatting for the `$predicate => {{}}` case isn't very robust. // There's the potential that we'll lose comments in between the predicate, the `=>` // and the body. We also don't handle comments like `{/*comment*/{}/*comment*/}`. diff --git a/src/tools/rustfmt/src/parse/macros/cfg_select.rs b/src/tools/rustfmt/src/parse/macros/cfg_select.rs index 599eb889dabf9..b2e7cafdee09b 100644 --- a/src/tools/rustfmt/src/parse/macros/cfg_select.rs +++ b/src/tools/rustfmt/src/parse/macros/cfg_select.rs @@ -13,9 +13,11 @@ use rustc_parse::parser::{AllowConstBlockItems, ForceCollect}; use rustc_span::Span; use tracing::debug; +use crate::comment::contains_comment; use crate::expr::is_unsafe_block; use crate::parse::macros::build_stream_parser; use crate::parse::session::ParseSess; +use crate::rewrite::RewriteContext; use crate::spanned::Spanned; // NB: parser recovery can return `Ok(expr)` (or other non-terminals) and still emit errors. We need @@ -161,26 +163,59 @@ impl std::fmt::Debug for CfgSelectArm { } impl CfgSelectArm { - /// Is the cfg_select! arm written with two consecutive blocks. - /// For example `$predicate => {{}}`. If so, extract the inner block. - /// rustfmt will preserve the double block syntax. - pub(crate) fn double_block_inner(&self) -> Option<&ast::Expr> { - // In order to see if we can reach in and grab the inner block we'll - // check the following properties of the outer block: + /// Is the `cfg_select!` arm written with two consecutive blocks in a way that we can apply a + /// concise formatting happy path? For example: + /// + /// ```text + /// $predicate => {{}} + /// ``` + /// + /// If so, extract the inner block. rustfmt will preserve the double block syntax. + pub(crate) fn maybe_double_block_concise_formatting( + &self, + context: &RewriteContext<'_>, + ) -> Option<&ast::Expr> { + // To be able to safely use the concise double block `{{ ... }}` concise formatting, the + // outer block must satisfy all of the following properties: + // // 1. It has a single statement that's also a block // 2. It doesn't have a label // 3. It isn't an unsafe block // 4. It doesn't have any attributes - match &self.expr.kind { - ast::ExprKind::Block(b, label) - if b.stmts.len() == 1 + // 5. There are no comments between the outer blocks and the inner blocks, i.e. + // ``` + // { /* A */ { ... } /* B */ } + // ``` + let outer_expr = &self.expr; + match &outer_expr.kind { + ast::ExprKind::Block(outer_block, label) + if let [stmt] = outer_block.stmts.as_slice() && label.is_none() - && !is_unsafe_block(b) + && !is_unsafe_block(&outer_block) && self.expr.attrs.is_empty() => { - match &b.stmts[0].kind { - // This is where we check that the inner block's statement is also a block - ast::StmtKind::Expr(e) if matches!(e.kind, ast::ExprKind::Block(..)) => Some(e), + match &stmt.kind { + // The inner expr has to be a block. + ast::StmtKind::Expr(e) + if let ast::ExprKind::Block(ref inner_block, _inner_label) = e.kind => + { + // Check for comments in between outer/inner blocks. If there are in-between + // comments, we should not apply the concise formatting. Let's call them + // ``` + // { { ... } } + // ^--- ^--- + // pre_sp post_sp + // ``` + let pre_sp = outer_block.span.until(inner_block.span); + let post_sp = inner_block.span.until(outer_block.span); + if contains_comment(context.snippet(pre_sp)) + || contains_comment(context.snippet(post_sp)) + { + None + } else { + Some(e) + } + } _ => None, } } diff --git a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs index 96a3373e339ba..e31cc1c209ead 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs @@ -72,7 +72,11 @@ fn check_match_block_formatting() { fn cfg_select_comments_that_might_get_lost() { cfg_select! { - _ => {{ x }} + _ => { + // after opening brace + { x } + // before closing brace + } } cfg_select! { _ => {{ x }} diff --git a/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs b/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs index 19d36edf1d2c7..4c06baa145027 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/issue_7209.rs @@ -3,8 +3,9 @@ fn gettid() -> u64 { cfg_select! { - any(target_os = "solaris", target_os = "illumos") => {unsafe { - libc::pthread_self() as u64 - }} + any(target_os = "solaris", target_os = "illumos") => { + // On Solaris and Illumos, the `pthread_t` is the OS TID. + unsafe { libc::pthread_self() as u64 } + } } } From 018f54c219ec3ae69498f9e529feffd9acf86c8e Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 23 Oct 2026 23:23:40 +0800 Subject: [PATCH 11/46] fix(cfg_select): don't lose comments/attrs between `=>` and arm expr --- src/tools/rustfmt/src/macros.rs | 77 +++++++++++---- .../rustfmt/src/parse/macros/cfg_select.rs | 2 +- .../cfg_select/cfg_select_edge_cases.rs | 52 ++++++++++ .../tests/target/cfg_select/cfg_select.rs | 20 +++- .../cfg_select/cfg_select_edge_cases.rs | 94 ++++++++++++++++++- 5 files changed, 221 insertions(+), 24 deletions(-) diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 46771ceb7a7b2..d4c9ac4b152e1 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -21,7 +21,7 @@ use rustc_span::{BytePos, DUMMY_SP, Ident, Pos, Span, Symbol}; use tracing::debug; use crate::comment::{ - CharClasses, FindUncommented, FullCodeCharKind, LineClasses, contains_comment, + CharClasses, FindUncommented, FullCodeCharKind, LineClasses, contains_comment, rewrite_comment, }; use crate::config::StyleEdition; use crate::config::lists::*; @@ -1636,31 +1636,72 @@ fn format_cfg_select( // and the body. We also don't handle comments like `{/*comment*/{}/*comment*/}`. // The `rewrite_match_body` branch also handles `control_brace_style` better. // That said, it helps fix . - Some(expr) => { + Some(inner_expr) => { + // ``` + // => /* comment */ {{ ... }} + // ^------------- between span + // ``` + let after_arrow_comment = context + .snippet(arm.arrow.span.between(arm.expr.span())) + .trim(); + let after_arrow_comment = if after_arrow_comment.is_empty() { + None + } else { + Some(rewrite_comment( + after_arrow_comment, + false, + nested_shape, + context.config, + )?) + }; + let trailing_comma = if arm.trailing_comma.is_some() { "," } else { "" }; - let offset = extra_offset(&predicate_str, nested_shape) + " => {".len(); - let nested_shape = nested_shape - .offset_left(offset, arm.span())? + + // Match `match` layout for after arrow comment, where comment and expr each go + // on own lines. + let prefix_width = if after_arrow_comment.is_some() { + 0 + } else { + extra_offset(&predicate_str, nested_shape) + " => ".len() + }; + + let body_shape = nested_shape + .offset_left(prefix_width + "{".len(), arm.span())? .sub_width("}".len() + trailing_comma.len(), arm.span())?; - let body = expr - .rewrite_result(context, nested_shape) - .map(|rw| format!("{{{rw}}}"))?; + let body = inner_expr.rewrite_result(context, body_shape)?; - Ok(format!("{predicate_str} => {body}{trailing_comma}")) + let body = format!("{{{body}}}{trailing_comma}"); + match after_arrow_comment { + Some(comment) => { + let indent = nested_shape.indent.to_string_with_newline(context.config); + Ok(format!("{predicate_str} =>{indent}{comment}{indent}{body}")) + } + None => Ok(format!("{predicate_str} => {body}")), + } + } + None => { + // Note that we can't _just_ pass the `=>`'s span, we need to pass + // + // ``` + // => /* comment */ #[attr] { ... } + // ^--------------- this whole span until outer expr + // ``` + // + // Using `Spanned::span` here for `arm.expr` to account for outer attributes. + crate::matches::rewrite_match_body( + context, + &arm.expr, + &predicate_str, + nested_shape, + false, + arm.arrow.span.until(arm.expr.span()), + last_arm.is_some_and(|la| la == arm), + ) } - None => 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, diff --git a/src/tools/rustfmt/src/parse/macros/cfg_select.rs b/src/tools/rustfmt/src/parse/macros/cfg_select.rs index b2e7cafdee09b..6db647eaabb88 100644 --- a/src/tools/rustfmt/src/parse/macros/cfg_select.rs +++ b/src/tools/rustfmt/src/parse/macros/cfg_select.rs @@ -207,7 +207,7 @@ impl CfgSelectArm { // pre_sp post_sp // ``` let pre_sp = outer_block.span.until(inner_block.span); - let post_sp = inner_block.span.until(outer_block.span); + let post_sp = outer_block.span.with_lo(inner_block.span.hi()); if contains_comment(context.snippet(pre_sp)) || contains_comment(context.snippet(post_sp)) { diff --git a/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs index 586a961864c64..955fa72c989e5 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs @@ -73,6 +73,41 @@ fn cfg_select_comments_that_might_get_lost() { // No trailing comma in source three_blocks_exclamation_point => {{/* a */{ b; c } /* d */}} } + + cfg_select! { + _ => { { x } /* after */ } + } + + cfg_select! { + _ => { { x } // after newline + } + } + + cfg_select! { + _ => /* between */ {{ x }} + } + + cfg_select! { + _ => // between trailing + {{ x }} + } + + // Trailing comma + cfg_select! { + _ => /* between */ {{ x }}, + } + cfg_select! { + _ => /* between */ {{ a; b }}, + } + + cfg_select! { + _ => /* between */ {{ a; b }} + } + + cfg_select! { + _ => /* multi + line */ {{ x }} + } } fn other_cfg_select_cases() { @@ -197,6 +232,23 @@ fn other_cfg_select_cases() { #[inline] || { }} }; + + cfg_select! { + _ => /* before attr */ #[attr] { z } + } + + cfg_select! { + _ => #[attr] /* after attr */ { z } + } + + cfg_select! { + _ => // line comment before attr + #[attr] { z } + } + + cfg_select! { + _ => /* before attr */ #[attr] /* after attr */ { z } + } } fn long_predicate() { diff --git a/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs index 1c2a1ed91a7e0..508dc0d931b1a 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs @@ -292,9 +292,15 @@ cfg_select! { cfg_select! { any(true, true, true, true,) => {} - not(false) => {} + not(false) => + /* comment before opening brace */ + {} - any(false) => "any", + any(false) => + // comment before brace + { + "any" + } } // A bunch of mixed predicates @@ -704,9 +710,15 @@ mod nested { cfg_select! { any(true, true, true, true,) => {} - not(false) => {} + not(false) => + /* comment before opening brace */ + {} - any(false) => "any", + any(false) => + // comment before brace + { + "any" + } } // A bunch of mixed predicates diff --git a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs index e31cc1c209ead..aa49a5e928c5a 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs @@ -79,7 +79,9 @@ fn cfg_select_comments_that_might_get_lost() { } } cfg_select! { - _ => {{ x }} + _ => + /* after => */ + {{ x }} } cfg_select! { _ => {{ x }} @@ -102,6 +104,61 @@ fn cfg_select_comments_that_might_get_lost() { } /* d */ }} } + + cfg_select! { + _ => { + { x } /* after */ + } + } + + cfg_select! { + _ => { + { x } // after newline + } + } + + cfg_select! { + _ => + /* between */ + {{ x }} + } + + cfg_select! { + _ => + // between trailing + {{ x }} + } + + // Trailing comma + cfg_select! { + _ => + /* between */ + {{ x }}, + } + cfg_select! { + _ => + /* between */ + {{ + a; + b + }}, + } + + cfg_select! { + _ => + /* between */ + {{ + a; + b + }} + } + + cfg_select! { + _ => + /* multi + line */ + {{ x }} + } } fn other_cfg_select_cases() { @@ -281,6 +338,41 @@ fn other_cfg_select_cases() { || {} } }; + + cfg_select! { + _ => + /* before attr */ + #[attr] + { + z + } + } + + cfg_select! { + _ => + #[attr] /* after attr */ + { + z + } + } + + cfg_select! { + _ => + // line comment before attr + #[attr] + { + z + } + } + + cfg_select! { + _ => + /* before attr */ + #[attr] /* after attr */ + { + z + } + } } fn long_predicate() { From 0aff56c6f448ccaf19ba3fdb53a12597919dee7d Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 24 Oct 2026 00:46:48 +0800 Subject: [PATCH 12/46] fix(cfg_select): don't lose comments between predicate and `=>` --- src/tools/rustfmt/src/macros.rs | 36 ++++++++++++++----- .../tests/source/cfg_select/cfg_select.rs | 21 +++++++---- .../cfg_select/cfg_select_edge_cases.rs | 15 ++++++++ .../tests/target/cfg_select/cfg_select.rs | 25 +++++++++---- .../cfg_select/cfg_select_edge_cases.rs | 20 ++++++++++- 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index d4c9ac4b152e1..065f1c64dfab2 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -21,7 +21,8 @@ use rustc_span::{BytePos, DUMMY_SP, Ident, Pos, Span, Symbol}; use tracing::debug; use crate::comment::{ - CharClasses, FindUncommented, FullCodeCharKind, LineClasses, contains_comment, rewrite_comment, + CharClasses, FindUncommented, FullCodeCharKind, LineClasses, + combine_strs_with_missing_comments, contains_comment, rewrite_comment, }; use crate::config::StyleEdition; use crate::config::lists::*; @@ -40,8 +41,9 @@ use crate::shape::{Indent, Shape}; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::utils::{ - NodeIdExt, extra_offset, filtered_str_fits, indent_next_line, is_empty_line, mk_sp, - remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout, + NodeIdExt, extra_offset, filtered_str_fits, indent_next_line, is_empty_line, + last_line_contains_single_line_comment, mk_sp, remove_trailing_white_spaces, rewrite_ident, + trim_left_preserve_layout, }; use crate::visitor::FmtVisitor; @@ -1630,12 +1632,30 @@ fn format_cfg_select( } }; + // We need to account for comments between predicate and `=>`. + let before_arrow_sp = arm.predicate.span().between(arm.arrow.span); + let before_arrow = context.snippet(before_arrow_sp); + let predicate_str = if contains_comment(before_arrow) { + // We cannot rewrite if the last line is a line comment, it will comment out `=>`. + if last_line_contains_single_line_comment(before_arrow.trim_end()) { + return Err(RewriteError::Unknown); + } + Cow::Owned(combine_strs_with_missing_comments( + context, + &predicate_str, + "", + before_arrow_sp, + nested_shape, + true, + )?) + } else { + predicate_str + }; + match arm.maybe_double_block_concise_formatting(context) { - // FIXME(ytmimi) The formatting for the `$predicate => {{}}` case isn't very robust. - // There's the potential that we'll lose comments in between the predicate, the `=>` - // and the body. We also don't handle comments like `{/*comment*/{}/*comment*/}`. - // The `rewrite_match_body` branch also handles `control_brace_style` better. - // That said, it helps fix . + // FIXME(ytmimi): the `rewrite_match_body` branch also handles `control_brace_style` + // better. That said, it helps fix + // . Some(inner_expr) => { // ``` // => /* comment */ {{ ... }} diff --git a/src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs index ca6abc8aa4af2..d6dc391dd564f 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs @@ -327,7 +327,7 @@ cfg_select! { any(true, /* comment */ true, true, // true, true, ) - // comment before arrow + /* comment before arrow */ => {} not(false // comment @@ -339,12 +339,12 @@ cfg_select! { ) => "any" } -// comments before and after the `=>` get dropped right now +// comments before and after the `=>` are kept cfg_select! { any(true, true, true, true, ) - // commetn before arrow + /* comment before arrow */ => {} not(false @@ -357,6 +357,15 @@ cfg_select! { "any" } +// FIXME: not formatted, a line comment before `=>` would comment out the `=>` +cfg_select! { + any(true, + true, true, + true, ) + // line comment before arrow + => {} +} + // A bunch of mixed predicates cfg_select! { @@ -752,7 +761,7 @@ cfg_select! { any(true, /* comment */ true, true, // true, true, ) - // comment before arrow + /* comment before arrow */ => {} not(false // comment @@ -764,12 +773,12 @@ cfg_select! { ) => "any" } -// comments before and after the `=>` get dropped right now +// comments before and after the `=>` are kept cfg_select! { any(true, true, true, true, ) - // commetn before arrow + /* comment before arrow */ => {} not(false diff --git a/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs index 955fa72c989e5..d029f8eb30469 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs @@ -108,6 +108,21 @@ fn cfg_select_comments_that_might_get_lost() { _ => /* multi line */ {{ x }} } + + // Comments before `=>` + cfg_select! { + _ /* before arrow */ => {{ x }} + _ /* before arrow */ => x, + _ /* before arrow */ => /* after arrow */ {{ x }} + all(target_os = "linux", target_arch = "x86_64", target_env = "gnu") /* long comment before arrow */ => {{ x }} + _ /* before arrow */ => {} + } + + cfg_select! { + // Not formatted: a line comment before `=>`. + c // before arrow + => x, + } } fn other_cfg_select_cases() { diff --git a/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs index 508dc0d931b1a..9d9bbfe0b4c9a 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs @@ -277,7 +277,8 @@ cfg_select! { true, /* comment */ true, true, // true, true, - ) => {} + ) + /* comment before arrow */ => {} not( false // comment @@ -288,9 +289,10 @@ cfg_select! { ) => "any", } -// comments before and after the `=>` get dropped right now +// comments before and after the `=>` are kept cfg_select! { - any(true, true, true, true,) => {} + any(true, true, true, true,) + /* comment before arrow */ => {} not(false) => /* comment before opening brace */ @@ -303,6 +305,15 @@ cfg_select! { } } +// FIXME: not formatted, a line comment before `=>` would comment out the `=>` +cfg_select! { + any(true, + true, true, + true, ) + // line comment before arrow + => {} +} + // A bunch of mixed predicates cfg_select! { // When all predicates are simple uses mixed list formatting @@ -695,7 +706,8 @@ mod nested { true, /* comment */ true, true, // true, true, - ) => {} + ) + /* comment before arrow */ => {} not( false // comment @@ -706,9 +718,10 @@ mod nested { ) => "any", } - // comments before and after the `=>` get dropped right now + // comments before and after the `=>` are kept cfg_select! { - any(true, true, true, true,) => {} + any(true, true, true, true,) + /* comment before arrow */ => {} not(false) => /* comment before opening brace */ diff --git a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs index aa49a5e928c5a..629570412180c 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs @@ -84,7 +84,7 @@ fn cfg_select_comments_that_might_get_lost() { {{ x }} } cfg_select! { - _ => {{ x }} + _ /* before arrow */ => {{ x }} } cfg_select! { @@ -159,6 +159,24 @@ fn cfg_select_comments_that_might_get_lost() { line */ {{ x }} } + + // Comments before `=>` + cfg_select! { + _ /* before arrow */ => {{ x }} + _ /* before arrow */ => x, + _ /* before arrow */ => + /* after arrow */ + {{ x }} + all(target_os = "linux", target_arch = "x86_64", target_env = "gnu") + /* long comment before arrow */ => {{ x }} + _ /* before arrow */ => {} + } + + cfg_select! { + // Not formatted: a line comment before `=>`. + c // before arrow + => x, + } } fn other_cfg_select_cases() { From 0dd546ab5f2008fbbc7494706c001c9f2c896ae3 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 24 Oct 2026 01:29:50 +0800 Subject: [PATCH 13/46] fix(cfg_select): don't lose comment between body and trailing comma --- src/tools/rustfmt/src/macros.rs | 222 ++++++++++-------- .../cfg_select/cfg_select_edge_cases.rs | 21 ++ .../cfg_select/cfg_select_edge_cases.rs | 21 ++ 3 files changed, 162 insertions(+), 102 deletions(-) diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 065f1c64dfab2..3eddfa6317a61 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -22,7 +22,7 @@ use tracing::debug; use crate::comment::{ CharClasses, FindUncommented, FullCodeCharKind, LineClasses, - combine_strs_with_missing_comments, contains_comment, rewrite_comment, + combine_strs_with_missing_comments, contains_comment, recover_comment_removed, rewrite_comment, }; use crate::config::StyleEdition; use crate::config::lists::*; @@ -31,7 +31,9 @@ use crate::header::{HeaderPart, format_header}; use crate::is_nightly_channel; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::overflow; -use crate::parse::macros::cfg_select::{CfgSelectFormatPredicate, parse_cfg_select_arms}; +use crate::parse::macros::cfg_select::{ + CfgSelectArm, 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::{ @@ -1624,106 +1626,7 @@ fn format_cfg_select( "}", |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)?) - } - }; - - // We need to account for comments between predicate and `=>`. - let before_arrow_sp = arm.predicate.span().between(arm.arrow.span); - let before_arrow = context.snippet(before_arrow_sp); - let predicate_str = if contains_comment(before_arrow) { - // We cannot rewrite if the last line is a line comment, it will comment out `=>`. - if last_line_contains_single_line_comment(before_arrow.trim_end()) { - return Err(RewriteError::Unknown); - } - Cow::Owned(combine_strs_with_missing_comments( - context, - &predicate_str, - "", - before_arrow_sp, - nested_shape, - true, - )?) - } else { - predicate_str - }; - - match arm.maybe_double_block_concise_formatting(context) { - // FIXME(ytmimi): the `rewrite_match_body` branch also handles `control_brace_style` - // better. That said, it helps fix - // . - Some(inner_expr) => { - // ``` - // => /* comment */ {{ ... }} - // ^------------- between span - // ``` - let after_arrow_comment = context - .snippet(arm.arrow.span.between(arm.expr.span())) - .trim(); - let after_arrow_comment = if after_arrow_comment.is_empty() { - None - } else { - Some(rewrite_comment( - after_arrow_comment, - false, - nested_shape, - context.config, - )?) - }; - - let trailing_comma = if arm.trailing_comma.is_some() { - "," - } else { - "" - }; - - // Match `match` layout for after arrow comment, where comment and expr each go - // on own lines. - let prefix_width = if after_arrow_comment.is_some() { - 0 - } else { - extra_offset(&predicate_str, nested_shape) + " => ".len() - }; - - let body_shape = nested_shape - .offset_left(prefix_width + "{".len(), arm.span())? - .sub_width("}".len() + trailing_comma.len(), arm.span())?; - let body = inner_expr.rewrite_result(context, body_shape)?; - - let body = format!("{{{body}}}{trailing_comma}"); - match after_arrow_comment { - Some(comment) => { - let indent = nested_shape.indent.to_string_with_newline(context.config); - Ok(format!("{predicate_str} =>{indent}{comment}{indent}{body}")) - } - None => Ok(format!("{predicate_str} => {body}")), - } - } - None => { - // Note that we can't _just_ pass the `=>`'s span, we need to pass - // - // ``` - // => /* comment */ #[attr] { ... } - // ^--------------- this whole span until outer expr - // ``` - // - // Using `Spanned::span` here for `arm.expr` to account for outer attributes. - crate::matches::rewrite_match_body( - context, - &arm.expr, - &predicate_str, - nested_shape, - false, - arm.arrow.span.until(arm.expr.span()), - last_arm.is_some_and(|la| la == arm), - ) - } - } - }, + |arm| rewrite_cfg_select_arm(context, nested_shape, last_arm, arm), // Start Span after the opening delimiter. For example, // ``` // cfg_select! { @@ -1755,3 +1658,118 @@ fn format_cfg_select( Ok(rewrite) } + +fn rewrite_cfg_select_arm( + context: &RewriteContext<'_>, + nested_shape: Shape, + last_arm: Option<&CfgSelectArm>, + arm: &CfgSelectArm, +) -> Result { + let try_rewrite = || { + 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)?) + } + }; + + // We need to account for comments between predicate and `=>`. + let before_arrow_sp = arm.predicate.span().between(arm.arrow.span); + let before_arrow = context.snippet(before_arrow_sp); + let predicate_str = if contains_comment(before_arrow) { + // We cannot rewrite if the last line is a line comment, it will comment out + // `=>`. + if last_line_contains_single_line_comment(before_arrow.trim_end()) { + return Ok(context.snippet(arm.span()).to_owned()); + } + + Cow::Owned(combine_strs_with_missing_comments( + context, + &predicate_str, + "", + before_arrow_sp, + nested_shape, + true, + )?) + } else { + predicate_str + }; + + match arm.maybe_double_block_concise_formatting(context) { + // FIXME(ytmimi): the `rewrite_match_body` branch also handles + // `control_brace_style` better. That said, it helps fix + // . + Some(inner_expr) => { + // ``` + // => /* comment */ {{ ... }} + // ^------------- between span + // ``` + let after_arrow_comment = context + .snippet(arm.arrow.span.between(arm.expr.span())) + .trim(); + let after_arrow_comment = if after_arrow_comment.is_empty() { + None + } else { + Some(rewrite_comment( + after_arrow_comment, + false, + nested_shape, + context.config, + )?) + }; + + let trailing_comma = if arm.trailing_comma.is_some() { + "," + } else { + "" + }; + + // Match `match` layout for after arrow comment, where comment and expr each + // go on own lines. + let prefix_width = if after_arrow_comment.is_some() { + 0 + } else { + extra_offset(&predicate_str, nested_shape) + " => ".len() + }; + + let body_shape = nested_shape + .offset_left(prefix_width + "{".len(), arm.span())? + .sub_width("}".len() + trailing_comma.len(), arm.span())?; + let body = inner_expr.rewrite_result(context, body_shape)?; + + let body = format!("{{{body}}}{trailing_comma}"); + match after_arrow_comment { + Some(comment) => { + let indent = nested_shape.indent.to_string_with_newline(context.config); + Ok(format!("{predicate_str} =>{indent}{comment}{indent}{body}")) + } + None => Ok(format!("{predicate_str} => {body}")), + } + } + None => { + // Note that we can't _just_ pass the `=>`'s span, we need to pass + // + // ``` + // => /* comment */ #[attr] { ... } + // ^--------------- this whole span until outer expr + // ``` + // + // Using `Spanned::span` here for `arm.expr` to account for outer + // attributes. + crate::matches::rewrite_match_body( + context, + &arm.expr, + &predicate_str, + nested_shape, + false, + arm.arrow.span.until(arm.expr.span()), + last_arm.is_some_and(|la| la == arm), + ) + } + } + }; + + let rw = try_rewrite()?; + // NB: there could be a comment between body and trailing comma. + Ok(recover_comment_removed(rw, arm.span(), context)) +} diff --git a/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs index d029f8eb30469..cec0bb4b9c239 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs @@ -123,6 +123,27 @@ fn cfg_select_comments_that_might_get_lost() { c // before arrow => x, } + + cfg_select! { + _ => x /* before comma */, + } + + cfg_select! { + _ => {{ x }} /* before comma */, + } + + cfg_select! { + a => b, + } + + cfg_select! { + _ => xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx /* too long */, + } + + cfg_select! { + _ => x // trailing newline + , + } } fn other_cfg_select_cases() { diff --git a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs index 629570412180c..6b2f713116e0c 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs @@ -177,6 +177,27 @@ fn cfg_select_comments_that_might_get_lost() { c // before arrow => x, } + + cfg_select! { + _ => x /* before comma */, + } + + cfg_select! { + _ => {{ x }} /* before comma */, + } + + cfg_select! { + a => b, + } + + cfg_select! { + _ => xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx /* too long */, + } + + cfg_select! { + _ => x // trailing newline + , + } } fn other_cfg_select_cases() { From a5198eb788defcf1b45900af0fe2dc3a38c3c183 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 24 Oct 2026 14:25:54 +0800 Subject: [PATCH 14/46] test(cfg_select): snapshot current `control_brace_style` behavior --- .../control_brace_style_always_next_line.rs | 33 +++++++++++++++++++ .../control_brace_style_always_same_line.rs | 33 +++++++++++++++++++ .../control_brace_style_always_next_line.rs | 28 ++++++++++++++++ .../control_brace_style_always_same_line.rs | 27 +++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs create mode 100644 src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs create mode 100644 src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs create mode 100644 src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs diff --git a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs new file mode 100644 index 0000000000000..0214052f3cdcd --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs @@ -0,0 +1,33 @@ +//@ rustfmt-unstable: true +//@ rustfmt-edition: 2024 +//@ rustfmt-control_brace_style: AlwaysNextLine + +cfg_select! { + _ => { x } +} + +cfg_select! { + _ => { + x + } +} + +cfg_select! { + _ => {{ x }} +} + +cfg_select! { + _ => {{ + x + }} +} + +cfg_select! { + _ => unsafe { x } +} + +cfg_select! { + _ => { + unsafe { x } + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs new file mode 100644 index 0000000000000..a3aacb709811c --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs @@ -0,0 +1,33 @@ +//@ rustfmt-unstable: true +//@ rustfmt-control_brace_style: AlwaysSameLine +// ^ NB: default value + +cfg_select! { + _ => { x } +} + +cfg_select! { + _ => { + x + } +} + +cfg_select! { + _ => {{ x }} +} + +cfg_select! { + _ => {{ + x + }} +} + +cfg_select! { + _ => unsafe { x } +} + +cfg_select! { + _ => { + unsafe { x } + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs new file mode 100644 index 0000000000000..156a9749696ec --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs @@ -0,0 +1,28 @@ +//@ rustfmt-unstable: true +//@ rustfmt-edition: 2024 +//@ rustfmt-control_brace_style: AlwaysNextLine + +cfg_select! { + _ => x, +} + +cfg_select! { + _ => x, +} + +cfg_select! { + _ => {{ x }} +} + +cfg_select! { + _ => {{ x }} +} + +cfg_select! { + _ => + unsafe { x }, +} + +cfg_select! { + _ => {unsafe { x }} +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs new file mode 100644 index 0000000000000..f77205cd03d32 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs @@ -0,0 +1,27 @@ +//@ rustfmt-unstable: true +//@ rustfmt-control_brace_style: AlwaysSameLine +// ^ NB: default value + +cfg_select! { + _ => x, +} + +cfg_select! { + _ => x, +} + +cfg_select! { + _ => {{ x }} +} + +cfg_select! { + _ => {{ x }} +} + +cfg_select! { + _ => unsafe { x }, +} + +cfg_select! { + _ => {unsafe { x }} +} From 19cdcb60c8adb276df8990393cd7800db5cd0e56 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sun, 25 Oct 2026 23:35:24 -0400 Subject: [PATCH 15/46] refactor: exhaustively match on `token::LitKind` Based on rustfmt issue 7215 it seems that we missed handling different kinds of string literals. Before trying to address that issue I thought it would be best if we refactored the match to be exhaustive. --- src/tools/rustfmt/src/expr.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 0b82acba264da..a9eccf1c7785a 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -1371,13 +1371,31 @@ pub(crate) fn rewrite_literal( token::LitKind::Str => rewrite_string_lit(context, span, shape), token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape), token::LitKind::Float => rewrite_float_lit(context, token_lit, span, shape), - _ => wrap_str( - context.snippet(span).to_owned(), - context.config.max_width(), - context.config.tab_spaces(), - shape, - ) - .max_width_error(shape.width, span), + token::LitKind::Bool + | token::LitKind::Byte + | token::LitKind::ByteStr + | token::LitKind::ByteStrRaw(_) + | token::LitKind::Char + | token::LitKind::CStr + | token::LitKind::CStrRaw(_) + | token::LitKind::StrRaw(_) + | token::LitKind::Err(_) => { + // token::LitKind::Bool is true or false + // token::LitKind::Byte is b'' + // token::LitKind::ByteStr is b"" + // token::LitKind::ByteStrRaw(_) is br"" or br#""# + // token::LitKind::Char is '' + // token::LitKind::CStr is c"" + // token::LitKind::CStrRaw(_) is cr"" or cr#""# + // token::LitKind::StrRaw(_) is r"" or r#""# + wrap_str( + context.snippet(span).to_owned(), + context.config.max_width(), + context.config.tab_spaces(), + shape, + ) + .max_width_error(shape.width, span) + } } } From e5ad9ad1b3eb83d01fea1b2742c6385781b178ea Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 24 Oct 2026 15:05:43 +0800 Subject: [PATCH 16/46] fix(cfg_select): respect `control_brace_style` config --- src/tools/rustfmt/src/macros.rs | 16 ++++++++++++---- .../control_brace_style_always_next_line.rs | 9 ++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 3eddfa6317a61..421094b25dfc9 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -24,8 +24,8 @@ use crate::comment::{ CharClasses, FindUncommented, FullCodeCharKind, LineClasses, combine_strs_with_missing_comments, contains_comment, recover_comment_removed, rewrite_comment, }; -use crate::config::StyleEdition; use crate::config::lists::*; +use crate::config::{ControlBraceStyle, StyleEdition}; use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::header::{HeaderPart, format_header}; use crate::is_nightly_channel; @@ -1724,9 +1724,13 @@ fn rewrite_cfg_select_arm( "" }; - // Match `match` layout for after arrow comment, where comment and expr each - // go on own lines. - let prefix_width = if after_arrow_comment.is_some() { + // Force next-line layout if there's a comment after `=>`, like `match` arms. + let next_line_open_brace = matches!( + context.config.control_brace_style(), + ControlBraceStyle::AlwaysNextLine, + ) || after_arrow_comment.is_some(); + + let prefix_width = if next_line_open_brace { 0 } else { extra_offset(&predicate_str, nested_shape) + " => ".len() @@ -1743,6 +1747,10 @@ fn rewrite_cfg_select_arm( let indent = nested_shape.indent.to_string_with_newline(context.config); Ok(format!("{predicate_str} =>{indent}{comment}{indent}{body}")) } + None if next_line_open_brace => { + let indent = nested_shape.indent.to_string_with_newline(context.config); + Ok(format!("{predicate_str} =>{indent}{body}")) + } None => Ok(format!("{predicate_str} => {body}")), } } diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs index 156a9749696ec..67b1c76d05c11 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs @@ -11,11 +11,13 @@ cfg_select! { } cfg_select! { - _ => {{ x }} + _ => + {{ x }} } cfg_select! { - _ => {{ x }} + _ => + {{ x }} } cfg_select! { @@ -24,5 +26,6 @@ cfg_select! { } cfg_select! { - _ => {unsafe { x }} + _ => + {unsafe { x }} } From 3d87f59b272499ce0f66983ae8242d5ad057622b Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sun, 25 Oct 2026 10:49:00 +0800 Subject: [PATCH 17/46] test(cfg_select): add coverage for `control_brace_style` with comments after `=>` --- .../control_brace_style_always_next_line.rs | 40 ++++++++++++ .../control_brace_style_always_same_line.rs | 40 ++++++++++++ .../control_brace_style_always_next_line.rs | 62 +++++++++++++++++++ .../control_brace_style_always_same_line.rs | 62 +++++++++++++++++++ 4 files changed, 204 insertions(+) diff --git a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs index 0214052f3cdcd..f749ec75094e2 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs @@ -31,3 +31,43 @@ cfg_select! { unsafe { x } } } + +cfg_select! { + _ => // line comment + {{ x }} +} + +cfg_select! { + _ => // line comment + { a; b } +} + +cfg_select! { + _ => /* after */ {{ x }} +} + +cfg_select! { + _ => /* after */ {a; b} +} + +cfg_select! { + _ => /* after + multiline */ {{ x }} +} + +cfg_select! { + _ => /* after + multiline */ { a; b } +} + +cfg_select! { + _ => /* after */ {{ + x + }} +} + +cfg_select! { + _ => /* after */ { + a; b + } +} diff --git a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs index a3aacb709811c..ee76980bd3cce 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs @@ -31,3 +31,43 @@ cfg_select! { unsafe { x } } } + +cfg_select! { + _ => // line comment + {{ x }} +} + +cfg_select! { + _ => // line comment + { a; b } +} + +cfg_select! { + _ => /* after */ {{ x }} +} + +cfg_select! { + _ => /* after */ {a; b} +} + +cfg_select! { + _ => /* after + multiline */ {{ x }} +} + +cfg_select! { + _ => /* after + multiline */ { a; b } +} + +cfg_select! { + _ => /* after */ {{ + x + }} +} + +cfg_select! { + _ => /* after */ { + a; b + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs index 67b1c76d05c11..bacaede7266de 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs @@ -29,3 +29,65 @@ cfg_select! { _ => {unsafe { x }} } + +cfg_select! { + _ => + // line comment + {{ x }} +} + +cfg_select! { + _ => + // line comment + { + a; + b + } +} + +cfg_select! { + _ => + /* after */ + {{ x }} +} + +cfg_select! { + _ => + /* after */ + { + a; + b + } +} + +cfg_select! { + _ => + /* after + multiline */ + {{ x }} +} + +cfg_select! { + _ => + /* after + multiline */ + { + a; + b + } +} + +cfg_select! { + _ => + /* after */ + {{ x }} +} + +cfg_select! { + _ => + /* after */ + { + a; + b + } +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs index f77205cd03d32..fed03ee4d2fab 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs @@ -25,3 +25,65 @@ cfg_select! { cfg_select! { _ => {unsafe { x }} } + +cfg_select! { + _ => + // line comment + {{ x }} +} + +cfg_select! { + _ => + // line comment + { + a; + b + } +} + +cfg_select! { + _ => + /* after */ + {{ x }} +} + +cfg_select! { + _ => + /* after */ + { + a; + b + } +} + +cfg_select! { + _ => + /* after + multiline */ + {{ x }} +} + +cfg_select! { + _ => + /* after + multiline */ + { + a; + b + } +} + +cfg_select! { + _ => + /* after */ + {{ x }} +} + +cfg_select! { + _ => + /* after */ + { + a; + b + } +} From 67bfa44ac8f4f194d23e1f89e8a36c813092d556 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sun, 25 Oct 2026 22:42:12 +0800 Subject: [PATCH 18/46] test(cfg_select): add coverage for long comment before arrow --- .../control_brace_style_always_next_line.rs | 27 +++++++++++++ .../control_brace_style_always_same_line.rs | 27 +++++++++++++ .../control_brace_style_always_next_line.rs | 39 +++++++++++++++++++ .../control_brace_style_always_same_line.rs | 38 ++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs index f749ec75094e2..a12391c20298d 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs @@ -71,3 +71,30 @@ cfg_select! { a; b } } + +cfg_select! { + _ /* this comment is _very_ long but brace should fit ---------------------------------- */ => { a; b } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => { a; b } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => { a; b } +} + +cfg_select! { + _ /* this comment is _very_ long but shouldn't change next next line --------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => + {{ ThisLineIsShort }} +} diff --git a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs index ee76980bd3cce..cda500f61afe6 100644 --- a/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs @@ -71,3 +71,30 @@ cfg_select! { a; b } } + +cfg_select! { + _ /* this comment is _very_ long but brace should fit ---------------------------------- */ => { a; b } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => { a; b } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => { a; b } +} + +cfg_select! { + _ /* this comment is _very_ long but shouldn't change next next line --------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => + {{ ThisLineIsShort }} +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs index bacaede7266de..d0a8880d97bd7 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs @@ -91,3 +91,42 @@ cfg_select! { b } } + +cfg_select! { + _ /* this comment is _very_ long but brace should fit ---------------------------------- */ => + { + a; + b + } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => + { + a; + b + } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => + { + a; + b + } +} + +cfg_select! { + _ /* this comment is _very_ long but shouldn't change next next line --------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => + {{ ThisLineIsShort }} +} diff --git a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs index fed03ee4d2fab..827b579509d84 100644 --- a/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs @@ -87,3 +87,41 @@ cfg_select! { b } } + +cfg_select! { + _ /* this comment is _very_ long but brace should fit ---------------------------------- */ => { + a; + b + } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => + { + a; + b + } +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => + { + a; + b + } +} + +cfg_select! { + _ /* this comment is _very_ long but shouldn't change next next line --------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 101 columns incl. arrow ------------------- */ => + {{ ThisLineIsShort }} +} + +cfg_select! { + _ /* this comment is _very_ long and adds up to 100 columns itself -------------------------- */ => + {{ ThisLineIsShort }} +} From ecc66a846c851d076b7c323e91e043cad4f82c2d Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:05:55 +0000 Subject: [PATCH 19/46] ci: Disable credential persistence Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 2 ++ src/tools/rustfmt/.github/workflows/clippy.yml | 4 ++++ src/tools/rustfmt/.github/workflows/integration.yml | 2 ++ src/tools/rustfmt/.github/workflows/rustdoc_check.yml | 2 ++ src/tools/rustfmt/.github/workflows/test.yml | 2 ++ src/tools/rustfmt/.github/workflows/upload-assets.yml | 2 ++ 6 files changed, 14 insertions(+) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index b3543a49c3e9b..dc9d74a79b538 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -51,6 +51,8 @@ jobs: steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Build check_diff binary working-directory: ./check_diff diff --git a/src/tools/rustfmt/.github/workflows/clippy.yml b/src/tools/rustfmt/.github/workflows/clippy.yml index a5aa91346dd84..a6ce1d0b9ceb5 100644 --- a/src/tools/rustfmt/.github/workflows/clippy.yml +++ b/src/tools/rustfmt/.github/workflows/clippy.yml @@ -13,6 +13,8 @@ jobs: steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: install rustup run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh @@ -35,6 +37,8 @@ jobs: steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: install rustup run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh diff --git a/src/tools/rustfmt/.github/workflows/integration.yml b/src/tools/rustfmt/.github/workflows/integration.yml index 224605740e55e..ea797e36f6896 100644 --- a/src/tools/rustfmt/.github/workflows/integration.yml +++ b/src/tools/rustfmt/.github/workflows/integration.yml @@ -60,6 +60,8 @@ jobs: steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # Run build - name: install rustup diff --git a/src/tools/rustfmt/.github/workflows/rustdoc_check.yml b/src/tools/rustfmt/.github/workflows/rustdoc_check.yml index b3978005fdf62..e2b5e5a68ed71 100644 --- a/src/tools/rustfmt/.github/workflows/rustdoc_check.yml +++ b/src/tools/rustfmt/.github/workflows/rustdoc_check.yml @@ -13,6 +13,8 @@ jobs: steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: install rustup run: | diff --git a/src/tools/rustfmt/.github/workflows/test.yml b/src/tools/rustfmt/.github/workflows/test.yml index 6d0f25834e596..73b87755e7659 100644 --- a/src/tools/rustfmt/.github/workflows/test.yml +++ b/src/tools/rustfmt/.github/workflows/test.yml @@ -43,6 +43,8 @@ jobs: run: git config --global core.autocrlf false - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rustup using win.rustup.rs if: ${{ matrix.os == 'windows-latest' }} diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index 991cc965e3d51..98013641005df 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -32,6 +32,8 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # Run build - name: install rustup From a754ca9c86cedade0bdfdd98bc3b0973281faee1 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:09:32 +0000 Subject: [PATCH 20/46] ci: Expand `github.event.inputs.commit_hash` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index dc9d74a79b538..5d3c943f09b95 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -62,12 +62,13 @@ jobs: working-directory: ./check_diff env: CHECK_DIFF_LOG: info + GITHUB_EVENT_INPUTS_COMMIT_HASH: ${{ github.event.inputs.commit_hash }} shell: bash run: | OPTIONS="" - if [[ -n "${{ github.event.inputs.commit_hash }}" ]]; then - OPTIONS+="--commit-hash ${{ github.event.inputs.commit_hash }} " + if [[ -n "${GITHUB_EVENT_INPUTS_COMMIT_HASH}" ]]; then + OPTIONS+="--commit-hash ${GITHUB_EVENT_INPUTS_COMMIT_HASH} " fi if [[ -n "${{ github.event.inputs.rustfmt_configs }}" ]]; then From 2c9df224467ed8f8f45345c158b04bd30719bfde Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:10:35 +0000 Subject: [PATCH 21/46] ci: Expand `github.event.inputs.rustfmt_configs` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index 5d3c943f09b95..31baf901072cd 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -63,6 +63,7 @@ jobs: env: CHECK_DIFF_LOG: info GITHUB_EVENT_INPUTS_COMMIT_HASH: ${{ github.event.inputs.commit_hash }} + GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS: ${{ github.event.inputs.rustfmt_configs }} shell: bash run: | OPTIONS="" @@ -71,8 +72,8 @@ jobs: OPTIONS+="--commit-hash ${GITHUB_EVENT_INPUTS_COMMIT_HASH} " fi - if [[ -n "${{ github.event.inputs.rustfmt_configs }}" ]]; then - OPTIONS+="--rustfmt-config ${{ github.event.inputs.rustfmt_configs }} " + if [[ -n "${GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS}" ]]; then + OPTIONS+="--rustfmt-config ${GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS} " fi target/release/check_diff ${{ github.event.inputs.clone_url }} ${{ github.event.inputs.branch_name }} \ From ad47c3e1c7bd2f40e239a146089ea094cb576802 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:12:41 +0000 Subject: [PATCH 22/46] ci: Expand `github.event.inputs.clone_url` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index 31baf901072cd..f554ce3467167 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -64,6 +64,7 @@ jobs: CHECK_DIFF_LOG: info GITHUB_EVENT_INPUTS_COMMIT_HASH: ${{ github.event.inputs.commit_hash }} GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS: ${{ github.event.inputs.rustfmt_configs }} + GITHUB_EVENT_INPUTS_CLONE_URL: ${{ github.event.inputs.clone_url }} shell: bash run: | OPTIONS="" @@ -76,7 +77,7 @@ jobs: OPTIONS+="--rustfmt-config ${GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS} " fi - target/release/check_diff ${{ github.event.inputs.clone_url }} ${{ github.event.inputs.branch_name }} \ + target/release/check_diff ${GITHUB_EVENT_INPUTS_CLONE_URL} ${{ github.event.inputs.branch_name }} \ --edition ${{ github.event.inputs.language_edition }} \ --style-edition ${{ github.event.inputs.style_edition }} \ $OPTIONS From e2e31602324caa8e2ba0ba3a1645b64f71ba6005 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:14:21 +0000 Subject: [PATCH 23/46] ci: Expand `github.event.inputs.branch_name` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index f554ce3467167..73bfd77dfe8ee 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -65,6 +65,7 @@ jobs: GITHUB_EVENT_INPUTS_COMMIT_HASH: ${{ github.event.inputs.commit_hash }} GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS: ${{ github.event.inputs.rustfmt_configs }} GITHUB_EVENT_INPUTS_CLONE_URL: ${{ github.event.inputs.clone_url }} + GITHUB_EVENT_INPUTS_BRANCH_NAME: ${{ github.event.inputs.branch_name }} shell: bash run: | OPTIONS="" @@ -77,7 +78,7 @@ jobs: OPTIONS+="--rustfmt-config ${GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS} " fi - target/release/check_diff ${GITHUB_EVENT_INPUTS_CLONE_URL} ${{ github.event.inputs.branch_name }} \ + target/release/check_diff ${GITHUB_EVENT_INPUTS_CLONE_URL} ${GITHUB_EVENT_INPUTS_BRANCH_NAME} \ --edition ${{ github.event.inputs.language_edition }} \ --style-edition ${{ github.event.inputs.style_edition }} \ $OPTIONS From ce40bba6fe534bc41968155a21b9aedf22c41999 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:15:08 +0000 Subject: [PATCH 24/46] ci: Expand `github.event.inputs.language_edition` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index 73bfd77dfe8ee..1a5e95c755ac9 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -66,6 +66,7 @@ jobs: GITHUB_EVENT_INPUTS_RUSTFMT_CONFIGS: ${{ github.event.inputs.rustfmt_configs }} GITHUB_EVENT_INPUTS_CLONE_URL: ${{ github.event.inputs.clone_url }} GITHUB_EVENT_INPUTS_BRANCH_NAME: ${{ github.event.inputs.branch_name }} + GITHUB_EVENT_INPUTS_LANGUAGE_EDITION: ${{ github.event.inputs.language_edition }} shell: bash run: | OPTIONS="" @@ -79,6 +80,6 @@ jobs: fi target/release/check_diff ${GITHUB_EVENT_INPUTS_CLONE_URL} ${GITHUB_EVENT_INPUTS_BRANCH_NAME} \ - --edition ${{ github.event.inputs.language_edition }} \ + --edition ${GITHUB_EVENT_INPUTS_LANGUAGE_EDITION} \ --style-edition ${{ github.event.inputs.style_edition }} \ $OPTIONS From 52d6915ea886769fe6ba0f23fc27885f0c818753 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:15:56 +0000 Subject: [PATCH 25/46] ci: Expand `github.event.inputs.style_edition` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/check_diff.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index 1a5e95c755ac9..7f7bc62846a3c 100644 --- a/src/tools/rustfmt/.github/workflows/check_diff.yml +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -67,6 +67,7 @@ jobs: GITHUB_EVENT_INPUTS_CLONE_URL: ${{ github.event.inputs.clone_url }} GITHUB_EVENT_INPUTS_BRANCH_NAME: ${{ github.event.inputs.branch_name }} GITHUB_EVENT_INPUTS_LANGUAGE_EDITION: ${{ github.event.inputs.language_edition }} + GITHUB_EVENT_INPUTS_STYLE_EDITION: ${{ github.event.inputs.style_edition }} shell: bash run: | OPTIONS="" @@ -81,5 +82,5 @@ jobs: target/release/check_diff ${GITHUB_EVENT_INPUTS_CLONE_URL} ${GITHUB_EVENT_INPUTS_BRANCH_NAME} \ --edition ${GITHUB_EVENT_INPUTS_LANGUAGE_EDITION} \ - --style-edition ${{ github.event.inputs.style_edition }} \ + --style-edition ${GITHUB_EVENT_INPUTS_STYLE_EDITION} \ $OPTIONS From 9cda3d38415573fe456677d1ff6fdcaaa086db79 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:19:37 +0000 Subject: [PATCH 26/46] ci: Expand `github.event.release.tag_name` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/upload-assets.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index 98013641005df..7f655437a6f4f 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -53,7 +53,7 @@ jobs: - name: Build archive shell: bash run: | - staging="rustfmt_${{ matrix.build }}_${{ github.event.release.tag_name }}" + staging="rustfmt_${{ matrix.build }}_${GITHUB_EVENT_RELEASE_TAG_NAME}" mkdir -p "$staging" cp {README.md,Configurations.md,CHANGELOG.md,LICENSE-MIT,LICENSE-APACHE} "$staging/" @@ -67,9 +67,13 @@ jobs: tar czf "$staging.tar.gz" "$staging" echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV fi + env: + GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - name: Upload Release Asset + shell: bash if: github.event_name == 'release' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release upload "${{ github.event.release.tag_name }}" "${{ env.ASSET }}" + GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} + run: gh release upload "${GITHUB_EVENT_RELEASE_TAG_NAME}" "${{ env.ASSET }}" From a16fdd3094ecb8ca4b5497cad34f02a681fbfed6 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:41:06 +0000 Subject: [PATCH 27/46] ci: Prefer shell expansion over template expansion Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/upload-assets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index 7f655437a6f4f..e4deb20c49b68 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -76,4 +76,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - run: gh release upload "${GITHUB_EVENT_RELEASE_TAG_NAME}" "${{ env.ASSET }}" + run: gh release upload "${GITHUB_EVENT_RELEASE_TAG_NAME}" "${ASSET}" From e6c6acbafc81f7aa3bd1c4a065c11877bcd730e3 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:48:11 +0000 Subject: [PATCH 28/46] ci: Expand `matrix.target` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/test.yml | 8 ++++++-- src/tools/rustfmt/.github/workflows/upload-assets.yml | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tools/rustfmt/.github/workflows/test.yml b/src/tools/rustfmt/.github/workflows/test.yml index 73b87755e7659..541c084578308 100644 --- a/src/tools/rustfmt/.github/workflows/test.yml +++ b/src/tools/rustfmt/.github/workflows/test.yml @@ -54,7 +54,9 @@ jobs: 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 }} + rustup target add ${env:MATRIX_TARGET} + env: + MATRIX_TARGET: ${{ matrix.target }} shell: powershell - name: Add mingw32 to path for i686-gnu @@ -73,7 +75,9 @@ jobs: 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 }} + rustup target add ${MATRIX_TARGET} + env: + MATRIX_TARGET: ${{ matrix.target }} # Run build - name: Build and Test diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index e4deb20c49b68..40ccf8b529b8f 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -37,10 +37,13 @@ jobs: # Run build - name: install rustup + shell: bash 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 }} + rustup target add ${MATRIX_TARGET} + env: + MATRIX_TARGET: ${{ matrix.target }} - name: Add mingw64 to path for x86_64-gnu run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH From c136f963a92af719344e180df2ae175ff48d3069 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:50:25 +0000 Subject: [PATCH 29/46] ci: Expand `matrix.os` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/upload-assets.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index 40ccf8b529b8f..a99305e7dd4b9 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -61,7 +61,7 @@ jobs: cp {README.md,Configurations.md,CHANGELOG.md,LICENSE-MIT,LICENSE-APACHE} "$staging/" - if [ "${{ matrix.os }}" = "windows-latest" ]; then + if [ "${MATRIX_OS}" = "windows-latest" ]; then cp target/release/{rustfmt.exe,cargo-fmt.exe,rustfmt-format-diff.exe,git-rustfmt.exe} "$staging/" 7z a "$staging.zip" "$staging" echo "ASSET=$staging.zip" >> $GITHUB_ENV @@ -71,6 +71,7 @@ jobs: echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV fi env: + MATRIX_OS: ${{ matrix.os }} GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - name: Upload Release Asset From c47f1f6a834ba08c9e0068e14b6f8e74c282553d Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:52:44 +0000 Subject: [PATCH 30/46] ci: Expand `matrix.build` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/upload-assets.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index a99305e7dd4b9..fd4bdf26d694a 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -56,7 +56,7 @@ jobs: - name: Build archive shell: bash run: | - staging="rustfmt_${{ matrix.build }}_${GITHUB_EVENT_RELEASE_TAG_NAME}" + staging="rustfmt_${MATRIX_BUILD}_${GITHUB_EVENT_RELEASE_TAG_NAME}" mkdir -p "$staging" cp {README.md,Configurations.md,CHANGELOG.md,LICENSE-MIT,LICENSE-APACHE} "$staging/" @@ -72,6 +72,7 @@ jobs: fi env: MATRIX_OS: ${{ matrix.os }} + MATRIX_BUILD: ${{ matrix.build }} GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - name: Upload Release Asset From 905157be81bdaeec182c726d24adb69e01089039 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:54:32 +0000 Subject: [PATCH 31/46] ci: Expand `matrix.integration` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/integration.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/integration.yml b/src/tools/rustfmt/.github/workflows/integration.yml index ea797e36f6896..e8da0d8d3aed1 100644 --- a/src/tools/rustfmt/.github/workflows/integration.yml +++ b/src/tools/rustfmt/.github/workflows/integration.yml @@ -72,5 +72,6 @@ jobs: - name: run integration tests env: TARGET: x86_64-unknown-linux-gnu - run: cargo run --locked --manifest-path ci/Cargo.toml integration ${{ matrix.integration }} + MATRIX_INTEGRATION: ${{ matrix.integration }} + run: cargo run --locked --manifest-path ci/Cargo.toml integration ${MATRIX_INTEGRATION} continue-on-error: ${{ matrix.allow-failure == true }} From 6c0248df6bfb6e64dad9e80cf18cb6243c218e0d Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:57:53 +0000 Subject: [PATCH 32/46] ci: Expand `matrix.package` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/clippy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/.github/workflows/clippy.yml b/src/tools/rustfmt/.github/workflows/clippy.yml index a6ce1d0b9ceb5..fc3eb8916a6bd 100644 --- a/src/tools/rustfmt/.github/workflows/clippy.yml +++ b/src/tools/rustfmt/.github/workflows/clippy.yml @@ -44,5 +44,7 @@ jobs: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh sh rustup-init.sh -y --default-toolchain none - name: clippy - run: cargo clippy --locked --manifest-path ${{ matrix.package }}/Cargo.toml + run: cargo clippy --locked --manifest-path "${MATRIX_PACKAGE}/Cargo.toml" + env: + MATRIX_PACKAGE: ${{ matrix.package }} From febf7c171e81e38a0da96304685372fb20ec8bca Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:59:44 +0000 Subject: [PATCH 33/46] ci: Expand `toJson(needs)` in `env` Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/rustfmt/.github/workflows/test.yml b/src/tools/rustfmt/.github/workflows/test.yml index 541c084578308..44d36898f216f 100644 --- a/src/tools/rustfmt/.github/workflows/test.yml +++ b/src/tools/rustfmt/.github/workflows/test.yml @@ -103,6 +103,8 @@ jobs: - name: Conclusion run: | # Print the dependent jobs to see them in the CI log - jq -C <<< '${{ toJson(needs) }}' + jq -C <<< "${JSON_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) }}' + jq --exit-status 'all(.result == "success" or .result == "skipped")' <<< "${JSON_NEEDS}" + env: + JSON_NEEDS: ${{ toJson(needs) }} From 17a6414e4305cbe568f5807b61ab7627f7d60b28 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:08:47 +0000 Subject: [PATCH 34/46] ci: Disable all permissions by default Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/upload-assets.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index fd4bdf26d694a..4a3e4de9a92f9 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -6,6 +6,8 @@ on: types: [created] workflow_dispatch: +permissions: {} + jobs: build-release: name: build-release From 1dde57ffd6b6df58639067a405857d703914745c Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:28:13 +0000 Subject: [PATCH 35/46] ci: Grant minimum required permissions Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/tools/rustfmt/.github/workflows/upload-assets.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml index 4a3e4de9a92f9..6d83e9b004a85 100644 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ b/src/tools/rustfmt/.github/workflows/upload-assets.yml @@ -11,6 +11,8 @@ permissions: {} jobs: build-release: name: build-release + permissions: + contents: write # Required by `gh release upload` strategy: matrix: build: [linux-x86_64, macos-x86_64, windows-x86_64-gnu, windows-x86_64-msvc] From f39d517432d9758ef2e8ac18c4da42fd27db84b6 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Mon, 19 Oct 2026 20:49:24 +0100 Subject: [PATCH 36/46] Integration test: assert on exit codes Asserting on the current state of things. Except for one case in `dont_emit_ICE`: this test reads a file that doesn't panic, but exits 1 because it has a too-long line, so split that line up to ensure we get a 0 exit. --- src/tools/rustfmt/tests/rustfmt/main.rs | 90 ++++++++++++++------ src/tools/rustfmt/tests/target/issue_6069.rs | 3 +- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index 71f52abe430c1..886de55fd7f12 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -61,32 +61,34 @@ fn rustfmt(args: &[&str]) -> (String, String, i32) { fn print_config_errors_on_unknown_option() { let args = &["--print-config", "unknown"]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(&stdout, ""); assert!( stderr.contains("Unknown print-config option"), "expected helpful error message, got: {stderr}" ); + assert_eq!(exit_code, 1); } #[test] fn print_config_default() { let args = &["--print-config", "default"]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); assert!( stdout.contains("max_width = 100"), "expected stdout to contain a default config, got: {stdout}", ); assert_eq!(&stderr, ""); + assert_eq!(exit_code, 0) } #[test] fn print_config_errors_minimal_with_no_path() { let args = &["--print-config", "minimal"]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); // NOTE: existing the behaviour: prints an empty line assert_eq!(stdout.trim(), ""); @@ -94,19 +96,21 @@ fn print_config_errors_minimal_with_no_path() { stderr.contains("PATH required"), "expected helpful error message, got: {stderr}" ); + assert_eq!(exit_code, 0) } #[test] fn print_config_errors_minimal_without_input_file() { let args = &["--print-config", "minimal", "minimal.toml"]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(&stdout, ""); assert!( stderr.contains("doesn't work with standard input"), "expected helpful error message, got: {stderr}" ); + assert_eq!(exit_code, 1); } // test writes to file within the repo, rust-lang/rust doesn't like that @@ -125,7 +129,7 @@ fn print_config_writes_to_file() { "src/shape.rs", ]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(stderr, ""); assert!( @@ -133,6 +137,7 @@ fn print_config_writes_to_file() { "stdout:\n{stdout}\nstderr:\n{stderr}" ); remove_file(out_file).unwrap(); + assert_eq!(exit_code, 0); } #[test] @@ -144,13 +149,14 @@ fn print_config_merges_override_from_args() { "--config=color=Never,edition=2018", ]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(stderr, ""); assert!( stdout.contains(r#"color = "Never""#) && stdout.contains(r#"edition = "2018""#), "expected overrides in stdout, got: {stdout}", ); + assert_eq!(exit_code, 0); } #[test] @@ -165,7 +171,7 @@ fn print_config_merges_multiple_overrides_from_args() { "color=always,format_strings=true", ]; - let (stdout, stderr, _) = rustfmt(args); + let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(stderr, ""); assert!( @@ -174,13 +180,14 @@ fn print_config_merges_multiple_overrides_from_args() { && stdout.contains("format_strings = true"), "expected overrides in stdout, got: {stdout}", ); + assert_eq!(exit_code, 0); } #[stable_only_test] #[test] fn rustfmt_usage_text() { let args = ["--help"]; - let (stdout, _, _) = rustfmt(&args); + let (stdout, _, exit_code) = rustfmt(&args); insta::assert_snapshot!(stdout, @" Format Rust code @@ -222,13 +229,14 @@ fn rustfmt_usage_text() { -h, --help [=TOPIC] Show this message or help about a specific topic: `config` "); + assert_eq!(exit_code, 0); } #[nightly_only_test] #[test] fn rustfmt_nightly_usage_text() { let args = ["--help"]; - let (stdout, _, _) = rustfmt(&args); + let (stdout, _, exit_code) = rustfmt(&args); insta::assert_snapshot!(stdout, @" Format Rust code @@ -282,6 +290,7 @@ fn rustfmt_nightly_usage_text() { -h, --help [=TOPIC] Show this message or help about a specific topic: `config` or `file-lines` "); + assert_eq!(exit_code, 0); } #[test] @@ -296,36 +305,44 @@ fn mod_resolution_error_multiple_candidate_files() { ); let args = ["tests/mod-resolver/issue-5167/src/lib.rs"]; - let (_stdout, stderr, _) = rustfmt(&args); - assert!(stderr.contains(&error_message)) + let (_stdout, stderr, exit_code) = rustfmt(&args); + + assert!(stderr.contains(&error_message)); + assert_eq!(exit_code, 1); } #[test] fn mod_resolution_error_sibling_module_not_found() { let args = ["tests/mod-resolver/module-not-found/sibling_module/lib.rs"]; - let (_stdout, stderr, _) = rustfmt(&args); + let (_stdout, stderr, exit_code) = rustfmt(&args); + // Module resolution fails because we're unable to find `a.rs` in the same directory as lib.rs - assert!(stderr.contains("a.rs does not exist")) + assert!(stderr.contains("a.rs does not exist")); + assert_eq!(exit_code, 1); } #[test] fn mod_resolution_error_relative_module_not_found() { let args = ["tests/mod-resolver/module-not-found/relative_module/lib.rs"]; - let (_stdout, stderr, _) = rustfmt(&args); + let (_stdout, stderr, exit_code) = rustfmt(&args); + // The file `./a.rs` and directory `./a` both exist. // Module resolution fails because we're unable to find `./a/b.rs` #[cfg(not(windows))] assert!(stderr.contains("a/b.rs does not exist")); #[cfg(windows)] assert!(stderr.contains("a\\b.rs does not exist")); + assert_eq!(exit_code, 1); } #[test] fn mod_resolution_error_path_attribute_does_not_exist() { let args = ["tests/mod-resolver/module-not-found/bad_path_attribute/lib.rs"]; - let (_stdout, stderr, _) = rustfmt(&args); + let (_stdout, stderr, exit_code) = rustfmt(&args); + // The path attribute points to a file that does not exist assert!(stderr.contains("does_not_exist.rs does not exist")); + assert_eq!(exit_code, 1); } #[test] @@ -337,10 +354,11 @@ fn rustfmt_emits_error_on_line_overflow_true() { "tests/cargo-fmt/source/issue_3164/src/main.rs", ]; - let (_stdout, stderr, _) = rustfmt(&args); + let (_stdout, stderr, exit_code) = rustfmt(&args); assert!(stderr.contains( "line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option)" - )) + )); + assert_eq!(exit_code, 1); } #[test] @@ -358,8 +376,11 @@ fn dont_emit_ICE() { let panic_re = regex::Regex::new("thread.*panicked").unwrap(); for file in files { let args = [file]; - let (_stdout, stderr, _) = rustfmt(&args); + + let (_stdout, stderr, exit_code) = rustfmt(&args); + assert!(!panic_re.is_match(&stderr)); + assert_eq!(exit_code, 0); } } @@ -375,8 +396,10 @@ fn rustfmt_emits_error_when_control_brace_style_is_always_next_line() { "tests/target/issue_5912.rs", ]; - let (_stdout, stderr, _) = rustfmt(&args); - assert!(!stderr.contains("error[internal]: left behind trailing whitespace")) + let (_stdout, stderr, exit_code) = rustfmt(&args); + + assert!(!stderr.contains("error[internal]: left behind trailing whitespace")); + assert_eq!(exit_code, 0); } #[nightly_only_test] @@ -386,9 +409,11 @@ fn rustfmt_generates_no_error_if_failed_format_code_in_doc_comments() { let file = "tests/target/issue-6109.rs"; let args = ["--config", "format_code_in_doc_comments=true", file]; - let (stdout, stderr, _) = rustfmt(&args); + let (stdout, stderr, exit_code) = rustfmt(&args); + assert!(stderr.is_empty()); assert!(stdout.is_empty()); + assert_eq!(exit_code, 0); } #[test] @@ -396,12 +421,13 @@ fn rustfmt_error_improvement_regarding_invalid_toml() { // See also https://github.com/rust-lang/rustfmt/issues/6302 let invalid_toml_config = "tests/config/issue-6302.toml"; let args = ["--config-path", invalid_toml_config]; - let (_stdout, stderr, _) = rustfmt(&args); + let (_stdout, stderr, exit_code) = rustfmt(&args); let toml_path = Path::new(invalid_toml_config).canonicalize().unwrap(); let expected_error_message = format!("The file `{}` failed to parse", toml_path.display()); assert!(stderr.contains(&expected_error_message)); + assert_eq!(exit_code, 1); } #[test] @@ -416,7 +442,7 @@ fn config_path_walks_parent_directories_with_dir_name() { &src_file.to_str().unwrap(), ]; - let (stdout, stderr, _) = rustfmt(&args); + let (stdout, stderr, exit_code) = rustfmt(&args); assert_eq!(stderr, ""); assert_eq!( @@ -425,6 +451,7 @@ fn config_path_walks_parent_directories_with_dir_name() { // this config sets `use_field_init_shorthand = true` "we should walk up to tests/config/issue_4660/rustfmt.toml" ); + assert_eq!(exit_code, 1); } // on unix [`std::env::home_dir`] reads the `HOME` env var. @@ -449,7 +476,7 @@ fn config_path_searches_home_dir_when_dir_path_no_resolve() { ]; let env = &[("HOME", homedir_path.to_str().unwrap())]; - let (stdout, stderr, _) = rustfmt_with_extra(&args, None, env, None); + let (stdout, stderr, exit_code) = rustfmt_with_extra(&args, None, env, None); assert_eq!(stderr, ""); assert_eq!( @@ -457,6 +484,7 @@ fn config_path_searches_home_dir_when_dir_path_no_resolve() { format!("{}\n", new_src_path.canonicalize().unwrap().display()), "should search in HOME" ); + assert_eq!(exit_code, 1); } // The behaviour [`dirs::config_dir`] is os-dependent, so pick one and test to that. @@ -482,7 +510,7 @@ fn config_path_searches_config_dir_when_dir_path_no_resolve() { ]; let env = &[("XDG_CONFIG_HOME", xdg_config_dir_path.to_str().unwrap())]; - let (stdout, stderr, _) = rustfmt_with_extra(&args, None, env, None); + let (stdout, stderr, exit_code) = rustfmt_with_extra(&args, None, env, None); assert_eq!(stderr, ""); assert_eq!( @@ -490,6 +518,7 @@ fn config_path_searches_config_dir_when_dir_path_no_resolve() { format!("{}\n", new_src_path.canonicalize().unwrap().display()), "should search in XDG_CONFIG_HOME" ); + assert_eq!(exit_code, 1); } #[test] @@ -498,7 +527,7 @@ fn config_path_errors_when_invalid_file_given() { let config_file = "also_not_a_file.toml"; let args = ["--config-path", &config_file, "--check", &src_file]; - let (stdout, stderr, _) = rustfmt(&args); + let (stdout, stderr, exit_code) = rustfmt(&args); assert_eq!( stderr, @@ -508,6 +537,7 @@ fn config_path_errors_when_invalid_file_given() { ) ); assert_eq!(stdout, ""); + assert_eq!(exit_code, 1); } #[test] @@ -534,11 +564,12 @@ fn rustfmt_allow_not_a_dir_errors() { let args = [empty_rs.to_str().unwrap()]; let envs = &[("HOME", fake_home_str)]; - let (stdout, stderr, _) = rustfmt_with_extra(&args, Some(fake_home_str), envs, None); + let (stdout, stderr, exit_code) = rustfmt_with_extra(&args, Some(fake_home_str), envs, None); // Should pass without any errors assert_eq!(stdout, ""); assert_eq!(stderr, ""); + assert_eq!(exit_code, 0); } // FIXME(cfg_select): ungate when `cfg_select!` formatting can be enabled on stable. @@ -548,8 +579,11 @@ fn cfg_select_parser_recovery_ice_7087() { // See also https://github.com/rust-lang/rustfmt/issues/7087 let source = "cfg_select! { true => { fn foo {} } }\n"; let panic_re = regex::Regex::new("thread.*panicked").unwrap(); - let (stdout, stderr, _) = rustfmt_with_extra(&[], None, &[], Some(source)); + + let (stdout, stderr, exit_code) = rustfmt_with_extra(&[], None, &[], Some(source)); + assert!(!panic_re.is_match(&stderr)); // Invalid arm should cause `cfg_select!` to be re-emitted unmodified. assert_eq!(source, stdout); + assert_eq!(exit_code, 0); } diff --git a/src/tools/rustfmt/tests/target/issue_6069.rs b/src/tools/rustfmt/tests/target/issue_6069.rs index d866ce7c646e3..febd840880a36 100644 --- a/src/tools/rustfmt/tests/target/issue_6069.rs +++ b/src/tools/rustfmt/tests/target/issue_6069.rs @@ -1,3 +1,4 @@ // `Foó` as written here ends with ASCII 6F `'o'` followed by `'\u{0301}'` COMBINING ACUTE ACCENT. -// The compiler normalizes that combination to NFC form, `'\u{00F3}'` LATIN SMALL LETTER O WITH ACUTE. +// The compiler normalizes that combination to NFC form, `'\u{00F3}'` +// LATIN SMALL LETTER O WITH ACUTE. trait Foó: Bar {} From 302895a9fccea10ffe68c5832b23b41afcc1d124 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Mon, 19 Oct 2026 21:11:46 +0100 Subject: [PATCH 37/46] Error on `--print-config minimal` with no output path Without an output path this option makes no sense, this also reflects what happens when you provide an output option but no input file. Also tidy up some of the code for this option handling. How it behaved previously: $ cargo run --quiet --bin rustfmt -- --print-config minimal for OperationError { @@ -490,23 +499,24 @@ fn determine_operation(matches: &Matches) -> Result { } let mut free_matches = matches.free.iter(); - let mut minimal_config_path = None; - if let Some(kind) = matches.opt_str("print-config") { - let path = free_matches.next().cloned(); - match kind.as_str() { - "default" => return Ok(Operation::ConfigOutputDefault { path }), - "current" => return Ok(Operation::ConfigOutputCurrent { path }), - "minimal" => { - minimal_config_path = path; - if minimal_config_path.is_none() { - eprintln!("WARNING: PATH required for `--print-config minimal`."); + let minimal_config_path = match matches.opt_str("print-config") { + None => None, + Some(kind) => { + let path = free_matches.next().cloned(); + match kind.as_str() { + "default" => return Ok(Operation::ConfigOutputDefault { path }), + "current" => return Ok(Operation::ConfigOutputCurrent { path }), + "minimal" => Some(path.ok_or(OperationError::PrintConfig( + PrintConfigError::MinimalPathNoPath, + ))?), + _ => { + return Err(OperationError::PrintConfig( + PrintConfigError::UnknownOption(kind), + )); } } - _ => { - return Err(OperationError::UnknownPrintConfigTopic(kind)); - } } - } + }; if matches.opt_present("version") { return Ok(Operation::Version); @@ -524,7 +534,9 @@ fn determine_operation(matches: &Matches) -> Result { // if no file argument is supplied, read from stdin if files.is_empty() { if minimal_config_path.is_some() { - return Err(OperationError::MinimalPathWithStdin); + return Err(OperationError::PrintConfig( + PrintConfigError::MinimalPathWithStdin, + )); } let mut buffer = String::new(); io::stdin().read_to_string(&mut buffer)?; diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index 886de55fd7f12..1656ea84db837 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -90,13 +90,12 @@ fn print_config_errors_minimal_with_no_path() { let (stdout, stderr, exit_code) = rustfmt(args); - // NOTE: existing the behaviour: prints an empty line - assert_eq!(stdout.trim(), ""); - assert!( - stderr.contains("PATH required"), + assert_eq!(stdout, ""); + assert_eq!( + stderr, "The `--print-config=minimal` option requires an output path to write to\n", "expected helpful error message, got: {stderr}" ); - assert_eq!(exit_code, 0) + assert_eq!(exit_code, 1) } #[test] @@ -106,8 +105,8 @@ fn print_config_errors_minimal_without_input_file() { let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(&stdout, ""); - assert!( - stderr.contains("doesn't work with standard input"), + assert_eq!( + stderr, "The `--print-config=minimal` option doesn't work with standard input.\n", "expected helpful error message, got: {stderr}" ); assert_eq!(exit_code, 1); From a7e179a2cde9698e01c06fd7ff3c479f1ca8017c Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Tue, 20 Oct 2026 18:19:43 +0100 Subject: [PATCH 38/46] Remove test that writes within the repo Because this is incompatible with how `rust-lang/rust` runs tests (see 8fdd3c21ed3df5edd211fd137b27c184d1c30ac6). But, to be safe, the `rustfmt_only_ci_test` marker is left in place, it can be removed upstream once we're sure this test really is safe to run there. --- src/tools/rustfmt/tests/rustfmt/main.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index 1656ea84db837..04ef79db920e3 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -1,12 +1,12 @@ //! Integration tests for rustfmt. use std::env; -use std::fs::{File, remove_file}; +use std::fs::File; use std::io::Write; use std::path::Path; use std::process::{Command, Stdio}; -use rustfmt_config_proc_macro::{nightly_only_test, rustfmt_only_ci_test, stable_only_test}; +use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test, rustfmt_only_ci_test}; /// Run the rustfmt executable with environment vars set and return its output. /// If `input` is given, it is piped to the executable's stdin. @@ -112,11 +112,11 @@ fn print_config_errors_minimal_without_input_file() { assert_eq!(exit_code, 1); } -// test writes to file within the repo, rust-lang/rust doesn't like that #[rustfmt_only_ci_test] #[test] fn print_config_writes_to_file() { - let out_file = "minimal.toml"; + let out_file = tempfile::tempdir().unwrap().keep().join("minimal.toml"); + let args = &[ "--config-path", Path::new("tests/config/disable_all_formatting.toml") @@ -124,18 +124,14 @@ fn print_config_writes_to_file() { .unwrap(), "--print-config", "minimal", - out_file, + out_file.to_str().unwrap(), "src/shape.rs", ]; let (stdout, stderr, exit_code) = rustfmt(args); assert_eq!(stderr, ""); - assert!( - Path::new(out_file).exists(), - "stdout:\n{stdout}\nstderr:\n{stderr}" - ); - remove_file(out_file).unwrap(); + assert!(out_file.exists(), "stdout:\n{stdout}\nstderr:\n{stderr}"); assert_eq!(exit_code, 0); } From b7e097e39fee54af413a70c44e81e7d8ed53a9f0 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 09:56:36 +0800 Subject: [PATCH 39/46] Revert "Use saturating arithmetics for `adjust_max_width`" Reverts rust-lang/rustfmt#7152. This reverts commit 677b954590cc0d5e86cb97e7db4e4b5fb6290add, reversing changes made to b4260f44f5d8cd538718b9046eb51baaa1eb8f5e. --- src/tools/rustfmt/src/config/config_type.rs | 10 ++++------ src/tools/rustfmt/src/config/mod.rs | 17 ----------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/tools/rustfmt/src/config/config_type.rs b/src/tools/rustfmt/src/config/config_type.rs index ee1ccdb437801..f1648df4e3c70 100644 --- a/src/tools/rustfmt/src/config/config_type.rs +++ b/src/tools/rustfmt/src/config/config_type.rs @@ -447,14 +447,14 @@ macro_rules! create_config { /// arms within a declarative macro. #[allow(unreachable_pub)] pub fn reduce_max_width(&mut self, delta: usize) { - self.adjust_max_width(-(delta.min(isize::MAX as usize) as isize)); + self.adjust_max_width(-(delta as isize)); } /// Increases the maximum width of the configuration. /// /// This method is intended to be used when creating a forked /// configuration for a particular formatting context in which the - /// total available width needs to be increased. This increases the + /// total available width needs to be reduced. This increases the /// size of max_width, and all other properties affected by small /// heuristics, without treating those adjustments as overrides. /// @@ -462,7 +462,7 @@ macro_rules! create_config { /// arms within a declarative macro. #[allow(unreachable_pub)] pub fn increase_max_width(&mut self, delta: usize) { - self.adjust_max_width(delta.min(isize::MAX as usize) as isize); + self.adjust_max_width(delta as isize); } /// Adjusts the maximum width of the configuration. @@ -476,9 +476,7 @@ macro_rules! create_config { /// As an example use case, this method is used when formatting /// arms within a declarative macro. fn adjust_max_width(&mut self, delta: isize) { - // NB: we need to either saturate or report an error (or some kind of testable - // warning). I picked to saturate here. - let adjust = |value: usize| value.saturating_add_signed(delta); + let adjust = |value: usize| (value as isize + delta) as usize; self.array_width.2 = adjust(self.array_width.2); self.attr_fn_like_width.2 = adjust(self.attr_fn_like_width.2); diff --git a/src/tools/rustfmt/src/config/mod.rs b/src/tools/rustfmt/src/config/mod.rs index 7ecf2ec3b58cf..0180bb89841c0 100644 --- a/src/tools/rustfmt/src/config/mod.rs +++ b/src/tools/rustfmt/src/config/mod.rs @@ -689,23 +689,6 @@ mod test { assert_eq!(config.verbose(), Verbosity::Normal); } - #[test] - fn test_adjust_max_width() { - // Regression tests for #7147, where we didn't handle overflows / integral cast outside of - // range properly. - - // Reduce with huge amount saturates to zero instead of wraparound. - let mut config = Config::default(); - config.reduce_max_width(usize::MAX); - assert_eq!(config.max_width(), 0); - - // Increase with huge amount saturates instead of wraparound. - let mut config = Config::default(); - config.set().max_width(usize::MAX - 1); - config.increase_max_width(2); - assert_eq!(config.max_width(), usize::MAX); - } - #[test] fn test_config_used_to_toml() { let config = Config::default(); From bfd66ed8eaa210054ac0d1c81c40d0b5f15c3701 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 10:05:20 +0800 Subject: [PATCH 40/46] Revert "Fixes adjustment of max_width within macros" Reverts rust-lang/rustfmt#6651 since it affects stable macro formatting, even though `format_macro_bodies` is unstable option (unstability applies only to the config, the "format macro bodies" value is stable default formatting). We generally cannot make this change on SE 2024. This reverts commit 93e78f2d3a0901718530aa871565257a41869983. --- src/tools/rustfmt/src/config/config_type.rs | 57 ------------------- src/tools/rustfmt/src/macros.rs | 6 +- .../tests/target/issue-6651/custom_width.rs | 7 +-- 3 files changed, 5 insertions(+), 65 deletions(-) diff --git a/src/tools/rustfmt/src/config/config_type.rs b/src/tools/rustfmt/src/config/config_type.rs index f1648df4e3c70..69749feb0f357 100644 --- a/src/tools/rustfmt/src/config/config_type.rs +++ b/src/tools/rustfmt/src/config/config_type.rs @@ -435,63 +435,6 @@ macro_rules! create_config { )+ } - /// Reduces the maximum width of the configuration. - /// - /// This method is intended to be used when creating a forked - /// configuration for a particular formatting context in which the - /// total available width needs to be reduced. This reduces the - /// size of max_width, and all other properties affected by small - /// heuristics, without treating those adjustments as overrides. - /// - /// As an example use case, this method is used when formatting - /// arms within a declarative macro. - #[allow(unreachable_pub)] - pub fn reduce_max_width(&mut self, delta: usize) { - self.adjust_max_width(-(delta as isize)); - } - - /// Increases the maximum width of the configuration. - /// - /// This method is intended to be used when creating a forked - /// configuration for a particular formatting context in which the - /// total available width needs to be reduced. This increases the - /// size of max_width, and all other properties affected by small - /// heuristics, without treating those adjustments as overrides. - /// - /// As an example use case, this method is used when formatting - /// arms within a declarative macro. - #[allow(unreachable_pub)] - pub fn increase_max_width(&mut self, delta: usize) { - self.adjust_max_width(delta as isize); - } - - /// Adjusts the maximum width of the configuration. - /// - /// This method is intended to be used when creating a forked - /// configuration for a particular formatting context in which the - /// total available width needs to be reduced. This adjusts the - /// size of max_width, and all other properties affected by small - /// heuristics, without treating those adjustments as overrides. - /// - /// As an example use case, this method is used when formatting - /// arms within a declarative macro. - fn adjust_max_width(&mut self, delta: isize) { - let adjust = |value: usize| (value as isize + delta) as usize; - - self.array_width.2 = adjust(self.array_width.2); - self.attr_fn_like_width.2 = adjust(self.attr_fn_like_width.2); - self.chain_width.2 = adjust(self.chain_width.2); - self.fn_call_width.2 = adjust(self.fn_call_width.2); - self.single_line_if_else_max_width.2 = adjust(self.single_line_if_else_max_width.2); - self.single_line_let_else_max_width.2 = - adjust(self.single_line_let_else_max_width.2); - self.struct_lit_width.2 = adjust(self.struct_lit_width.2); - self.struct_variant_width.2 = adjust(self.struct_variant_width.2); - - self.max_width.2 = adjust(self.max_width.2); - self.set_heuristics(); - } - fn set_width_heuristics(&mut self, heuristics: WidthHeuristics) { let max_width = self.max_width.2; let get_width_value = | diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 421094b25dfc9..d5c27400106c6 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -1365,13 +1365,15 @@ impl MacroBranch { } else { shape.indent.block_indent(&config) }; - config.reduce_max_width(body_indent.width()); + let new_width = config.max_width() - body_indent.width(); + config.set().max_width(new_width); // First try to format as items, then as statements. let new_body_snippet = match crate::format_snippet(&body_str, &config, true) { Some(new_body) => new_body, None => { - config.increase_max_width(config.tab_spaces()); + let new_width = new_width + config.tab_spaces(); + config.set().max_width(new_width); match crate::format_code_block(&body_str, &config, true) { Some(new_body) => new_body, None => { diff --git a/src/tools/rustfmt/tests/target/issue-6651/custom_width.rs b/src/tools/rustfmt/tests/target/issue-6651/custom_width.rs index dfe747871018f..6bdfb562f5386 100644 --- a/src/tools/rustfmt/tests/target/issue-6651/custom_width.rs +++ b/src/tools/rustfmt/tests/target/issue-6651/custom_width.rs @@ -16,11 +16,6 @@ macro_rules! medium { macro_rules! long { () => { - m!( - aaaaaaaaaaaaaaaa, - bbbbbbbbbbbbbbbb, - cccccccccccccccc, - ddddddddddddddd - ); + m!(aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc, ddddddddddddddd); }; } From 107a6617b4bbaf3ab9db8527889a529f8c809407 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 19:09:10 +0800 Subject: [PATCH 41/46] Prepare for merging from rust-lang/rust This updates the rust-toolchain.toml file to nightly-2026-11-02 (3e92e1a2d20b991ef23a6c6064c34d57ee8c98d3). --- src/tools/rustfmt/rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rustfmt/rust-toolchain.toml b/src/tools/rustfmt/rust-toolchain.toml index d621e4fb08484..0a85b69efd3e9 100644 --- a/src/tools/rustfmt/rust-toolchain.toml +++ b/src/tools/rustfmt/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-10-22" +channel = "nightly-2026-11-02" components = ["llvm-tools", "rustc-dev"] From 0e44319b2b5da111c94e0e152cb13d8172dad4f1 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 20:04:38 +0800 Subject: [PATCH 42/46] chore: prepare changelog for v1.12.0 --- src/tools/rustfmt/CHANGELOG.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/tools/rustfmt/CHANGELOG.md b/src/tools/rustfmt/CHANGELOG.md index 454ffc1a91cca..72b916164aefa 100644 --- a/src/tools/rustfmt/CHANGELOG.md +++ b/src/tools/rustfmt/CHANGELOG.md @@ -2,9 +2,34 @@ ## [Unreleased] -### Changed -- The unstable `required_version` configuration option now defaults to a caret (`^`) semver requirement instead of an exact match when no comparator is given, matching how Cargo treats dependency version requirements. Use the explicit `=` operator (e.g. `required_version = "=1.2.3"`) to opt back into exact matching [#6729](https://github.com/rust-lang/rustfmt/issues/6729) + +## [1.12.0] 2026-12-24 +### Changed +- The unstable `required_version` configuration option now defaults to a caret (`^`) semver requirement instead of an exact match when no comparator is given, matching how Cargo treats dependency version requirements. Use the explicit `=` operator (e.g. `required_version = "=1.2.3"`) to opt back into exact matching [#6729](https://github.com/rust-lang/rustfmt/issues/6729) [#7048](https://github.com/rust-lang/rustfmt/pull/7048) +> [!IMPORTANT] +> Potential breaking changes: +> - When `--config-path` is a directory, rustfmt now also searches its parent directories for config files [#4660](https://github.com/rust-lang/rustfmt/issues/4660) [#7115](https://github.com/rust-lang/rustfmt/pull/7115) +> - Chains with comments now format under `use_try_shorthand=true` [#6121](https://github.com/rust-lang/rustfmt/issues/6121) [#7095](https://github.com/rust-lang/rustfmt/pull/7095) +> - Error on `--print-config minimal` with no output path [#7208](https://github.com/rust-lang/rustfmt/pull/7208) +> - Previously, this printed a warning and exited with `0`. + +### Fixed +- Fix ragged statement comment wrapping under unstable option `wrap_comments=true` and `comment_width` [#7167](https://github.com/rust-lang/rustfmt/issues/7167) [#7170](https://github.com/rust-lang/rustfmt/pull/7170) +- Enforce comment width in item lists [#7174](https://github.com/rust-lang/rustfmt/pull/7174) +- Fix lookup of global config files [#7178](https://github.com/rust-lang/rustfmt/issues/7178) [#7179](https://github.com/rust-lang/rustfmt/pull/7179) +- Don't remove empty generic args when formatting in a macro context [#7136](https://github.com/rust-lang/rustfmt/issues/7136) [#7181](https://github.com/rust-lang/rustfmt/pull/7181) +- Various `cfg_select!` fixes. Note that `cfg_select!` formatting is still nightly-gated. + - Fix `cfg_select!` parser recovery handling and error count leak [#7171](https://github.com/rust-lang/rustfmt/pull/7171) + - Don't remove necessary blocks when formatting `cfg_select!` arms [#7045](https://github.com/rust-lang/rustfmt/issues/7045) [#7183](https://github.com/rust-lang/rustfmt/pull/7183) + - Prevent more comment loss in `cfg_select!` [#7209](https://github.com/rust-lang/rustfmt/issues/7209) [rust-lang/rust#164815](https://github.com/rust-lang/rust/pull/164815) + - Don't lose comments and attributes between `cfg_select!` predicates, outer/inner blocks, arm body and trailing commas [#7209](https://github.com/rust-lang/rustfmt/issues/7209) [#7211](https://github.com/rust-lang/rustfmt/pull/7211) + - Have `cfg_select!` double-brace concise formatting respect `control_brace_style` unstable option [#7214](https://github.com/rust-lang/rustfmt/pull/7214) +- Prevent rustfmt from removing expression attributes when removing nested parentheses [#7190](https://github.com/rust-lang/rustfmt/issues/7190) [#7192](https://github.com/rust-lang/rustfmt/pull/7192) +- Fix `cfg_if!` error count leaks [#7195](https://github.com/rust-lang/rustfmt/pull/7195) +- Format comments after `where` clauses using `clause_shape` budget (affecting unstable options `wrap_comments=true` and `comment_width`) [#7148](https://github.com/rust-lang/rustfmt/issues/7148) [#7156](https://github.com/rust-lang/rustfmt/pull/7156) +- Fix ICE on failure to format generics in enum [#5738](https://github.com/rust-lang/rustfmt/issues/5738) [#6137](https://github.com/rust-lang/rustfmt/issues/6137) [#6318](https://github.com/rust-lang/rustfmt/issues/6318) [#6646](https://github.com/rust-lang/rustfmt/issues/6646) [#6396](https://github.com/rust-lang/rustfmt/pull/6396) +- Avoid leaking wrapper space from doc comment code block formatting (affecting unstable options `format_code_in_doc_comments=true` and `fn_single_line=true`) [#6832](https://github.com/rust-lang/rustfmt/issues/6832) [#6851](https://github.com/rust-lang/rustfmt/pull/6851) ## [1.11.0] 2026-11-12 From 3512466d8f99c4b74a4eddcdc8f5b7af67395444 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 20:35:24 +0800 Subject: [PATCH 43/46] chore: remove changelog entry that's intended to be reverted in beta See . --- src/tools/rustfmt/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/rustfmt/CHANGELOG.md b/src/tools/rustfmt/CHANGELOG.md index 72b916164aefa..b28723aa08e5f 100644 --- a/src/tools/rustfmt/CHANGELOG.md +++ b/src/tools/rustfmt/CHANGELOG.md @@ -53,7 +53,6 @@ - Correct visibility and defaultness order on associated impl type alias [#7064](https://github.com/rust-lang/rustfmt/pull/7064). - Correctly calculate the `shape` for inline const blocks when using `style_edition=2027` [#7065](https://github.com/rust-lang/rustfmt/pull/7065). - Prevent invalid code generation in macros that use raw identifiers [#7110](https://github.com/rust-lang/rustfmt/pull/7110). -- Prevent rustfmt from reporting on `max_width` configuration issues when formatting macros [#6651](https://github.com/rust-lang/rustfmt/pull/6651). ### Changed - No longer apply `use_field_init_shorthand` when formatting macros [#6981](https://github.com/rust-lang/rustfmt/pull/6981). From f237e206cc1f11bc08750822833dec2457d0c490 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 20:05:54 +0800 Subject: [PATCH 44/46] chore: bump rustfmt version to v1.12.0 --- src/tools/rustfmt/Cargo.lock | 2 +- src/tools/rustfmt/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rustfmt/Cargo.lock b/src/tools/rustfmt/Cargo.lock index b83c8ec9612c5..38d5f7724e84d 100644 --- a/src/tools/rustfmt/Cargo.lock +++ b/src/tools/rustfmt/Cargo.lock @@ -582,7 +582,7 @@ dependencies = [ [[package]] name = "rustfmt-nightly" -version = "1.11.0" +version = "1.12.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/src/tools/rustfmt/Cargo.toml b/src/tools/rustfmt/Cargo.toml index 9e689fc0041f1..78ee1fb2742a0 100644 --- a/src/tools/rustfmt/Cargo.toml +++ b/src/tools/rustfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustfmt-nightly" -version = "1.11.0" +version = "1.12.0" description = "Tool to find and fix Rust formatting issues" repository = "https://github.com/rust-lang/rustfmt" license = "Apache-2.0 OR MIT" From 003a89309645c7edaca9ab830a2415e59893639b Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 2 Nov 2026 20:55:47 +0800 Subject: [PATCH 45/46] ci: remove no longer used `upload-assets` workflow We used to need them for standalone binaries, but we no longer do. Remove the no longer used GHA workflow. Discussed in . --- .../.github/workflows/upload-assets.yml | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 src/tools/rustfmt/.github/workflows/upload-assets.yml diff --git a/src/tools/rustfmt/.github/workflows/upload-assets.yml b/src/tools/rustfmt/.github/workflows/upload-assets.yml deleted file mode 100644 index 6d83e9b004a85..0000000000000 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: upload - -on: - push: - release: - types: [created] - workflow_dispatch: - -permissions: {} - -jobs: - build-release: - name: build-release - permissions: - contents: write # Required by `gh release upload` - strategy: - matrix: - build: [linux-x86_64, macos-x86_64, windows-x86_64-gnu, windows-x86_64-msvc] - include: - - build: linux-x86_64 - os: ubuntu-latest - rust: nightly - target: x86_64-unknown-linux-gnu - - build: macos-x86_64 - os: macos-latest - rust: nightly - target: x86_64-apple-darwin - - build: windows-x86_64-gnu - os: windows-latest - rust: nightly-x86_64-gnu - target: x86_64-pc-windows-gnu - - build: windows-x86_64-msvc - os: windows-latest - rust: nightly-x86_64-msvc - target: x86_64-pc-windows-msvc - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - # Run build - - name: install rustup - shell: bash - 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} - env: - MATRIX_TARGET: ${{ matrix.target }} - - - name: Add mingw64 to path for x86_64-gnu - run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH - if: matrix.rust == 'nightly-x86_64-gnu' - shell: bash - - - name: Build release binaries - run: cargo build --release --locked - - - name: Build archive - shell: bash - run: | - staging="rustfmt_${MATRIX_BUILD}_${GITHUB_EVENT_RELEASE_TAG_NAME}" - mkdir -p "$staging" - - cp {README.md,Configurations.md,CHANGELOG.md,LICENSE-MIT,LICENSE-APACHE} "$staging/" - - if [ "${MATRIX_OS}" = "windows-latest" ]; then - cp target/release/{rustfmt.exe,cargo-fmt.exe,rustfmt-format-diff.exe,git-rustfmt.exe} "$staging/" - 7z a "$staging.zip" "$staging" - echo "ASSET=$staging.zip" >> $GITHUB_ENV - else - cp target/release/{rustfmt,cargo-fmt,rustfmt-format-diff,git-rustfmt} "$staging/" - tar czf "$staging.tar.gz" "$staging" - echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV - fi - env: - MATRIX_OS: ${{ matrix.os }} - MATRIX_BUILD: ${{ matrix.build }} - GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - - - name: Upload Release Asset - shell: bash - if: github.event_name == 'release' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - run: gh release upload "${GITHUB_EVENT_RELEASE_TAG_NAME}" "${ASSET}" From bb2e46179de95ab458b53b9cf7a349c7ab237897 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Mon, 2 Nov 2026 20:03:43 -0500 Subject: [PATCH 46/46] chore: bump rustfmt version to v1.12.0 in the r-l/r workspace --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0b47aae128338..27ebe6d13c2c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5178,7 +5178,7 @@ dependencies = [ [[package]] name = "rustfmt-nightly" -version = "1.11.0" +version = "1.12.0" dependencies = [ "annotate-snippets 0.12.16", "anyhow",