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", diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml index b3543a49c3e9b..7f7bc62846a3c 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 @@ -60,19 +62,25 @@ jobs: working-directory: ./check_diff 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 }} + 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="" - 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 - 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 }} \ - --edition ${{ github.event.inputs.language_edition }} \ - --style-edition ${{ github.event.inputs.style_edition }} \ + 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 diff --git a/src/tools/rustfmt/.github/workflows/clippy.yml b/src/tools/rustfmt/.github/workflows/clippy.yml index a5aa91346dd84..fc3eb8916a6bd 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,10 +37,14 @@ 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 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 }} diff --git a/src/tools/rustfmt/.github/workflows/integration.yml b/src/tools/rustfmt/.github/workflows/integration.yml index 224605740e55e..e8da0d8d3aed1 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 @@ -70,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 }} 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..44d36898f216f 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' }} @@ -52,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 @@ -71,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 @@ -97,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) }} 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 991cc965e3d51..0000000000000 --- a/src/tools/rustfmt/.github/workflows/upload-assets.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: upload - -on: - push: - release: - types: [created] - workflow_dispatch: - -jobs: - build-release: - name: build-release - 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 - - # Run build - - name: install rustup - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh - sh rustup-init.sh -y --default-toolchain none - rustup target add ${{ matrix.target }} - - - name: 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 - - - name: Upload Release Asset - if: github.event_name == 'release' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release upload "${{ github.event.release.tag_name }}" "${{ env.ASSET }}" diff --git a/src/tools/rustfmt/CHANGELOG.md b/src/tools/rustfmt/CHANGELOG.md index 454ffc1a91cca..b28723aa08e5f 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 @@ -28,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). 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" 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"] diff --git a/src/tools/rustfmt/src/bin/main.rs b/src/tools/rustfmt/src/bin/main.rs index 7b003672795bd..0653ad3d93758 100644 --- a/src/tools/rustfmt/src/bin/main.rs +++ b/src/tools/rustfmt/src/bin/main.rs @@ -93,12 +93,6 @@ pub enum OperationError { /// An unknown help topic was requested. #[error("Unknown help topic: `{0}`.")] UnknownHelpTopic(String), - /// An unknown print-config option was requested. - #[error("Unknown print-config option: `{0}`.")] - UnknownPrintConfigTopic(String), - /// Attempt to generate a minimal config from standard input. - #[error("The `--print-config=minimal` option doesn't work with standard input.")] - MinimalPathWithStdin, /// An io error during reading or writing. #[error("{0}")] IoError(IoError), @@ -106,6 +100,21 @@ pub enum OperationError { /// supported with standard input. #[error("Emit mode {0} not supported with standard output.")] StdinBadEmit(EmitMode), + #[error(transparent)] + PrintConfig(PrintConfigError), +} + +// Errors related to the '--print-config' option. +#[derive(Error, Debug)] +pub enum PrintConfigError { + /// An unknown print-config option was requested. + #[error("Unknown print-config option: `{0}`.")] + UnknownOption(String), + #[error("The `--print-config=minimal` option requires an output path to write to")] + MinimalPathNoPath, + /// Attempt to generate a minimal config from standard input. + #[error("The `--print-config=minimal` option doesn't work with standard input.")] + MinimalPathWithStdin, } impl From 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/src/config/config_type.rs b/src/tools/rustfmt/src/config/config_type.rs index ee1ccdb437801..69749feb0f357 100644 --- a/src/tools/rustfmt/src/config/config_type.rs +++ b/src/tools/rustfmt/src/config/config_type.rs @@ -435,65 +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.min(isize::MAX as usize) 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 - /// 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.min(isize::MAX as usize) 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) { - // 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); - - 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/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(); 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) + } } } diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 4e82f89e437b1..d5c27400106c6 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -21,16 +21,19 @@ 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, + 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; 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::{ @@ -40,8 +43,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; @@ -1361,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 => { @@ -1622,47 +1628,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)?) - } - }; - - match arm.double_block_inner(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 . - Some(expr) => { - 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())? - .sub_width("}".len() + trailing_comma.len(), arm.span())?; - let body = expr - .rewrite_result(context, nested_shape) - .map(|rw| format!("{{{rw}}}"))?; - - Ok(format!("{predicate_str} => {body}{trailing_comma}")) - } - 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), - ), - } - }, + |arm| rewrite_cfg_select_arm(context, nested_shape, last_arm, arm), // Start Span after the opening delimiter. For example, // ``` // cfg_select! { @@ -1694,3 +1660,126 @@ 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 { + "" + }; + + // 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() + }; + + 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 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}")), + } + } + 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/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/src/parse/macros/cfg_select.rs b/src/tools/rustfmt/src/parse/macros/cfg_select.rs index 5c5963320d6f7..6db647eaabb88 100644 --- a/src/tools/rustfmt/src/parse/macros/cfg_select.rs +++ b/src/tools/rustfmt/src/parse/macros/cfg_select.rs @@ -163,38 +163,57 @@ 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, context: &RewriteContext<'_>) -> 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(..)) => { - let pre_span = b.span.shrink_to_lo().between(e.span()); - let pre_snippet = context.snippet(pre_span).trim(); - - let post_span = e.span().shrink_to_hi().between(b.span.shrink_to_hi()); - let post_snippet = context.snippet(post_span).trim(); - - if !contains_comment(pre_snippet) && !contains_comment(post_snippet) { - // To prevent comment loss between the blocks we only want to return - // the inner block if there aren't any pre / post comments. - Some(e) - } else { + 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 = outer_block.span.with_lo(inner_block.span.hi()); + if contains_comment(context.snippet(pre_sp)) + || contains_comment(context.snippet(post_sp)) + { None + } else { + Some(e) } } _ => None, 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/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 5fbf64d8862c1..04ef79db920e3 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -1,13 +1,12 @@ //! Integration tests for rustfmt. -use indoc::panicdoc; 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. @@ -16,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(); @@ -48,96 +47,142 @@ 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) } -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, 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); } -#[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, exit_code) = 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, ""); + assert_eq!(exit_code, 0) +} + +#[test] +fn print_config_errors_minimal_with_no_path() { + let args = &["--print-config", "minimal"]; + + let (stdout, stderr, exit_code) = rustfmt(args); + + 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, 1) +} - let (stdout, stderr) = rustfmt(&[ +#[test] +fn print_config_errors_minimal_without_input_file() { + let args = &["--print-config", "minimal", "minimal.toml"]; + + let (stdout, stderr, exit_code) = rustfmt(args); + + assert_eq!(&stdout, ""); + 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); +} + +#[rustfmt_only_ci_test] +#[test] +fn print_config_writes_to_file() { + let out_file = tempfile::tempdir().unwrap().keep().join("minimal.toml"); + + let args = &[ + "--config-path", + Path::new("tests/config/disable_all_formatting.toml") + .to_str() + .unwrap(), "--print-config", "minimal", - "minimal-config", + out_file.to_str().unwrap(), "src/shape.rs", - ]); + ]; + + let (stdout, stderr, exit_code) = rustfmt(args); + + assert_eq!(stderr, ""); + assert!(out_file.exists(), "stdout:\n{stdout}\nstderr:\n{stderr}"); + assert_eq!(exit_code, 0); +} + +#[test] +fn print_config_merges_override_from_args() { + let args = &[ + "--print-config", + "current", + ".", + "--config=color=Never,edition=2018", + ]; + + let (stdout, stderr, exit_code) = rustfmt(args); + + assert_eq!(stderr, ""); assert!( - Path::new("minimal-config").exists(), - "stdout:\n{stdout}\nstderr:\n{stderr}" + stdout.contains(r#"color = "Never""#) && stdout.contains(r#"edition = "2018""#), + "expected overrides in stdout, got: {stdout}", ); - remove_file("minimal-config").unwrap(); + assert_eq!(exit_code, 0); } -#[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_merges_multiple_overrides_from_args() { + let args = &[ + "--print-config", + "current", + ".", + "--config", + "color=never,edition=2018", + "--config", + "color=always,format_strings=true", + ]; + + let (stdout, stderr, exit_code) = rustfmt(args); - // 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") + 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}", ); + 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 @@ -179,13 +224,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 @@ -239,6 +285,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] @@ -253,36 +300,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] @@ -294,10 +349,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] @@ -315,8 +371,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); } } @@ -332,8 +391,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] @@ -343,9 +404,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] @@ -353,43 +416,123 @@ 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] 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 (stdout, stderr) = rustfmt(&args); + 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, exit_code) = 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" + ); + assert_eq!(exit_code, 1); } +// on unix [`std::env::home_dir`] reads the `HOME` env var. +#[cfg(unix)] #[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]; - let (stdout, stderr) = rustfmt(&args); +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, exit_code) = 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" + ); + assert_eq!(exit_code, 1); +} + +// 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, exit_code) = 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" + ); + assert_eq!(exit_code, 1); +} + +#[test] +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, exit_code) = 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, ""); + assert_eq!(exit_code, 1); } #[test] @@ -416,11 +559,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. @@ -430,8 +574,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/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..d5e289ec95b12 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg-if-error-count-leak.rs @@ -0,0 +1,12 @@ +//@ rustfmt-show_parse_errors: false + +cfg_if::cfg_if! { + if #[cfg(unix)] { + fn foo {} + } +} + +fn main() { + foo!(1 + 2); + foo!(1 + 2); +} diff --git a/src/tools/rustfmt/tests/source/cfg_select.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs similarity index 98% rename from src/tools/rustfmt/tests/source/cfg_select.rs rename to src/tools/rustfmt/tests/source/cfg_select/cfg_select.rs index ca6abc8aa4af2..d6dc391dd564f 100644 --- a/src/tools/rustfmt/tests/source/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_edge_cases.rs b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs similarity index 82% 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 index 3c591d3afd5a3..8bf6a3240a945 100644 --- a/src/tools/rustfmt/tests/source/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/source/cfg_select/cfg_select_edge_cases.rs @@ -79,6 +79,77 @@ 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 }} + } + + // 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, + } + + 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() { @@ -203,6 +274,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/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/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..a12391c20298d --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_next_line.rs @@ -0,0 +1,100 @@ +//@ 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 } + } +} + +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 + } +} + +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 new file mode 100644 index 0000000000000..cda500f61afe6 --- /dev/null +++ b/src/tools/rustfmt/tests/source/cfg_select/control_brace_style_always_same_line.rs @@ -0,0 +1,100 @@ +//@ 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 } + } +} + +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 + } +} + +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/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/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/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-if-error-count-leak.rs b/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs new file mode 100644 index 0000000000000..48ef7ddc3ba7a --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg-if-error-count-leak.rs @@ -0,0 +1,12 @@ +//@ rustfmt-show_parse_errors: false + +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_select.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs similarity index 96% rename from src/tools/rustfmt/tests/target/cfg_select.rs rename to src/tools/rustfmt/tests/target/cfg_select/cfg_select.rs index 1c2a1ed91a7e0..9d9bbfe0b4c9a 100644 --- a/src/tools/rustfmt/tests/target/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,13 +289,29 @@ 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) => {} + not(false) => + /* comment before opening brace */ + {} - any(false) => "any", + any(false) => + // comment before brace + { + "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 @@ -689,7 +706,8 @@ mod nested { true, /* comment */ true, true, // true, true, - ) => {} + ) + /* comment before arrow */ => {} not( false // comment @@ -700,13 +718,20 @@ 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) => {} + 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_edge_cases.rs b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs similarity index 80% 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 index dec05d032e316..ea6eaf9205693 100644 --- a/src/tools/rustfmt/tests/target/cfg_select_edge_cases.rs +++ b/src/tools/rustfmt/tests/target/cfg_select/cfg_select_edge_cases.rs @@ -86,10 +86,12 @@ fn cfg_select_comments_that_might_get_lost() { } } cfg_select! { - _ => {{ x }} + _ => + /* after => */ + {{ x }} } cfg_select! { - _ => {{ x }} + _ /* before arrow */ => {{ x }} } cfg_select! { @@ -109,6 +111,100 @@ 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 }} + } + + // 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, + } + + 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() { @@ -288,6 +384,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() { 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/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..d0a8880d97bd7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_next_line.rs @@ -0,0 +1,132 @@ +//@ 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 }} +} + +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 + } +} + +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 new file mode 100644 index 0000000000000..827b579509d84 --- /dev/null +++ b/src/tools/rustfmt/tests/target/cfg_select/control_brace_style_always_same_line.rs @@ -0,0 +1,127 @@ +//@ 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 }} +} + +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 + } +} + +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/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 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..4c06baa145027 --- /dev/null +++ b/src/tools/rustfmt/tests/target/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/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 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); }; } 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 {} 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-*",