Merge commit 'b23b69900eab1260be510b2bd8922f4b6de6cf1e' into sync-from-rustfmt

This commit is contained in:
Yacin Tmimi 2024-09-19 21:46:44 -04:00
commit eeda9dd070
197 changed files with 4709 additions and 1557 deletions

View File

@ -4630,7 +4630,7 @@ dependencies = [
[[package]]
name = "rustfmt-nightly"
version = "1.7.1"
version = "1.8.0"
dependencies = [
"annotate-snippets 0.9.2",
"anyhow",

View File

@ -21,7 +21,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: install rustup
run: |

View File

@ -64,7 +64,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Run build
- name: install rustup

View File

@ -26,7 +26,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Run build
- name: install rustup

View File

@ -8,7 +8,6 @@ on:
jobs:
test:
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources
# macOS Catalina 10.15
runs-on: macos-latest
name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }})
env:
@ -23,7 +22,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Run build
- name: install rustup

View File

@ -11,7 +11,7 @@ jobs:
name: rustdoc check
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: install rustup
run: |

View File

@ -31,7 +31,7 @@ jobs:
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# Run build
- name: install rustup

View File

@ -33,7 +33,7 @@ jobs:
- name: disable git eol translation
run: git config --global core.autocrlf false
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Run build
- name: Install Rustup using win.rustup.rs

View File

@ -1,6 +1,66 @@
# Changelog
## [Unreleased]
## [1.8.0] 2024-09-20
### Fixed
- Fix issue where rustfmt would crash on Windows when using the `ignore` option [#6178](https://github.com/rust-lang/rustfmt/issues/6178)
### Changed
- `rustfmt --version` now prints a commit hash that is 10 characters long [#6258](https://github.com/rust-lang/rustfmt/pull/6258)
- `rustfmt --version` will no longer print empty git information when git information isn't available at build time.
For example, git information is not available when building rustfmt from a source tarball [#6266](https://github.com/rust-lang/rustfmt/pull/6266)
- `version` has been soft deprecated and replaced by `style_edition`.
`style_edition=2024` is equivalent to `version=Two` and `style_edition={2015|2018|2021}`
are equivalent to `version=One` [#6247](https://github.com/rust-lang/rustfmt/pull/6247)
- When `style_edition=2024` is configured `overflow_delimited_expr` will default to `true` [#6260](https://github.com/rust-lang/rustfmt/pull/6260).
```rust
// with style_edition=2015
do_thing(
x,
Bar {
x: value,
y: value2,
},
);
// with style_edition=2024
do_thing(x, Bar {
x: value,
y: value2,
});
```
- When `style_edition=2024` is configured rustfmt will apply the [style guide's version sorting algorithm]
when sorting imports [#6284](https://github.com/rust-lang/rustfmt/pull/6284)
```rust
// with style_edition=2015
use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8};
// with style_edition=2024
use std::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64};
```
[style guide's version sorting algorithm]: https://doc.rust-lang.org/nightly/style-guide/#sorting
- When parsing rustfmt configurations fails, rustfmt will now include the path to the toml file in the erorr message [#6302](https://github.com/rust-lang/rustfmt/issues/6302)
### Added
- rustfmt now formats trailing where clauses in type aliases [#5887](https://github.com/rust-lang/rustfmt/pull/5887)
```rust
type Foo
= Bar
where
A: B,
C: D;
```
- Users can now configure which `style_edition` rustfmt uses when formatting their code as specified
in [RFC 3338](https://rust-lang.github.io/rfcs/3338-style-evolution.html). Users are encouraged to configure `style_edition`
in their `rustfmt.toml` files, but the value can also be specified via the cli with `--unstable-features --style-edition={style_edition}`.
When `style_edition` is not explicitly configured it will be inferred from the `edition` configuration.
When neither `style_edition` nor `edition` are configured `style_edition` defaults to `2015` [#6247](https://github.com/rust-lang/rustfmt/pull/6247)
### Misc
- Removed `tracing-attributes` dependency [#6208](https://github.com/rust-lang/rustfmt/pull/6208)
- Reduced syn's features in the internal `config_proc_macro` crate [#6237](https://github.com/rust-lang/rustfmt/pull/6237)
## [1.7.1] 2024-06-24
### Fixed
@ -238,7 +298,7 @@
### Added
- New configuration option (`skip_macro_invocations`)[https://rust-lang.github.io/rustfmt/?version=master&search=#skip_macro_invocations] [#5347](https://github.com/rust-lang/rustfmt/pull/5347) that can be used to globally define a single enumerated list of macro calls that rustfmt should skip formatting. rustfmt [currently also supports this via a custom tool attribute](https://github.com/rust-lang/rustfmt#tips), however, these cannot be used in all contexts because [custom inner attributes are unstable](https://github.com/rust-lang/rust/issues/54726)
- New configuration option [`skip_macro_invocations`](https://rust-lang.github.io/rustfmt/?version=master&search=#skip_macro_invocations) [#5347](https://github.com/rust-lang/rustfmt/pull/5347) that can be used to globally define a single enumerated list of macro calls that rustfmt should skip formatting. rustfmt [currently also supports this via a custom tool attribute](https://github.com/rust-lang/rustfmt#tips), however, these cannot be used in all contexts because [custom inner attributes are unstable](https://github.com/rust-lang/rust/issues/54726)
### Misc

View File

@ -499,7 +499,7 @@ dependencies = [
[[package]]
name = "rustfmt-nightly"
version = "1.7.1"
version = "1.8.0"
dependencies = [
"annotate-snippets",
"anyhow",
@ -710,21 +710,9 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.31"

View File

@ -1,7 +1,7 @@
[package]
name = "rustfmt-nightly"
version = "1.7.1"
version = "1.8.0"
description = "Tool to find and fix Rust formatting issues"
repository = "https://github.com/rust-lang/rustfmt"
readme = "README.md"
@ -50,7 +50,7 @@ serde_json = "1.0"
term = "0.7"
thiserror = "1.0.40"
toml = "0.7.4"
tracing = "0.1.37"
tracing = { version = "0.1.37", default-features = false, features = ["std"] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
unicode-segmentation = "1.9"
unicode-width = "0.1"

View File

@ -534,7 +534,7 @@ Note that this option may be soft-deprecated in the future once the [ignore](#ig
Specifies which edition is used by the parser.
- **Default value**: `"2015"`
- **Possible values**: `"2015"`, `"2018"`, `"2021"`
- **Possible values**: `"2015"`, `"2018"`, `"2021"`, `"2024"`
- **Stable**: Yes
Rustfmt is able to pick up the edition used by reading the `Cargo.toml` file if executed
@ -2692,6 +2692,17 @@ By default this option is set as a percentage of [`max_width`](#max_width) provi
See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics)
## `style_edition`
Controls the edition of the [Rust Style Guide] to use for formatting ([RFC 3338])
- **Default value**: `"2015"`
- **Possible values**: `"2015"`, `"2018"`, `"2021"`, `"2024"` (unstable variant)
- **Stable**: No
[Rust Style Guide]: https://doc.rust-lang.org/nightly/style-guide/
[RFC 3338]: https://rust-lang.github.io/rfcs/3338-style-evolution.html
## `tab_spaces`
Number of spaces per tab
@ -3051,9 +3062,7 @@ fn main() {
## `version`
Which version of the formatting rules to use. `Version::One` is backwards-compatible
with Rustfmt 1.0. Other versions are only backwards compatible within a major
version number.
This option is deprecated and has been replaced by [`style_edition`](#style_edition)
- **Default value**: `One`
- **Possible values**: `One`, `Two`

View File

@ -109,17 +109,17 @@ If you want to test modified `cargo-fmt`, or run `rustfmt` on the whole project
RUSTFMT="./target/debug/rustfmt" cargo run --bin cargo-fmt -- --manifest-path path/to/project/you/want2test/Cargo.toml
```
### Version-gate formatting changes
### Gate formatting changes
A change that introduces a different code-formatting should be gated on the
`version` configuration. This is to ensure the formatting of the current major
release is preserved, while allowing fixes to be implemented for the next
release.
A change that introduces a different code-formatting must be gated on the
`style_edition` configuration. This is to ensure rustfmt upholds its formatting
stability guarantees and adheres to the Style Edition process set in [RFC 3338]
This is done by conditionally guarding the change like so:
This can be done by conditionally guarding the formatting change, e.g.:
```rust
if config.version() == Version::One { // if the current major release is 1.x
// if the current stable Style Edition is Edition 2024
if config.style_edition() <= StyleEdition::Edition2024 {
// current formatting
} else {
// new formatting
@ -129,13 +129,14 @@ if config.version() == Version::One { // if the current major release is 1.x
This allows the user to apply the next formatting explicitly via the
configuration, while being stable by default.
When the next major release is done, the code block of the previous formatting
can be deleted, e.g., the first block in the example above when going from `1.x`
to `2.x`.
This can then be enhanced as needed if and when there are
new Style Editions with differing formatting prescriptions.
| Note: Only formatting changes with default options need to be gated. |
| --- |
[RFC 3338]: https://rust-lang.github.io/rfcs/3338-style-evolution.html
### A quick tour of Rustfmt
Rustfmt is basically a pretty printer - that is, its mode of operation is to

View File

@ -25,7 +25,7 @@ fn main() {
// (git not installed or if this is not a git repository) just return an empty string.
fn commit_info() -> String {
match (channel(), commit_hash(), commit_date()) {
(channel, Some(hash), Some(date)) => format!("{} ({} {})", channel, hash.trim_end(), date),
(channel, Some(hash), Some(date)) => format!("{} ({} {})", channel, hash, date),
_ => String::new(),
}
}
@ -39,17 +39,20 @@ fn channel() -> String {
}
fn commit_hash() -> Option<String> {
Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
let output = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.and_then(|r| String::from_utf8(r.stdout).ok())
.ok()?;
let mut stdout = output.status.success().then_some(output.stdout)?;
stdout.truncate(10);
String::from_utf8(stdout).ok()
}
fn commit_date() -> Option<String> {
Command::new("git")
let output = Command::new("git")
.args(["log", "-1", "--date=short", "--pretty=format:%cd"])
.output()
.ok()
.and_then(|r| String::from_utf8(r.stdout).ok())
.ok()?;
let stdout = output.status.success().then_some(output.stdout)?;
String::from_utf8(stdout).ok()
}

View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.14"
@ -51,11 +60,26 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "check_diff"
version = "0.1.0"
dependencies = [
"clap",
"tempfile",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -104,6 +128,22 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "heck"
version = "0.5.0"
@ -116,6 +156,73 @@ version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "proc-macro2"
version = "1.0.83"
@ -134,6 +241,78 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "strsim"
version = "0.11.1"
@ -151,6 +330,89 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
@ -163,6 +425,34 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"

View File

@ -7,3 +7,7 @@ edition = "2021"
[dependencies]
clap = { version = "4.4.2", features = ["derive"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
[dev-dependencies]
tempfile = "3"

View File

@ -0,0 +1,58 @@
use std::env;
use std::io;
use std::path::Path;
use std::process::Command;
use tracing::info;
pub enum GitError {
FailedClone { stdout: Vec<u8>, stderr: Vec<u8> },
IO(std::io::Error),
}
impl From<io::Error> for GitError {
fn from(error: io::Error) -> Self {
GitError::IO(error)
}
}
/// Clone a git repository
///
/// Parameters:
/// url: git clone url
/// dest: directory where the repo should be cloned
pub fn clone_git_repo(url: &str, dest: &Path) -> Result<(), GitError> {
let git_cmd = Command::new("git")
.env("GIT_TERMINAL_PROMPT", "0")
.args([
"clone",
"--quiet",
url,
"--depth",
"1",
dest.to_str().unwrap(),
])
.output()?;
// if the git command does not return successfully,
// any command on the repo will fail. So fail fast.
if !git_cmd.status.success() {
let error = GitError::FailedClone {
stdout: git_cmd.stdout,
stderr: git_cmd.stderr,
};
return Err(error);
}
info!("Successfully clone repository.");
return Ok(());
}
pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
let dest_path = Path::new(&dest);
env::set_current_dir(&dest_path)?;
info!(
"Current directory: {}",
env::current_dir().unwrap().display()
);
return Ok(());
}

View File

@ -1,4 +1,5 @@
use clap::Parser;
/// Inputs for the check_diff script
#[derive(Parser)]
struct CliInputs {
@ -16,10 +17,5 @@ struct CliInputs {
}
fn main() {
let args = CliInputs::parse();
println!(
"remote_repo_url: {:?}, feature_branch: {:?},
optional_commit_hash: {:?}, optional_rustfmt_config: {:?}",
args.remote_repo_url, args.feature_branch, args.commit_hash, args.rustfmt_config
);
let _args = CliInputs::parse();
}

View File

@ -0,0 +1,12 @@
use check_diff::change_directory_to_path;
use std::env;
use tempfile::Builder;
#[test]
fn cd_test() {
// Creates an empty directory in the current working directory
let dir = Builder::new().tempdir_in("").unwrap();
let dest_path = dir.path();
change_directory_to_path(dest_path).unwrap();
assert_eq!(env::current_dir().unwrap(), dest_path);
}

View File

@ -0,0 +1,16 @@
use check_diff::clone_git_repo;
use tempfile::Builder;
#[test]
fn clone_repo_test() {
// Creates an empty directory in the current working directory
let dir = Builder::new().tempdir_in("").unwrap();
let sample_repo = "https://github.com/rust-lang/rustfmt.git";
let dest_path = dir.path();
let result = clone_git_repo(sample_repo, dest_path);
assert!(result.is_ok());
// check whether a .git folder exists after cloning the repo
let git_repo = dest_path.join(".git");
assert!(git_repo.exists());
}

View File

@ -13,7 +13,13 @@ if "%CFG_RELEASE_CHANNEL%"=="nightly" (
)
cargo test || exit /b 1
:: Build and test other crates
:: Build and test config_proc_macro
cd config_proc_macro || exit /b 1
cargo build --locked || exit /b 1
cargo test || exit /b 1
:: Build and test check_diff
cd ..
cd check_diff || exit /b 1
cargo build --locked || exit /b 1
cargo test || exit /b 1

View File

@ -17,7 +17,13 @@ else
fi
cargo test
# Build and test other crates
# Build and test config_proc_macro
cd config_proc_macro
cargo build --locked
cargo test
# Build and test check_diff
cd ..
cd check_diff
cargo build --locked
cargo test

View File

@ -1,7 +1,7 @@
[package]
name = "rustfmt-config_proc_macro"
version = "0.3.0"
edition = "2018"
edition = "2021"
description = "A collection of procedural macros for rustfmt"
license = "Apache-2.0 OR MIT"
categories = ["development-tools::procedural-macro-helpers"]
@ -13,7 +13,7 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full", "visit"] }
syn = { version = "2.0", default-features = false, features = ["full", "parsing", "proc-macro", "printing"] }
[dev-dependencies]
serde = { version = "1.0.160", features = ["derive"] }

View File

@ -1,5 +1,5 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use quote::{ToTokens, quote};
pub fn fold_quote<F, I, T>(input: impl Iterator<Item = I>, f: F) -> TokenStream
where

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2024-06-13"
channel = "nightly-2024-09-10"
components = ["llvm-tools", "rustc-dev"]

View File

@ -1,3 +1,4 @@
error_on_line_overflow = true
error_on_unformatted = true
version = "Two"
style_edition = "2024"
overflow_delimited_expr = false

View File

@ -1,21 +1,21 @@
//! Format attributes and meta items.
use rustc_ast::ast;
use rustc_ast::HasAttrs;
use rustc_span::{symbol::sym, Span};
use rustc_ast::ast;
use rustc_span::{Span, symbol::sym};
use tracing::debug;
use self::doc_comment::DocCommentFormatter;
use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle};
use crate::config::lists::*;
use crate::comment::{CommentStyle, contains_comment, rewrite_doc_comment};
use crate::config::IndentStyle;
use crate::config::lists::*;
use crate::expr::rewrite_literal;
use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator};
use crate::lists::{ListFormatting, Separator, definitive_tactic, itemize_list, write_list};
use crate::overflow;
use crate::rewrite::{Rewrite, RewriteContext};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::types::{rewrite_path, PathContext};
use crate::types::{PathContext, rewrite_path};
use crate::utils::{count_newlines, mk_sp};
mod doc_comment;
@ -100,7 +100,7 @@ fn format_derive(
",",
|span| span.lo(),
|span| span.hi(),
|span| Some(context.snippet(*span).to_owned()),
|span| Ok(context.snippet(*span).to_owned()),
// We update derive attribute spans to start after the opening '('
// This helps us focus parsing to just what's inside #[derive(...)]
context.snippet_provider.span_after(attr.span, "("),
@ -148,7 +148,7 @@ fn format_derive(
.tactic(tactic)
.trailing_separator(trailing_separator)
.ends_with_newline(false);
let item_str = write_list(&all_items, &fmt)?;
let item_str = write_list(&all_items, &fmt).ok()?;
debug!("item_str: '{}'", item_str);
@ -218,9 +218,9 @@ fn rewrite_initial_doc_comments(
context: &RewriteContext<'_>,
attrs: &[ast::Attribute],
shape: Shape,
) -> Option<(usize, Option<String>)> {
) -> Result<(usize, Option<String>), RewriteError> {
if attrs.is_empty() {
return Some((0, None));
return Ok((0, None));
}
// Rewrite doc comments
let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_doc_comment());
@ -230,7 +230,7 @@ fn rewrite_initial_doc_comments(
.map(|a| context.snippet(a.span))
.collect::<Vec<_>>()
.join("\n");
return Some((
return Ok((
sugared_docs.len(),
Some(rewrite_doc_comment(
&snippet,
@ -240,13 +240,19 @@ fn rewrite_initial_doc_comments(
));
}
Some((0, None))
Ok((0, None))
}
impl Rewrite for ast::NestedMetaItem {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match self {
ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
ast::NestedMetaItem::MetaItem(ref meta_item) => {
meta_item.rewrite_result(context, shape)
}
ast::NestedMetaItem::Lit(ref l) => {
rewrite_literal(context, l.as_token_lit(), l.span, shape)
}
@ -275,7 +281,11 @@ fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
impl Rewrite for ast::MetaItem {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
Some(match self.kind {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
Ok(match self.kind {
ast::MetaItemKind::Word => {
rewrite_path(context, PathContext::Type, &None, &self.path, shape)?
}
@ -287,7 +297,7 @@ impl Rewrite for ast::MetaItem {
&path,
list.iter(),
// 1 = "]"
shape.sub_width(1)?,
shape.sub_width(1).max_width_error(shape.width, self.span)?,
self.span,
context.config.attr_fn_like_width(),
Some(if has_trailing_comma {
@ -300,7 +310,9 @@ impl Rewrite for ast::MetaItem {
ast::MetaItemKind::NameValue(ref lit) => {
let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?;
// 3 = ` = `
let lit_shape = shape.shrink_left(path.len() + 3)?;
let lit_shape = shape
.shrink_left(path.len() + 3)
.max_width_error(shape.width, self.span)?;
// `rewrite_literal` returns `None` when `lit` exceeds max
// width. Since a literal is basically unformattable unless it
// is a string literal (and only if `format_strings` is set),
@ -308,7 +320,7 @@ impl Rewrite for ast::MetaItem {
// is longer than the max width and continue on formatting.
// See #2479 for example.
let value = rewrite_literal(context, lit.as_token_lit(), lit.span, lit_shape)
.unwrap_or_else(|| context.snippet(lit.span).to_owned());
.unwrap_or_else(|_| context.snippet(lit.span).to_owned());
format!("{path} = {value}")
}
})
@ -317,6 +329,10 @@ impl Rewrite for ast::MetaItem {
impl Rewrite for ast::Attribute {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
let snippet = context.snippet(self.span);
if self.is_doc_comment() {
rewrite_doc_comment(snippet, shape.comment(context.config), context.config)
@ -328,7 +344,7 @@ impl Rewrite for ast::Attribute {
let prefix = attr_prefix(self);
if should_skip || contains_comment(snippet) {
return Some(snippet.to_owned());
return Ok(snippet.to_owned());
}
if let Some(ref meta) = self.meta() {
@ -353,9 +369,11 @@ impl Rewrite for ast::Attribute {
}
// 1 = `[`
let shape = shape.offset_left(prefix.len() + 1)?;
Some(meta.rewrite(context, shape).map_or_else(
|| snippet.to_owned(),
let shape = shape
.offset_left(prefix.len() + 1)
.max_width_error(shape.width, self.span)?;
Ok(meta.rewrite_result(context, shape).map_or_else(
|_| snippet.to_owned(),
|rw| match &self.kind {
ast::AttrKind::Normal(normal_attr) => match normal_attr.item.unsafety {
// For #![feature(unsafe_attributes)]
@ -367,7 +385,7 @@ impl Rewrite for ast::Attribute {
},
))
} else {
Some(snippet.to_owned())
Ok(snippet.to_owned())
}
}
}
@ -375,8 +393,12 @@ impl Rewrite for ast::Attribute {
impl Rewrite for [ast::Attribute] {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
if self.is_empty() {
return Some(String::new());
return Ok(String::new());
}
// The current remaining attributes.
@ -392,7 +414,7 @@ impl Rewrite for [ast::Attribute] {
// merging derives into a single attribute.
loop {
if attrs.is_empty() {
return Some(result);
return Ok(result);
}
// Handle doc comments.
@ -431,7 +453,7 @@ impl Rewrite for [ast::Attribute] {
// Handle derives if we will merge them.
if !skip_derives && context.config.merge_derives() && is_derive(&attrs[0]) {
let derives = take_while_with_pred(context, attrs, is_derive);
let derive_str = format_derive(derives, shape, context)?;
let derive_str = format_derive(derives, shape, context).unknown_error()?;
result.push_str(&derive_str);
let missing_span = attrs
@ -464,7 +486,7 @@ impl Rewrite for [ast::Attribute] {
// If we get here, then we have a regular attribute, just handle one
// at a time.
let formatted_attr = attrs[0].rewrite(context, shape)?;
let formatted_attr = attrs[0].rewrite_result(context, shape)?;
result.push_str(&formatted_attr);
let missing_span = attrs

View File

@ -1,6 +1,6 @@
#![feature(rustc_private)]
use anyhow::{format_err, Result};
use anyhow::{Result, format_err};
use io::Error as IoError;
use thiserror::Error;
@ -11,15 +11,15 @@ use tracing_subscriber::EnvFilter;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::{self, stdout, Read, Write};
use std::io::{self, Read, Write, stdout};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use getopts::{Matches, Options};
use crate::rustfmt::{
load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
FormatReportFormatterBuilder, Input, Session, Verbosity,
CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
FormatReportFormatterBuilder, Input, Session, StyleEdition, Verbosity, Version, load_config,
};
const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rustfmt/issues/new?labels=bug";
@ -129,7 +129,12 @@ fn make_opts() -> Options {
found reverts to the input file path",
"[Path for the configuration file]",
);
opts.optopt("", "edition", "Rust edition to use", "[2015|2018|2021]");
opts.optopt(
"",
"edition",
"Rust edition to use",
"[2015|2018|2021|2024]",
);
opts.optopt(
"",
"color",
@ -181,6 +186,12 @@ fn make_opts() -> Options {
"skip-children",
"Don't reformat child modules (unstable).",
);
opts.optopt(
"",
"style-edition",
"The edition of the Style Guide (unstable).",
"[2015|2018|2021|2024]",
);
}
opts.optflag("v", "verbose", "Print verbose output");
@ -263,24 +274,35 @@ fn format_string(input: String, options: GetOptsOptions) -> Result<i32> {
let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
if options.check {
config.set().emit_mode(EmitMode::Diff);
config.set_cli().emit_mode(EmitMode::Diff);
} else {
match options.emit_mode {
// Emit modes which work with standard input
// None means default, which is Stdout.
None | Some(EmitMode::Stdout) | Some(EmitMode::Checkstyle) | Some(EmitMode::Json) => {}
Some(emit_mode) => {
return Err(OperationError::StdinBadEmit(emit_mode).into());
}
}
None => {
config
.set()
.emit_mode(options.emit_mode.unwrap_or(EmitMode::Stdout));
}
Some(EmitMode::Stdout) | Some(EmitMode::Checkstyle) | Some(EmitMode::Json) => {
config
.set_cli()
.emit_mode(options.emit_mode.unwrap_or(EmitMode::Stdout));
}
Some(emit_mode) => {
return Err(OperationError::StdinBadEmit(emit_mode).into());
}
}
}
config.set().verbose(Verbosity::Quiet);
// parse file_lines
if options.file_lines.is_all() {
config.set().file_lines(options.file_lines);
} else {
config.set_cli().file_lines(options.file_lines);
}
for f in config.file_lines().files() {
match *f {
FileName::Stdin => {}
@ -319,10 +341,10 @@ fn format(
for file in files {
if !file.exists() {
eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
eprintln!("Error: file `{}` does not exist", file.display());
session.add_operational_error();
} else if file.is_dir() {
eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
eprintln!("Error: `{}` is a directory", file.display());
session.add_operational_error();
} else {
// Check the file directory if the config-path could not be read or not provided
@ -428,27 +450,27 @@ are included as out of line modules from `src/lib.rs`."
}
fn print_version() {
let version_info = format!(
"{}-{}",
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
);
let version_number = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
let commit_info = include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"));
println!("rustfmt {version_info}");
if commit_info.is_empty() {
println!("rustfmt {version_number}");
} else {
println!("rustfmt {version_number}-{commit_info}");
}
}
fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
if matches.opt_present("h") {
let topic = matches.opt_str("h");
if topic.is_none() {
let Some(topic) = matches.opt_str("h") else {
return Ok(Operation::Help(HelpOp::None));
} else if topic == Some("config".to_owned()) {
return Ok(Operation::Help(HelpOp::Config));
} else if topic == Some("file-lines".to_owned()) && is_nightly() {
return Ok(Operation::Help(HelpOp::FileLines));
} else {
return Err(OperationError::UnknownHelpTopic(topic.unwrap()));
}
};
return match topic.as_str() {
"config" => Ok(Operation::Help(HelpOp::Config)),
"file-lines" if is_nightly() => Ok(Operation::Help(HelpOp::FileLines)),
_ => Err(OperationError::UnknownHelpTopic(topic)),
};
}
let mut free_matches = matches.free.iter();
@ -514,6 +536,7 @@ struct GetOptsOptions {
backup: bool,
check: bool,
edition: Option<Edition>,
style_edition: Option<StyleEdition>,
color: Option<Color>,
file_lines: FileLines, // Default is all lines in all files.
unstable_features: bool,
@ -545,6 +568,10 @@ impl GetOptsOptions {
if let Some(ref file_lines) = matches.opt_str("file-lines") {
options.file_lines = file_lines.parse()?;
}
if let Some(ref edition_str) = matches.opt_str("style-edition") {
options.style_edition =
Some(style_edition_from_style_edition_str(edition_str)?);
}
} else {
let mut unstable_options = vec![];
if matches.opt_present("skip-children") {
@ -556,6 +583,9 @@ impl GetOptsOptions {
if matches.opt_present("file-lines") {
unstable_options.push("`--file-lines`");
}
if matches.opt_present("style-edition") {
unstable_options.push("`--style-edition`");
}
if !unstable_options.is_empty() {
let s = if unstable_options.len() == 1 { "" } else { "s" };
return Err(format_err!(
@ -650,36 +680,49 @@ impl GetOptsOptions {
impl CliOptions for GetOptsOptions {
fn apply_to(self, config: &mut Config) {
if self.verbose {
config.set().verbose(Verbosity::Verbose);
config.set_cli().verbose(Verbosity::Verbose);
} else if self.quiet {
config.set().verbose(Verbosity::Quiet);
config.set_cli().verbose(Verbosity::Quiet);
} else {
config.set().verbose(Verbosity::Normal);
}
if self.file_lines.is_all() {
config.set().file_lines(self.file_lines);
} else {
config.set_cli().file_lines(self.file_lines);
}
if self.unstable_features {
config.set_cli().unstable_features(self.unstable_features);
} else {
config.set().unstable_features(self.unstable_features);
}
if let Some(skip_children) = self.skip_children {
config.set().skip_children(skip_children);
config.set_cli().skip_children(skip_children);
}
if let Some(error_on_unformatted) = self.error_on_unformatted {
config.set().error_on_unformatted(error_on_unformatted);
config.set_cli().error_on_unformatted(error_on_unformatted);
}
if let Some(edition) = self.edition {
config.set().edition(edition);
config.set_cli().edition(edition);
}
if let Some(edition) = self.style_edition {
config.set_cli().style_edition(edition);
}
if self.check {
config.set().emit_mode(EmitMode::Diff);
config.set_cli().emit_mode(EmitMode::Diff);
} else if let Some(emit_mode) = self.emit_mode {
config.set().emit_mode(emit_mode);
config.set_cli().emit_mode(emit_mode);
}
if self.backup {
config.set().make_backup(true);
config.set_cli().make_backup(true);
}
if let Some(color) = self.color {
config.set().color(color);
config.set_cli().color(color);
}
if self.print_misformatted_file_names {
config.set().print_misformatted_file_names(true);
config.set_cli().print_misformatted_file_names(true);
}
for (key, val) in self.inline_config {
@ -690,6 +733,25 @@ impl CliOptions for GetOptsOptions {
fn config_path(&self) -> Option<&Path> {
self.config_path.as_deref()
}
fn edition(&self) -> Option<Edition> {
self.inline_config
.get("edition")
.map_or(self.edition, |e| Edition::from_str(e).ok())
}
fn style_edition(&self) -> Option<StyleEdition> {
self.inline_config
.get("style_edition")
.map_or(self.style_edition, |se| StyleEdition::from_str(se).ok())
}
fn version(&self) -> Option<Version> {
self.inline_config
.get("version")
.map(|version| Version::from_str(version).ok())
.flatten()
}
}
fn edition_from_edition_str(edition_str: &str) -> Result<Edition> {
@ -702,6 +764,16 @@ fn edition_from_edition_str(edition_str: &str) -> Result<Edition> {
}
}
fn style_edition_from_style_edition_str(edition_str: &str) -> Result<StyleEdition> {
match edition_str {
"2015" => Ok(StyleEdition::Edition2015),
"2018" => Ok(StyleEdition::Edition2018),
"2021" => Ok(StyleEdition::Edition2021),
"2024" => Ok(StyleEdition::Edition2024),
_ => Err(format_err!("Invalid value for `--style-edition`")),
}
}
fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode> {
match emit_str {
"files" => Ok(EmitMode::Files),
@ -712,3 +784,185 @@ fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode> {
_ => Err(format_err!("Invalid value for `--emit`")),
}
}
#[cfg(test)]
#[allow(dead_code)]
mod test {
use super::*;
use rustfmt_config_proc_macro::nightly_only_test;
fn get_config<O: CliOptions>(path: Option<&Path>, options: Option<O>) -> Config {
load_config(path, options).unwrap().0
}
#[nightly_only_test]
#[test]
fn flag_sets_style_edition_override_correctly() {
let mut options = GetOptsOptions::default();
options.style_edition = Some(StyleEdition::Edition2024);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
}
#[nightly_only_test]
#[test]
fn edition_sets_style_edition_override_correctly() {
let mut options = GetOptsOptions::default();
options.edition = Some(Edition::Edition2024);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
}
#[nightly_only_test]
#[test]
fn version_sets_style_edition_override_correctly() {
let mut options = GetOptsOptions::default();
options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
assert_eq!(config.overflow_delimited_expr(), true);
}
#[nightly_only_test]
#[test]
fn version_config_file_sets_style_edition_override_correctly() {
let options = GetOptsOptions::default();
let config_file = Some(Path::new("tests/config/style-edition/just-version"));
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
assert_eq!(config.overflow_delimited_expr(), true);
}
#[nightly_only_test]
#[test]
fn style_edition_flag_has_correct_precedence_over_edition() {
let mut options = GetOptsOptions::default();
options.style_edition = Some(StyleEdition::Edition2021);
options.edition = Some(Edition::Edition2024);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2021);
}
#[nightly_only_test]
#[test]
fn style_edition_flag_has_correct_precedence_over_version() {
let mut options = GetOptsOptions::default();
options.style_edition = Some(StyleEdition::Edition2018);
options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2018);
}
#[nightly_only_test]
#[test]
fn style_edition_flag_has_correct_precedence_over_edition_version() {
let mut options = GetOptsOptions::default();
options.style_edition = Some(StyleEdition::Edition2021);
options.edition = Some(Edition::Edition2018);
options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2021);
}
#[nightly_only_test]
#[test]
fn style_edition_inline_has_correct_precedence_over_edition_version() {
let mut options = GetOptsOptions::default();
options.edition = Some(Edition::Edition2018);
options.inline_config = HashMap::from([
("version".to_owned(), "One".to_owned()),
("style_edition".to_owned(), "2024".to_owned()),
]);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
assert_eq!(config.overflow_delimited_expr(), true);
}
#[nightly_only_test]
#[test]
fn style_edition_config_file_trumps_edition_flag_version_inline() {
let mut options = GetOptsOptions::default();
let config_file = Some(Path::new("tests/config/style-edition/just-style-edition"));
options.edition = Some(Edition::Edition2018);
options.inline_config = HashMap::from([("version".to_owned(), "One".to_owned())]);
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
}
#[nightly_only_test]
#[test]
fn style_edition_config_file_trumps_edition_config_and_version_inline() {
let mut options = GetOptsOptions::default();
let config_file = Some(Path::new(
"tests/config/style-edition/style-edition-and-edition",
));
options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2021);
assert_eq!(config.edition(), Edition::Edition2024);
}
#[nightly_only_test]
#[test]
fn version_config_trumps_edition_config_and_flag() {
let mut options = GetOptsOptions::default();
let config_file = Some(Path::new("tests/config/style-edition/version-edition"));
options.edition = Some(Edition::Edition2018);
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
}
#[nightly_only_test]
#[test]
fn style_edition_config_file_trumps_version_config() {
let options = GetOptsOptions::default();
let config_file = Some(Path::new(
"tests/config/style-edition/version-style-edition",
));
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2021);
}
#[nightly_only_test]
#[test]
fn style_edition_config_file_trumps_edition_version_config() {
let options = GetOptsOptions::default();
let config_file = Some(Path::new(
"tests/config/style-edition/version-style-edition-and-edition",
));
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2021);
}
#[nightly_only_test]
#[test]
fn correct_defaults_for_style_edition_loaded() {
let mut options = GetOptsOptions::default();
options.style_edition = Some(StyleEdition::Edition2024);
let config = get_config(None, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
assert_eq!(config.overflow_delimited_expr(), true);
}
#[nightly_only_test]
#[test]
fn style_edition_defaults_overridden_from_config() {
let options = GetOptsOptions::default();
let config_file = Some(Path::new("tests/config/style-edition/overrides"));
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
assert_eq!(config.overflow_delimited_expr(), false);
}
#[nightly_only_test]
#[test]
fn style_edition_defaults_overridden_from_cli() {
let mut options = GetOptsOptions::default();
let config_file = Some(Path::new("tests/config/style-edition/just-style-edition"));
options.inline_config =
HashMap::from([("overflow_delimited_expr".to_owned(), "false".to_owned())]);
let config = get_config(config_file, Some(options));
assert_eq!(config.style_edition(), StyleEdition::Edition2024);
assert_eq!(config.overflow_delimited_expr(), false);
}
}

View File

@ -59,15 +59,15 @@ use std::borrow::Cow;
use std::cmp::min;
use rustc_ast::{ast, ptr};
use rustc_span::{symbol, BytePos, Span};
use rustc_span::{BytePos, Span, symbol};
use tracing::debug;
use crate::comment::{rewrite_comment, CharClasses, FullCodeCharKind, RichChar};
use crate::config::{IndentStyle, Version};
use crate::comment::{CharClasses, FullCodeCharKind, RichChar, rewrite_comment};
use crate::config::{IndentStyle, StyleEdition};
use crate::expr::rewrite_call;
use crate::lists::extract_pre_comment;
use crate::macros::convert_try_mac;
use crate::rewrite::{Rewrite, RewriteContext};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::utils::{
@ -80,6 +80,9 @@ use thin_vec::ThinVec;
/// Provides the original input contents from the span
/// of a chain element with trailing spaces trimmed.
fn format_overflow_style(span: Span, context: &RewriteContext<'_>) -> Option<String> {
// TODO(ding-young): Currently returning None when the given span is out of the range
// covered by the snippet provider. If this is a common cause for internal
// rewrite failure, add a new enum variant and return RewriteError instead of None
context.snippet_provider.span_to_snippet(span).map(|s| {
s.lines()
.map(|l| l.trim_end())
@ -93,12 +96,16 @@ fn format_chain_item(
context: &RewriteContext<'_>,
rewrite_shape: Shape,
allow_overflow: bool,
) -> Option<String> {
) -> RewriteResult {
if allow_overflow {
item.rewrite(context, rewrite_shape)
.or_else(|| format_overflow_style(item.span, context))
// TODO(ding-young): Consider calling format_overflow_style()
// only when item.rewrite_result() returns RewriteError::ExceedsMaxWidth.
// It may be inappropriate to call format_overflow_style on other RewriteError
// since the current approach retries formatting if allow_overflow is true
item.rewrite_result(context, rewrite_shape)
.or_else(|_| format_overflow_style(item.span, context).unknown_error())
} else {
item.rewrite(context, rewrite_shape)
item.rewrite_result(context, rewrite_shape)
}
}
@ -135,17 +142,17 @@ pub(crate) fn rewrite_chain(
expr: &ast::Expr,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let chain = Chain::from_ast(expr, context);
debug!("rewrite_chain {:?} {:?}", chain, shape);
// If this is just an expression with some `?`s, then format it trivially and
// return early.
if chain.children.is_empty() {
return chain.parent.rewrite(context, shape);
return chain.parent.rewrite_result(context, shape);
}
chain.rewrite(context, shape)
chain.rewrite_result(context, shape)
}
#[derive(Debug)]
@ -203,7 +210,7 @@ impl ChainItemKind {
fn is_tup_field_access(expr: &ast::Expr) -> bool {
match expr.kind {
ast::ExprKind::Field(_, ref field) => {
field.name.to_string().chars().all(|c| c.is_digit(10))
field.name.as_str().chars().all(|c| c.is_digit(10))
}
_ => false,
}
@ -269,7 +276,13 @@ impl ChainItemKind {
impl Rewrite for ChainItem {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
let shape = shape.sub_width(self.tries)?;
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
let shape = shape
.sub_width(self.tries)
.max_width_error(shape.width, self.span)?;
let rewrite = match self.kind {
ChainItemKind::Parent {
ref expr,
@ -278,14 +291,14 @@ impl Rewrite for ChainItem {
ChainItemKind::Parent {
ref expr,
parens: false,
} => expr.rewrite(context, shape)?,
} => expr.rewrite_result(context, shape)?,
ChainItemKind::MethodCall(ref segment, ref types, ref exprs) => {
Self::rewrite_method_call(segment.ident, types, exprs, self.span, context, shape)?
}
ChainItemKind::StructField(ident) => format!(".{}", rewrite_ident(context, ident)),
ChainItemKind::TupleField(ident, nested) => format!(
"{}.{}",
if nested && context.config.version() == Version::One {
if nested && context.config.style_edition() <= StyleEdition::Edition2021 {
" "
} else {
""
@ -297,7 +310,7 @@ impl Rewrite for ChainItem {
rewrite_comment(comment, false, shape, context.config)?
}
};
Some(format!("{rewrite}{}", "?".repeat(self.tries)))
Ok(format!("{rewrite}{}", "?".repeat(self.tries)))
}
}
@ -327,14 +340,14 @@ impl ChainItem {
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let type_str = if types.is_empty() {
String::new()
} else {
let type_list = types
.iter()
.map(|ty| ty.rewrite(context, shape))
.collect::<Option<Vec<_>>>()?;
.map(|ty| ty.rewrite_result(context, shape))
.collect::<Result<Vec<_>, RewriteError>>()?;
format!("::<{}>", type_list.join(", "))
};
@ -519,6 +532,10 @@ impl Chain {
impl Rewrite for Chain {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
debug!("rewrite chain {:?} {:?}", self, shape);
let mut formatter = match context.config.indent_style() {
@ -532,17 +549,25 @@ impl Rewrite for Chain {
formatter.format_root(&self.parent, context, shape)?;
if let Some(result) = formatter.pure_root() {
return wrap_str(result, context.config.max_width(), shape);
return wrap_str(result, context.config.max_width(), shape)
.max_width_error(shape.width, self.parent.span);
}
let first = self.children.first().unwrap_or(&self.parent);
let last = self.children.last().unwrap_or(&self.parent);
let children_span = mk_sp(first.span.lo(), last.span.hi());
let full_span = self.parent.span.with_hi(children_span.hi());
// Decide how to layout the rest of the chain.
let child_shape = formatter.child_shape(context, shape)?;
let child_shape = formatter
.child_shape(context, shape)
.max_width_error(shape.width, children_span)?;
formatter.format_children(context, child_shape)?;
formatter.format_last_child(context, shape, child_shape)?;
let result = formatter.join_rewrites(context, child_shape)?;
wrap_str(result, context.config.max_width(), shape)
wrap_str(result, context.config.max_width(), shape).max_width_error(shape.width, full_span)
}
}
@ -564,16 +589,20 @@ trait ChainFormatter {
parent: &ChainItem,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<()>;
) -> Result<(), RewriteError>;
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape>;
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()>;
fn format_children(
&mut self,
context: &RewriteContext<'_>,
child_shape: Shape,
) -> Result<(), RewriteError>;
fn format_last_child(
&mut self,
context: &RewriteContext<'_>,
shape: Shape,
child_shape: Shape,
) -> Option<()>;
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String>;
) -> Result<(), RewriteError>;
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> RewriteResult;
// Returns `Some` if the chain is only a root, None otherwise.
fn pure_root(&mut self) -> Option<String>;
}
@ -616,12 +645,16 @@ impl<'a> ChainFormatterShared<'a> {
}
}
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
fn format_children(
&mut self,
context: &RewriteContext<'_>,
child_shape: Shape,
) -> Result<(), RewriteError> {
for item in &self.children[..self.children.len() - 1] {
let rewrite = format_chain_item(item, context, child_shape, self.allow_overflow)?;
self.rewrites.push(rewrite);
}
Some(())
Ok(())
}
// Rewrite the last child. The last child of a chain requires special treatment. We need to
@ -662,8 +695,8 @@ impl<'a> ChainFormatterShared<'a> {
context: &RewriteContext<'_>,
shape: Shape,
child_shape: Shape,
) -> Option<()> {
let last = self.children.last()?;
) -> Result<(), RewriteError> {
let last = self.children.last().unknown_error()?;
let extendable = may_extend && last_line_extendable(&self.rewrites[0]);
let prev_last_line_width = last_line_width(&self.rewrites[0]);
@ -687,11 +720,17 @@ impl<'a> ChainFormatterShared<'a> {
&& self.rewrites.iter().all(|s| !s.contains('\n'))
&& one_line_budget > 0;
let last_shape = if all_in_one_line {
shape.sub_width(last.tries)?
shape
.sub_width(last.tries)
.max_width_error(shape.width, last.span)?
} else if extendable {
child_shape.sub_width(last.tries)?
child_shape
.sub_width(last.tries)
.max_width_error(child_shape.width, last.span)?
} else {
child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)?
child_shape
.sub_width(shape.rhs_overhead(context.config) + last.tries)
.max_width_error(child_shape.width, last.span)?
};
let mut last_subexpr_str = None;
@ -707,7 +746,7 @@ impl<'a> ChainFormatterShared<'a> {
};
if let Some(one_line_shape) = one_line_shape {
if let Some(rw) = last.rewrite(context, one_line_shape) {
if let Ok(rw) = last.rewrite_result(context, one_line_shape) {
// We allow overflowing here only if both of the following conditions match:
// 1. The entire chain fits in a single line except the last child.
// 2. `last_child_str.lines().count() >= 5`.
@ -722,17 +761,18 @@ impl<'a> ChainFormatterShared<'a> {
// last child on its own line, and compare two rewrites to choose which is
// better.
let last_shape = child_shape
.sub_width(shape.rhs_overhead(context.config) + last.tries)?;
match last.rewrite(context, last_shape) {
Some(ref new_rw) if !could_fit_single_line => {
.sub_width(shape.rhs_overhead(context.config) + last.tries)
.max_width_error(child_shape.width, last.span)?;
match last.rewrite_result(context, last_shape) {
Ok(ref new_rw) if !could_fit_single_line => {
last_subexpr_str = Some(new_rw.clone());
}
Some(ref new_rw) if new_rw.lines().count() >= line_count => {
Ok(ref new_rw) if new_rw.lines().count() >= line_count => {
last_subexpr_str = Some(rw);
self.fits_single_line = could_fit_single_line && all_in_one_line;
}
new_rw @ Some(..) => {
last_subexpr_str = new_rw;
Ok(new_rw) => {
last_subexpr_str = Some(new_rw);
}
_ => {
last_subexpr_str = Some(rw);
@ -747,22 +787,28 @@ impl<'a> ChainFormatterShared<'a> {
let last_shape = if context.use_block_indent() {
last_shape
} else {
child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)?
child_shape
.sub_width(shape.rhs_overhead(context.config) + last.tries)
.max_width_error(child_shape.width, last.span)?
};
last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite(context, last_shape));
self.rewrites.push(last_subexpr_str?);
Some(())
let last_subexpr_str =
last_subexpr_str.unwrap_or(last.rewrite_result(context, last_shape)?);
self.rewrites.push(last_subexpr_str);
Ok(())
}
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String> {
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> RewriteResult {
let connector = if self.fits_single_line {
// Yay, we can put everything on one line.
Cow::from("")
} else {
// Use new lines.
if context.force_one_line_chain.get() {
return None;
return Err(RewriteError::ExceedsMaxWidth {
configured_width: child_shape.width,
span: self.children.last().unknown_error()?.span,
});
}
child_shape.to_string_with_newline(context.config)
};
@ -781,7 +827,7 @@ impl<'a> ChainFormatterShared<'a> {
result.push_str(rewrite);
}
Some(result)
Ok(result)
}
}
@ -806,8 +852,8 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
parent: &ChainItem,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<()> {
let mut root_rewrite: String = parent.rewrite(context, shape)?;
) -> Result<(), RewriteError> {
let mut root_rewrite: String = parent.rewrite_result(context, shape)?;
let mut root_ends_with_block = parent.kind.is_block_like(context, &root_rewrite);
let tab_width = context.config.tab_spaces().saturating_sub(shape.offset);
@ -817,10 +863,12 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
if let ChainItemKind::Comment(..) = item.kind {
break;
}
let shape = shape.offset_left(root_rewrite.len())?;
match &item.rewrite(context, shape) {
Some(rewrite) => root_rewrite.push_str(rewrite),
None => break,
let shape = shape
.offset_left(root_rewrite.len())
.max_width_error(shape.width, item.span)?;
match &item.rewrite_result(context, shape) {
Ok(rewrite) => root_rewrite.push_str(rewrite),
Err(_) => break,
}
root_ends_with_block = last_line_extendable(&root_rewrite);
@ -832,7 +880,7 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
}
self.shared.rewrites.push(root_rewrite);
self.root_ends_with_block = root_ends_with_block;
Some(())
Ok(())
}
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
@ -840,7 +888,11 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
Some(get_block_child_shape(block_end, context, shape))
}
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
fn format_children(
&mut self,
context: &RewriteContext<'_>,
child_shape: Shape,
) -> Result<(), RewriteError> {
self.shared.format_children(context, child_shape)
}
@ -849,12 +901,12 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
context: &RewriteContext<'_>,
shape: Shape,
child_shape: Shape,
) -> Option<()> {
) -> Result<(), RewriteError> {
self.shared
.format_last_child(true, context, shape, child_shape)
}
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String> {
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> RewriteResult {
self.shared.join_rewrites(context, child_shape)
}
@ -885,9 +937,9 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
parent: &ChainItem,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<()> {
) -> Result<(), RewriteError> {
let parent_shape = shape.visual_indent(0);
let mut root_rewrite = parent.rewrite(context, parent_shape)?;
let mut root_rewrite = parent.rewrite_result(context, parent_shape)?;
let multiline = root_rewrite.contains('\n');
self.offset = if multiline {
last_line_width(&root_rewrite).saturating_sub(shape.used_width())
@ -899,18 +951,19 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
let item = &self.shared.children[0];
if let ChainItemKind::Comment(..) = item.kind {
self.shared.rewrites.push(root_rewrite);
return Some(());
return Ok(());
}
let child_shape = parent_shape
.visual_indent(self.offset)
.sub_width(self.offset)?;
let rewrite = item.rewrite(context, child_shape)?;
.sub_width(self.offset)
.max_width_error(parent_shape.width, item.span)?;
let rewrite = item.rewrite_result(context, child_shape)?;
if filtered_str_fits(&rewrite, context.config.max_width(), shape) {
root_rewrite.push_str(&rewrite);
} else {
// We couldn't fit in at the visual indent, try the last
// indent.
let rewrite = item.rewrite(context, parent_shape)?;
let rewrite = item.rewrite_result(context, parent_shape)?;
root_rewrite.push_str(&rewrite);
self.offset = 0;
}
@ -919,7 +972,7 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
}
self.shared.rewrites.push(root_rewrite);
Some(())
Ok(())
}
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
@ -932,7 +985,11 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
)
}
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
fn format_children(
&mut self,
context: &RewriteContext<'_>,
child_shape: Shape,
) -> Result<(), RewriteError> {
self.shared.format_children(context, child_shape)
}
@ -941,12 +998,12 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
context: &RewriteContext<'_>,
shape: Shape,
child_shape: Shape,
) -> Option<()> {
) -> Result<(), RewriteError> {
self.shared
.format_last_child(false, context, shape, child_shape)
}
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String> {
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> RewriteResult {
self.shared.join_rewrites(context, child_shape)
}

View File

@ -4,17 +4,17 @@ use thin_vec::thin_vec;
use tracing::debug;
use crate::attr::get_attrs_from_stmt;
use crate::config::StyleEdition;
use crate::config::lists::*;
use crate::config::Version;
use crate::expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond};
use crate::items::{span_hi_for_param, span_lo_for_param};
use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator};
use crate::lists::{ListFormatting, Separator, definitive_tactic, itemize_list, write_list};
use crate::overflow::OverflowableItem;
use crate::rewrite::{Rewrite, RewriteContext};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::types::rewrite_bound_params;
use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt};
use crate::utils::{NodeIdExt, last_line_width, left_most_sub_expr, stmt_expr};
// This module is pretty messy because of the rules around closures and blocks:
// FIXME - the below is probably no longer true in full.
@ -37,7 +37,7 @@ pub(crate) fn rewrite_closure(
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
debug!("rewrite_closure {:?}", body);
let (prefix, extra_offset) = rewrite_closure_fn_decl(
@ -53,13 +53,15 @@ pub(crate) fn rewrite_closure(
shape,
)?;
// 1 = space between `|...|` and body.
let body_shape = shape.offset_left(extra_offset)?;
let body_shape = shape
.offset_left(extra_offset)
.max_width_error(shape.width, span)?;
if let ast::ExprKind::Block(ref block, _) = body.kind {
// The body of the closure is an empty block.
if block.stmts.is_empty() && !block_contains_comment(context, block) {
return body
.rewrite(context, shape)
.rewrite_result(context, shape)
.map(|s| format!("{} {}", prefix, s));
}
@ -67,15 +69,15 @@ pub(crate) fn rewrite_closure(
ast::FnRetTy::Default(_) if !context.inside_macro() => {
try_rewrite_without_block(body, &prefix, context, shape, body_shape)
}
_ => None,
_ => Err(RewriteError::Unknown),
};
result.or_else(|| {
result.or_else(|_| {
// Either we require a block, or tried without and failed.
rewrite_closure_block(block, &prefix, context, body_shape)
})
} else {
rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| {
rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|_| {
// The closure originally had a non-block expression, but we can't fit on
// one line, so we'll insert a block.
rewrite_closure_with_block(body, &prefix, context, body_shape)
@ -89,7 +91,7 @@ fn try_rewrite_without_block(
context: &RewriteContext<'_>,
shape: Shape,
body_shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let expr = get_inner_expr(expr, prefix, context);
if is_block_closure_forced(context, expr) {
@ -153,11 +155,11 @@ fn rewrite_closure_with_block(
prefix: &str,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let left_most = left_most_sub_expr(body);
let veto_block = veto_block(body) && !expr_requires_semi_to_be_stmt(left_most);
if veto_block {
return None;
return Err(RewriteError::Unknown);
}
let block = ast::Block {
@ -185,7 +187,7 @@ fn rewrite_closure_with_block(
shape,
false,
)?;
Some(format!("{prefix} {block}"))
Ok(format!("{prefix} {block}"))
}
// Rewrite closure with a single expression without wrapping its body with block.
@ -194,7 +196,7 @@ fn rewrite_closure_expr(
prefix: &str,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
fn allow_multi_line(expr: &ast::Expr) -> bool {
match expr.kind {
ast::ExprKind::Match(..)
@ -217,12 +219,12 @@ fn rewrite_closure_expr(
// unless it is a block-like expression or we are inside macro call.
let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro())
|| context.config.force_multiline_blocks();
expr.rewrite(context, shape)
expr.rewrite_result(context, shape)
.and_then(|rw| {
if veto_multiline && rw.contains('\n') {
None
Err(RewriteError::Unknown)
} else {
Some(rw)
Ok(rw)
}
})
.map(|rw| format!("{} {}", prefix, rw))
@ -234,8 +236,12 @@ fn rewrite_closure_block(
prefix: &str,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
Some(format!("{} {}", prefix, block.rewrite(context, shape)?))
) -> RewriteResult {
Ok(format!(
"{} {}",
prefix,
block.rewrite_result(context, shape)?
))
}
// Return type is (prefix, extra_offset)
@ -250,13 +256,14 @@ fn rewrite_closure_fn_decl(
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<(String, usize)> {
) -> Result<(String, usize), RewriteError> {
let binder = match binder {
ast::ClosureBinder::For { generic_params, .. } if generic_params.is_empty() => {
"for<> ".to_owned()
}
ast::ClosureBinder::For { generic_params, .. } => {
let lifetime_str = rewrite_bound_params(context, shape, generic_params)?;
let lifetime_str =
rewrite_bound_params(context, shape, generic_params).unknown_error()?;
format!("for<{lifetime_str}> ")
}
ast::ClosureBinder::NotPresent => "".to_owned(),
@ -287,13 +294,17 @@ fn rewrite_closure_fn_decl(
// 4 = "|| {".len(), which is overconservative when the closure consists of
// a single expression.
let nested_shape = shape
.shrink_left(binder.len() + const_.len() + immovable.len() + coro.len() + mover.len())?
.sub_width(4)?;
.shrink_left(binder.len() + const_.len() + immovable.len() + coro.len() + mover.len())
.and_then(|shape| shape.sub_width(4))
.max_width_error(shape.width, span)?;
// 1 = |
let param_offset = nested_shape.indent + 1;
let param_shape = nested_shape.offset_left(1)?.visual_indent(0);
let ret_str = fn_decl.output.rewrite(context, param_shape)?;
let param_shape = nested_shape
.offset_left(1)
.max_width_error(nested_shape.width, span)?
.visual_indent(0);
let ret_str = fn_decl.output.rewrite_result(context, param_shape)?;
let param_items = itemize_list(
context.snippet_provider,
@ -302,7 +313,7 @@ fn rewrite_closure_fn_decl(
",",
|param| span_lo_for_param(param),
|param| span_hi_for_param(context, param),
|param| param.rewrite(context, param_shape),
|param| param.rewrite_result(context, param_shape),
context.snippet_provider.span_after(span, "|"),
body.span.lo(),
false,
@ -317,7 +328,9 @@ fn rewrite_closure_fn_decl(
horizontal_budget,
);
let param_shape = match tactic {
DefinitiveListTactic::Horizontal => param_shape.sub_width(ret_str.len() + 1)?,
DefinitiveListTactic::Horizontal => param_shape
.sub_width(ret_str.len() + 1)
.max_width_error(param_shape.width, span)?,
_ => param_shape,
};
@ -339,7 +352,7 @@ fn rewrite_closure_fn_decl(
// 1 = space between `|...|` and body.
let extra_offset = last_line_width(&prefix) + 1;
Some((prefix, extra_offset))
Ok((prefix, extra_offset))
}
// Rewriting closure which is placed at the end of the function call's arg.
@ -348,7 +361,7 @@ pub(crate) fn rewrite_last_closure(
context: &RewriteContext<'_>,
expr: &ast::Expr,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
if let ast::ExprKind::Closure(ref closure) = expr.kind {
let ast::Closure {
ref binder,
@ -385,10 +398,12 @@ pub(crate) fn rewrite_last_closure(
)?;
// If the closure goes multi line before its body, do not overflow the closure.
if prefix.contains('\n') {
return None;
return Err(RewriteError::Unknown);
}
let body_shape = shape.offset_left(extra_offset)?;
let body_shape = shape
.offset_left(extra_offset)
.max_width_error(shape.width, expr.span)?;
// We force to use block for the body of the closure for certain kinds of expressions.
if is_block_closure_forced(context, body) {
@ -400,7 +415,7 @@ pub(crate) fn rewrite_last_closure(
// closure. However, if the closure has a return type, then we must
// keep the blocks.
match rewrite_closure_expr(body, &prefix, context, shape) {
Some(single_line_body_str)
Ok(single_line_body_str)
if !single_line_body_str.contains('\n') =>
{
single_line_body_str
@ -424,9 +439,9 @@ pub(crate) fn rewrite_last_closure(
}
// Seems fine, just format the closure in usual manner.
return expr.rewrite(context, shape);
return expr.rewrite_result(context, shape);
}
None
Err(RewriteError::Unknown)
}
/// Returns `true` if the given vector of arguments has more than one `ast::ExprKind::Closure`.
@ -443,18 +458,18 @@ fn is_block_closure_forced(context: &RewriteContext<'_>, expr: &ast::Expr) -> bo
if context.inside_macro() {
false
} else {
is_block_closure_forced_inner(expr, context.config.version())
is_block_closure_forced_inner(expr, context.config.style_edition())
}
}
fn is_block_closure_forced_inner(expr: &ast::Expr, version: Version) -> bool {
fn is_block_closure_forced_inner(expr: &ast::Expr, style_edition: StyleEdition) -> bool {
match expr.kind {
ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop { .. } => true,
ast::ExprKind::Loop(..) if version == Version::Two => true,
ast::ExprKind::Loop(..) if style_edition >= StyleEdition::Edition2024 => true,
ast::ExprKind::AddrOf(_, _, ref expr)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr, version),
| ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr, style_edition),
_ => false,
}
}

View File

@ -2,14 +2,14 @@
use std::{borrow::Cow, iter};
use itertools::{multipeek, MultiPeek};
use itertools::{Itertools as _, MultiPeek, multipeek};
use rustc_span::Span;
use tracing::{debug, trace};
use crate::config::Config;
use crate::rewrite::RewriteContext;
use crate::rewrite::{RewriteContext, RewriteErrorExt, RewriteResult};
use crate::shape::{Indent, Shape};
use crate::string::{rewrite_string, StringFormat};
use crate::string::{StringFormat, rewrite_string};
use crate::utils::{
count_newlines, first_line_width, last_line_width, trim_left_preserve_layout,
trimmed_last_line_width, unicode_str_width,
@ -158,7 +158,7 @@ pub(crate) fn combine_strs_with_missing_comments(
span: Span,
shape: Shape,
allow_extend: bool,
) -> Option<String> {
) -> RewriteResult {
trace!(
"combine_strs_with_missing_comments `{}` `{}` {:?} {:?}",
prev_str, next_str, span, shape
@ -188,7 +188,7 @@ pub(crate) fn combine_strs_with_missing_comments(
result.push_str(&indent.to_string_with_newline(config))
}
result.push_str(next_str);
return Some(result);
return Ok(result);
}
// We have a missing comment between the first expression and the second expression.
@ -233,10 +233,10 @@ pub(crate) fn combine_strs_with_missing_comments(
result.push_str(&second_sep);
result.push_str(next_str);
Some(result)
Ok(result)
}
pub(crate) fn rewrite_doc_comment(orig: &str, shape: Shape, config: &Config) -> Option<String> {
pub(crate) fn rewrite_doc_comment(orig: &str, shape: Shape, config: &Config) -> RewriteResult {
identify_comment(orig, false, shape, config, true)
}
@ -245,7 +245,7 @@ pub(crate) fn rewrite_comment(
block_style: bool,
shape: Shape,
config: &Config,
) -> Option<String> {
) -> RewriteResult {
identify_comment(orig, block_style, shape, config, false)
}
@ -255,7 +255,7 @@ fn identify_comment(
shape: Shape,
config: &Config,
is_doc_comment: bool,
) -> Option<String> {
) -> RewriteResult {
let style = comment_style(orig, false);
// Computes the byte length of line taking into account a newline if the line is part of a
@ -347,7 +347,7 @@ fn identify_comment(
let (first_group, rest) = orig.split_at(first_group_ending);
let rewritten_first_group =
if !config.normalize_comments() && has_bare_lines && style.is_block_comment() {
trim_left_preserve_layout(first_group, shape.indent, config)?
trim_left_preserve_layout(first_group, shape.indent, config).unknown_error()?
} else if !config.normalize_comments()
&& !config.wrap_comments()
&& !(
@ -368,7 +368,7 @@ fn identify_comment(
)?
};
if rest.is_empty() {
Some(rewritten_first_group)
Ok(rewritten_first_group)
} else {
identify_comment(
rest.trim_start(),
@ -534,10 +534,11 @@ impl ItemizedBlock {
/// Returns the block as a string, with each line trimmed at the start.
fn trimmed_block_as_string(&self) -> String {
self.lines
.iter()
.map(|line| format!("{} ", line.trim_start()))
.collect::<String>()
self.lines.iter().fold(String::new(), |mut acc, line| {
acc.push_str(line.trim_start());
acc.push(' ');
acc
})
}
/// Returns the block as a string under its original form.
@ -900,7 +901,7 @@ fn rewrite_comment_inner(
shape: Shape,
config: &Config,
is_doc_comment: bool,
) -> Option<String> {
) -> RewriteResult {
let mut rewriter = CommentRewrite::new(orig, block_style, shape, config);
let line_breaks = count_newlines(orig.trim_end());
@ -934,7 +935,7 @@ fn rewrite_comment_inner(
}
}
Some(rewriter.finish())
Ok(rewriter.finish())
}
const RUSTFMT_CUSTOM_COMMENT_PREFIX: &str = "//#### ";
@ -999,7 +1000,7 @@ pub(crate) fn rewrite_missing_comment(
span: Span,
shape: Shape,
context: &RewriteContext<'_>,
) -> Option<String> {
) -> RewriteResult {
let missing_snippet = context.snippet(span);
let trimmed_snippet = missing_snippet.trim();
// check the span starts with a comment
@ -1007,7 +1008,7 @@ pub(crate) fn rewrite_missing_comment(
if !trimmed_snippet.is_empty() && pos.is_some() {
rewrite_comment(trimmed_snippet, false, shape, context.config)
} else {
Some(String::new())
Ok(String::new())
}
}
@ -1019,13 +1020,13 @@ pub(crate) fn recover_missing_comment_in_span(
shape: Shape,
context: &RewriteContext<'_>,
used_width: usize,
) -> Option<String> {
) -> RewriteResult {
let missing_comment = rewrite_missing_comment(span, shape, context)?;
if missing_comment.is_empty() {
Some(String::new())
Ok(String::new())
} else {
let missing_snippet = context.snippet(span);
let pos = missing_snippet.find('/')?;
let pos = missing_snippet.find('/').unknown_error()?;
// 1 = ` `
let total_width = missing_comment.len() + used_width + 1;
let force_new_line_before_comment =
@ -1035,7 +1036,7 @@ pub(crate) fn recover_missing_comment_in_span(
} else {
Cow::from(" ")
};
Some(format!("{sep}{missing_comment}"))
Ok(format!("{sep}{missing_comment}"))
}
}
@ -1055,8 +1056,7 @@ fn light_rewrite_comment(
config: &Config,
is_doc_comment: bool,
) -> String {
let lines: Vec<&str> = orig
.lines()
orig.lines()
.map(|l| {
// This is basically just l.trim(), but in the case that a line starts
// with `*` we want to leave one space before it, so it aligns with the
@ -1074,8 +1074,7 @@ fn light_rewrite_comment(
// Preserve markdown's double-space line break syntax in doc comment.
trim_end_unless_two_whitespaces(left_trimmed, is_doc_comment)
})
.collect();
lines.join(&format!("\n{}", offset.to_string(config)))
.join(&format!("\n{}", offset.to_string(config)))
}
/// Trims comment characters and possibly a single space from the left of a string.
@ -1704,12 +1703,11 @@ impl<'a> Iterator for CommentCodeSlices<'a> {
}
/// Checks is `new` didn't miss any comment from `span`, if it removed any, return previous text
/// (if it fits in the width/offset, else return `None`), else return `new`
pub(crate) fn recover_comment_removed(
new: String,
span: Span,
context: &RewriteContext<'_>,
) -> Option<String> {
) -> String {
let snippet = context.snippet(span);
if snippet != new && changed_comment_content(snippet, &new) {
// We missed some comments. Warn and keep the original text.
@ -1723,9 +1721,9 @@ pub(crate) fn recover_comment_removed(
)],
);
}
Some(snippet.to_owned())
snippet.to_owned()
} else {
Some(new)
new
}
}

View File

@ -70,15 +70,15 @@ macro_rules! create_config {
//
// - $i: the ident name of the option
// - $ty: the type of the option value
// - $def: the default value of the option
// - $stb: true if the option is stable
// - $dstring: description of the option
($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
($($i:ident: $ty:ty, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
#[cfg(test)]
use std::collections::HashSet;
use std::io::Write;
use serde::{Deserialize, Serialize};
use $crate::config::style_edition::StyleEditionDefault;
#[derive(Clone)]
#[allow(unreachable_pub)]
@ -89,7 +89,10 @@ macro_rules! create_config {
// - 1: true if the option was manually initialized
// - 2: the option value
// - 3: true if the option is unstable
$($i: (Cell<bool>, bool, $ty, bool)),+
// - 4: true if the option was set manually from a CLI flag
// FIXME: 4 is probably unnecessary and duplicative
// https://github.com/rust-lang/rustfmt/issues/6252
$($i: (Cell<bool>, bool, <$ty as StyleEditionDefault>::ConfigType, bool, bool)),+
}
// Just like the Config struct but with each property wrapped
@ -100,7 +103,7 @@ macro_rules! create_config {
#[derive(Deserialize, Serialize, Clone)]
#[allow(unreachable_pub)]
pub struct PartialConfig {
$(pub $i: Option<$ty>),+
$(pub $i: Option<<$ty as StyleEditionDefault>::ConfigType>),+
}
// Macro hygiene won't allow us to make `set_$i()` methods on Config
@ -114,7 +117,7 @@ macro_rules! create_config {
impl<'a> ConfigSetter<'a> {
$(
#[allow(unreachable_pub)]
pub fn $i(&mut self, value: $ty) {
pub fn $i(&mut self, value: <$ty as StyleEditionDefault>::ConfigType) {
(self.0).$i.2 = value;
match stringify!($i) {
"max_width"
@ -130,6 +133,37 @@ macro_rules! create_config {
"merge_imports" => self.0.set_merge_imports(),
"fn_args_layout" => self.0.set_fn_args_layout(),
"hide_parse_errors" => self.0.set_hide_parse_errors(),
"version" => self.0.set_version(),
&_ => (),
}
}
)+
}
#[allow(unreachable_pub)]
pub struct CliConfigSetter<'a>(&'a mut Config);
impl<'a> CliConfigSetter<'a> {
$(
#[allow(unreachable_pub)]
pub fn $i(&mut self, value: <$ty as StyleEditionDefault>::ConfigType) {
(self.0).$i.2 = value;
(self.0).$i.4 = true;
match stringify!($i) {
"max_width"
| "use_small_heuristics"
| "fn_call_width"
| "single_line_if_else_max_width"
| "single_line_let_else_max_width"
| "attr_fn_like_width"
| "struct_lit_width"
| "struct_variant_width"
| "array_width"
| "chain_width" => self.0.set_heuristics(),
"merge_imports" => self.0.set_merge_imports(),
"fn_args_layout" => self.0.set_fn_args_layout(),
"hide_parse_errors" => self.0.set_hide_parse_errors(),
"version" => self.0.set_version(),
&_ => (),
}
}
@ -150,25 +184,66 @@ macro_rules! create_config {
)+
}
// Query each option, returns true if the user set the option via a CLI flag,
// false if a default was used.
#[allow(unreachable_pub)]
pub struct CliConfigWasSet<'a>(&'a Config);
impl<'a> CliConfigWasSet<'a> {
$(
#[allow(unreachable_pub)]
pub fn $i(&self) -> bool {
(self.0).$i.4
}
)+
}
impl Config {
$(
#[allow(unreachable_pub)]
pub fn $i(&self) -> $ty {
pub fn $i(&self) -> <$ty as StyleEditionDefault>::ConfigType {
self.$i.0.set(true);
self.$i.2.clone()
}
)+
#[allow(unreachable_pub)]
pub(super) fn default_with_style_edition(style_edition: StyleEdition) -> Config {
Config {
$(
$i: (
Cell::new(false),
false,
<$ty as StyleEditionDefault>::style_edition_default(
style_edition
),
$stb,
false,
),
)+
}
}
#[allow(unreachable_pub)]
pub fn set(&mut self) -> ConfigSetter<'_> {
ConfigSetter(self)
}
#[allow(unreachable_pub)]
pub fn set_cli(&mut self) -> CliConfigSetter<'_> {
CliConfigSetter(self)
}
#[allow(unreachable_pub)]
pub fn was_set(&self) -> ConfigWasSet<'_> {
ConfigWasSet(self)
}
#[allow(unreachable_pub)]
pub fn was_set_cli(&self) -> CliConfigWasSet<'_> {
CliConfigWasSet(self)
}
fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
$(
if let Some(option_value) = parsed.$i {
@ -186,6 +261,7 @@ macro_rules! create_config {
self.set_merge_imports();
self.set_fn_args_layout();
self.set_hide_parse_errors();
self.set_version();
self
}
@ -212,7 +288,9 @@ macro_rules! create_config {
pub fn is_valid_key_val(key: &str, val: &str) -> bool {
match key {
$(
stringify!($i) => val.parse::<$ty>().is_ok(),
stringify!($i) => {
val.parse::<<$ty as StyleEditionDefault>::ConfigType>().is_ok()
}
)+
_ => false,
}
@ -246,11 +324,15 @@ macro_rules! create_config {
match key {
$(
stringify!($i) => {
let option_value = val.parse::<$ty>()
.expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
let value = val.parse::<<$ty as StyleEditionDefault>::ConfigType>()
.expect(
&format!(
"Failed to parse override for {} (\"{}\") as a {}",
stringify!($i),
val,
stringify!($ty)));
stringify!(<$ty as StyleEditionDefault>::ConfigType)
)
);
// Users are currently allowed to set unstable
// options/variants via the `--config` options override.
@ -261,7 +343,7 @@ macro_rules! create_config {
// For now, do not validate whether the option or value is stable,
// just always set it.
self.$i.1 = true;
self.$i.2 = option_value;
self.$i.2 = value;
}
)+
_ => panic!("Unknown config key in override: {}", key)
@ -281,6 +363,7 @@ macro_rules! create_config {
"merge_imports" => self.set_merge_imports(),
"fn_args_layout" => self.set_fn_args_layout(),
"hide_parse_errors" => self.set_hide_parse_errors(),
"version" => self.set_version(),
&_ => (),
}
}
@ -301,6 +384,7 @@ macro_rules! create_config {
#[allow(unreachable_pub)]
pub fn print_docs(out: &mut dyn Write, include_unstable: bool) {
let style_edition = StyleEdition::Edition2015;
use std::cmp;
let max = 0;
$( let max = cmp::max(max, stringify!($i).len()+1); )+
@ -317,14 +401,17 @@ macro_rules! create_config {
}
name_out.push_str(name_raw);
name_out.push(' ');
let mut default_str = format!("{}", $def);
let default_value = <$ty as StyleEditionDefault>::style_edition_default(
style_edition
);
let mut default_str = format!("{}", default_value);
if default_str.is_empty() {
default_str = String::from("\"\"");
}
writeln!(out,
"{}{} Default: {}{}",
name_out,
<$ty>::doc_hint(),
<<$ty as StyleEditionDefault>::ConfigType>::doc_hint(),
default_str,
if !$stb { " (unstable)" } else { "" }).unwrap();
$(
@ -477,12 +564,36 @@ macro_rules! create_config {
}
}
fn set_version(&mut self) {
if !self.was_set().version() {
return;
}
eprintln!(
"Warning: the `version` option is deprecated. \
Use `style_edition` instead."
);
if self.was_set().style_edition() || self.was_set_cli().style_edition() {
eprintln!(
"Warning: the deprecated `version` option was \
used in conjunction with the `style_edition` \
option which takes precedence. \
The value of the `version` option will be ignored."
);
}
}
#[allow(unreachable_pub)]
/// Returns `true` if the config key was explicitly set and is the default value.
pub fn is_default(&self, key: &str) -> bool {
let style_edition = StyleEdition::Edition2015;
$(
let default_value = <$ty as StyleEditionDefault>::style_edition_default(
style_edition
);
if let stringify!($i) = key {
return self.$i.1 && self.$i.2 == $def;
return self.$i.1 && self.$i.2 == default_value;
}
)+
false
@ -492,11 +603,7 @@ macro_rules! create_config {
// Template for the default configuration
impl Default for Config {
fn default() -> Config {
Config {
$(
$i: (Cell::new(false), false, $def, $stb),
)+
}
Config::default_with_style_edition(StyleEdition::Edition2015)
}
}
)

View File

@ -7,7 +7,7 @@ use std::{cmp, fmt, iter, str};
use rustc_data_structures::sync::Lrc;
use rustc_span::SourceFile;
use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer, ser};
use serde_json as json;
use thiserror::Error;
@ -38,7 +38,7 @@ impl From<rustc_span::FileName> for FileName {
impl fmt::Display for FileName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
FileName::Real(p) => write!(f, "{}", p.display()),
FileName::Stdin => write!(f, "<stdin>"),
}
}
@ -201,7 +201,7 @@ impl FileLines {
}
/// Returns `true` if this `FileLines` contains all lines in all files.
pub(crate) fn is_all(&self) -> bool {
pub fn is_all(&self) -> bool {
self.0.is_none()
}

View File

@ -10,9 +10,7 @@ use crate::config::config_type::ConfigType;
#[allow(unreachable_pub)]
pub use crate::config::file_lines::{FileLines, FileName, Range};
#[allow(unreachable_pub)]
pub use crate::config::lists::*;
#[allow(unreachable_pub)]
pub use crate::config::macro_names::{MacroSelector, MacroSelectors};
pub use crate::config::macro_names::MacroSelector;
#[allow(unreachable_pub)]
pub use crate::config::options::*;
@ -34,161 +32,169 @@ pub(crate) mod style_edition;
// `name: value type, default value, is stable, description;`
create_config! {
// Fundamental stuff
max_width: usize, 100, true, "Maximum width of each line";
hard_tabs: bool, false, true, "Use tab characters for indentation, spaces for alignment";
tab_spaces: usize, 4, true, "Number of spaces per tab";
newline_style: NewlineStyle, NewlineStyle::Auto, true, "Unix or Windows line endings";
indent_style: IndentStyle, IndentStyle::Block, false, "How do we indent expressions or items";
max_width: MaxWidth, true, "Maximum width of each line";
hard_tabs: HardTabs, true, "Use tab characters for indentation, spaces for alignment";
tab_spaces: TabSpaces, true, "Number of spaces per tab";
newline_style: NewlineStyleConfig, true, "Unix or Windows line endings";
indent_style: IndentStyleConfig, false, "How do we indent expressions or items";
// Width Heuristics
use_small_heuristics: Heuristics, Heuristics::Default, true, "Whether to use different \
use_small_heuristics: UseSmallHeuristics, true, "Whether to use different \
formatting for items and expressions if they satisfy a heuristic notion of 'small'";
width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false,
"'small' heuristic values";
fn_call_width: usize, 60, true, "Maximum width of the args of a function call before \
width_heuristics: WidthHeuristicsConfig, false, "'small' heuristic values";
fn_call_width: FnCallWidth, true, "Maximum width of the args of a function call before \
falling back to vertical formatting.";
attr_fn_like_width: usize, 70, true, "Maximum width of the args of a function-like \
attr_fn_like_width: AttrFnLikeWidth, true, "Maximum width of the args of a function-like \
attributes before falling back to vertical formatting.";
struct_lit_width: usize, 18, true, "Maximum width in the body of a struct lit before \
struct_lit_width: StructLitWidth, true, "Maximum width in the body of a struct lit before \
falling back to vertical formatting.";
struct_variant_width: usize, 35, true, "Maximum width in the body of a struct variant before \
falling back to vertical formatting.";
array_width: usize, 60, true, "Maximum width of an array literal before falling \
struct_variant_width: StructVariantWidth, true, "Maximum width in the body of a struct variant \
before falling back to vertical formatting.";
array_width: ArrayWidth, true, "Maximum width of an array literal before falling \
back to vertical formatting.";
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single line if-else \
expressions. A value of zero means always break if-else expressions.";
single_line_let_else_max_width: usize, 50, true, "Maximum line length for single line \
let-else statements. A value of zero means always format the divergent `else` block \
over multiple lines.";
chain_width: ChainWidth, true, "Maximum length of a chain to fit on a single line.";
single_line_if_else_max_width: SingleLineIfElseMaxWidth, true, "Maximum line length for single \
line if-else expressions. A value of zero means always break if-else expressions.";
single_line_let_else_max_width: SingleLineLetElseMaxWidth, true, "Maximum line length for \
single line let-else statements. A value of zero means always format the divergent `else` \
block over multiple lines.";
// Comments. macros, and strings
wrap_comments: bool, false, false, "Break comments to fit on the line";
format_code_in_doc_comments: bool, false, false, "Format the code snippet in doc comments.";
doc_comment_code_block_width: usize, 100, false, "Maximum width for code snippets in doc \
comments. No effect unless format_code_in_doc_comments = true";
comment_width: usize, 80, false,
wrap_comments: WrapComments, false, "Break comments to fit on the line";
format_code_in_doc_comments: FormatCodeInDocComments, false, "Format the code snippet in \
doc comments.";
doc_comment_code_block_width: DocCommentCodeBlockWidth, false, "Maximum width for code \
snippets in doc comments. No effect unless format_code_in_doc_comments = true";
comment_width: CommentWidth, false,
"Maximum length of comments. No effect unless wrap_comments = true";
normalize_comments: bool, false, false, "Convert /* */ comments to // comments where possible";
normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments";
format_strings: bool, false, false, "Format string literals where necessary";
format_macro_matchers: bool, false, false,
normalize_comments: NormalizeComments, false, "Convert /* */ comments to // comments where \
possible";
normalize_doc_attributes: NormalizeDocAttributes, false, "Normalize doc attributes as doc \
comments";
format_strings: FormatStrings, false, "Format string literals where necessary";
format_macro_matchers: FormatMacroMatchers, false,
"Format the metavariable matching patterns in macros";
format_macro_bodies: bool, true, false, "Format the bodies of declarative macro definitions";
skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false,
format_macro_bodies: FormatMacroBodies, false,
"Format the bodies of declarative macro definitions";
skip_macro_invocations: SkipMacroInvocations, false,
"Skip formatting the bodies of macros invoked with the following names.";
hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
"Format hexadecimal integer literals";
hex_literal_case: HexLiteralCaseConfig, false, "Format hexadecimal integer literals";
// Single line expressions and items
empty_item_single_line: bool, true, false,
empty_item_single_line: EmptyItemSingleLine, false,
"Put empty-body functions and impls on a single line";
struct_lit_single_line: bool, true, false,
struct_lit_single_line: StructLitSingleLine, false,
"Put small struct literals on a single line";
fn_single_line: bool, false, false, "Put single-expression functions on a single line";
where_single_line: bool, false, false, "Force where-clauses to be on a single line";
fn_single_line: FnSingleLine, false, "Put single-expression functions on a single line";
where_single_line: WhereSingleLine, false, "Force where-clauses to be on a single line";
// Imports
imports_indent: IndentStyle, IndentStyle::Block, false, "Indent of imports";
imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block";
imports_granularity: ImportGranularity, ImportGranularity::Preserve, false,
imports_indent: ImportsIndent, false, "Indent of imports";
imports_layout: ImportsLayout, false, "Item layout inside a import block";
imports_granularity: ImportsGranularityConfig, false,
"Merge or split imports to the provided granularity";
group_imports: GroupImportsTactic, GroupImportsTactic::Preserve, false,
group_imports: GroupImportsTacticConfig, false,
"Controls the strategy for how imports are grouped together";
merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)";
merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
// Ordering
reorder_imports: bool, true, true, "Reorder import and extern crate statements alphabetically";
reorder_modules: bool, true, true, "Reorder module statements alphabetically in group";
reorder_impl_items: bool, false, false, "Reorder impl items";
reorder_imports: ReorderImports, true, "Reorder import and extern crate statements \
alphabetically";
reorder_modules: ReorderModules, true, "Reorder module statements alphabetically in group";
reorder_impl_items: ReorderImplItems, false, "Reorder impl items";
// Spaces around punctuation
type_punctuation_density: TypeDensity, TypeDensity::Wide, false,
type_punctuation_density: TypePunctuationDensity, false,
"Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
space_before_colon: bool, false, false, "Leave a space before the colon";
space_after_colon: bool, true, false, "Leave a space after the colon";
spaces_around_ranges: bool, false, false, "Put spaces around the .. and ..= range operators";
binop_separator: SeparatorPlace, SeparatorPlace::Front, false,
space_before_colon: SpaceBeforeColon, false, "Leave a space before the colon";
space_after_colon: SpaceAfterColon, false, "Leave a space after the colon";
spaces_around_ranges: SpacesAroundRanges, false, "Put spaces around the .. and ..= range \
operators";
binop_separator: BinopSeparator, false,
"Where to put a binary operator when a binary expression goes multiline";
// Misc.
remove_nested_parens: bool, true, true, "Remove nested parens";
combine_control_expr: bool, true, false, "Combine control expressions with function calls";
short_array_element_width_threshold: usize, 10, true,
remove_nested_parens: RemoveNestedParens, true, "Remove nested parens";
combine_control_expr: CombineControlExpr, false, "Combine control expressions with function \
calls";
short_array_element_width_threshold: ShortArrayElementWidthThreshold, true,
"Width threshold for an array element to be considered short";
overflow_delimited_expr: bool, false, false,
overflow_delimited_expr: OverflowDelimitedExpr, false,
"Allow trailing bracket/brace delimited expressions to overflow";
struct_field_align_threshold: usize, 0, false,
struct_field_align_threshold: StructFieldAlignThreshold, false,
"Align struct fields if their diffs fits within threshold";
enum_discrim_align_threshold: usize, 0, false,
enum_discrim_align_threshold: EnumDiscrimAlignThreshold, false,
"Align enum variants discrims, if their diffs fit within threshold";
match_arm_blocks: bool, true, false, "Wrap the body of arms in blocks when it does not fit on \
the same line with the pattern of arms";
match_arm_leading_pipes: MatchArmLeadingPipe, MatchArmLeadingPipe::Never, true,
match_arm_blocks: MatchArmBlocks, false, "Wrap the body of arms in blocks when it does not fit \
on the same line with the pattern of arms";
match_arm_leading_pipes: MatchArmLeadingPipeConfig, true,
"Determines whether leading pipes are emitted on match arms";
force_multiline_blocks: bool, false, false,
force_multiline_blocks: ForceMultilineBlocks, false,
"Force multiline closure bodies and match arms to be wrapped in a block";
fn_args_layout: Density, Density::Tall, true,
fn_args_layout: FnArgsLayout, true,
"(deprecated: use fn_params_layout instead)";
fn_params_layout: Density, Density::Tall, true,
fn_params_layout: FnParamsLayout, true,
"Control the layout of parameters in function signatures.";
brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for items";
control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false,
brace_style: BraceStyleConfig, false, "Brace style for items";
control_brace_style: ControlBraceStyleConfig, false,
"Brace style for control flow constructs";
trailing_semicolon: bool, true, false,
trailing_semicolon: TrailingSemicolon, false,
"Add trailing semicolon after break, continue and return";
trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, false,
trailing_comma: TrailingComma, false,
"How to handle trailing commas for lists";
match_block_trailing_comma: bool, false, true,
match_block_trailing_comma: MatchBlockTrailingComma, true,
"Put a trailing comma after a block based match arm (non-block arms are not affected)";
blank_lines_upper_bound: usize, 1, false,
blank_lines_upper_bound: BlankLinesUpperBound, false,
"Maximum number of blank lines which can be put between items";
blank_lines_lower_bound: usize, 0, false,
blank_lines_lower_bound: BlankLinesLowerBound, false,
"Minimum number of blank lines which must be put between items";
edition: Edition, Edition::Edition2015, true, "The edition of the parser (RFC 2052)";
version: Version, Version::One, false, "Version of formatting rules";
inline_attribute_width: usize, 0, false,
edition: EditionConfig, true, "The edition of the parser (RFC 2052)";
style_edition: StyleEditionConfig, false, "The edition of the Style Guide (RFC 3338)";
version: VersionConfig, false, "Version of formatting rules";
inline_attribute_width: InlineAttributeWidth, false,
"Write an item and its attribute on the same line \
if their combined width is below a threshold";
format_generated_files: bool, true, false, "Format generated files";
generated_marker_line_search_limit: usize, 5, false, "Number of lines to check for a \
`@generated` marker when `format_generated_files` is enabled";
format_generated_files: FormatGeneratedFiles, false, "Format generated files";
generated_marker_line_search_limit: GeneratedMarkerLineSearchLimit, false, "Number of lines to \
check for a `@generated` marker when `format_generated_files` is enabled";
// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one";
use_try_shorthand: bool, false, true, "Replace uses of the try! macro by the ? shorthand";
use_field_init_shorthand: bool, false, true, "Use field initialization shorthand if possible";
force_explicit_abi: bool, true, true, "Always print the abi for extern items";
condense_wildcard_suffixes: bool, false, false, "Replace strings of _ wildcards by a single .. \
in tuple patterns";
merge_derives: MergeDerives, true, "Merge multiple `#[derive(...)]` into a single one";
use_try_shorthand: UseTryShorthand, true, "Replace uses of the try! macro by the ? shorthand";
use_field_init_shorthand: UseFieldInitShorthand, true, "Use field initialization shorthand if \
possible";
force_explicit_abi: ForceExplicitAbi, true, "Always print the abi for extern items";
condense_wildcard_suffixes: CondenseWildcardSuffixes, false, "Replace strings of _ wildcards \
by a single .. in tuple patterns";
// Control options (changes the operation of rustfmt, rather than the formatting)
color: Color, Color::Auto, false,
color: ColorConfig, false,
"What Color option to use when none is supplied: Always, Never, Auto";
required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false,
required_version: RequiredVersion, false,
"Require a specific version of rustfmt";
unstable_features: bool, false, false,
unstable_features: UnstableFeatures, false,
"Enables unstable features. Only available on nightly channel";
disable_all_formatting: bool, false, true, "Don't reformat anything";
skip_children: bool, false, false, "Don't reformat out of line modules";
hide_parse_errors: bool, false, false, "(deprecated: use show_parse_errors instead)";
show_parse_errors: bool, true, false, "Show errors from the parser (unstable)";
error_on_line_overflow: bool, false, false, "Error if unable to get all lines within max_width";
error_on_unformatted: bool, false, false,
disable_all_formatting: DisableAllFormatting, true, "Don't reformat anything";
skip_children: SkipChildren, false, "Don't reformat out of line modules";
hide_parse_errors: HideParseErrors, false, "Hide errors from the parser";
show_parse_errors: ShowParseErrors, false, "Show errors from the parser (unstable)";
error_on_line_overflow: ErrorOnLineOverflow, false, "Error if unable to get all lines within \
max_width";
error_on_unformatted: ErrorOnUnformatted, false,
"Error if unable to get comments or string literals within max_width, \
or they are left with trailing whitespaces";
ignore: IgnoreList, IgnoreList::default(), false,
ignore: Ignore, false,
"Skip formatting the specified files and directories";
// Not user-facing
verbose: Verbosity, Verbosity::Normal, false, "How much to information to emit to the user";
file_lines: FileLines, FileLines::all(), false,
verbose: Verbose, false, "How much to information to emit to the user";
file_lines: FileLinesConfig, false,
"Lines to format; this is not supported in rustfmt.toml, and can only be specified \
via the --file-lines option";
emit_mode: EmitMode, EmitMode::Files, false,
emit_mode: EmitModeConfig, false,
"What emit Mode to use when none is supplied";
make_backup: bool, false, false, "Backup changed files";
print_misformatted_file_names: bool, false, true,
make_backup: MakeBackup, false, "Backup changed files";
print_misformatted_file_names: PrintMisformattedFileNames, true,
"Prints the names of mismatched files that were formatted. Prints the names of \
files that would be formatted when used with `--check` mode. ";
}
@ -211,9 +217,48 @@ impl PartialConfig {
::toml::to_string(&cloned).map_err(ToTomlError)
}
pub(super) fn to_parsed_config(
self,
style_edition_override: Option<StyleEdition>,
edition_override: Option<Edition>,
version_override: Option<Version>,
dir: &Path,
) -> Config {
Config::default_for_possible_style_edition(
style_edition_override.or(self.style_edition),
edition_override.or(self.edition),
version_override.or(self.version),
)
.fill_from_parsed_config(self, dir)
}
}
impl Config {
pub fn default_for_possible_style_edition(
style_edition: Option<StyleEdition>,
edition: Option<Edition>,
version: Option<Version>,
) -> Config {
// Ensures the configuration defaults associated with Style Editions
// follow the precedence set in
// https://rust-lang.github.io/rfcs/3338-style-evolution.html
// 'version' is a legacy alias for 'style_edition' that we'll support
// for some period of time
// FIXME(calebcartwright) - remove 'version' at some point
match (style_edition, version, edition) {
(Some(se), _, _) => Self::default_with_style_edition(se),
(None, Some(Version::Two), _) => {
Self::default_with_style_edition(StyleEdition::Edition2024)
}
(None, Some(Version::One), _) => {
Self::default_with_style_edition(StyleEdition::Edition2015)
}
(None, None, Some(e)) => Self::default_with_style_edition(e.into()),
(None, None, None) => Config::default(),
}
}
pub(crate) fn version_meets_requirement(&self) -> bool {
if self.was_set().required_version() {
let version = env!("CARGO_PKG_VERSION");
@ -237,11 +282,16 @@ impl Config {
///
/// Returns a `Config` if the config could be read and parsed from
/// the file, otherwise errors.
pub(super) fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
pub(super) fn from_toml_path(
file_path: &Path,
edition: Option<Edition>,
style_edition: Option<StyleEdition>,
version: Option<Version>,
) -> Result<Config, Error> {
let mut file = File::open(&file_path)?;
let mut toml = String::new();
file.read_to_string(&mut toml)?;
Config::from_toml(&toml, file_path.parent().unwrap())
Config::from_toml_for_style_edition(&toml, file_path, edition, style_edition, version)
.map_err(|err| Error::new(ErrorKind::InvalidData, err))
}
@ -254,9 +304,14 @@ impl Config {
///
/// Returns the `Config` to use, and the path of the project file if there was
/// one.
pub(super) fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> {
pub(super) fn from_resolved_toml_path(
dir: &Path,
edition: Option<Edition>,
style_edition: Option<StyleEdition>,
version: Option<Version>,
) -> Result<(Config, Option<PathBuf>), Error> {
/// Try to find a project file in the given directory and its parents.
/// Returns the path of a the nearest project file if one exists,
/// Returns the path of the nearest project file if one exists,
/// or `None` if no project file was found.
fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
let mut current = if dir.is_relative() {
@ -299,12 +354,27 @@ impl Config {
}
match resolve_project_file(dir)? {
None => Ok((Config::default(), None)),
Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
None => Ok((
Config::default_for_possible_style_edition(style_edition, edition, version),
None,
)),
Some(path) => Config::from_toml_path(&path, edition, style_edition, version)
.map(|config| (config, Some(path))),
}
}
pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result<Config, String> {
#[allow(dead_code)]
pub(super) fn from_toml(toml: &str, file_path: &Path) -> Result<Config, String> {
Self::from_toml_for_style_edition(toml, file_path, None, None, None)
}
pub(crate) fn from_toml_for_style_edition(
toml: &str,
file_path: &Path,
edition: Option<Edition>,
style_edition: Option<StyleEdition>,
version: Option<Version>,
) -> Result<Config, String> {
let parsed: ::toml::Value = toml
.parse()
.map_err(|e| format!("Could not parse TOML: {}", e))?;
@ -318,18 +388,25 @@ impl Config {
err.push_str(msg)
}
}
match parsed.try_into() {
match parsed.try_into::<PartialConfig>() {
Ok(parsed_config) => {
if !err.is_empty() {
eprint!("{err}");
}
Ok(Config::default().fill_from_parsed_config(parsed_config, dir))
let dir = file_path.parent().ok_or_else(|| {
format!("failed to get parent directory for {}", file_path.display())
})?;
Ok(parsed_config.to_parsed_config(style_edition, edition, version, dir))
}
Err(e) => {
err.push_str("Error: Decoding config file failed:\n");
err.push_str(format!("{e}\n").as_str());
err.push_str("Please check your config file.");
Err(err)
let err_msg = format!(
"The file `{}` failed to parse.\nError details: {e}",
file_path.display()
);
err.push_str(&err_msg);
Err(err_msg)
}
}
}
@ -341,17 +418,26 @@ pub fn load_config<O: CliOptions>(
file_path: Option<&Path>,
options: Option<O>,
) -> Result<(Config, Option<PathBuf>), Error> {
let over_ride = match options {
Some(ref opts) => config_path(opts)?,
None => None,
let (over_ride, edition, style_edition, version) = match options {
Some(ref opts) => (
config_path(opts)?,
opts.edition(),
opts.style_edition(),
opts.version(),
),
None => (None, None, None, None),
};
let result = if let Some(over_ride) = over_ride {
Config::from_toml_path(over_ride.as_ref()).map(|p| (p, Some(over_ride.to_owned())))
Config::from_toml_path(over_ride.as_ref(), edition, style_edition, version)
.map(|p| (p, Some(over_ride.to_owned())))
} else if let Some(file_path) = file_path {
Config::from_resolved_toml_path(file_path)
Config::from_resolved_toml_path(file_path, edition, style_edition, version)
} else {
Ok((Config::default(), None))
Ok((
Config::default_for_possible_style_edition(style_edition, edition, version),
None,
))
};
result.map(|(mut c, p)| {
@ -362,7 +448,7 @@ pub fn load_config<O: CliOptions>(
})
}
// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir`
// Check for the presence of known config file names (`rustfmt.toml`, `.rustfmt.toml`) in `dir`
//
// Return the path if a config file exists, empty if no file exists, and Error for IO errors
fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
@ -372,7 +458,7 @@ fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
match fs::metadata(&config_file) {
// Only return if it's a file to handle the unlikely situation of a directory named
// `rustfmt.toml`.
Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
Ok(ref md) if md.is_file() => return Ok(Some(config_file.canonicalize()?)),
// Return the error if it's something other than `NotFound`; otherwise we didn't
// find the project file yet, and continue searching.
Err(e) => {
@ -411,7 +497,11 @@ fn config_path(options: &dyn CliOptions) -> Result<Option<PathBuf>, Error> {
config_path_not_found(path.to_str().unwrap())
}
}
path => Ok(path.map(ToOwned::to_owned)),
Some(path) => Ok(Some(
// Canonicalize only after checking above that the `path.exists()`.
path.canonicalize()?,
)),
None => Ok(None),
}
}
@ -420,12 +510,13 @@ mod test {
use super::*;
use std::str;
use crate::config::macro_names::MacroName;
use crate::config::macro_names::{MacroName, MacroSelectors};
use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
#[allow(dead_code)]
mod mock {
use super::super::*;
use crate::config_option_with_style_edition_default;
use rustfmt_config_proc_macro::config_type;
#[config_type]
@ -436,66 +527,69 @@ mod test {
V3,
}
config_option_with_style_edition_default!(
StableOption, bool, _ => false;
UnstableOption, bool, _ => false;
PartiallyUnstable, PartiallyUnstableOption, _ => PartiallyUnstableOption::V1;
);
create_config! {
// Options that are used by the generated functions
max_width: usize, 100, true, "Maximum width of each line";
required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false,
"Require a specific version of rustfmt.";
ignore: IgnoreList, IgnoreList::default(), false,
"Skip formatting the specified files and directories.";
verbose: Verbosity, Verbosity::Normal, false,
"How much to information to emit to the user";
file_lines: FileLines, FileLines::all(), false,
max_width: MaxWidth, true, "Maximum width of each line";
required_version: RequiredVersion, false, "Require a specific version of rustfmt.";
ignore: Ignore, false, "Skip formatting the specified files and directories.";
verbose: Verbose, false, "How much to information to emit to the user";
file_lines: FileLinesConfig, false,
"Lines to format; this is not supported in rustfmt.toml, and can only be specified \
via the --file-lines option";
// merge_imports deprecation
imports_granularity: ImportGranularity, ImportGranularity::Preserve, false,
"Merge imports";
merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)";
imports_granularity: ImportsGranularityConfig, false, "Merge imports";
merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
// fn_args_layout renamed to fn_params_layout
fn_args_layout: Density, Density::Tall, true,
"(deprecated: use fn_params_layout instead)";
fn_params_layout: Density, Density::Tall, true,
fn_args_layout: FnArgsLayout, true, "(deprecated: use fn_params_layout instead)";
fn_params_layout: FnParamsLayout, true,
"Control the layout of parameters in a function signatures.";
// hide_parse_errors renamed to show_parse_errors
hide_parse_errors: bool, false, false,
hide_parse_errors: HideParseErrors, false,
"(deprecated: use show_parse_errors instead)";
show_parse_errors: bool, true, false,
show_parse_errors: ShowParseErrors, false,
"Show errors from the parser (unstable)";
// Width Heuristics
use_small_heuristics: Heuristics, Heuristics::Default, true,
use_small_heuristics: UseSmallHeuristics, true,
"Whether to use different formatting for items and \
expressions if they satisfy a heuristic notion of 'small'.";
width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false,
"'small' heuristic values";
width_heuristics: WidthHeuristicsConfig, false, "'small' heuristic values";
fn_call_width: usize, 60, true, "Maximum width of the args of a function call before \
fn_call_width: FnCallWidth, true, "Maximum width of the args of a function call before \
falling back to vertical formatting.";
attr_fn_like_width: usize, 70, true, "Maximum width of the args of a function-like \
attributes before falling back to vertical formatting.";
struct_lit_width: usize, 18, true, "Maximum width in the body of a struct lit before \
falling back to vertical formatting.";
struct_variant_width: usize, 35, true, "Maximum width in the body of a struct \
attr_fn_like_width: AttrFnLikeWidth, true, "Maximum width of the args of a \
function-like attributes before falling back to vertical formatting.";
struct_lit_width: StructLitWidth, true, "Maximum width in the body of a struct lit \
before falling back to vertical formatting.";
struct_variant_width: StructVariantWidth, true, "Maximum width in the body of a struct \
variant before falling back to vertical formatting.";
array_width: usize, 60, true, "Maximum width of an array literal before falling \
array_width: ArrayWidth, true, "Maximum width of an array literal before falling \
back to vertical formatting.";
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single \
line if-else expressions. A value of zero means always break if-else expressions.";
single_line_let_else_max_width: usize, 50, false, "Maximum line length for single \
line let-else statements. A value of zero means always format the divergent \
`else` block over multiple lines.";
chain_width: ChainWidth, true, "Maximum length of a chain to fit on a single line.";
single_line_if_else_max_width: SingleLineIfElseMaxWidth, true, "Maximum line length \
for single line if-else expressions. A value of zero means always break if-else \
expressions.";
single_line_let_else_max_width: SingleLineLetElseMaxWidth, false, "Maximum line length \
for single line let-else statements. A value of zero means always format the \
divergent `else` block over multiple lines.";
// Options that are used by the tests
stable_option: bool, false, true, "A stable option";
unstable_option: bool, false, false, "An unstable option";
partially_unstable_option: PartiallyUnstableOption, PartiallyUnstableOption::V1, true,
"A partially unstable option";
stable_option: StableOption, true, "A stable option";
unstable_option: UnstableOption, false, "An unstable option";
partially_unstable_option: PartiallyUnstable, true, "A partially unstable option";
edition: EditionConfig, true, "blah";
style_edition: StyleEditionConfig, true, "blah";
version: VersionConfig, false, "blah blah"
}
#[cfg(test)]
@ -580,7 +674,7 @@ mod test {
#[test]
fn test_was_set() {
let config = Config::from_toml("hard_tabs = true", Path::new("")).unwrap();
let config = Config::from_toml("hard_tabs = true", Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.was_set().hard_tabs(), true);
assert_eq!(config.was_set().verbose(), false);
@ -679,6 +773,7 @@ match_block_trailing_comma = false
blank_lines_upper_bound = 1
blank_lines_lower_bound = 0
edition = "2015"
style_edition = "2015"
version = "One"
inline_attribute_width = 0
format_generated_files = true
@ -706,6 +801,114 @@ make_backup = false
assert_eq!(&toml, &default_config);
}
#[test]
fn test_dump_style_edition_2024_config() {
let edition_2024_config = format!(
r#"max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
indent_style = "Block"
use_small_heuristics = "Default"
fn_call_width = 60
attr_fn_like_width = 70
struct_lit_width = 18
struct_variant_width = 35
array_width = 60
chain_width = 60
single_line_if_else_max_width = 50
single_line_let_else_max_width = 50
wrap_comments = false
format_code_in_doc_comments = false
doc_comment_code_block_width = 100
comment_width = 80
normalize_comments = false
normalize_doc_attributes = false
format_strings = false
format_macro_matchers = false
format_macro_bodies = true
skip_macro_invocations = []
hex_literal_case = "Preserve"
empty_item_single_line = true
struct_lit_single_line = true
fn_single_line = false
where_single_line = false
imports_indent = "Block"
imports_layout = "Mixed"
imports_granularity = "Preserve"
group_imports = "Preserve"
reorder_imports = true
reorder_modules = true
reorder_impl_items = false
type_punctuation_density = "Wide"
space_before_colon = false
space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
combine_control_expr = true
short_array_element_width_threshold = 10
overflow_delimited_expr = true
struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = true
match_arm_leading_pipes = "Never"
force_multiline_blocks = false
fn_params_layout = "Tall"
brace_style = "SameLineWhere"
control_brace_style = "AlwaysSameLine"
trailing_semicolon = true
trailing_comma = "Vertical"
match_block_trailing_comma = false
blank_lines_upper_bound = 1
blank_lines_lower_bound = 0
edition = "2015"
style_edition = "2024"
version = "Two"
inline_attribute_width = 0
format_generated_files = true
generated_marker_line_search_limit = 5
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
condense_wildcard_suffixes = false
color = "Auto"
required_version = "{}"
unstable_features = false
disable_all_formatting = false
skip_children = false
show_parse_errors = true
error_on_line_overflow = false
error_on_unformatted = false
ignore = []
emit_mode = "Files"
make_backup = false
"#,
env!("CARGO_PKG_VERSION")
);
let toml = Config::default_with_style_edition(StyleEdition::Edition2024)
.all_options()
.to_toml()
.unwrap();
assert_eq!(&toml, &edition_2024_config);
}
#[test]
fn test_editions_2015_2018_2021_identical() {
let get_edition_toml = |style_edition: StyleEdition| {
Config::default_with_style_edition(style_edition)
.all_options()
.to_toml()
.unwrap()
};
let edition2015 = get_edition_toml(StyleEdition::Edition2015);
let edition2018 = get_edition_toml(StyleEdition::Edition2018);
let edition2021 = get_edition_toml(StyleEdition::Edition2021);
assert_eq!(edition2015, edition2018);
assert_eq!(edition2018, edition2021);
}
#[stable_only_test]
#[test]
fn test_as_not_nightly_channel() {
@ -730,11 +933,26 @@ make_backup = false
#[nightly_only_test]
#[test]
fn test_unstable_from_toml() {
let config = Config::from_toml("unstable_features = true", Path::new("")).unwrap();
let config =
Config::from_toml("unstable_features = true", Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.was_set().unstable_features(), true);
assert_eq!(config.unstable_features(), true);
}
#[test]
fn test_set_cli() {
let mut config = Config::default();
assert_eq!(config.was_set().edition(), false);
assert_eq!(config.was_set_cli().edition(), false);
config.set().edition(Edition::Edition2021);
assert_eq!(config.was_set().edition(), false);
assert_eq!(config.was_set_cli().edition(), false);
config.set_cli().edition(Edition::Edition2021);
assert_eq!(config.was_set().edition(), false);
assert_eq!(config.was_set_cli().edition(), true);
assert_eq!(config.was_set_cli().emit_mode(), false);
}
#[cfg(test)]
mod deprecated_option_merge_imports {
use super::*;
@ -746,7 +964,7 @@ make_backup = false
unstable_features = true
merge_imports = true
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.imports_granularity(), ImportGranularity::Crate);
}
@ -758,7 +976,7 @@ make_backup = false
merge_imports = true
imports_granularity = "Preserve"
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
}
@ -769,7 +987,7 @@ make_backup = false
unstable_features = true
merge_imports = true
"#;
let mut config = Config::from_toml(toml, Path::new("")).unwrap();
let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
config.override_value("imports_granularity", "Preserve");
assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
}
@ -781,7 +999,7 @@ make_backup = false
unstable_features = true
imports_granularity = "Module"
"#;
let mut config = Config::from_toml(toml, Path::new("")).unwrap();
let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
config.override_value("merge_imports", "true");
// no effect: the new option always takes precedence
assert_eq!(config.imports_granularity(), ImportGranularity::Module);
@ -798,7 +1016,7 @@ make_backup = false
use_small_heuristics = "Default"
max_width = 200
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), 120);
assert_eq!(config.attr_fn_like_width(), 140);
assert_eq!(config.chain_width(), 120);
@ -814,7 +1032,7 @@ make_backup = false
use_small_heuristics = "Max"
max_width = 120
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), 120);
assert_eq!(config.attr_fn_like_width(), 120);
assert_eq!(config.chain_width(), 120);
@ -830,11 +1048,11 @@ make_backup = false
use_small_heuristics = "Off"
max_width = 100
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
assert_eq!(config.array_width(), usize::max_value());
assert_eq!(config.attr_fn_like_width(), usize::max_value());
assert_eq!(config.chain_width(), usize::max_value());
assert_eq!(config.fn_call_width(), usize::max_value());
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), usize::MAX);
assert_eq!(config.attr_fn_like_width(), usize::MAX);
assert_eq!(config.chain_width(), usize::MAX);
assert_eq!(config.fn_call_width(), usize::MAX);
assert_eq!(config.single_line_if_else_max_width(), 0);
assert_eq!(config.struct_lit_width(), 0);
assert_eq!(config.struct_variant_width(), 0);
@ -852,7 +1070,7 @@ make_backup = false
struct_lit_width = 30
struct_variant_width = 34
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), 20);
assert_eq!(config.attr_fn_like_width(), 40);
assert_eq!(config.chain_width(), 20);
@ -874,7 +1092,7 @@ make_backup = false
struct_lit_width = 30
struct_variant_width = 34
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), 20);
assert_eq!(config.attr_fn_like_width(), 40);
assert_eq!(config.chain_width(), 20);
@ -896,7 +1114,7 @@ make_backup = false
struct_lit_width = 30
struct_variant_width = 34
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), 20);
assert_eq!(config.attr_fn_like_width(), 40);
assert_eq!(config.chain_width(), 20);
@ -912,7 +1130,7 @@ make_backup = false
max_width = 90
fn_call_width = 95
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.fn_call_width(), 90);
}
@ -922,7 +1140,7 @@ make_backup = false
max_width = 80
attr_fn_like_width = 90
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.attr_fn_like_width(), 80);
}
@ -932,7 +1150,7 @@ make_backup = false
max_width = 78
struct_lit_width = 90
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.struct_lit_width(), 78);
}
@ -942,7 +1160,7 @@ make_backup = false
max_width = 80
struct_variant_width = 90
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.struct_variant_width(), 80);
}
@ -952,7 +1170,7 @@ make_backup = false
max_width = 60
array_width = 80
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.array_width(), 60);
}
@ -962,7 +1180,7 @@ make_backup = false
max_width = 80
chain_width = 90
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.chain_width(), 80);
}
@ -972,7 +1190,7 @@ make_backup = false
max_width = 70
single_line_if_else_max_width = 90
"#;
let config = Config::from_toml(toml, Path::new("")).unwrap();
let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
assert_eq!(config.single_line_if_else_max_width(), 70);
}

View File

@ -1,6 +1,6 @@
#![allow(unused_imports)]
use std::collections::{hash_set, HashSet};
use std::collections::{HashSet, hash_set};
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
@ -11,8 +11,10 @@ use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::config::lists::*;
use crate::config::Config;
use crate::config::file_lines::FileLines;
use crate::config::lists::*;
use crate::config::macro_names::MacroSelectors;
#[config_type]
pub enum NewlineStyle {
@ -253,12 +255,12 @@ impl WidthHeuristics {
// Using this WidthHeuristics means we ignore heuristics.
pub fn null() -> WidthHeuristics {
WidthHeuristics {
fn_call_width: usize::max_value(),
attr_fn_like_width: usize::max_value(),
fn_call_width: usize::MAX,
attr_fn_like_width: usize::MAX,
struct_lit_width: 0,
struct_variant_width: 0,
array_width: usize::max_value(),
chain_width: usize::max_value(),
array_width: usize::MAX,
chain_width: usize::MAX,
single_line_if_else_max_width: 0,
single_line_let_else_max_width: 0,
}
@ -413,7 +415,13 @@ impl FromStr for IgnoreList {
/// values in a config with values from the command line.
pub trait CliOptions {
fn apply_to(self, config: &mut Config);
/// It is ok if the returned path doesn't exist or is not canonicalized
/// (i.e. the callers are expected to handle such cases).
fn config_path(&self) -> Option<&Path>;
fn edition(&self) -> Option<Edition>;
fn style_edition(&self) -> Option<StyleEdition>;
fn version(&self) -> Option<Version>;
}
/// The edition of the syntax and semantics of code (RFC 2052).
@ -454,6 +462,17 @@ impl From<Edition> for rustc_span::edition::Edition {
}
}
impl From<Edition> for StyleEdition {
fn from(edition: Edition) -> Self {
match edition {
Edition::Edition2015 => StyleEdition::Edition2015,
Edition::Edition2018 => StyleEdition::Edition2018,
Edition::Edition2021 => StyleEdition::Edition2021,
Edition::Edition2024 => StyleEdition::Edition2024,
}
}
}
impl PartialOrd for Edition {
fn partial_cmp(&self, other: &Edition) -> Option<std::cmp::Ordering> {
rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into())
@ -471,10 +490,11 @@ pub enum MatchArmLeadingPipe {
Preserve,
}
/// Defines the default values for each config according to [the style guide].
/// rustfmt output may differ between style editions.
/// Defines the default values for each config according to the edition of the
/// [Style Guide] as per [RFC 3338]. Rustfmt output may differ between Style editions.
///
/// [the style guide]: https://doc.rust-lang.org/nightly/style-guide/
/// [Style Guide]: https://doc.rust-lang.org/nightly/style-guide/
/// [RFC 3338]: https://rust-lang.github.io/rfcs/3338-style-evolution.html
#[config_type]
pub enum StyleEdition {
#[value = "2015"]
@ -491,6 +511,169 @@ pub enum StyleEdition {
Edition2021,
#[value = "2024"]
#[doc_hint = "2024"]
#[unstable_variant]
/// [Edition 2024]().
Edition2024,
}
impl From<StyleEdition> for rustc_span::edition::Edition {
fn from(edition: StyleEdition) -> Self {
match edition {
StyleEdition::Edition2015 => Self::Edition2015,
StyleEdition::Edition2018 => Self::Edition2018,
StyleEdition::Edition2021 => Self::Edition2021,
StyleEdition::Edition2024 => Self::Edition2024,
}
}
}
impl PartialOrd for StyleEdition {
fn partial_cmp(&self, other: &StyleEdition) -> Option<std::cmp::Ordering> {
rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into())
}
}
/// Defines unit structs to implement `StyleEditionDefault` for.
#[macro_export]
macro_rules! config_option_with_style_edition_default {
($name:ident, $config_ty:ty, _ => $default:expr) => {
#[allow(unreachable_pub)]
pub struct $name;
$crate::style_edition_default!($name, $config_ty, _ => $default);
};
($name:ident, $config_ty:ty, Edition2024 => $default_2024:expr, _ => $default_2015:expr) => {
pub struct $name;
$crate::style_edition_default!(
$name,
$config_ty,
Edition2024 => $default_2024,
_ => $default_2015
);
};
(
$($name:ident, $config_ty:ty, $(Edition2024 => $default_2024:expr,)? _ => $default:expr);*
$(;)*
) => {
$(
config_option_with_style_edition_default!(
$name, $config_ty, $(Edition2024 => $default_2024,)? _ => $default
);
)*
};
}
// TODO(ytmimi) Some of the configuration values have a `Config` suffix, while others don't.
// I chose to add a `Config` suffix in cases where a type for the config option was already
// defined. For example, `NewlineStyle` and `NewlineStyleConfig`. There was some discussion
// about using the `Config` suffix more consistently.
config_option_with_style_edition_default!(
// Fundamental stuff
MaxWidth, usize, _ => 100;
HardTabs, bool, _ => false;
TabSpaces, usize, _ => 4;
NewlineStyleConfig, NewlineStyle, _ => NewlineStyle::Auto;
IndentStyleConfig, IndentStyle, _ => IndentStyle::Block;
// Width Heuristics
UseSmallHeuristics, Heuristics, _ => Heuristics::Default;
WidthHeuristicsConfig, WidthHeuristics, _ => WidthHeuristics::scaled(100);
FnCallWidth, usize, _ => 60;
AttrFnLikeWidth, usize, _ => 70;
StructLitWidth, usize, _ => 18;
StructVariantWidth, usize, _ => 35;
ArrayWidth, usize, _ => 60;
ChainWidth, usize, _ => 60;
SingleLineIfElseMaxWidth, usize, _ => 50;
SingleLineLetElseMaxWidth, usize, _ => 50;
// Comments. macros, and strings
WrapComments, bool, _ => false;
FormatCodeInDocComments, bool, _ => false;
DocCommentCodeBlockWidth, usize, _ => 100;
CommentWidth, usize, _ => 80;
NormalizeComments, bool, _ => false;
NormalizeDocAttributes, bool, _ => false;
FormatStrings, bool, _ => false;
FormatMacroMatchers, bool, _ => false;
FormatMacroBodies, bool, _ => true;
SkipMacroInvocations, MacroSelectors, _ => MacroSelectors::default();
HexLiteralCaseConfig, HexLiteralCase, _ => HexLiteralCase::Preserve;
// Single line expressions and items
EmptyItemSingleLine, bool, _ => true;
StructLitSingleLine, bool, _ => true;
FnSingleLine, bool, _ => false;
WhereSingleLine, bool, _ => false;
// Imports
ImportsIndent, IndentStyle, _ => IndentStyle::Block;
ImportsLayout, ListTactic, _ => ListTactic::Mixed;
ImportsGranularityConfig, ImportGranularity, _ => ImportGranularity::Preserve;
GroupImportsTacticConfig, GroupImportsTactic, _ => GroupImportsTactic::Preserve;
MergeImports, bool, _ => false;
// Ordering
ReorderImports, bool, _ => true;
ReorderModules, bool, _ => true;
ReorderImplItems, bool, _ => false;
// Spaces around punctuation
TypePunctuationDensity, TypeDensity, _ => TypeDensity::Wide;
SpaceBeforeColon, bool, _ => false;
SpaceAfterColon, bool, _ => true;
SpacesAroundRanges, bool, _ => false;
BinopSeparator, SeparatorPlace, _ => SeparatorPlace::Front;
// Misc.
RemoveNestedParens, bool, _ => true;
CombineControlExpr, bool, _ => true;
ShortArrayElementWidthThreshold, usize, _ => 10;
OverflowDelimitedExpr, bool, Edition2024 => true, _ => false;
StructFieldAlignThreshold, usize, _ => 0;
EnumDiscrimAlignThreshold, usize, _ => 0;
MatchArmBlocks, bool, _ => true;
MatchArmLeadingPipeConfig, MatchArmLeadingPipe, _ => MatchArmLeadingPipe::Never;
ForceMultilineBlocks, bool, _ => false;
FnArgsLayout, Density, _ => Density::Tall;
FnParamsLayout, Density, _ => Density::Tall;
BraceStyleConfig, BraceStyle, _ => BraceStyle::SameLineWhere;
ControlBraceStyleConfig, ControlBraceStyle, _ => ControlBraceStyle::AlwaysSameLine;
TrailingSemicolon, bool, _ => true;
TrailingComma, SeparatorTactic, _ => SeparatorTactic::Vertical;
MatchBlockTrailingComma, bool, _ => false;
BlankLinesUpperBound, usize, _ => 1;
BlankLinesLowerBound, usize, _ => 0;
EditionConfig, Edition, _ => Edition::Edition2015;
StyleEditionConfig, StyleEdition,
Edition2024 => StyleEdition::Edition2024, _ => StyleEdition::Edition2015;
VersionConfig, Version, Edition2024 => Version::Two, _ => Version::One;
InlineAttributeWidth, usize, _ => 0;
FormatGeneratedFiles, bool, _ => true;
GeneratedMarkerLineSearchLimit, usize, _ => 5;
// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
MergeDerives, bool, _ => true;
UseTryShorthand, bool, _ => false;
UseFieldInitShorthand, bool, _ => false;
ForceExplicitAbi, bool, _ => true;
CondenseWildcardSuffixes, bool, _ => false;
// Control options (changes the operation of rustfmt, rather than the formatting)
ColorConfig, Color, _ => Color::Auto;
RequiredVersion, String, _ => env!("CARGO_PKG_VERSION").to_owned();
UnstableFeatures, bool, _ => false;
DisableAllFormatting, bool, _ => false;
SkipChildren, bool, _ => false;
HideParseErrors, bool, _ => false;
ShowParseErrors, bool, _ => true;
ErrorOnLineOverflow, bool, _ => false;
ErrorOnUnformatted, bool, _ => false;
Ignore, IgnoreList, _ => IgnoreList::default();
// Not user-facing
Verbose, Verbosity, _ => Verbosity::Normal;
FileLinesConfig, FileLines, _ => FileLines::all();
EmitModeConfig, EmitMode, _ => EmitMode::Files;
MakeBackup, bool, _ => false;
PrintMisformattedFileNames, bool, _ => false;
);

View File

@ -2,7 +2,7 @@ use crate::config::StyleEdition;
/// Defines the default value for the given style edition
#[allow(dead_code)]
pub(crate) trait StyleEditionDefault {
pub trait StyleEditionDefault {
type ConfigType;
fn style_edition_default(style_edition: StyleEdition) -> Self::ConfigType;
}

View File

@ -1,6 +1,6 @@
use self::xml::XmlEscaped;
use super::*;
use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
use crate::rustfmt_diff::{DiffLine, Mismatch, make_diff};
mod xml;

View File

@ -1,5 +1,5 @@
use super::*;
use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
use crate::rustfmt_diff::{DiffLine, Mismatch, make_diff};
use serde::Serialize;
use serde_json::to_string as to_json_string;

View File

@ -1,5 +1,5 @@
use super::*;
use crate::rustfmt_diff::{make_diff, ModifiedLines};
use crate::rustfmt_diff::{ModifiedLines, make_diff};
#[derive(Debug, Default)]
pub(crate) struct ModifiedLinesEmitter;

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ use crate::parse::parser::{DirectoryOwnership, Parser, ParserError};
use crate::parse::session::ParseSess;
use crate::utils::{contains_skip, count_newlines};
use crate::visitor::FmtVisitor;
use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session};
use crate::{ErrorKind, FormatReport, Input, Session, modules, source_file};
mod generated;
mod newline_style;

View File

@ -13,7 +13,9 @@ use rustfmt_nightly as rustfmt;
use tracing::debug;
use tracing_subscriber::EnvFilter;
use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
use crate::rustfmt::{
CliOptions, FormatReportFormatterBuilder, Input, Session, Version, load_config,
};
fn prune_files(files: Vec<&str>) -> Vec<&str> {
let prefixes: Vec<_> = files
@ -87,6 +89,15 @@ impl CliOptions for NullOptions {
fn config_path(&self) -> Option<&Path> {
unreachable!();
}
fn edition(&self) -> Option<rustfmt_nightly::Edition> {
unreachable!();
}
fn style_edition(&self) -> Option<rustfmt_nightly::StyleEdition> {
unreachable!();
}
fn version(&self) -> Option<Version> {
unreachable!();
}
}
fn uncommitted_files() -> Vec<String> {

View File

@ -41,8 +41,11 @@ mod test {
use crate::ignore_path::IgnorePathSet;
use std::path::{Path, PathBuf};
let config =
Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new("")).unwrap();
let config = Config::from_toml(
r#"ignore = ["foo.rs", "bar_dir/*"]"#,
Path::new("./rustfmt.toml"),
)
.unwrap();
let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap();
assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/foo.rs"))));
@ -59,7 +62,7 @@ mod test {
let config = Config::from_toml(
r#"ignore = ["foo.rs", "bar_dir/*", "!bar_dir/*/what.rs"]"#,
Path::new(""),
Path::new("./rustfmt.toml"),
)
.unwrap();
let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap();

View File

@ -8,19 +8,20 @@ use itertools::Itertools;
use rustc_ast::ast::{self, UseTreeKind};
use rustc_span::{
BytePos, DUMMY_SP, Span,
symbol::{self, sym},
BytePos, Span, DUMMY_SP,
};
use crate::comment::combine_strs_with_missing_comments;
use crate::config::lists::*;
use crate::config::ImportGranularity;
use crate::config::{Edition, IndentStyle, Version};
use crate::config::lists::*;
use crate::config::{Edition, IndentStyle, StyleEdition};
use crate::lists::{
definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list,
};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::rewrite::{Rewrite, RewriteContext, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::sort::version_sort;
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
use crate::utils::{is_same_visibility, mk_sp, rewrite_ident};
@ -44,7 +45,8 @@ impl<'a> FmtVisitor<'a> {
Some(item.span.lo()),
Some(item.attrs.clone()),
)
.rewrite_top_level(&self.get_context(), shape);
.rewrite_top_level(&self.get_context(), shape)
.ok();
match rw {
Some(ref s) if s.is_empty() => {
// Format up to last newline
@ -104,7 +106,7 @@ pub(crate) enum UseSegmentKind {
#[derive(Clone, Eq, PartialEq)]
pub(crate) struct UseSegment {
pub(crate) kind: UseSegmentKind,
pub(crate) version: Version,
pub(crate) style_edition: StyleEdition,
}
#[derive(Clone)]
@ -149,7 +151,7 @@ impl UseSegment {
};
UseSegment {
kind,
version: self.version,
style_edition: self.style_edition,
}
}
@ -197,7 +199,7 @@ impl UseSegment {
Some(UseSegment {
kind,
version: context.config.version(),
style_edition: context.config.style_edition(),
})
}
@ -331,12 +333,17 @@ impl UseTree {
&self,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let vis = self.visibility.as_ref().map_or(Cow::from(""), |vis| {
crate::utils::format_visibility(context, vis)
});
let use_str = self
.rewrite(context, shape.offset_left(vis.len())?)
.rewrite_result(
context,
shape
.offset_left(vis.len())
.max_width_error(shape.width, self.span())?,
)
.map(|s| {
if s.is_empty() {
s
@ -346,8 +353,8 @@ impl UseTree {
})?;
match self.attrs {
Some(ref attrs) if !attrs.is_empty() => {
let attr_str = attrs.rewrite(context, shape)?;
let lo = attrs.last().as_ref()?.span.hi();
let attr_str = attrs.rewrite_result(context, shape)?;
let lo = attrs.last().unknown_error()?.span.hi();
let hi = self.span.lo();
let span = mk_sp(lo, hi);
@ -368,7 +375,7 @@ impl UseTree {
allow_extend,
)
}
_ => Some(use_str),
_ => Ok(use_str),
}
}
@ -444,18 +451,21 @@ impl UseTree {
}
}
let version = context.config.version();
let style_edition = context.config.style_edition();
match a.kind {
UseTreeKind::Glob => {
// in case of a global path and the glob starts at the root, e.g., "::*"
if a.prefix.segments.len() == 1 && leading_modsep {
let kind = UseSegmentKind::Ident("".to_owned(), None);
result.path.push(UseSegment { kind, version });
result.path.push(UseSegment {
kind,
style_edition,
});
}
result.path.push(UseSegment {
kind: UseSegmentKind::Glob,
version,
style_edition,
});
}
UseTreeKind::Nested {
@ -470,7 +480,7 @@ impl UseTree {
",",
|tree| tree.span.lo(),
|tree| tree.span.hi(),
|_| Some("".to_owned()), // We only need comments for now.
|_| Ok("".to_owned()), // We only need comments for now.
context.snippet_provider.span_after(a.span, "{"),
a.span.hi(),
false,
@ -480,7 +490,10 @@ impl UseTree {
// e.g., "::{foo, bar}"
if a.prefix.segments.len() == 1 && leading_modsep {
let kind = UseSegmentKind::Ident("".to_owned(), None);
result.path.push(UseSegment { kind, version });
result.path.push(UseSegment {
kind,
style_edition,
});
}
let kind = UseSegmentKind::List(
list.iter()
@ -490,7 +503,10 @@ impl UseTree {
})
.collect(),
);
result.path.push(UseSegment { kind, version });
result.path.push(UseSegment {
kind,
style_edition,
});
}
UseTreeKind::Simple(ref rename) => {
// If the path has leading double colons and is composed of only 2 segments, then we
@ -519,7 +535,10 @@ impl UseTree {
_ => UseSegmentKind::Ident(name, alias),
};
let segment = UseSegment { kind, version };
let segment = UseSegment {
kind,
style_edition,
};
// `name` is already in result.
result.path.pop();
@ -614,7 +633,7 @@ impl UseTree {
list.sort();
last = UseSegment {
kind: UseSegmentKind::List(list),
version: last.version,
style_edition: last.style_edition,
};
}
@ -732,9 +751,12 @@ impl UseTree {
}) = self.path.last()
{
let self_segment = self.path.pop().unwrap();
let version = self_segment.version;
let style_edition = self_segment.style_edition;
let kind = UseSegmentKind::List(vec![UseTree::from_path(vec![self_segment], DUMMY_SP)]);
self.path.push(UseSegment { kind, version });
self.path.push(UseSegment {
kind,
style_edition,
});
}
self
}
@ -750,7 +772,7 @@ fn merge_rest(
return None;
}
if a.len() != len && b.len() != len {
let version = a[len].version;
let style_edition = a[len].style_edition;
if let UseSegmentKind::List(ref list) = a[len].kind {
let mut list = list.clone();
merge_use_trees_inner(
@ -760,7 +782,10 @@ fn merge_rest(
);
let mut new_path = b[..len].to_vec();
let kind = UseSegmentKind::List(list);
new_path.push(UseSegment { kind, version });
new_path.push(UseSegment {
kind,
style_edition,
});
return Some(new_path);
}
} else if len == 1 {
@ -770,9 +795,12 @@ fn merge_rest(
(&b[0], &a[1..])
};
let kind = UseSegmentKind::Slf(common.get_alias().map(ToString::to_string));
let version = a[0].version;
let style_edition = a[0].style_edition;
let mut list = vec![UseTree::from_path(
vec![UseSegment { kind, version }],
vec![UseSegment {
kind,
style_edition,
}],
DUMMY_SP,
)];
match rest {
@ -788,7 +816,7 @@ fn merge_rest(
b[0].clone(),
UseSegment {
kind: UseSegmentKind::List(list),
version,
style_edition,
},
]);
} else {
@ -801,8 +829,11 @@ fn merge_rest(
list.sort();
let mut new_path = b[..len].to_vec();
let kind = UseSegmentKind::List(list);
let version = a[0].version;
new_path.push(UseSegment { kind, version });
let style_edition = a[0].style_edition;
new_path.push(UseSegment {
kind,
style_edition,
});
Some(new_path)
}
@ -892,8 +923,8 @@ impl Ord for UseSegment {
| (Super(ref a), Super(ref b))
| (Crate(ref a), Crate(ref b)) => match (a, b) {
(Some(sa), Some(sb)) => {
if self.version == Version::Two {
sa.trim_start_matches("r#").cmp(sb.trim_start_matches("r#"))
if self.style_edition >= StyleEdition::Edition2024 {
version_sort(sa.trim_start_matches("r#"), sb.trim_start_matches("r#"))
} else {
a.cmp(b)
}
@ -902,11 +933,15 @@ impl Ord for UseSegment {
},
(Glob, Glob) => Ordering::Equal,
(Ident(ref pia, ref aa), Ident(ref pib, ref ab)) => {
let (ia, ib) = if self.version == Version::Two {
let (ia, ib) = if self.style_edition >= StyleEdition::Edition2024 {
(pia.trim_start_matches("r#"), pib.trim_start_matches("r#"))
} else {
(pia.as_str(), pib.as_str())
};
let ident_ord = if self.style_edition >= StyleEdition::Edition2024 {
version_sort(ia, ib)
} else {
// snake_case < CamelCase < UPPER_SNAKE_CASE
if ia.starts_with(char::is_uppercase) && ib.starts_with(char::is_lowercase) {
return Ordering::Greater;
@ -920,7 +955,9 @@ impl Ord for UseSegment {
if !is_upper_snake_case(ia) && is_upper_snake_case(ib) {
return Ordering::Less;
}
let ident_ord = ia.cmp(ib);
ia.cmp(ib)
};
if ident_ord != Ordering::Equal {
return ident_ord;
}
@ -928,9 +965,8 @@ impl Ord for UseSegment {
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(aas), Some(abs)) => {
if self.version == Version::Two {
aas.trim_start_matches("r#")
.cmp(abs.trim_start_matches("r#"))
if self.style_edition >= StyleEdition::Edition2024 {
version_sort(aas.trim_start_matches("r#"), abs.trim_start_matches("r#"))
} else {
aas.cmp(abs)
}
@ -982,21 +1018,24 @@ fn rewrite_nested_use_tree(
context: &RewriteContext<'_>,
use_tree_list: &[UseTree],
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let mut list_items = Vec::with_capacity(use_tree_list.len());
let nested_shape = match context.config.imports_indent() {
IndentStyle::Block => shape
.block_indent(context.config.tab_spaces())
.with_max_width(context.config)
.sub_width(1)?,
.sub_width(1)
.unknown_error()?,
IndentStyle::Visual => shape.visual_indent(0),
};
for use_tree in use_tree_list {
if let Some(mut list_item) = use_tree.list_item.clone() {
list_item.item = use_tree.rewrite(context, nested_shape);
list_item.item = use_tree.rewrite_result(context, nested_shape);
list_items.push(list_item);
} else {
list_items.push(ListItem::from_str(use_tree.rewrite(context, nested_shape)?));
list_items.push(ListItem::from_str(
use_tree.rewrite_result(context, nested_shape)?,
));
}
}
let has_nested_list = use_tree_list.iter().any(|use_segment| {
@ -1049,12 +1088,16 @@ fn rewrite_nested_use_tree(
format!("{{{list_str}}}")
};
Some(result)
Ok(result)
}
impl Rewrite for UseSegment {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
Some(match self.kind {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
Ok(match self.kind {
UseSegmentKind::Ident(ref ident, Some(ref rename)) => {
format!("{ident} as {rename}")
}
@ -1066,31 +1109,42 @@ impl Rewrite for UseSegment {
UseSegmentKind::Crate(Some(ref rename)) => format!("crate as {rename}"),
UseSegmentKind::Crate(None) => "crate".to_owned(),
UseSegmentKind::Glob => "*".to_owned(),
UseSegmentKind::List(ref use_tree_list) => rewrite_nested_use_tree(
UseSegmentKind::List(ref use_tree_list) => {
rewrite_nested_use_tree(
context,
use_tree_list,
// 1 = "{" and "}"
shape.offset_left(1)?.sub_width(1)?,
)?,
shape
.offset_left(1)
.and_then(|s| s.sub_width(1))
.unknown_error()?,
)?
}
})
}
}
impl Rewrite for UseTree {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
// This does NOT format attributes and visibility or add a trailing `;`.
fn rewrite(&self, context: &RewriteContext<'_>, mut shape: Shape) -> Option<String> {
fn rewrite_result(&self, context: &RewriteContext<'_>, mut shape: Shape) -> RewriteResult {
let mut result = String::with_capacity(256);
let mut iter = self.path.iter().peekable();
while let Some(segment) = iter.next() {
let segment_str = segment.rewrite(context, shape)?;
let segment_str = segment.rewrite_result(context, shape)?;
result.push_str(&segment_str);
if iter.peek().is_some() {
result.push_str("::");
// 2 = "::"
shape = shape.offset_left(2 + segment_str.len())?;
shape = shape
.offset_left(2 + segment_str.len())
.max_width_error(shape.width, self.span())?;
}
}
Some(result)
Ok(result)
}
}
@ -1114,7 +1168,7 @@ mod test {
struct Parser<'a> {
input: Peekable<Chars<'a>>,
version: Version,
style_edition: StyleEdition,
}
impl<'a> Parser<'a> {
@ -1132,7 +1186,7 @@ mod test {
buf: &mut String,
alias_buf: &mut Option<String>,
) {
let version = self.version;
let style_edition = self.style_edition;
if !buf.is_empty() {
let mut alias = None;
swap(alias_buf, &mut alias);
@ -1140,19 +1194,28 @@ mod test {
match buf.as_ref() {
"self" => {
let kind = UseSegmentKind::Slf(alias);
result.push(UseSegment { kind, version });
result.push(UseSegment {
kind,
style_edition,
});
*buf = String::new();
*alias_buf = None;
}
"super" => {
let kind = UseSegmentKind::Super(alias);
result.push(UseSegment { kind, version });
result.push(UseSegment {
kind,
style_edition,
});
*buf = String::new();
*alias_buf = None;
}
"crate" => {
let kind = UseSegmentKind::Crate(alias);
result.push(UseSegment { kind, version });
result.push(UseSegment {
kind,
style_edition,
});
*buf = String::new();
*alias_buf = None;
}
@ -1160,7 +1223,10 @@ mod test {
let mut name = String::new();
swap(buf, &mut name);
let kind = UseSegmentKind::Ident(name, alias);
result.push(UseSegment { kind, version });
result.push(UseSegment {
kind,
style_edition,
});
}
}
}
@ -1178,7 +1244,7 @@ mod test {
let kind = UseSegmentKind::List(self.parse_list());
result.push(UseSegment {
kind,
version: self.version,
style_edition: self.style_edition,
});
self.eat('}');
}
@ -1188,7 +1254,7 @@ mod test {
let kind = UseSegmentKind::Glob;
result.push(UseSegment {
kind,
version: self.version,
style_edition: self.style_edition,
});
}
':' => {
@ -1249,7 +1315,7 @@ mod test {
let mut parser = Parser {
input: s.chars().peekable(),
version: Version::One,
style_edition: StyleEdition::Edition2015,
};
parser.parse_in_list()
}

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,6 @@
#![allow(clippy::match_like_matches_macro)]
#![allow(unreachable_pub)]
// #[macro_use]
// extern crate tracing;
// N.B. these crates are loaded from the sysroot, so they need extern crate.
extern crate rustc_ast;
extern crate rustc_ast_pretty;
@ -48,8 +45,8 @@ use crate::shape::Indent;
use crate::utils::indent_next_line;
pub use crate::config::{
load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle,
Range, Verbosity,
CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle, Range,
StyleEdition, Verbosity, Version, load_config,
};
pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder};
@ -94,6 +91,7 @@ mod rewrite;
pub(crate) mod rustfmt_diff;
mod shape;
mod skip;
mod sort;
pub(crate) mod source_file;
pub(crate) mod source_map;
mod spanned;

View File

@ -5,10 +5,10 @@ use std::iter::Peekable;
use rustc_span::BytePos;
use crate::comment::{find_comment_end, rewrite_comment, FindUncommented};
use crate::comment::{FindUncommented, find_comment_end, rewrite_comment};
use crate::config::lists::*;
use crate::config::{Config, IndentStyle};
use crate::rewrite::RewriteContext;
use crate::rewrite::{RewriteContext, RewriteError, RewriteResult};
use crate::shape::{Indent, Shape};
use crate::utils::{
count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline,
@ -125,18 +125,18 @@ pub(crate) struct ListItem {
pub(crate) pre_comment_style: ListItemCommentStyle,
// Item should include attributes and doc comments. None indicates a failed
// rewrite.
pub(crate) item: Option<String>,
pub(crate) item: RewriteResult,
pub(crate) post_comment: Option<String>,
// Whether there is extra whitespace before this item.
pub(crate) new_lines: bool,
}
impl ListItem {
pub(crate) fn empty() -> ListItem {
pub(crate) fn from_item(item: RewriteResult) -> ListItem {
ListItem {
pre_comment: None,
pre_comment_style: ListItemCommentStyle::None,
item: None,
item: item,
post_comment: None,
new_lines: false,
}
@ -185,7 +185,7 @@ impl ListItem {
ListItem {
pre_comment: None,
pre_comment_style: ListItemCommentStyle::None,
item: Some(s.into()),
item: Ok(s.into()),
post_comment: None,
new_lines: false,
}
@ -197,7 +197,11 @@ impl ListItem {
!matches!(*s, Some(ref s) if !s.is_empty())
}
!(empty(&self.pre_comment) && empty(&self.item) && empty(&self.post_comment))
fn empty_result(s: &RewriteResult) -> bool {
!matches!(*s, Ok(ref s) if !s.is_empty())
}
!(empty(&self.pre_comment) && empty_result(&self.item) && empty(&self.post_comment))
}
}
@ -257,7 +261,7 @@ where
}
// Format a list of commented items into a string.
pub(crate) fn write_list<I, T>(items: I, formatting: &ListFormatting<'_>) -> Option<String>
pub(crate) fn write_list<I, T>(items: I, formatting: &ListFormatting<'_>) -> RewriteResult
where
I: IntoIterator<Item = T> + Clone,
T: AsRef<ListItem>,
@ -281,7 +285,7 @@ where
let indent_str = &formatting.shape.indent.to_string(formatting.config);
while let Some((i, item)) = iter.next() {
let item = item.as_ref();
let inner_item = item.item.as_ref()?;
let inner_item = item.item.as_ref().or_else(|err| Err(err.clone()))?;
let first = i == 0;
let last = iter.peek().is_none();
let mut separate = match sep_place {
@ -516,7 +520,7 @@ where
prev_item_is_nested_import = inner_item.contains("::");
}
Some(result)
Ok(result)
}
fn max_width_of_item_with_post_comment<I, T>(
@ -741,7 +745,7 @@ where
I: Iterator<Item = T>,
F1: Fn(&T) -> BytePos,
F2: Fn(&T) -> BytePos,
F3: Fn(&T) -> Option<String>,
F3: Fn(&T) -> RewriteResult,
{
type Item = ListItem;
@ -775,8 +779,9 @@ where
ListItem {
pre_comment,
pre_comment_style,
// leave_last is set to true only for rewrite_items
item: if self.inner.peek().is_none() && self.leave_last {
None
Err(RewriteError::SkipFormatting)
} else {
(self.get_item_string)(&item)
},
@ -805,7 +810,7 @@ where
I: Iterator<Item = T>,
F1: Fn(&T) -> BytePos,
F2: Fn(&T) -> BytePos,
F3: Fn(&T) -> Option<String>,
F3: Fn(&T) -> RewriteResult,
{
ListItems {
snippet_provider,

View File

@ -10,35 +10,37 @@
// and those with brackets will be formatted as array literals.
use std::collections::HashMap;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::panic::{AssertUnwindSafe, catch_unwind};
use rustc_ast::token::{BinOpToken, Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree};
use rustc_ast::{ast, ptr};
use rustc_ast_pretty::pprust;
use rustc_span::{
BytePos, DUMMY_SP, Span, Symbol,
symbol::{self, kw},
BytePos, Span, Symbol, DUMMY_SP,
};
use tracing::debug;
use crate::comment::{
contains_comment, CharClasses, FindUncommented, FullCodeCharKind, LineClasses,
CharClasses, FindUncommented, FullCodeCharKind, LineClasses, contains_comment,
};
use crate::config::StyleEdition;
use crate::config::lists::*;
use crate::config::Version;
use crate::expr::{rewrite_array, rewrite_assign_rhs, RhsAssignKind};
use crate::lists::{itemize_list, write_list, ListFormatting};
use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs};
use crate::lists::{ListFormatting, itemize_list, write_list};
use crate::overflow;
use crate::parse::macros::lazy_static::parse_lazy_static;
use crate::parse::macros::{parse_expr, parse_macro_args, ParsedMacroArgs};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args};
use crate::rewrite::{
MacroErrorKind, Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult,
};
use crate::shape::{Indent, Shape};
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
use crate::utils::{
filtered_str_fits, format_visibility, indent_next_line, is_empty_line, mk_sp,
remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout, NodeIdExt,
NodeIdExt, filtered_str_fits, format_visibility, indent_next_line, is_empty_line, mk_sp,
remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout,
};
use crate::visitor::FmtVisitor;
@ -72,22 +74,30 @@ impl MacroArg {
impl Rewrite for ast::Item {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
let mut visitor = crate::visitor::FmtVisitor::from_context(context);
visitor.block_indent = shape.indent;
visitor.last_pos = self.span().lo();
visitor.visit_item(self);
Some(visitor.buffer.to_owned())
Ok(visitor.buffer.to_owned())
}
}
impl Rewrite for MacroArg {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match *self {
MacroArg::Expr(ref expr) => expr.rewrite(context, shape),
MacroArg::Ty(ref ty) => ty.rewrite(context, shape),
MacroArg::Pat(ref pat) => pat.rewrite(context, shape),
MacroArg::Item(ref item) => item.rewrite(context, shape),
MacroArg::Keyword(ident, _) => Some(ident.name.to_string()),
MacroArg::Expr(ref expr) => expr.rewrite_result(context, shape),
MacroArg::Ty(ref ty) => ty.rewrite_result(context, shape),
MacroArg::Pat(ref pat) => pat.rewrite_result(context, shape),
MacroArg::Item(ref item) => item.rewrite_result(context, shape),
MacroArg::Keyword(ident, _) => Ok(ident.name.to_string()),
}
}
}
@ -111,12 +121,14 @@ fn rewrite_macro_name(
}
// Use this on failing to format the macro call.
// TODO(ding-young) We should also report macro parse failure to tell users why given snippet
// is left unformatted. One possible improvement is appending formatting error to context.report
fn return_macro_parse_failure_fallback(
context: &RewriteContext<'_>,
indent: Indent,
position: MacroPosition,
span: Span,
) -> Option<String> {
) -> RewriteResult {
// Mark this as a failure however we format it
context.macro_rewrite_failure.replace(true);
@ -134,7 +146,8 @@ fn return_macro_parse_failure_fallback(
})
.unwrap_or(false);
if is_like_block_indent_style {
return trim_left_preserve_layout(context.snippet(span), indent, context.config);
return trim_left_preserve_layout(context.snippet(span), indent, context.config)
.macro_error(MacroErrorKind::Unknown, span);
}
context.skipped_range.borrow_mut().push((
@ -147,7 +160,7 @@ fn return_macro_parse_failure_fallback(
if position == MacroPosition::Item {
snippet.push(';');
}
Some(snippet)
Ok(snippet)
}
pub(crate) fn rewrite_macro(
@ -156,13 +169,13 @@ pub(crate) fn rewrite_macro(
context: &RewriteContext<'_>,
shape: Shape,
position: MacroPosition,
) -> Option<String> {
) -> RewriteResult {
let should_skip = context
.skip_context
.macros
.skip(context.snippet(mac.path.span));
if should_skip {
None
Err(RewriteError::SkipFormatting)
} else {
let guard = context.enter_macro();
let result = catch_unwind(AssertUnwindSafe(|| {
@ -176,9 +189,16 @@ pub(crate) fn rewrite_macro(
)
}));
match result {
Err(..) | Ok(None) => {
Err(..) => {
context.macro_rewrite_failure.replace(true);
None
Err(RewriteError::MacroFailure {
kind: MacroErrorKind::Unknown,
span: mac.span(),
})
}
Ok(Err(e)) => {
context.macro_rewrite_failure.replace(true);
Err(e)
}
Ok(rw) => rw,
}
@ -192,11 +212,11 @@ fn rewrite_macro_inner(
shape: Shape,
position: MacroPosition,
is_nested_macro: bool,
) -> Option<String> {
) -> RewriteResult {
if context.config.use_try_shorthand() {
if let Some(expr) = convert_try_mac(mac, context) {
context.leave_macro();
return expr.rewrite(context, shape);
return expr.rewrite_result(context, shape);
}
}
@ -216,21 +236,27 @@ fn rewrite_macro_inner(
if ts.is_empty() && !has_comment {
return match style {
Delimiter::Parenthesis if position == MacroPosition::Item => {
Some(format!("{macro_name}();"))
Ok(format!("{macro_name}();"))
}
Delimiter::Bracket if position == MacroPosition::Item => {
Some(format!("{macro_name}[];"))
}
Delimiter::Parenthesis => Some(format!("{macro_name}()")),
Delimiter::Bracket => Some(format!("{macro_name}[]")),
Delimiter::Brace => Some(format!("{macro_name} {{}}")),
Delimiter::Bracket if position == MacroPosition::Item => Ok(format!("{macro_name}[];")),
Delimiter::Parenthesis => Ok(format!("{macro_name}()")),
Delimiter::Bracket => Ok(format!("{macro_name}[]")),
Delimiter::Brace => Ok(format!("{macro_name} {{}}")),
_ => unreachable!(),
};
}
// Format well-known macros which cannot be parsed as a valid AST.
if macro_name == "lazy_static!" && !has_comment {
if let success @ Some(..) = format_lazy_static(context, shape, ts.clone()) {
return success;
match format_lazy_static(context, shape, ts.clone(), mac.span()) {
Ok(rw) => return Ok(rw),
Err(err) => match err {
// We will move on to parsing macro args just like other macros
// if we could not parse lazy_static! with known syntax
RewriteError::MacroFailure { kind, span: _ }
if kind == MacroErrorKind::ParseFailure => {}
// If formatting fails even though parsing succeeds, return the err early
_ => return Err(err),
},
}
}
@ -267,7 +293,7 @@ fn rewrite_macro_inner(
Delimiter::Parenthesis => {
// Handle special case: `vec!(expr; expr)`
if vec_with_semi {
handle_vec_semi(context, shape, arg_vec, macro_name, style)
handle_vec_semi(context, shape, arg_vec, macro_name, style, mac.span())
} else {
// Format macro invocation as function call, preserve the trailing
// comma because not all macros support them.
@ -293,7 +319,7 @@ fn rewrite_macro_inner(
Delimiter::Bracket => {
// Handle special case: `vec![expr; expr]`
if vec_with_semi {
handle_vec_semi(context, shape, arg_vec, macro_name, style)
handle_vec_semi(context, shape, arg_vec, macro_name, style, mac.span())
} else {
// If we are rewriting `vec!` macro or other special macros,
// then we can rewrite this as a usual array literal.
@ -323,7 +349,7 @@ fn rewrite_macro_inner(
_ => "",
};
Some(format!("{rewrite}{comma}"))
Ok(format!("{rewrite}{comma}"))
}
}
Delimiter::Brace => {
@ -332,8 +358,8 @@ fn rewrite_macro_inner(
// anything in between the braces (for now).
let snippet = context.snippet(mac.span()).trim_start_matches(|c| c != '{');
match trim_left_preserve_layout(snippet, shape.indent, context.config) {
Some(macro_body) => Some(format!("{macro_name} {macro_body}")),
None => Some(format!("{macro_name} {snippet}")),
Some(macro_body) => Ok(format!("{macro_name} {macro_body}")),
None => Ok(format!("{macro_name} {snippet}")),
}
}
_ => unreachable!(),
@ -346,28 +372,32 @@ fn handle_vec_semi(
arg_vec: Vec<MacroArg>,
macro_name: String,
delim_token: Delimiter,
) -> Option<String> {
span: Span,
) -> RewriteResult {
let (left, right) = match delim_token {
Delimiter::Parenthesis => ("(", ")"),
Delimiter::Bracket => ("[", "]"),
_ => unreachable!(),
};
let mac_shape = shape.offset_left(macro_name.len())?;
// Should we return MaxWidthError, Or Macro failure
let mac_shape = shape
.offset_left(macro_name.len())
.max_width_error(shape.width, span)?;
// 8 = `vec![]` + `; ` or `vec!()` + `; `
let total_overhead = 8;
let nested_shape = mac_shape.block_indent(context.config.tab_spaces());
let lhs = arg_vec[0].rewrite(context, nested_shape)?;
let rhs = arg_vec[1].rewrite(context, nested_shape)?;
let lhs = arg_vec[0].rewrite_result(context, nested_shape)?;
let rhs = arg_vec[1].rewrite_result(context, nested_shape)?;
if !lhs.contains('\n')
&& !rhs.contains('\n')
&& lhs.len() + rhs.len() + total_overhead <= shape.width
{
// macro_name(lhs; rhs) or macro_name[lhs; rhs]
Some(format!("{macro_name}{left}{lhs}; {rhs}{right}"))
Ok(format!("{macro_name}{left}{lhs}; {rhs}{right}"))
} else {
// macro_name(\nlhs;\nrhs\n) or macro_name[\nlhs;\nrhs\n]
Some(format!(
Ok(format!(
"{}{}{}{};{}{}{}{}",
macro_name,
left,
@ -385,7 +415,7 @@ fn rewrite_empty_macro_def_body(
context: &RewriteContext<'_>,
span: Span,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
// Create an empty, dummy `ast::Block` representing an empty macro body
let block = ast::Block {
stmts: vec![].into(),
@ -395,7 +425,7 @@ fn rewrite_empty_macro_def_body(
tokens: None,
could_be_bare_literal: false,
};
block.rewrite(context, shape)
block.rewrite_result(context, shape)
}
pub(crate) fn rewrite_macro_def(
@ -406,8 +436,8 @@ pub(crate) fn rewrite_macro_def(
ident: symbol::Ident,
vis: &ast::Visibility,
span: Span,
) -> Option<String> {
let snippet = Some(remove_trailing_white_spaces(context.snippet(span)));
) -> RewriteResult {
let snippet = Ok(remove_trailing_white_spaces(context.snippet(span)));
if snippet.as_ref().map_or(true, |s| s.ends_with(';')) {
return snippet;
}
@ -442,7 +472,7 @@ pub(crate) fn rewrite_macro_def(
let lo = context.snippet_provider.span_before(span, "{");
result += " ";
result += &rewrite_empty_macro_def_body(context, span.with_lo(lo), shape)?;
return Some(result);
return Ok(result);
}
let branch_items = itemize_list(
@ -453,13 +483,14 @@ pub(crate) fn rewrite_macro_def(
|branch| branch.span.lo(),
|branch| branch.span.hi(),
|branch| match branch.rewrite(context, arm_shape, multi_branch_style) {
Some(v) => Some(v),
Ok(v) => Ok(v),
// if the rewrite returned None because a macro could not be rewritten, then return the
// original body
None if context.macro_rewrite_failure.get() => {
Some(context.snippet(branch.body).trim().to_string())
// TODO(ding-young) report rewrite error even if we return Ok with original snippet
Err(_) if context.macro_rewrite_failure.get() => {
Ok(context.snippet(branch.body).trim().to_string())
}
None => None,
Err(e) => Err(e),
},
context.snippet_provider.span_after(span, "{"),
span.hi(),
@ -478,8 +509,8 @@ pub(crate) fn rewrite_macro_def(
}
match write_list(&branch_items, &fmt) {
Some(ref s) => result += s,
None => return snippet,
Ok(ref s) => result += s,
Err(_) => return snippet,
}
if multi_branch_style {
@ -487,7 +518,7 @@ pub(crate) fn rewrite_macro_def(
result += "}";
}
Some(result)
Ok(result)
}
fn register_metavariable(
@ -639,12 +670,13 @@ impl MacroArgKind {
context: &RewriteContext<'_>,
shape: Shape,
use_multiple_lines: bool,
) -> Option<String> {
let rewrite_delimited_inner = |delim_tok, args| -> Option<(String, String, String)> {
) -> RewriteResult {
type DelimitedArgsRewrite = Result<(String, String, String), RewriteError>;
let rewrite_delimited_inner = |delim_tok, args| -> DelimitedArgsRewrite {
let inner = wrap_macro_args(context, args, shape)?;
let (lhs, rhs) = delim_token_to_str(context, delim_tok, shape, false, inner.is_empty());
if lhs.len() + inner.len() + rhs.len() <= shape.width {
return Some((lhs, inner, rhs));
return Ok((lhs, inner, rhs));
}
let (lhs, rhs) = delim_token_to_str(context, delim_tok, shape, true, false);
@ -652,27 +684,27 @@ impl MacroArgKind {
.block_indent(context.config.tab_spaces())
.with_max_width(context.config);
let inner = wrap_macro_args(context, args, nested_shape)?;
Some((lhs, inner, rhs))
Ok((lhs, inner, rhs))
};
match *self {
MacroArgKind::MetaVariable(ty, ref name) => Some(format!("${name}:{ty}")),
MacroArgKind::MetaVariable(ty, ref name) => Ok(format!("${name}:{ty}")),
MacroArgKind::Repeat(delim_tok, ref args, ref another, ref tok) => {
let (lhs, inner, rhs) = rewrite_delimited_inner(delim_tok, args)?;
let another = another
.as_ref()
.and_then(|a| a.rewrite(context, shape, use_multiple_lines))
.and_then(|a| a.rewrite(context, shape, use_multiple_lines).ok())
.unwrap_or_else(|| "".to_owned());
let repeat_tok = pprust::token_to_string(tok);
Some(format!("${lhs}{inner}{rhs}{another}{repeat_tok}"))
Ok(format!("${lhs}{inner}{rhs}{another}{repeat_tok}"))
}
MacroArgKind::Delimited(delim_tok, ref args) => {
rewrite_delimited_inner(delim_tok, args)
.map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs))
}
MacroArgKind::Separator(ref sep, ref prefix) => Some(format!("{prefix}{sep} ")),
MacroArgKind::Other(ref inner, ref prefix) => Some(format!("{prefix}{inner}")),
MacroArgKind::Separator(ref sep, ref prefix) => Ok(format!("{prefix}{sep} ")),
MacroArgKind::Other(ref inner, ref prefix) => Ok(format!("{prefix}{inner}")),
}
}
}
@ -688,7 +720,7 @@ impl ParsedMacroArg {
context: &RewriteContext<'_>,
shape: Shape,
use_multiple_lines: bool,
) -> Option<String> {
) -> RewriteResult {
self.kind.rewrite(context, shape, use_multiple_lines)
}
}
@ -966,9 +998,9 @@ fn wrap_macro_args(
context: &RewriteContext<'_>,
args: &[ParsedMacroArg],
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
wrap_macro_args_inner(context, args, shape, false)
.or_else(|| wrap_macro_args_inner(context, args, shape, true))
.or_else(|_| wrap_macro_args_inner(context, args, shape, true))
}
fn wrap_macro_args_inner(
@ -976,7 +1008,7 @@ fn wrap_macro_args_inner(
args: &[ParsedMacroArg],
shape: Shape,
use_multiple_lines: bool,
) -> Option<String> {
) -> RewriteResult {
let mut result = String::with_capacity(128);
let mut iter = args.iter().peekable();
let indent_str = shape.indent.to_string_with_newline(context.config);
@ -1002,9 +1034,9 @@ fn wrap_macro_args_inner(
}
if !use_multiple_lines && result.len() >= shape.width {
None
Err(RewriteError::Unknown)
} else {
Some(result)
Ok(result)
}
}
@ -1012,22 +1044,21 @@ fn wrap_macro_args_inner(
// for some common cases. I hope the basic logic is sufficient. Note that the
// meaning of some tokens is a bit different here from usual Rust, e.g., `*`
// and `(`/`)` have special meaning.
//
// We always try and format on one line.
// FIXME: Use multi-line when every thing does not fit on one line.
fn format_macro_args(
context: &RewriteContext<'_>,
token_stream: TokenStream,
shape: Shape,
) -> Option<String> {
if !context.config.format_macro_matchers() {
) -> RewriteResult {
let span = span_for_token_stream(&token_stream);
return Some(match span {
if !context.config.format_macro_matchers() {
return Ok(match span {
Some(span) => context.snippet(span).to_owned(),
None => String::new(),
});
}
let parsed_args = MacroArgParser::new().parse(token_stream)?;
let parsed_args = MacroArgParser::new()
.parse(token_stream)
.macro_error(MacroErrorKind::ParseFailure, span.unwrap())?;
wrap_macro_args(context, &parsed_args, shape)
}
@ -1132,9 +1163,9 @@ pub(crate) fn convert_try_mac(
pub(crate) fn macro_style(mac: &ast::MacCall, context: &RewriteContext<'_>) -> Delimiter {
let snippet = context.snippet(mac.span());
let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::max_value());
let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::max_value());
let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::max_value());
let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::MAX);
let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::MAX);
let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::MAX);
if paren_pos < bracket_pos && paren_pos < brace_pos {
Delimiter::Parenthesis
@ -1242,23 +1273,31 @@ impl MacroBranch {
context: &RewriteContext<'_>,
shape: Shape,
multi_branch_style: bool,
) -> Option<String> {
) -> RewriteResult {
// Only attempt to format function-like macros.
if self.args_paren_kind != Delimiter::Parenthesis {
// FIXME(#1539): implement for non-sugared macros.
return None;
return Err(RewriteError::MacroFailure {
kind: MacroErrorKind::Unknown,
span: self.span,
});
}
let old_body = context.snippet(self.body).trim();
let has_block_body = old_body.starts_with('{');
let mut prefix_width = 5; // 5 = " => {"
if context.config.version() == Version::Two {
if context.config.style_edition() >= StyleEdition::Edition2024 {
if has_block_body {
prefix_width = 6; // 6 = " => {{"
}
}
let mut result =
format_macro_args(context, self.args.clone(), shape.sub_width(prefix_width)?)?;
let mut result = format_macro_args(
context,
self.args.clone(),
shape
.sub_width(prefix_width)
.max_width_error(shape.width, self.span)?,
)?;
if multi_branch_style {
result += " =>";
@ -1267,7 +1306,7 @@ impl MacroBranch {
if !context.config.format_macro_bodies() {
result += " ";
result += context.snippet(self.whole_body);
return Some(result);
return Ok(result);
}
// The macro body is the most interesting part. It might end up as various
@ -1276,7 +1315,8 @@ impl MacroBranch {
// `$$`). We'll try and format like an AST node, but we'll substitute
// variables for new names with the same length first.
let (body_str, substs) = replace_names(old_body)?;
let (body_str, substs) =
replace_names(old_body).macro_error(MacroErrorKind::ReplaceMacroVariable, self.span)?;
let mut config = context.config.clone();
config.set().show_parse_errors(false);
@ -1299,13 +1339,21 @@ impl MacroBranch {
config.set().max_width(new_width);
match crate::format_code_block(&body_str, &config, true) {
Some(new_body) => new_body,
None => return None,
None => {
return Err(RewriteError::MacroFailure {
kind: MacroErrorKind::Unknown,
span: self.span,
});
}
}
}
};
if !filtered_str_fits(&new_body_snippet.snippet, config.max_width(), shape) {
return None;
return Err(RewriteError::ExceedsMaxWidth {
configured_width: shape.width,
span: self.span,
});
}
// Indent the body since it is in a block.
@ -1331,7 +1379,10 @@ impl MacroBranch {
for (old, new) in &substs {
if old_body.contains(new) {
debug!("rewrite_macro_def: bailing matching variable: `{}`", new);
return None;
return Err(RewriteError::MacroFailure {
kind: MacroErrorKind::ReplaceMacroVariable,
span: self.span,
});
}
new_body = new_body.replace(new, old);
}
@ -1346,7 +1397,7 @@ impl MacroBranch {
result += "}";
Some(result)
Ok(result)
}
}
@ -1366,7 +1417,8 @@ fn format_lazy_static(
context: &RewriteContext<'_>,
shape: Shape,
ts: TokenStream,
) -> Option<String> {
span: Span,
) -> RewriteResult {
let mut result = String::with_capacity(1024);
let nested_shape = shape
.block_indent(context.config.tab_spaces())
@ -1375,7 +1427,8 @@ fn format_lazy_static(
result.push_str("lazy_static! {");
result.push_str(&nested_shape.indent.to_string_with_newline(context.config));
let parsed_elems = parse_lazy_static(context, ts)?;
let parsed_elems =
parse_lazy_static(context, ts).macro_error(MacroErrorKind::ParseFailure, span)?;
let last = parsed_elems.len() - 1;
for (i, (vis, id, ty, expr)) in parsed_elems.iter().enumerate() {
// Rewrite as a static item.
@ -1385,14 +1438,16 @@ fn format_lazy_static(
"{}static ref {}: {} =",
vis,
id,
ty.rewrite(context, nested_shape)?
ty.rewrite_result(context, nested_shape)?
));
result.push_str(&rewrite_assign_rhs(
context,
stmt,
&*expr,
&RhsAssignKind::Expr(&expr.kind, expr.span),
nested_shape.sub_width(1)?,
nested_shape
.sub_width(1)
.max_width_error(nested_shape.width, expr.span)?,
)?);
result.push(';');
if i != last {
@ -1403,7 +1458,7 @@ fn format_lazy_static(
result.push_str(&shape.indent.to_string_with_newline(context.config));
result.push('}');
Some(result)
Ok(result)
}
fn rewrite_macro_with_items(
@ -1415,12 +1470,12 @@ fn rewrite_macro_with_items(
original_style: Delimiter,
position: MacroPosition,
span: Span,
) -> Option<String> {
) -> RewriteResult {
let style_to_delims = |style| match style {
Delimiter::Parenthesis => Some(("(", ")")),
Delimiter::Bracket => Some(("[", "]")),
Delimiter::Brace => Some((" {", "}")),
_ => None,
Delimiter::Parenthesis => Ok(("(", ")")),
Delimiter::Bracket => Ok(("[", "]")),
Delimiter::Brace => Ok((" {", "}")),
_ => Err(RewriteError::Unknown),
};
let (opener, closer) = style_to_delims(style)?;
@ -1442,7 +1497,7 @@ fn rewrite_macro_with_items(
for item in items {
let item = match item {
MacroArg::Item(item) => item,
_ => return None,
_ => return Err(RewriteError::Unknown),
};
visitor.visit_item(item);
}
@ -1455,5 +1510,5 @@ fn rewrite_macro_with_items(
result.push_str(&shape.indent.to_string_with_newline(context.config));
result.push_str(closer);
result.push_str(trailing_semicolon);
Some(result)
Ok(result)
}

View File

@ -2,19 +2,19 @@
use std::iter::repeat;
use rustc_ast::{ast, ptr, MatchKind};
use rustc_ast::{MatchKind, ast, ptr};
use rustc_span::{BytePos, Span};
use tracing::debug;
use crate::comment::{combine_strs_with_missing_comments, rewrite_comment, FindUncommented};
use crate::comment::{FindUncommented, combine_strs_with_missing_comments, rewrite_comment};
use crate::config::lists::*;
use crate::config::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, Version};
use crate::config::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, StyleEdition};
use crate::expr::{
format_expr, is_empty_block, is_simple_block, is_unsafe_block, prefer_next_line, rewrite_cond,
ExprType, RhsTactics,
ExprType, RhsTactics, format_expr, is_empty_block, is_simple_block, is_unsafe_block,
prefer_next_line, rewrite_cond,
};
use crate::lists::{itemize_list, write_list, ListFormatting};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::lists::{ListFormatting, itemize_list, write_list};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
@ -56,6 +56,10 @@ impl<'a> Spanned for ArmWrapper<'a> {
impl<'a> Rewrite for ArmWrapper<'a> {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
rewrite_match_arm(
context,
self.arm,
@ -74,7 +78,7 @@ pub(crate) fn rewrite_match(
span: Span,
attrs: &[ast::Attribute],
match_kind: MatchKind,
) -> Option<String> {
) -> RewriteResult {
// Do not take the rhs overhead from the upper expressions into account
// when rewriting match condition.
let cond_shape = Shape {
@ -83,10 +87,14 @@ pub(crate) fn rewrite_match(
};
// 6 = `match `
let cond_shape = match context.config.indent_style() {
IndentStyle::Visual => cond_shape.shrink_left(6)?,
IndentStyle::Block => cond_shape.offset_left(6)?,
IndentStyle::Visual => cond_shape
.shrink_left(6)
.max_width_error(shape.width, span)?,
IndentStyle::Block => cond_shape
.offset_left(6)
.max_width_error(shape.width, span)?,
};
let cond_str = cond.rewrite(context, cond_shape)?;
let cond_str = cond.rewrite_result(context, cond_shape)?;
let alt_block_sep = &shape.indent.to_string_with_newline(context.config);
let block_sep = match context.config.control_brace_style() {
ControlBraceStyle::AlwaysNextLine => alt_block_sep,
@ -105,12 +113,13 @@ pub(crate) fn rewrite_match(
let inner_attrs_str = if inner_attrs.is_empty() {
String::new()
} else {
let shape = match context.config.version() {
Version::One => shape,
_ => shape.block_indent(context.config.tab_spaces()),
let shape = if context.config.style_edition() <= StyleEdition::Edition2021 {
shape
} else {
shape.block_indent(context.config.tab_spaces())
};
inner_attrs
.rewrite(context, shape)
.rewrite_result(context, shape)
.map(|s| format!("{}{}\n", nested_indent_str, s))?
};
@ -130,16 +139,16 @@ pub(crate) fn rewrite_match(
if arms.is_empty() {
let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1)));
if snippet.trim().is_empty() {
Some(format!("match {cond_str} {{}}"))
Ok(format!("match {cond_str} {{}}"))
} else {
// Empty match with comments or inner attributes? We are not going to bother, sorry ;)
Some(context.snippet(span).to_owned())
Ok(context.snippet(span).to_owned())
}
} else {
let span_after_cond = mk_sp(cond.span.hi(), span.hi());
match match_kind {
MatchKind::Prefix => Some(format!(
MatchKind::Prefix => Ok(format!(
"match {}{}{{\n{}{}{}\n{}}}",
cond_str,
block_sep,
@ -148,7 +157,7 @@ pub(crate) fn rewrite_match(
rewrite_match_arms(context, arms, shape, span_after_cond, open_brace_pos)?,
shape.indent.to_string(context.config),
)),
MatchKind::Postfix => Some(format!(
MatchKind::Postfix => Ok(format!(
"{}.match{}{{\n{}{}{}\n{}}}",
cond_str,
block_sep,
@ -198,7 +207,7 @@ fn rewrite_match_arms(
shape: Shape,
span: Span,
open_brace_pos: BytePos,
) -> Option<String> {
) -> RewriteResult {
let arm_shape = shape
.block_indent(context.config.tab_spaces())
.with_max_width(context.config);
@ -218,7 +227,7 @@ fn rewrite_match_arms(
"|",
|arm| arm.span().lo(),
|arm| arm.span().hi(),
|arm| arm.rewrite(context, arm_shape),
|arm| arm.rewrite_result(context, arm_shape),
open_brace_pos,
span.hi(),
false,
@ -238,19 +247,19 @@ fn rewrite_match_arm(
shape: Shape,
is_last: bool,
has_leading_pipe: bool,
) -> Option<String> {
) -> RewriteResult {
let (missing_span, attrs_str) = if !arm.attrs.is_empty() {
if contains_skip(&arm.attrs) {
let (_, body) = flatten_arm_body(context, arm.body.as_deref()?, None);
let (_, body) = flatten_arm_body(context, arm.body.as_deref().unknown_error()?, None);
// `arm.span()` does not include trailing comma, add it manually.
return Some(format!(
return Ok(format!(
"{}{}",
context.snippet(arm.span()),
arm_comma(context.config, body, is_last),
));
}
let missing_span = mk_sp(arm.attrs[arm.attrs.len() - 1].span.hi(), arm.pat.span.lo());
(missing_span, arm.attrs.rewrite(context, shape)?)
(missing_span, arm.attrs.rewrite_result(context, shape)?)
} else {
(mk_sp(arm.span().lo(), arm.span().lo()), String::new())
};
@ -264,19 +273,25 @@ fn rewrite_match_arm(
};
// Patterns
let pat_shape = match &arm.body.as_ref()?.kind {
let pat_shape = match &arm.body.as_ref().unknown_error()?.kind {
ast::ExprKind::Block(_, Some(label)) => {
// Some block with a label ` => 'label: {`
// 7 = ` => : {`
let label_len = label.ident.as_str().len();
shape.sub_width(7 + label_len)?.offset_left(pipe_offset)?
shape
.sub_width(7 + label_len)
.and_then(|s| s.offset_left(pipe_offset))
.max_width_error(shape.width, arm.span)?
}
_ => {
// 5 = ` => {`
shape.sub_width(5)?.offset_left(pipe_offset)?
shape
.sub_width(5)
.and_then(|s| s.offset_left(pipe_offset))
.max_width_error(shape.width, arm.span)?
}
};
let pats_str = arm.pat.rewrite(context, pat_shape)?;
let pats_str = arm.pat.rewrite_result(context, pat_shape)?;
// Guard
let block_like_pat = trimmed_last_line_width(&pats_str) <= context.config.tab_spaces();
@ -298,10 +313,13 @@ fn rewrite_match_arm(
false,
)?;
let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.as_ref()?.span().lo());
let arrow_span = mk_sp(
arm.pat.span.hi(),
arm.body.as_ref().unknown_error()?.span().lo(),
);
rewrite_match_body(
context,
arm.body.as_ref()?,
arm.body.as_ref().unknown_error()?,
&lhs_str,
shape,
guard_str.contains('\n'),
@ -382,7 +400,7 @@ fn rewrite_match_body(
has_guard: bool,
arrow_span: Span,
is_last: bool,
) -> Option<String> {
) -> RewriteResult {
let (extend, body) = flatten_arm_body(
context,
body,
@ -403,7 +421,7 @@ fn rewrite_match_body(
_ => " ",
};
Some(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma))
Ok(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma))
};
let next_line_indent = if !is_block || is_empty_block {
@ -420,7 +438,7 @@ fn rewrite_match_body(
let arrow_snippet = context.snippet(arrow_span).trim();
// search for the arrow starting from the end of the snippet since there may be a match
// expression within the guard
let arrow_index = if context.config.version() == Version::One {
let arrow_index = if context.config.style_edition() <= StyleEdition::Edition2021 {
arrow_snippet.rfind("=>").unwrap()
} else {
arrow_snippet.find_last_uncommented("=>").unwrap()
@ -447,7 +465,7 @@ fn rewrite_match_body(
result.push_str(&nested_indent_str);
result.push_str(body_str);
result.push_str(comma);
return Some(result);
return Ok(result);
}
let indent_str = shape.indent.to_string_with_newline(context.config);
@ -458,7 +476,7 @@ fn rewrite_match_body(
} else {
""
};
let semicolon = if context.config.version() == Version::One {
let semicolon = if context.config.style_edition() <= StyleEdition::Edition2021 {
""
} else {
if semicolon_for_expr(context, body) {
@ -490,7 +508,7 @@ fn rewrite_match_body(
result.push_str(&block_sep);
result.push_str(body_str);
result.push_str(&body_suffix);
Some(result)
Ok(result)
};
// Let's try and get the arm body on the same line as the condition.
@ -499,7 +517,7 @@ fn rewrite_match_body(
.offset_left(extra_offset(pats_str, shape) + 4)
.and_then(|shape| shape.sub_width(comma.len()));
let orig_body = if forbid_same_line || !arrow_comment.is_empty() {
None
Err(RewriteError::Unknown)
} else if let Some(body_shape) = orig_body_shape {
let rewrite = nop_block_collapse(
format_expr(body, ExprType::Statement, context, body_shape),
@ -507,7 +525,7 @@ fn rewrite_match_body(
);
match rewrite {
Some(ref body_str)
Ok(ref body_str)
if is_block
|| (!body_str.contains('\n')
&& unicode_str_width(body_str) <= body_shape.width) =>
@ -517,7 +535,7 @@ fn rewrite_match_body(
_ => rewrite,
}
} else {
None
Err(RewriteError::Unknown)
};
let orig_budget = orig_body_shape.map_or(0, |shape| shape.width);
@ -528,20 +546,23 @@ fn rewrite_match_body(
next_line_body_shape.width,
);
match (orig_body, next_line_body) {
(Some(ref orig_str), Some(ref next_line_str))
(Ok(ref orig_str), Ok(ref next_line_str))
if prefer_next_line(orig_str, next_line_str, RhsTactics::Default) =>
{
combine_next_line_body(next_line_str)
}
(Some(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => {
(Ok(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => {
combine_orig_body(orig_str)
}
(Some(ref orig_str), Some(ref next_line_str)) if orig_str.contains('\n') => {
(Ok(ref orig_str), Ok(ref next_line_str)) if orig_str.contains('\n') => {
combine_next_line_body(next_line_str)
}
(None, Some(ref next_line_str)) => combine_next_line_body(next_line_str),
(None, None) => None,
(Some(ref orig_str), _) => combine_orig_body(orig_str),
(Err(_), Ok(ref next_line_str)) => combine_next_line_body(next_line_str),
// When both orig_body and next_line_body result in errors, we currently propagate the
// error from the second attempt since it is more generous with width constraints.
// This decision is somewhat arbitrary and is open to change.
(Err(_), Err(next_line_err)) => Err(next_line_err),
(Ok(ref orig_str), _) => combine_orig_body(orig_str),
}
}
@ -554,7 +575,7 @@ fn rewrite_guard(
// the arm (excludes offset).
pattern_width: usize,
multiline_pattern: bool,
) -> Option<String> {
) -> RewriteResult {
if let Some(ref guard) = *guard {
// First try to fit the guard string on the same line as the pattern.
// 4 = ` if `, 5 = ` => {`
@ -563,9 +584,9 @@ fn rewrite_guard(
.and_then(|s| s.sub_width(5));
if !multiline_pattern {
if let Some(cond_shape) = cond_shape {
if let Some(cond_str) = guard.rewrite(context, cond_shape) {
if let Ok(cond_str) = guard.rewrite_result(context, cond_shape) {
if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() {
return Some(format!(" if {cond_str}"));
return Ok(format!(" if {cond_str}"));
}
}
}
@ -575,24 +596,20 @@ fn rewrite_guard(
// 3 = `if `, 5 = ` => {`
let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config)
.offset_left(3)
.and_then(|s| s.sub_width(5));
if let Some(cond_shape) = cond_shape {
if let Some(cond_str) = guard.rewrite(context, cond_shape) {
return Some(format!(
.and_then(|s| s.sub_width(5))
.max_width_error(shape.width, guard.span)?;
let cond_str = guard.rewrite_result(context, cond_shape)?;
Ok(format!(
"{}if {}",
cond_shape.indent.to_string_with_newline(context.config),
cond_str
));
}
}
None
))
} else {
Some(String::new())
Ok(String::new())
}
}
fn nop_block_collapse(block_str: Option<String>, budget: usize) -> Option<String> {
fn nop_block_collapse(block_str: RewriteResult, budget: usize) -> RewriteResult {
debug!("nop_block_collapse {:?} {}", block_str, budget);
block_str.map(|block_str| {
if block_str.starts_with('{')

View File

@ -1,10 +1,10 @@
use rustc_span::{BytePos, Pos, Span};
use tracing::debug;
use crate::comment::{is_last_comment_block, rewrite_comment, CodeCharKind, CommentCodeSlices};
use crate::config::file_lines::FileLines;
use crate::comment::{CodeCharKind, CommentCodeSlices, is_last_comment_block, rewrite_comment};
use crate::config::FileName;
use crate::config::Version;
use crate::config::StyleEdition;
use crate::config::file_lines::FileLines;
use crate::coverage::transform_missing_snippet;
use crate::shape::{Indent, Shape};
use crate::source_map::LineRangeUtils;
@ -247,7 +247,9 @@ impl<'a> FmtVisitor<'a> {
let indent_str = self.block_indent.to_string(self.config);
self.push_str(&indent_str);
self.block_indent
} else if self.config.version() == Version::Two && !snippet.starts_with('\n') {
} else if self.config.style_edition() >= StyleEdition::Edition2024
&& !snippet.starts_with('\n')
{
// The comment appears on the same line as the previous formatted code.
// Assuming that comment is logically associated with that code, we want to keep it on
// the same level and avoid mixing it with possible other comment.
@ -283,13 +285,13 @@ impl<'a> FmtVisitor<'a> {
let other_lines = &subslice[offset + 1..];
let comment_str =
rewrite_comment(other_lines, false, comment_shape, self.config)
.unwrap_or_else(|| String::from(other_lines));
.unwrap_or_else(|_| String::from(other_lines));
self.push_str(&comment_str);
}
}
} else {
let comment_str = rewrite_comment(subslice, false, comment_shape, self.config)
.unwrap_or_else(|| String::from(subslice));
.unwrap_or_else(|_| String::from(subslice));
self.push_str(&comment_str);
}

View File

@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
use rustc_ast::ast;
use rustc_ast::visit::Visitor;
use rustc_span::symbol::{self, sym, Symbol};
use rustc_span::Span;
use rustc_span::symbol::{self, Symbol, sym};
use thin_vec::ThinVec;
use thiserror::Error;

View File

@ -9,22 +9,22 @@ use rustc_span::Span;
use tracing::debug;
use crate::closures;
use crate::config::Version;
use crate::config::{lists::*, Config};
use crate::config::StyleEdition;
use crate::config::{Config, lists::*};
use crate::expr::{
can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr,
rewrite_cond,
};
use crate::lists::{
definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list,
};
use crate::macros::MacroArg;
use crate::patterns::{can_be_overflowed_pat, TuplePatField};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::patterns::{TuplePatField, can_be_overflowed_pat};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
use crate::types::{can_be_overflowed_type, SegmentParam};
use crate::types::{SegmentParam, can_be_overflowed_type};
use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};
/// A list of `format!`-like macros, that take a long format string and a list of arguments to
@ -91,6 +91,10 @@ impl<'a> Rewrite for OverflowableItem<'a> {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.map(|item| item.rewrite(context, shape))
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
self.map(|item| item.rewrite_result(context, shape))
}
}
impl<'a> Spanned for OverflowableItem<'a> {
@ -201,8 +205,12 @@ impl<'a> OverflowableItem<'a> {
OverflowableItem::NestedMetaItem(..) => SPECIAL_CASE_ATTR,
_ => &[],
};
let additional_cases = match (self, config.version()) {
(OverflowableItem::MacroArg(..), Version::Two) => SPECIAL_CASE_MACROS_V2,
let additional_cases = match self {
OverflowableItem::MacroArg(..)
if config.style_edition() >= StyleEdition::Edition2024 =>
{
SPECIAL_CASE_MACROS_V2
}
_ => &[],
};
base_cases.iter().chain(additional_cases)
@ -278,7 +286,7 @@ pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>(
span: Span,
item_max_width: usize,
force_separator_tactic: Option<SeparatorTactic>,
) -> Option<String> {
) -> RewriteResult {
Context::new(
context,
items,
@ -300,7 +308,7 @@ pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>(
items: impl Iterator<Item = &'a T>,
shape: Shape,
span: Span,
) -> Option<String> {
) -> RewriteResult {
Context::new(
context,
items,
@ -324,7 +332,7 @@ pub(crate) fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>
span: Span,
force_separator_tactic: Option<SeparatorTactic>,
delim_token: Option<Delimiter>,
) -> Option<String> {
) -> RewriteResult {
let (lhs, rhs) = match delim_token {
Some(Delimiter::Parenthesis) => ("(", ")"),
Some(Delimiter::Brace) => ("{", "}"),
@ -428,7 +436,7 @@ impl<'a> Context<'a> {
if closures::args_have_many_closure(&self.items) {
None
} else {
closures::rewrite_last_closure(self.context, expr, shape)
closures::rewrite_last_closure(self.context, expr, shape).ok()
}
}
@ -457,7 +465,7 @@ impl<'a> Context<'a> {
if let Some(rewrite) = rewrite {
// splitn(2, *).next().unwrap() is always safe.
let rewrite_first_line = Some(rewrite.splitn(2, '\n').next().unwrap().to_owned());
let rewrite_first_line = Ok(rewrite.splitn(2, '\n').next().unwrap().to_owned());
last_list_item.item = rewrite_first_line;
Some(rewrite)
} else {
@ -495,7 +503,7 @@ impl<'a> Context<'a> {
Some(OverflowableItem::MacroArg(MacroArg::Expr(expr)))
if !combine_arg_with_callee
&& is_method_call(expr)
&& self.context.config.version() == Version::Two =>
&& self.context.config.style_edition() >= StyleEdition::Edition2024 =>
{
self.context.force_one_line_chain.replace(true);
}
@ -545,22 +553,23 @@ impl<'a> Context<'a> {
.and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n'));
if no_newline {
list_items[self.items.len() - 1].item = rw;
list_items[self.items.len() - 1].item = rw.unknown_error();
} else {
list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
list_items[self.items.len() - 1].item = Ok(overflowed.to_owned());
}
} else {
list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
list_items[self.items.len() - 1].item = Ok(overflowed.to_owned());
}
}
(true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => {
list_items[self.items.len() - 1].item = placeholder;
list_items[self.items.len() - 1].item = placeholder.unknown_error();
}
_ if !self.items.is_empty() => {
list_items[self.items.len() - 1].item = self
.items
.last()
.and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
.and_then(|last_item| last_item.rewrite(self.context, self.nested_shape))
.unknown_error();
// Use horizontal layout for a function with a single argument as long as
// everything fits in a single line.
@ -613,7 +622,7 @@ impl<'a> Context<'a> {
tactic
}
fn rewrite_items(&self) -> Option<(bool, String)> {
fn rewrite_items(&self) -> Result<(bool, String), RewriteError> {
let span = self.items_span();
debug!("items: {:?}", self.items);
@ -624,7 +633,7 @@ impl<'a> Context<'a> {
",",
|item| item.span().lo(),
|item| item.span().hi(),
|item| item.rewrite(self.context, self.nested_shape),
|item| item.rewrite_result(self.context, self.nested_shape),
span.lo(),
span.hi(),
true,
@ -689,7 +698,8 @@ impl<'a> Context<'a> {
);
result.push_str(self.ident);
result.push_str(prefix);
let force_single_line = if self.context.config.version() == Version::Two {
let force_single_line = if self.context.config.style_edition() >= StyleEdition::Edition2024
{
!self.context.use_block_indent() || (is_extendable && extend_width <= shape.width)
} else {
// 2 = `()`
@ -711,7 +721,7 @@ impl<'a> Context<'a> {
result
}
fn rewrite(&self, shape: Shape) -> Option<String> {
fn rewrite(&self, shape: Shape) -> RewriteResult {
let (extendable, items_str) = self.rewrite_items()?;
// If we are using visual indent style and failed to format, retry with block indent.
@ -725,7 +735,7 @@ impl<'a> Context<'a> {
return result;
}
Some(self.wrap_items(&items_str, shape, extendable))
Ok(self.wrap_items(&items_str, shape, extendable))
}
}

View File

@ -1,9 +1,11 @@
use rustc_ast::ast;
use rustc_span::Span;
use crate::config::lists::*;
use crate::config::IndentStyle;
use crate::rewrite::{Rewrite, RewriteContext};
use crate::config::lists::*;
use crate::rewrite::{Rewrite, RewriteContext, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::spanned::Spanned;
use crate::utils::{
first_line_width, is_single_line, last_line_width, trimmed_last_line_width, wrap_str,
};
@ -40,14 +42,17 @@ pub(crate) fn rewrite_all_pairs(
expr: &ast::Expr,
shape: Shape,
context: &RewriteContext<'_>,
) -> Option<String> {
expr.flatten(context, shape).and_then(|list| {
) -> RewriteResult {
expr.flatten(context, shape)
.unknown_error()
.and_then(|list| {
if list.let_chain_count() > 0 && !list.can_rewrite_let_chain_single_line() {
rewrite_pairs_multiline(&list, shape, context)
} else {
// First we try formatting on one line.
rewrite_pairs_one_line(&list, shape, context)
.or_else(|| rewrite_pairs_multiline(&list, shape, context))
.unknown_error()
.or_else(|_| rewrite_pairs_multiline(&list, shape, context))
}
})
}
@ -65,7 +70,7 @@ fn rewrite_pairs_one_line<T: Rewrite>(
let base_shape = shape.block();
for ((_, rewrite), s) in list.list.iter().zip(list.separators.iter()) {
if let Some(rewrite) = rewrite {
if let Ok(rewrite) = rewrite {
if !is_single_line(rewrite) || result.len() > shape.width {
return None;
}
@ -104,19 +109,20 @@ fn rewrite_pairs_multiline<T: Rewrite>(
list: &PairList<'_, '_, T>,
shape: Shape,
context: &RewriteContext<'_>,
) -> Option<String> {
) -> RewriteResult {
let rhs_offset = shape.rhs_overhead(context.config);
let nested_shape = (match context.config.indent_style() {
IndentStyle::Visual => shape.visual_indent(0),
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
})
.with_max_width(context.config)
.sub_width(rhs_offset)?;
.sub_width(rhs_offset)
.max_width_error(shape.width, list.span)?;
let indent_str = nested_shape.indent.to_string_with_newline(context.config);
let mut result = String::new();
result.push_str(list.list[0].1.as_ref()?);
result.push_str(list.list[0].1.as_ref().map_err(|err| err.clone())?);
for ((e, default_rw), s) in list.list[1..].iter().zip(list.separators.iter()) {
// The following test checks if we should keep two subexprs on the same
@ -132,7 +138,7 @@ fn rewrite_pairs_multiline<T: Rewrite>(
if let Some(line_shape) =
shape.offset_left(s.len() + 2 + trimmed_last_line_width(&result))
{
if let Some(rewrite) = e.rewrite(context, line_shape) {
if let Ok(rewrite) = e.rewrite_result(context, line_shape) {
result.push(' ');
result.push_str(s);
result.push(' ');
@ -155,9 +161,9 @@ fn rewrite_pairs_multiline<T: Rewrite>(
}
}
result.push_str(default_rw.as_ref()?);
result.push_str(default_rw.as_ref().map_err(|err| err.clone())?);
}
Some(result)
Ok(result)
}
// Rewrites a single pair.
@ -168,10 +174,10 @@ pub(crate) fn rewrite_pair<LHS, RHS>(
context: &RewriteContext<'_>,
shape: Shape,
separator_place: SeparatorPlace,
) -> Option<String>
) -> RewriteResult
where
LHS: Rewrite,
RHS: Rewrite,
LHS: Rewrite + Spanned,
RHS: Rewrite + Spanned,
{
let tab_spaces = context.config.tab_spaces();
let lhs_overhead = match separator_place {
@ -183,15 +189,17 @@ where
..shape
};
let lhs_result = lhs
.rewrite(context, lhs_shape)
.rewrite_result(context, lhs_shape)
.map(|lhs_str| format!("{}{}", pp.prefix, lhs_str))?;
// Try to put both lhs and rhs on the same line.
let rhs_orig_result = shape
.offset_left(last_line_width(&lhs_result) + pp.infix.len())
.and_then(|s| s.sub_width(pp.suffix.len()))
.and_then(|rhs_shape| rhs.rewrite(context, rhs_shape));
if let Some(ref rhs_result) = rhs_orig_result {
.max_width_error(shape.width, rhs.span())
.and_then(|rhs_shape| rhs.rewrite_result(context, rhs_shape));
if let Ok(ref rhs_result) = rhs_orig_result {
// If the length of the lhs is equal to or shorter than the tab width or
// the rhs looks like block expression, we put the rhs on the same
// line with the lhs even if the rhs is multi-lined.
@ -207,7 +215,7 @@ where
+ first_line_width(rhs_result)
+ pp.suffix.len();
if one_line_width <= shape.width {
return Some(format!(
return Ok(format!(
"{}{}{}{}",
lhs_result, pp.infix, rhs_result, pp.suffix
));
@ -219,13 +227,15 @@ where
// Re-evaluate the rhs because we have more space now:
let mut rhs_shape = match context.config.indent_style() {
IndentStyle::Visual => shape
.sub_width(pp.suffix.len() + pp.prefix.len())?
.sub_width(pp.suffix.len() + pp.prefix.len())
.max_width_error(shape.width, rhs.span())?
.visual_indent(pp.prefix.len()),
IndentStyle::Block => {
// Try to calculate the initial constraint on the right hand side.
let rhs_overhead = shape.rhs_overhead(context.config);
Shape::indented(shape.indent.block_indent(context.config), context.config)
.sub_width(rhs_overhead)?
.sub_width(rhs_overhead)
.max_width_error(shape.width, rhs.span())?
}
};
let infix = match separator_place {
@ -233,15 +243,17 @@ where
SeparatorPlace::Front => pp.infix.trim_start(),
};
if separator_place == SeparatorPlace::Front {
rhs_shape = rhs_shape.offset_left(infix.len())?;
rhs_shape = rhs_shape
.offset_left(infix.len())
.max_width_error(rhs_shape.width, rhs.span())?;
}
let rhs_result = rhs.rewrite(context, rhs_shape)?;
let rhs_result = rhs.rewrite_result(context, rhs_shape)?;
let indent_str = rhs_shape.indent.to_string_with_newline(context.config);
let infix_with_sep = match separator_place {
SeparatorPlace::Back => format!("{infix}{indent_str}"),
SeparatorPlace::Front => format!("{indent_str}{infix}"),
};
Some(format!(
Ok(format!(
"{}{}{}{}",
lhs_result, infix_with_sep, rhs_result, pp.suffix
))
@ -255,8 +267,9 @@ trait FlattenPair: Rewrite + Sized {
}
struct PairList<'a, 'b, T: Rewrite> {
list: Vec<(&'b T, Option<String>)>,
list: Vec<(&'b T, RewriteResult)>,
separators: Vec<&'a str>,
span: Span,
}
fn is_ident(expr: &ast::Expr) -> bool {
@ -303,7 +316,7 @@ impl FlattenPair for ast::Expr {
let default_rewrite = |node: &ast::Expr, sep: usize, is_first: bool| {
if is_first {
return node.rewrite(context, shape);
return node.rewrite_result(context, shape);
}
let nested_overhead = sep + 1;
let rhs_offset = shape.rhs_overhead(context.config);
@ -312,12 +325,17 @@ impl FlattenPair for ast::Expr {
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
})
.with_max_width(context.config)
.sub_width(rhs_offset)?;
.sub_width(rhs_offset)
.max_width_error(shape.width, node.span)?;
let default_shape = match context.config.binop_separator() {
SeparatorPlace::Back => nested_shape.sub_width(nested_overhead)?,
SeparatorPlace::Front => nested_shape.offset_left(nested_overhead)?,
SeparatorPlace::Back => nested_shape
.sub_width(nested_overhead)
.max_width_error(nested_shape.width, node.span)?,
SeparatorPlace::Front => nested_shape
.offset_left(nested_overhead)
.max_width_error(nested_shape.width, node.span)?,
};
node.rewrite(context, default_shape)
node.rewrite_result(context, default_shape)
};
// Turn a tree of binop expressions into a list using a depth-first,
@ -326,6 +344,7 @@ impl FlattenPair for ast::Expr {
let mut list = vec![];
let mut separators = vec![];
let mut node = self;
let span = self.span();
loop {
match node.kind {
ast::ExprKind::Binary(op, ref lhs, _) if op.node == top_op => {
@ -352,7 +371,11 @@ impl FlattenPair for ast::Expr {
}
assert_eq!(list.len() - 1, separators.len());
Some(PairList { list, separators })
Some(PairList {
list,
separators,
span,
})
}
}

View File

@ -1,5 +1,5 @@
use rustc_ast::ast;
use rustc_builtin_macros::asm::{parse_asm_args, AsmArgs};
use rustc_builtin_macros::asm::{AsmArgs, parse_asm_args};
use crate::rewrite::RewriteContext;

View File

@ -1,4 +1,4 @@
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::panic::{AssertUnwindSafe, catch_unwind};
use rustc_ast::ast;
use rustc_ast::token::{Delimiter, TokenKind};

View File

@ -1,11 +1,11 @@
use rustc_ast::token::{Delimiter, NonterminalKind, NtExprKind::*, NtPatKind::*, TokenKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{ast, ptr};
use rustc_parse::parser::{ForceCollect, Parser, Recovery};
use rustc_parse::MACRO_ARGUMENTS;
use rustc_parse::parser::{ForceCollect, Parser, Recovery};
use rustc_session::parse::ParseSess;
use rustc_span::symbol::{self, kw};
use rustc_span::Symbol;
use rustc_span::symbol::{self, kw};
use crate::macros::MacroArg;
use crate::rewrite::RewriteContext;

View File

@ -1,4 +1,4 @@
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::path::{Path, PathBuf};
use rustc_ast::token::TokenKind;
@ -6,11 +6,11 @@ use rustc_ast::{ast, attr, ptr};
use rustc_errors::Diag;
use rustc_parse::parser::Parser as RawParser;
use rustc_parse::{new_parser_from_file, new_parser_from_source_str, unwrap_or_emit_fatal};
use rustc_span::{sym, Span};
use rustc_span::{Span, sym};
use thin_vec::ThinVec;
use crate::parse::session::ParseSess;
use crate::Input;
use crate::parse::session::ParseSess;
pub(crate) type DirectoryOwnership = rustc_expand::module::DirOwnership;
pub(crate) type ModulePathSuccess = rustc_expand::module::ModulePathSuccess;

View File

@ -2,13 +2,14 @@ use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use rustc_data_structures::sync::{IntoDynSyncSend, Lrc};
use rustc_errors::emitter::{stderr_destination, DynEmitter, Emitter, HumanEmitter, SilentEmitter};
use rustc_errors::emitter::{DynEmitter, Emitter, HumanEmitter, SilentEmitter, stderr_destination};
use rustc_errors::translation::Translate;
use rustc_errors::{ColorConfig, Diag, DiagCtxt, DiagInner, Level as DiagnosticLevel};
use rustc_session::parse::ParseSess as RawParseSess;
use rustc_span::{
BytePos, Span,
source_map::{FilePathMapping, SourceMap},
symbol, BytePos, Span,
symbol,
};
use crate::config::file_lines::LineRange;
@ -394,7 +395,9 @@ mod tests {
}
fn get_ignore_list(config: &str) -> IgnoreList {
Config::from_toml(config, Path::new("")).unwrap().ignore()
Config::from_toml(config, Path::new("./rustfmt.toml"))
.unwrap()
.ignore()
}
#[test]

View File

@ -2,22 +2,22 @@ use rustc_ast::ast::{self, BindingMode, ByRef, Pat, PatField, PatKind, RangeEnd,
use rustc_ast::ptr;
use rustc_span::{BytePos, Span};
use crate::comment::{combine_strs_with_missing_comments, FindUncommented};
use crate::comment::{FindUncommented, combine_strs_with_missing_comments};
use crate::config::StyleEdition;
use crate::config::lists::*;
use crate::config::Version;
use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field};
use crate::lists::{
definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape,
struct_lit_tactic, write_list, ListFormatting, ListItem, Separator,
ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, shape_for_tactic,
struct_lit_formatting, struct_lit_shape, struct_lit_tactic, write_list,
};
use crate::macros::{rewrite_macro, MacroPosition};
use crate::macros::{MacroPosition, rewrite_macro};
use crate::overflow;
use crate::pairs::{rewrite_pair, PairParts};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::pairs::{PairParts, rewrite_pair};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
use crate::types::{rewrite_path, PathContext};
use crate::types::{PathContext, rewrite_path};
use crate::utils::{format_mutability, mk_sp, mk_sp_lo_plus_one, rewrite_ident};
/// Returns `true` if the given pattern is "short".
@ -61,25 +61,36 @@ fn is_short_pattern_inner(pat: &ast::Pat) -> bool {
}
}
struct RangeOperand<'a>(&'a Option<ptr::P<ast::Expr>>);
pub(crate) struct RangeOperand<'a> {
operand: &'a Option<ptr::P<ast::Expr>>,
pub(crate) span: Span,
}
impl<'a> Rewrite for RangeOperand<'a> {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
match &self.0 {
None => Some("".to_owned()),
Some(ref exp) => exp.rewrite(context, shape),
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match &self.operand {
None => Ok("".to_owned()),
Some(ref exp) => exp.rewrite_result(context, shape),
}
}
}
impl Rewrite for Pat {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match self.kind {
PatKind::Or(ref pats) => {
let pat_strs = pats
.iter()
.map(|p| p.rewrite(context, shape))
.collect::<Option<Vec<_>>>()?;
.map(|p| p.rewrite_result(context, shape))
.collect::<Result<Vec<_>, RewriteError>>()?;
let use_mixed_layout = pats
.iter()
@ -115,14 +126,21 @@ impl Rewrite for Pat {
let sub_pat = match *sub_pat {
Some(ref p) => {
// 2 - `@ `.
let width = shape.width.checked_sub(
mut_prefix.len() + ref_kw.len() + mut_infix.len() + id_str.len() + 2,
)?;
let width = shape
.width
.checked_sub(
mut_prefix.len()
+ ref_kw.len()
+ mut_infix.len()
+ id_str.len()
+ 2,
)
.max_width_error(shape.width, p.span())?;
let lo = context.snippet_provider.span_after(self.span, "@");
combine_strs_with_missing_comments(
context,
"@",
&p.rewrite(context, Shape::legacy(width, shape.indent))?,
&p.rewrite_result(context, Shape::legacy(width, shape.indent))?,
mk_sp(lo, p.span.lo()),
shape,
true,
@ -207,19 +225,25 @@ impl Rewrite for Pat {
}
PatKind::Wild => {
if 1 <= shape.width {
Some("_".to_owned())
Ok("_".to_owned())
} else {
None
Err(RewriteError::ExceedsMaxWidth {
configured_width: 1,
span: self.span,
})
}
}
PatKind::Rest => {
if 1 <= shape.width {
Some("..".to_owned())
Ok("..".to_owned())
} else {
None
Err(RewriteError::ExceedsMaxWidth {
configured_width: 1,
span: self.span,
})
}
}
PatKind::Never => None,
PatKind::Never => Err(RewriteError::Unknown),
PatKind::Range(ref lhs, ref rhs, ref end_kind) => {
let infix = match end_kind.node {
RangeEnd::Included(RangeSyntax::DotDotDot) => "...",
@ -239,9 +263,17 @@ impl Rewrite for Pat {
} else {
infix.to_owned()
};
let lspan = self.span.with_hi(end_kind.span.lo());
let rspan = self.span.with_lo(end_kind.span.hi());
rewrite_pair(
&RangeOperand(lhs),
&RangeOperand(rhs),
&RangeOperand {
operand: lhs,
span: lspan,
},
&RangeOperand {
operand: rhs,
span: rspan,
},
PairParts::infix(&infix),
context,
shape,
@ -260,19 +292,21 @@ impl Rewrite for Pat {
let path_str = rewrite_path(context, PathContext::Expr, q_self, path, shape)?;
rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape)
}
PatKind::Lit(ref expr) => expr.rewrite(context, shape),
PatKind::Slice(ref slice_pat) if context.config.version() == Version::One => {
PatKind::Lit(ref expr) => expr.rewrite_result(context, shape),
PatKind::Slice(ref slice_pat)
if context.config.style_edition() <= StyleEdition::Edition2021 =>
{
let rw: Vec<String> = slice_pat
.iter()
.map(|p| {
if let Some(rw) = p.rewrite(context, shape) {
if let Ok(rw) = p.rewrite_result(context, shape) {
rw
} else {
context.snippet(p.span).to_string()
}
})
.collect();
Some(format!("[{}]", rw.join(", ")))
Ok(format!("[{}]", rw.join(", ")))
}
PatKind::Slice(ref slice_pat) => overflow::rewrite_with_square_brackets(
context,
@ -296,10 +330,16 @@ impl Rewrite for Pat {
rewrite_macro(mac, None, context, shape, MacroPosition::Pat)
}
PatKind::Paren(ref pat) => pat
.rewrite(context, shape.offset_left(1)?.sub_width(1)?)
.rewrite_result(
context,
shape
.offset_left(1)
.and_then(|s| s.sub_width(1))
.max_width_error(shape.width, self.span)?,
)
.map(|inner_pat| format!("({})", inner_pat)),
PatKind::Err(_) => None,
PatKind::Deref(_) => None,
PatKind::Err(_) => Err(RewriteError::Unknown),
PatKind::Deref(_) => Err(RewriteError::Unknown),
}
}
}
@ -312,20 +352,21 @@ fn rewrite_struct_pat(
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
// 2 = ` {`
let path_shape = shape.sub_width(2)?;
let path_shape = shape.sub_width(2).max_width_error(shape.width, span)?;
let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
if fields.is_empty() && !ellipsis {
return Some(format!("{path_str} {{}}"));
return Ok(format!("{path_str} {{}}"));
}
let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
// 3 = ` { `, 2 = ` }`.
let (h_shape, v_shape) =
struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)?;
struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)
.max_width_error(shape.width, span)?;
let items = itemize_list(
context.snippet_provider,
@ -340,7 +381,7 @@ fn rewrite_struct_pat(
}
},
|f| f.span.hi(),
|f| f.rewrite(context, v_shape),
|f| f.rewrite_result(context, v_shape),
context.snippet_provider.span_after(span, "{"),
span.hi(),
false,
@ -379,11 +420,15 @@ fn rewrite_struct_pat(
// ast::Pat doesn't have attrs so use &[]
let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
Some(format!("{path_str} {{{fields_str}}}"))
Ok(format!("{path_str} {{{fields_str}}}"))
}
impl Rewrite for PatField {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
let hi_pos = if let Some(last) = self.attrs.last() {
last.span.hi()
} else {
@ -393,10 +438,10 @@ impl Rewrite for PatField {
let attrs_str = if self.attrs.is_empty() {
String::from("")
} else {
self.attrs.rewrite(context, shape)?
self.attrs.rewrite_result(context, shape)?
};
let pat_str = self.pat.rewrite(context, shape)?;
let pat_str = self.pat.rewrite_result(context, shape)?;
if self.is_shorthand {
combine_strs_with_missing_comments(
context,
@ -417,7 +462,7 @@ impl Rewrite for PatField {
"{}:\n{}{}",
id_str,
nested_shape.indent.to_string(context.config),
self.pat.rewrite(context, nested_shape)?
self.pat.rewrite_result(context, nested_shape)?
)
};
combine_strs_with_missing_comments(
@ -440,9 +485,13 @@ pub(crate) enum TuplePatField<'a> {
impl<'a> Rewrite for TuplePatField<'a> {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match *self {
TuplePatField::Pat(p) => p.rewrite(context, shape),
TuplePatField::Dotdot(_) => Some("..".to_string()),
TuplePatField::Pat(p) => p.rewrite_result(context, shape),
TuplePatField::Dotdot(_) => Ok("..".to_string()),
}
}
}
@ -492,9 +541,9 @@ fn rewrite_tuple_pat(
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
if pats.is_empty() {
return Some(format!("{}()", path_str.unwrap_or_default()));
return Ok(format!("{}()", path_str.unwrap_or_default()));
}
let mut pat_vec: Vec<_> = pats.iter().map(TuplePatField::Pat).collect();
@ -548,7 +597,7 @@ fn count_wildcard_suffix_len(
",",
|item| item.span().lo(),
|item| item.span().hi(),
|item| item.rewrite(context, shape),
|item| item.rewrite_result(context, shape),
context.snippet_provider.span_after(span, "("),
span.hi() - BytePos(1),
false,
@ -558,7 +607,7 @@ fn count_wildcard_suffix_len(
for item in items
.iter()
.rev()
.take_while(|i| matches!(i.item, Some(ref internal_string) if internal_string == "_"))
.take_while(|i| matches!(i.item, Ok(ref internal_string) if internal_string == "_"))
{
suffix_len += 1;

View File

@ -9,13 +9,13 @@
use std::cmp::Ordering;
use rustc_ast::{ast, attr};
use rustc_span::{symbol::sym, Span};
use rustc_span::{Span, symbol::sym};
use crate::config::{Config, GroupImportsTactic};
use crate::imports::{normalize_use_trees_with_granularity, UseSegmentKind, UseTree};
use crate::imports::{UseSegmentKind, UseTree, normalize_use_trees_with_granularity};
use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod};
use crate::lists::{itemize_list, write_list, ListFormatting, ListItem};
use crate::rewrite::RewriteContext;
use crate::lists::{ListFormatting, ListItem, itemize_list, write_list};
use crate::rewrite::{RewriteContext, RewriteErrorExt};
use crate::shape::Shape;
use crate::source_map::LineRangeUtils;
use crate::spanned::Spanned;
@ -59,7 +59,7 @@ fn wrap_reorderable_items(
let fmt = ListFormatting::new(shape, context.config)
.separator("")
.align_comments(false);
write_list(list_items, &fmt)
write_list(list_items, &fmt).ok()
}
fn rewrite_reorderable_item(
@ -99,7 +99,7 @@ fn rewrite_reorderable_or_regroupable_items(
";",
|item| item.span().lo(),
|item| item.span().hi(),
|_item| Some("".to_owned()),
|_item| Ok("".to_owned()),
span.lo(),
span.hi(),
false,
@ -131,9 +131,16 @@ fn rewrite_reorderable_or_regroupable_items(
.map(|use_group| {
let item_vec: Vec<_> = use_group
.into_iter()
.map(|use_tree| ListItem {
item: use_tree.rewrite_top_level(context, nested_shape),
..use_tree.list_item.unwrap_or_else(ListItem::empty)
.map(|use_tree| {
let item = use_tree.rewrite_top_level(context, nested_shape);
if let Some(list_item) = use_tree.list_item {
ListItem {
item: item,
..list_item
}
} else {
ListItem::from_item(item)
}
})
.collect();
wrap_reorderable_items(context, &item_vec, nested_shape)
@ -151,7 +158,7 @@ fn rewrite_reorderable_or_regroupable_items(
";",
|item| item.span().lo(),
|item| item.span().hi(),
|item| rewrite_reorderable_item(context, item, shape),
|item| rewrite_reorderable_item(context, item, shape).unknown_error(),
span.lo(),
span.hi(),
false,

View File

@ -5,17 +5,23 @@ use std::rc::Rc;
use rustc_ast::ptr;
use rustc_span::Span;
use thiserror::Error;
use crate::FormatReport;
use crate::config::{Config, IndentStyle};
use crate::parse::session::ParseSess;
use crate::shape::Shape;
use crate::skip::SkipContext;
use crate::visitor::SnippetProvider;
use crate::FormatReport;
pub(crate) type RewriteResult = Result<String, RewriteError>;
pub(crate) trait Rewrite {
/// Rewrite self into shape.
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>;
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
self.rewrite(context, shape).unknown_error()
}
}
impl<T: Rewrite> Rewrite for ptr::P<T> {
@ -24,6 +30,66 @@ impl<T: Rewrite> Rewrite for ptr::P<T> {
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum MacroErrorKind {
ParseFailure,
ReplaceMacroVariable,
Unknown,
}
impl std::fmt::Display for MacroErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MacroErrorKind::ParseFailure => write!(f, "(parse failure)"),
MacroErrorKind::ReplaceMacroVariable => write!(f, "(replacing macro variables with $)"),
MacroErrorKind::Unknown => write!(f, ""),
}
}
}
#[derive(Clone, Error, Debug)]
pub(crate) enum RewriteError {
#[error("Formatting was skipped due to skip attribute or out of file range.")]
SkipFormatting,
#[error("It exceeds the required width of {configured_width} for the span: {span:?}")]
ExceedsMaxWidth { configured_width: usize, span: Span },
#[error("Failed to format given macro{} at: {span:?}", kind)]
MacroFailure { kind: MacroErrorKind, span: Span },
/// Format failure that does not fit to above categories.
#[error("An unknown error occurred during formatting.")]
Unknown,
}
/// Extension trait used to conveniently convert to RewriteError
pub(crate) trait RewriteErrorExt<T> {
fn max_width_error(self, width: usize, span: Span) -> Result<T, RewriteError>;
fn macro_error(self, kind: MacroErrorKind, span: Span) -> Result<T, RewriteError>;
fn unknown_error(self) -> Result<T, RewriteError>;
}
impl<T> RewriteErrorExt<T> for Option<T> {
fn max_width_error(self, width: usize, span: Span) -> Result<T, RewriteError> {
self.ok_or_else(|| RewriteError::ExceedsMaxWidth {
configured_width: width,
span: span,
})
}
fn macro_error(self, kind: MacroErrorKind, span: Span) -> Result<T, RewriteError> {
self.ok_or_else(|| RewriteError::MacroFailure {
kind: kind,
span: span,
})
}
fn unknown_error(self) -> Result<T, RewriteError> {
self.ok_or_else(|| RewriteError::Unknown)
}
}
#[derive(Clone)]
pub(crate) struct RewriteContext<'a> {
pub(crate) psess: &'a ParseSess,

View File

@ -282,7 +282,7 @@ where
#[cfg(test)]
mod test {
use super::DiffLine::*;
use super::{make_diff, Mismatch};
use super::{Mismatch, make_diff};
use super::{ModifiedChunk, ModifiedLines};
#[test]

View File

@ -0,0 +1,368 @@
use itertools::EitherOrBoth;
use itertools::Itertools;
/// Iterator which breaks an identifier into various [VersionChunk]s.
struct VersionChunkIter<'a> {
ident: &'a str,
start: usize,
}
impl<'a> VersionChunkIter<'a> {
pub(crate) fn new(ident: &'a str) -> Self {
Self { ident, start: 0 }
}
fn parse_numeric_chunk(
&mut self,
mut chars: std::str::CharIndices<'a>,
) -> Option<VersionChunk<'a>> {
let mut end = self.start;
let mut is_end_of_chunk = false;
while let Some((idx, c)) = chars.next() {
end = self.start + idx;
if c.is_ascii_digit() {
continue;
}
is_end_of_chunk = true;
break;
}
let source = if is_end_of_chunk {
let value = &self.ident[self.start..end];
self.start = end;
value
} else {
let value = &self.ident[self.start..];
self.start = self.ident.len();
value
};
let zeros = source.chars().take_while(|c| *c == '0').count();
let value = source.parse::<usize>().ok()?;
Some(VersionChunk::Number {
value,
zeros,
source,
})
}
fn parse_str_chunk(
&mut self,
mut chars: std::str::CharIndices<'a>,
) -> Option<VersionChunk<'a>> {
let mut end = self.start;
let mut is_end_of_chunk = false;
while let Some((idx, c)) = chars.next() {
end = self.start + idx;
if c == '_' {
is_end_of_chunk = true;
break;
}
if !c.is_numeric() {
continue;
}
is_end_of_chunk = true;
break;
}
let source = if is_end_of_chunk {
let value = &self.ident[self.start..end];
self.start = end;
value
} else {
let value = &self.ident[self.start..];
self.start = self.ident.len();
value
};
Some(VersionChunk::Str(source))
}
}
impl<'a> Iterator for VersionChunkIter<'a> {
type Item = VersionChunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut chars = self.ident[self.start..].char_indices();
let (_, next) = chars.next()?;
if next == '_' {
self.start = self.start + next.len_utf8();
return Some(VersionChunk::Underscore);
}
if next.is_ascii_digit() {
return self.parse_numeric_chunk(chars);
}
self.parse_str_chunk(chars)
}
}
/// Represents a chunk in the version-sort algorithm
#[derive(Debug, PartialEq, Eq)]
enum VersionChunk<'a> {
/// A single `_` in an identifier. Underscores are sorted before all other characters.
Underscore,
/// A &str chunk in the version sort.
Str(&'a str),
/// A numeric chunk in the version sort. Keeps track of the numeric value and leading zeros.
Number {
value: usize,
zeros: usize,
source: &'a str,
},
}
/// Determine which side of the version-sort comparison had more leading zeros.
#[derive(Debug, PartialEq, Eq)]
enum MoreLeadingZeros {
Left,
Right,
Equal,
}
/// Compare two identifiers based on the version sorting algorithm described in [the style guide]
///
/// [the style guide]: https://doc.rust-lang.org/nightly/style-guide/#sorting
pub(crate) fn version_sort(a: &str, b: &str) -> std::cmp::Ordering {
let iter_a = VersionChunkIter::new(a);
let iter_b = VersionChunkIter::new(b);
let mut more_leading_zeros = MoreLeadingZeros::Equal;
for either_or_both in iter_a.zip_longest(iter_b) {
match either_or_both {
EitherOrBoth::Left(_) => return std::cmp::Ordering::Greater,
EitherOrBoth::Right(_) => return std::cmp::Ordering::Less,
EitherOrBoth::Both(a, b) => match (a, b) {
(VersionChunk::Underscore, VersionChunk::Underscore) => {
continue;
}
(VersionChunk::Underscore, _) => return std::cmp::Ordering::Less,
(_, VersionChunk::Underscore) => return std::cmp::Ordering::Greater,
(VersionChunk::Str(ca), VersionChunk::Str(cb))
| (VersionChunk::Str(ca), VersionChunk::Number { source: cb, .. })
| (VersionChunk::Number { source: ca, .. }, VersionChunk::Str(cb)) => {
match ca.cmp(&cb) {
std::cmp::Ordering::Equal => {
continue;
}
order @ _ => return order,
}
}
(
VersionChunk::Number {
value: va,
zeros: lza,
..
},
VersionChunk::Number {
value: vb,
zeros: lzb,
..
},
) => match va.cmp(&vb) {
std::cmp::Ordering::Equal => {
if lza == lzb {
continue;
}
if more_leading_zeros == MoreLeadingZeros::Equal && lza > lzb {
more_leading_zeros = MoreLeadingZeros::Left;
} else if more_leading_zeros == MoreLeadingZeros::Equal && lza < lzb {
more_leading_zeros = MoreLeadingZeros::Right;
}
continue;
}
order @ _ => return order,
},
},
}
}
match more_leading_zeros {
MoreLeadingZeros::Equal => std::cmp::Ordering::Equal,
MoreLeadingZeros::Left => std::cmp::Ordering::Less,
MoreLeadingZeros::Right => std::cmp::Ordering::Greater,
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_chunks() {
let mut iter = VersionChunkIter::new("x86_128");
assert_eq!(iter.next(), Some(VersionChunk::Str("x")));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 86,
zeros: 0,
source: "86"
})
);
assert_eq!(iter.next(), Some(VersionChunk::Underscore));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 128,
zeros: 0,
source: "128"
})
);
assert_eq!(iter.next(), None);
let mut iter = VersionChunkIter::new("w005s09t");
assert_eq!(iter.next(), Some(VersionChunk::Str("w")));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 5,
zeros: 2,
source: "005"
})
);
assert_eq!(iter.next(), Some(VersionChunk::Str("s")));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 9,
zeros: 1,
source: "09"
})
);
assert_eq!(iter.next(), Some(VersionChunk::Str("t")));
assert_eq!(iter.next(), None);
let mut iter = VersionChunkIter::new("ZY_WX");
assert_eq!(iter.next(), Some(VersionChunk::Str("ZY")));
assert_eq!(iter.next(), Some(VersionChunk::Underscore));
assert_eq!(iter.next(), Some(VersionChunk::Str("WX")));
let mut iter = VersionChunkIter::new("_v1");
assert_eq!(iter.next(), Some(VersionChunk::Underscore));
assert_eq!(iter.next(), Some(VersionChunk::Str("v")));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 1,
zeros: 0,
source: "1"
})
);
let mut iter = VersionChunkIter::new("_1v");
assert_eq!(iter.next(), Some(VersionChunk::Underscore));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 1,
zeros: 0,
source: "1"
})
);
assert_eq!(iter.next(), Some(VersionChunk::Str("v")));
let mut iter = VersionChunkIter::new("v009");
assert_eq!(iter.next(), Some(VersionChunk::Str("v")));
assert_eq!(
iter.next(),
Some(VersionChunk::Number {
value: 9,
zeros: 2,
source: "009"
})
);
}
#[test]
fn test_version_sort() {
let mut input = vec!["", "b", "a"];
let expected = vec!["", "a", "b"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["x7x", "xxx"];
let expected = vec!["x7x", "xxx"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["applesauce", "apple"];
let expected = vec!["apple", "applesauce"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["aaaaa", "aaa_a"];
let expected = vec!["aaa_a", "aaaaa"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["AAAAA", "AAA1A", "BBBBB", "BB_BB", "C3CCC"];
let expected = vec!["AAA1A", "AAAAA", "BB_BB", "BBBBB", "C3CCC"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["1_000_000", "1_010_001"];
let expected = vec!["1_000_000", "1_010_001"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec![
"5", "50", "500", "5_000", "5_005", "5_050", "5_500", "50_000", "50_005", "50_050",
"50_500",
];
let expected = vec![
"5", "5_000", "5_005", "5_050", "5_500", "50", "50_000", "50_005", "50_050", "50_500",
"500",
];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["X86_64", "x86_64", "X86_128", "x86_128"];
let expected = vec!["X86_64", "X86_128", "x86_64", "x86_128"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["__", "_"];
let expected = vec!["_", "__"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["foo_", "foo"];
let expected = vec!["foo", "foo_"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec!["A", "AA", "B", "a", "aA", "aa", "b"];
let expected = vec!["A", "AA", "B", "a", "aA", "aa", "b"];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected);
let mut input = vec![
"x86_128", "usize", "uz", "v000", "v00", "v0", "v0s", "v00t", "v0u", "v001", "v01",
"v1", "v009", "x87", "zyxw", "_ZYXW", "_abcd", "A2", "ABCD", "Z_YXW", "ZY_XW", "ZY_XW",
"ZYXW", "v09", "v9", "v010", "v10", "w005s09t", "w5s009t", "x64", "x86", "x86_32",
"ua", "x86_64", "ZYXW_", "a1", "abcd", "u_zzz", "u8", "u16", "u32", "u64", "u128",
"u256",
];
let expected = vec![
"_ZYXW", "_abcd", "A2", "ABCD", "Z_YXW", "ZY_XW", "ZY_XW", "ZYXW", "ZYXW_", "a1",
"abcd", "u_zzz", "u8", "u16", "u32", "u64", "u128", "u256", "ua", "usize", "uz",
"v000", "v00", "v0", "v0s", "v00t", "v0u", "v001", "v01", "v1", "v009", "v09", "v9",
"v010", "v10", "w005s09t", "w5s009t", "x64", "x86", "x86_32", "x86_64", "x86_128",
"x87", "zyxw",
];
input.sort_by(|a, b| version_sort(a, b));
assert_eq!(input, expected)
}
}

View File

@ -2,10 +2,10 @@ use std::fs;
use std::io::{self, Write};
use std::path::Path;
use crate::NewlineStyle;
use crate::config::FileName;
use crate::emitter::{self, Emitter};
use crate::parse::session::ParseSess;
use crate::NewlineStyle;
#[cfg(test)]
use crate::config::Config;

View File

@ -1,9 +1,10 @@
use std::cmp::max;
use rustc_ast::{ast, ptr};
use rustc_span::{source_map, Span};
use rustc_span::{Span, source_map};
use crate::macros::MacroArg;
use crate::patterns::RangeOperand;
use crate::utils::{mk_sp, outer_attributes};
/// Spanned returns a span including attributes, if available.
@ -212,3 +213,9 @@ impl Spanned for ast::PreciseCapturingArg {
}
}
}
impl<'a> Spanned for RangeOperand<'a> {
fn span(&self) -> Span {
self.span
}
}

View File

@ -2,9 +2,9 @@ use rustc_ast::ast;
use rustc_span::Span;
use crate::comment::recover_comment_removed;
use crate::config::Version;
use crate::expr::{format_expr, is_simple_block, ExprType};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::config::StyleEdition;
use crate::expr::{ExprType, format_expr, is_simple_block};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::LineRangeUtils;
use crate::spanned::Spanned;
@ -90,7 +90,16 @@ impl<'a> Stmt<'a> {
impl<'a> Rewrite for Stmt<'a> {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
let expr_type = if context.config.version() == Version::Two && self.is_last_expr() {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(
&self,
context: &RewriteContext<'_>,
shape: Shape,
) -> crate::rewrite::RewriteResult {
let expr_type =
if context.config.style_edition() >= StyleEdition::Edition2024 && self.is_last_expr() {
ExprType::SubExpression
} else {
ExprType::Statement
@ -111,11 +120,11 @@ fn format_stmt(
stmt: &ast::Stmt,
expr_type: ExprType,
is_last_expr: bool,
) -> Option<String> {
skip_out_of_file_lines_range!(context, stmt.span());
) -> RewriteResult {
skip_out_of_file_lines_range_err!(context, stmt.span());
let result = match stmt.kind {
ast::StmtKind::Let(ref local) => local.rewrite(context, shape),
ast::StmtKind::Let(ref local) => local.rewrite_result(context, shape),
ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => {
let suffix = if semicolon_for_stmt(context, stmt, is_last_expr) {
";"
@ -123,10 +132,14 @@ fn format_stmt(
""
};
let shape = shape.sub_width(suffix.len())?;
let shape = shape
.sub_width(suffix.len())
.max_width_error(shape.width, ex.span())?;
format_expr(ex, expr_type, context, shape).map(|s| s + suffix)
}
ast::StmtKind::MacCall(..) | ast::StmtKind::Item(..) | ast::StmtKind::Empty => None,
};
result.and_then(|res| recover_comment_removed(res, stmt.span(), context))
ast::StmtKind::MacCall(..) | ast::StmtKind::Item(..) | ast::StmtKind::Empty => {
Err(RewriteError::Unknown)
}
};
result.map(|res| recover_comment_removed(res, stmt.span(), context))
}

View File

@ -84,7 +84,7 @@ pub(crate) fn rewrite_string<'a>(
stripped_str
.len()
.checked_next_power_of_two()
.unwrap_or(usize::max_value()),
.unwrap_or(usize::MAX),
);
result.push_str(fmt.opener);
@ -375,7 +375,7 @@ fn graphemes_width(graphemes: &[&str]) -> usize {
#[cfg(test)]
mod test {
use super::{break_string, detect_url, rewrite_string, SnippetState, StringFormat};
use super::{SnippetState, StringFormat, break_string, detect_url, rewrite_string};
use crate::config::Config;
use crate::shape::{Indent, Shape};
use unicode_segmentation::UnicodeSegmentation;

View File

@ -4,9 +4,9 @@ use std::io::{BufRead, BufReader, Write};
use std::iter::Enumerate;
use std::path::{Path, PathBuf};
use super::{print_mismatches, write_message, DIFF_CONTEXT_SIZE};
use super::{DIFF_CONTEXT_SIZE, print_mismatches, write_message};
use crate::config::{Config, EmitMode, Verbosity};
use crate::rustfmt_diff::{make_diff, Mismatch};
use crate::rustfmt_diff::{Mismatch, make_diff};
use crate::{Input, Session};
const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md";

View File

@ -6,14 +6,17 @@ use std::iter::Peekable;
use std::mem;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str::Chars;
use std::str::{Chars, FromStr};
use std::thread;
use crate::config::{Color, Config, EmitMode, FileName, NewlineStyle};
use crate::formatting::{ReportedErrors, SourceFile};
use crate::rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, ModifiedChunk, OutputWriter};
use crate::rustfmt_diff::{DiffLine, Mismatch, ModifiedChunk, OutputWriter, make_diff, print_diff};
use crate::source_file;
use crate::{is_nightly_channel, FormatReport, FormatReportFormatterBuilder, Input, Session};
use crate::{
Edition, FormatReport, FormatReportFormatterBuilder, Input, Session, StyleEdition, Version,
is_nightly_channel,
};
use rustfmt_config_proc_macro::nightly_only_test;
use tracing::{debug, warn};
@ -102,10 +105,9 @@ fn is_file_skip(path: &Path) -> bool {
fn get_test_files(path: &Path, recursive: bool) -> Vec<PathBuf> {
let mut files = vec![];
if path.is_dir() {
for entry in fs::read_dir(path).expect(&format!(
"couldn't read directory {}",
path.to_str().unwrap()
)) {
for entry in
fs::read_dir(path).expect(&format!("couldn't read directory {}", path.display()))
{
let entry = entry.expect("couldn't get `DirEntry`");
let path = entry.path();
if path.is_dir() && recursive {
@ -119,10 +121,7 @@ fn get_test_files(path: &Path, recursive: bool) -> Vec<PathBuf> {
}
fn verify_config_used(path: &Path, config_name: &str) {
for entry in fs::read_dir(path).expect(&format!(
"couldn't read {} directory",
path.to_str().unwrap()
)) {
for entry in fs::read_dir(path).expect(&format!("couldn't read {} directory", path.display())) {
let entry = entry.expect("couldn't get directory entry");
let path = entry.path();
if path.extension().map_or(false, |f| f == "rs") {
@ -711,13 +710,24 @@ fn print_mismatches<T: Fn(u32) -> String>(
fn read_config(filename: &Path) -> Config {
let sig_comments = read_significant_comments(filename);
let (edition, style_edition, version) = get_editions_from_comments(&sig_comments);
// Look for a config file. If there is a 'config' property in the significant comments, use
// that. Otherwise, if there are no significant comments at all, look for a config file with
// the same name as the test file.
let mut config = if !sig_comments.is_empty() {
get_config(sig_comments.get("config").map(Path::new))
get_config(
sig_comments.get("config").map(Path::new),
edition,
style_edition,
version,
)
} else {
get_config(filename.with_extension("toml").file_name().map(Path::new))
get_config(
filename.with_extension("toml").file_name().map(Path::new),
edition,
style_edition,
version,
)
};
for (key, val) in &sig_comments {
@ -748,13 +758,31 @@ enum IdempotentCheckError {
Parse,
}
fn get_editions_from_comments(
comments: &HashMap<String, String>,
) -> (Option<Edition>, Option<StyleEdition>, Option<Version>) {
(
comments
.get("edition")
.map(|e| Edition::from_str(e).expect(&format!("invalid edition value: '{}'", e))),
comments.get("style_edition").map(|se| {
StyleEdition::from_str(se).expect(&format!("invalid style_edition value: '{}'", se))
}),
comments
.get("version")
.map(|v| Version::from_str(v).expect(&format!("invalid version value: '{}'", v))),
)
}
fn idempotent_check(
filename: &PathBuf,
opt_config: &Option<PathBuf>,
) -> Result<FormatReport, IdempotentCheckError> {
let sig_comments = read_significant_comments(filename);
let config = if let Some(ref config_file_path) = opt_config {
Config::from_toml_path(config_file_path).expect("`rustfmt.toml` not found")
let (edition, style_edition, version) = get_editions_from_comments(&sig_comments);
Config::from_toml_path(config_file_path, edition, style_edition, version)
.expect("`rustfmt.toml` not found")
} else {
read_config(filename)
};
@ -778,14 +806,19 @@ fn idempotent_check(
// Reads test config file using the supplied (optional) file name. If there's no file name or the
// file doesn't exist, just return the default config. Otherwise, the file must be read
// successfully.
fn get_config(config_file: Option<&Path>) -> Config {
fn get_config(
config_file: Option<&Path>,
edition: Option<Edition>,
style_edition: Option<StyleEdition>,
version: Option<Version>,
) -> Config {
let config_file_name = match config_file {
None => return Default::default(),
None => return Config::default_for_possible_style_edition(style_edition, edition, version),
Some(file_name) => {
let mut full_path = PathBuf::from("tests/config/");
full_path.push(file_name);
if !full_path.exists() {
return Default::default();
return Config::default_for_possible_style_edition(style_edition, edition, version);
};
full_path
}
@ -797,7 +830,14 @@ fn get_config(config_file: Option<&Path>) -> Config {
.read_to_string(&mut def_config)
.expect("Couldn't read config");
Config::from_toml(&def_config, Path::new("tests/config/")).expect("invalid TOML")
Config::from_toml_for_style_edition(
&def_config,
Path::new("tests/config/"),
edition,
style_edition,
version,
)
.expect("invalid TOML")
}
// Reads significant comments of the form: `// rustfmt-key: value` into a hash map.

View File

@ -2,23 +2,23 @@ use std::ops::Deref;
use rustc_ast::ast::{self, FnRetTy, Mutability, Term};
use rustc_ast::ptr;
use rustc_span::{symbol::kw, BytePos, Pos, Span};
use rustc_span::{BytePos, Pos, Span, symbol::kw};
use tracing::debug;
use crate::comment::{combine_strs_with_missing_comments, contains_comment};
use crate::config::lists::*;
use crate::config::{IndentStyle, TypeDensity, Version};
use crate::config::{IndentStyle, StyleEdition, TypeDensity};
use crate::expr::{
format_expr, rewrite_assign_rhs, rewrite_call, rewrite_tuple, rewrite_unary_prefix, ExprType,
RhsAssignKind,
ExprType, RhsAssignKind, format_expr, rewrite_assign_rhs, rewrite_call, rewrite_tuple,
rewrite_unary_prefix,
};
use crate::lists::{
definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list,
};
use crate::macros::{rewrite_macro, MacroPosition};
use crate::macros::{MacroPosition, rewrite_macro};
use crate::overflow;
use crate::pairs::{rewrite_pair, PairParts};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::pairs::{PairParts, rewrite_pair};
use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
use crate::shape::Shape;
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
@ -41,7 +41,7 @@ pub(crate) fn rewrite_path(
qself: &Option<ptr::P<ast::QSelf>>,
path: &ast::Path,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let skip_count = qself.as_ref().map_or(0, |x| x.position);
// 32 covers almost all path lengths measured when compiling core, and there isn't a big
@ -57,7 +57,7 @@ pub(crate) fn rewrite_path(
if let Some(qself) = qself {
result.push('<');
let fmt_ty = qself.ty.rewrite(context, shape)?;
let fmt_ty = qself.ty.rewrite_result(context, shape)?;
result.push_str(&fmt_ty);
if skip_count > 0 {
@ -67,7 +67,7 @@ pub(crate) fn rewrite_path(
}
// 3 = ">::".len()
let shape = shape.sub_width(3)?;
let shape = shape.sub_width(3).max_width_error(shape.width, path.span)?;
result = rewrite_path_segments(
PathContext::Type,
@ -103,7 +103,7 @@ fn rewrite_path_segments<'a, I>(
span_hi: BytePos,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String>
) -> RewriteResult
where
I: Iterator<Item = &'a ast::PathSegment>,
{
@ -122,7 +122,9 @@ where
}
let extra_offset = extra_offset(&buffer, shape);
let new_shape = shape.shrink_left(extra_offset)?;
let new_shape = shape
.shrink_left(extra_offset)
.max_width_error(shape.width, mk_sp(span_lo, span_hi))?;
let segment_string = rewrite_segment(
path_context,
segment,
@ -135,7 +137,7 @@ where
buffer.push_str(&segment_string);
}
Some(buffer)
Ok(buffer)
}
#[derive(Debug)]
@ -169,19 +171,27 @@ impl<'a> Spanned for SegmentParam<'a> {
impl<'a> Rewrite for SegmentParam<'a> {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match *self {
SegmentParam::Const(const_) => const_.rewrite(context, shape),
SegmentParam::LifeTime(lt) => lt.rewrite(context, shape),
SegmentParam::Type(ty) => ty.rewrite(context, shape),
SegmentParam::Binding(atc) => atc.rewrite(context, shape),
SegmentParam::Const(const_) => const_.rewrite_result(context, shape),
SegmentParam::LifeTime(lt) => lt.rewrite_result(context, shape),
SegmentParam::Type(ty) => ty.rewrite_result(context, shape),
SegmentParam::Binding(atc) => atc.rewrite_result(context, shape),
}
}
}
impl Rewrite for ast::PreciseCapturingArg {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match self {
ast::PreciseCapturingArg::Lifetime(lt) => lt.rewrite(context, shape),
ast::PreciseCapturingArg::Lifetime(lt) => lt.rewrite_result(context, shape),
ast::PreciseCapturingArg::Arg(p, _) => {
rewrite_path(context, PathContext::Type, &None, p, shape)
}
@ -191,13 +201,20 @@ impl Rewrite for ast::PreciseCapturingArg {
impl Rewrite for ast::AssocItemConstraint {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
use ast::AssocItemConstraintKind::{Bound, Equality};
let mut result = String::with_capacity(128);
result.push_str(rewrite_ident(context, self.ident));
if let Some(ref gen_args) = self.gen_args {
let budget = shape.width.checked_sub(result.len())?;
let budget = shape
.width
.checked_sub(result.len())
.max_width_error(shape.width, self.span)?;
let shape = Shape::legacy(budget, shape.indent + result.len());
let gen_str = rewrite_generic_args(gen_args, context, shape, gen_args.span())?;
result.push_str(&gen_str);
@ -210,23 +227,30 @@ impl Rewrite for ast::AssocItemConstraint {
};
result.push_str(infix);
let budget = shape.width.checked_sub(result.len())?;
let budget = shape
.width
.checked_sub(result.len())
.max_width_error(shape.width, self.span)?;
let shape = Shape::legacy(budget, shape.indent + result.len());
let rewrite = self.kind.rewrite(context, shape)?;
let rewrite = self.kind.rewrite_result(context, shape)?;
result.push_str(&rewrite);
Some(result)
Ok(result)
}
}
impl Rewrite for ast::AssocItemConstraintKind {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match self {
ast::AssocItemConstraintKind::Equality { term } => match term {
Term::Ty(ty) => ty.rewrite(context, shape),
Term::Const(c) => c.rewrite(context, shape),
Term::Ty(ty) => ty.rewrite_result(context, shape),
Term::Const(c) => c.rewrite_result(context, shape),
},
ast::AssocItemConstraintKind::Bound { bounds } => bounds.rewrite(context, shape),
ast::AssocItemConstraintKind::Bound { bounds } => bounds.rewrite_result(context, shape),
}
}
}
@ -248,16 +272,17 @@ fn rewrite_segment(
span_hi: BytePos,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
let mut result = String::with_capacity(128);
result.push_str(rewrite_ident(context, segment.ident));
let ident_len = result.len();
let shape = if context.use_block_indent() {
shape.offset_left(ident_len)?
shape.offset_left(ident_len)
} else {
shape.shrink_left(ident_len)?
};
shape.shrink_left(ident_len)
}
.max_width_error(shape.width, mk_sp(*span_lo, span_hi))?;
if let Some(ref args) = segment.args {
let generics_str = rewrite_generic_args(args, context, shape, mk_sp(*span_lo, span_hi))?;
@ -288,7 +313,7 @@ fn rewrite_segment(
result.push_str(&generics_str)
}
Some(result)
Ok(result)
}
fn format_function_type<'a, I>(
@ -298,7 +323,7 @@ fn format_function_type<'a, I>(
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String>
) -> RewriteResult
where
I: ExactSizeIterator,
<I as Iterator>::Item: Deref,
@ -308,12 +333,12 @@ where
let ty_shape = match context.config.indent_style() {
// 4 = " -> "
IndentStyle::Block => shape.offset_left(4)?,
IndentStyle::Visual => shape.block_left(4)?,
IndentStyle::Block => shape.offset_left(4).max_width_error(shape.width, span)?,
IndentStyle::Visual => shape.block_left(4).max_width_error(shape.width, span)?,
};
let output = match *output {
FnRetTy::Ty(ref ty) => {
let type_str = ty.rewrite(context, ty_shape)?;
let type_str = ty.rewrite_result(context, ty_shape)?;
format!(" -> {type_str}")
}
FnRetTy::Default(..) => String::new(),
@ -326,7 +351,10 @@ where
)
} else {
// 2 for ()
let budget = shape.width.checked_sub(2)?;
let budget = shape
.width
.checked_sub(2)
.max_width_error(shape.width, span)?;
// 1 for (
let offset = shape.indent + 1;
Shape::legacy(budget, offset)
@ -339,7 +367,8 @@ where
let list_hi = context.snippet_provider.span_before(span, ")");
let comment = context
.snippet_provider
.span_to_snippet(mk_sp(list_lo, list_hi))?
.span_to_snippet(mk_sp(list_lo, list_hi))
.unknown_error()?
.trim();
let comment = if comment.starts_with("//") {
format!(
@ -360,7 +389,7 @@ where
",",
|arg| arg.span().lo(),
|arg| arg.span().hi(),
|arg| arg.rewrite(context, list_shape),
|arg| arg.rewrite_result(context, list_shape),
list_lo,
span.hi(),
false,
@ -396,9 +425,9 @@ where
)
};
if output.is_empty() || last_line_width(&args) + first_line_width(&output) <= shape.width {
Some(format!("{args}{output}"))
Ok(format!("{args}{output}"))
} else {
Some(format!(
Ok(format!(
"{}\n{}{}",
args,
list_shape.indent.to_string(context.config),
@ -429,6 +458,10 @@ fn get_tactics(item_vec: &[ListItem], output: &str, shape: Shape) -> DefinitiveL
impl Rewrite for ast::WherePredicate {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
// FIXME: dead spans?
let result = match *self {
ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
@ -437,7 +470,7 @@ impl Rewrite for ast::WherePredicate {
ref bounds,
..
}) => {
let type_str = bounded_ty.rewrite(context, shape)?;
let type_str = bounded_ty.rewrite_result(context, shape)?;
let colon = type_bound_colon(context).trim_end();
let lhs = if let Some(binder_str) =
rewrite_bound_params(context, shape, bound_generic_params)
@ -452,28 +485,34 @@ impl Rewrite for ast::WherePredicate {
ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate {
ref lifetime,
ref bounds,
..
}) => rewrite_bounded_lifetime(lifetime, bounds, context, shape)?,
span,
}) => rewrite_bounded_lifetime(lifetime, bounds, span, context, shape)?,
ast::WherePredicate::EqPredicate(ast::WhereEqPredicate {
ref lhs_ty,
ref rhs_ty,
..
}) => {
let lhs_ty_str = lhs_ty.rewrite(context, shape).map(|lhs| lhs + " =")?;
let lhs_ty_str = lhs_ty
.rewrite_result(context, shape)
.map(|lhs| lhs + " =")?;
rewrite_assign_rhs(context, lhs_ty_str, &**rhs_ty, &RhsAssignKind::Ty, shape)?
}
};
Some(result)
Ok(result)
}
}
impl Rewrite for ast::GenericArg {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match *self {
ast::GenericArg::Lifetime(ref lt) => lt.rewrite(context, shape),
ast::GenericArg::Type(ref ty) => ty.rewrite(context, shape),
ast::GenericArg::Const(ref const_) => const_.rewrite(context, shape),
ast::GenericArg::Lifetime(ref lt) => lt.rewrite_result(context, shape),
ast::GenericArg::Type(ref ty) => ty.rewrite_result(context, shape),
ast::GenericArg::Const(ref const_) => const_.rewrite_result(context, shape),
}
}
}
@ -483,11 +522,11 @@ fn rewrite_generic_args(
context: &RewriteContext<'_>,
shape: Shape,
span: Span,
) -> Option<String> {
) -> RewriteResult {
match gen_args {
ast::GenericArgs::AngleBracketed(ref data) => {
if data.args.is_empty() {
Some("".to_owned())
Ok("".to_owned())
} else {
let args = data
.args
@ -513,47 +552,63 @@ fn rewrite_generic_args(
context,
shape,
),
ast::GenericArgs::ParenthesizedElided(..) => Some("(..)".to_owned()),
ast::GenericArgs::ParenthesizedElided(..) => Ok("(..)".to_owned()),
}
}
fn rewrite_bounded_lifetime(
lt: &ast::Lifetime,
bounds: &[ast::GenericBound],
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
let result = lt.rewrite(context, shape)?;
) -> RewriteResult {
let result = lt.rewrite_result(context, shape)?;
if bounds.is_empty() {
Some(result)
Ok(result)
} else {
let colon = type_bound_colon(context);
let overhead = last_line_width(&result) + colon.len();
let shape = shape
.sub_width(overhead)
.max_width_error(shape.width, span)?;
let result = format!(
"{}{}{}",
result,
colon,
join_bounds(context, shape.sub_width(overhead)?, bounds, true)?
join_bounds(context, shape, bounds, true)?
);
Some(result)
Ok(result)
}
}
impl Rewrite for ast::AnonConst {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
format_expr(&self.value, ExprType::SubExpression, context, shape)
}
}
impl Rewrite for ast::Lifetime {
fn rewrite(&self, context: &RewriteContext<'_>, _: Shape) -> Option<String> {
Some(context.snippet(self.ident.span).to_owned())
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, _: Shape) -> RewriteResult {
Ok(context.snippet(self.ident.span).to_owned())
}
}
impl Rewrite for ast::GenericBound {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match *self {
ast::GenericBound::Trait(
ref poly_trait_ref,
@ -574,24 +629,30 @@ impl Rewrite for ast::GenericBound {
asyncness.push(' ');
}
let polarity = polarity.as_str();
let shape = shape.offset_left(constness.len() + polarity.len())?;
let shape = shape
.offset_left(constness.len() + polarity.len())
.max_width_error(shape.width, self.span())?;
poly_trait_ref
.rewrite(context, shape)
.rewrite_result(context, shape)
.map(|s| format!("{constness}{asyncness}{polarity}{s}"))
.map(|s| if has_paren { format!("({})", s) } else { s })
}
ast::GenericBound::Use(ref args, span) => {
overflow::rewrite_with_angle_brackets(context, "use", args.iter(), shape, span)
}
ast::GenericBound::Outlives(ref lifetime) => lifetime.rewrite(context, shape),
ast::GenericBound::Outlives(ref lifetime) => lifetime.rewrite_result(context, shape),
}
}
}
impl Rewrite for ast::GenericBounds {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
if self.is_empty() {
return Some(String::new());
return Ok(String::new());
}
join_bounds(context, shape, self, true)
@ -600,8 +661,15 @@ impl Rewrite for ast::GenericBounds {
impl Rewrite for ast::GenericParam {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
// FIXME: If there are more than one attributes, this will force multiline.
let mut result = self.attrs.rewrite(context, shape).unwrap_or(String::new());
let mut result = self
.attrs
.rewrite_result(context, shape)
.unwrap_or(String::new());
let has_attrs = !result.is_empty();
let mut param = String::with_capacity(128);
@ -615,15 +683,19 @@ impl Rewrite for ast::GenericParam {
param.push_str("const ");
param.push_str(rewrite_ident(context, self.ident));
param.push_str(": ");
param.push_str(&ty.rewrite(context, shape)?);
param.push_str(&ty.rewrite_result(context, shape)?);
if let Some(default) = default {
let eq_str = match context.config.type_punctuation_density() {
TypeDensity::Compressed => "=",
TypeDensity::Wide => " = ",
};
param.push_str(eq_str);
let budget = shape.width.checked_sub(param.len())?;
let rewrite = default.rewrite(context, Shape::legacy(budget, shape.indent))?;
let budget = shape
.width
.checked_sub(param.len())
.max_width_error(shape.width, self.span())?;
let rewrite =
default.rewrite_result(context, Shape::legacy(budget, shape.indent))?;
param.push_str(&rewrite);
}
kw_span.lo()
@ -634,7 +706,7 @@ impl Rewrite for ast::GenericParam {
if !self.bounds.is_empty() {
param.push_str(type_bound_colon(context));
param.push_str(&self.bounds.rewrite(context, shape)?)
param.push_str(&self.bounds.rewrite_result(context, shape)?)
}
if let ast::GenericParamKind::Type {
default: Some(ref def),
@ -645,9 +717,12 @@ impl Rewrite for ast::GenericParam {
TypeDensity::Wide => " = ",
};
param.push_str(eq_str);
let budget = shape.width.checked_sub(param.len())?;
let budget = shape
.width
.checked_sub(param.len())
.max_width_error(shape.width, self.span())?;
let rewrite =
def.rewrite(context, Shape::legacy(budget, shape.indent + param.len()))?;
def.rewrite_result(context, Shape::legacy(budget, shape.indent + param.len()))?;
param.push_str(&rewrite);
}
@ -673,44 +748,67 @@ impl Rewrite for ast::GenericParam {
result.push_str(&param);
}
Some(result)
Ok(result)
}
}
impl Rewrite for ast::PolyTraitRef {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
if let Some(lifetime_str) = rewrite_bound_params(context, shape, &self.bound_generic_params)
{
// 6 is "for<> ".len()
let extra_offset = lifetime_str.len() + 6;
let path_str = self
.trait_ref
.rewrite(context, shape.offset_left(extra_offset)?)?;
let shape = shape
.offset_left(extra_offset)
.max_width_error(shape.width, self.span)?;
let path_str = self.trait_ref.rewrite_result(context, shape)?;
Some(format!("for<{lifetime_str}> {path_str}"))
Ok(format!("for<{lifetime_str}> {path_str}"))
} else {
self.trait_ref.rewrite(context, shape)
self.trait_ref.rewrite_result(context, shape)
}
}
}
impl Rewrite for ast::TraitRef {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
rewrite_path(context, PathContext::Type, &None, &self.path, shape)
}
}
impl Rewrite for ast::Ty {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}
fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match self.kind {
ast::TyKind::TraitObject(ref bounds, tobj_syntax) => {
// we have to consider 'dyn' keyword is used or not!!!
let (shape, prefix) = match tobj_syntax {
ast::TraitObjectSyntax::Dyn => (shape.offset_left(4)?, "dyn "),
ast::TraitObjectSyntax::DynStar => (shape.offset_left(5)?, "dyn* "),
ast::TraitObjectSyntax::Dyn => {
let shape = shape
.offset_left(4)
.max_width_error(shape.width, self.span())?;
(shape, "dyn ")
}
ast::TraitObjectSyntax::DynStar => {
let shape = shape
.offset_left(5)
.max_width_error(shape.width, self.span())?;
(shape, "dyn* ")
}
ast::TraitObjectSyntax::None => (shape, ""),
};
let mut res = bounds.rewrite(context, shape)?;
let mut res = bounds.rewrite_result(context, shape)?;
// We may have falsely removed a trailing `+` inside macro call.
if context.inside_macro()
&& bounds.len() == 1
@ -719,7 +817,7 @@ impl Rewrite for ast::Ty {
{
res.push('+');
}
Some(format!("{prefix}{res}"))
Ok(format!("{prefix}{res}"))
}
ast::TyKind::Ptr(ref mt) => {
let prefix = match mt.mutbl {
@ -738,8 +836,11 @@ impl Rewrite for ast::Ty {
let mut cmnt_lo = ref_hi;
if let Some(ref lifetime) = *lifetime {
let lt_budget = shape.width.checked_sub(2 + mut_len)?;
let lt_str = lifetime.rewrite(
let lt_budget = shape
.width
.checked_sub(2 + mut_len)
.max_width_error(shape.width, self.span())?;
let lt_str = lifetime.rewrite_result(
context,
Shape::legacy(lt_budget, shape.indent + 2 + mut_len),
)?;
@ -783,39 +884,46 @@ impl Rewrite for ast::Ty {
result = combine_strs_with_missing_comments(
context,
result.trim_end(),
&mt.ty.rewrite(context, shape)?,
&mt.ty.rewrite_result(context, shape)?,
before_ty_span,
shape,
true,
)?;
} else {
let used_width = last_line_width(&result);
let budget = shape.width.checked_sub(used_width)?;
let ty_str = mt
.ty
.rewrite(context, Shape::legacy(budget, shape.indent + used_width))?;
let budget = shape
.width
.checked_sub(used_width)
.max_width_error(shape.width, self.span())?;
let ty_str = mt.ty.rewrite_result(
context,
Shape::legacy(budget, shape.indent + used_width),
)?;
result.push_str(&ty_str);
}
Some(result)
Ok(result)
}
// FIXME: we drop any comments here, even though it's a silly place to put
// comments.
ast::TyKind::Paren(ref ty) => {
if context.config.version() == Version::One
if context.config.style_edition() <= StyleEdition::Edition2021
|| context.config.indent_style() == IndentStyle::Visual
{
let budget = shape.width.checked_sub(2)?;
let budget = shape
.width
.checked_sub(2)
.max_width_error(shape.width, self.span())?;
return ty
.rewrite(context, Shape::legacy(budget, shape.indent + 1))
.rewrite_result(context, Shape::legacy(budget, shape.indent + 1))
.map(|ty_str| format!("({})", ty_str));
}
// 2 = ()
if let Some(sh) = shape.sub_width(2) {
if let Some(ref s) = ty.rewrite(context, sh) {
if let Ok(ref s) = ty.rewrite_result(context, sh) {
if !s.contains('\n') {
return Some(format!("({s})"));
return Ok(format!("({s})"));
}
}
}
@ -824,8 +932,8 @@ impl Rewrite for ast::Ty {
let shape = shape
.block_indent(context.config.tab_spaces())
.with_max_width(context.config);
let rw = ty.rewrite(context, shape)?;
Some(format!(
let rw = ty.rewrite_result(context, shape)?;
Ok(format!(
"({}{}{})",
shape.to_string_with_newline(context.config),
rw,
@ -833,15 +941,18 @@ impl Rewrite for ast::Ty {
))
}
ast::TyKind::Slice(ref ty) => {
let budget = shape.width.checked_sub(4)?;
ty.rewrite(context, Shape::legacy(budget, shape.indent + 1))
let budget = shape
.width
.checked_sub(4)
.max_width_error(shape.width, self.span())?;
ty.rewrite_result(context, Shape::legacy(budget, shape.indent + 1))
.map(|ty_str| format!("[{}]", ty_str))
}
ast::TyKind::Tup(ref items) => {
rewrite_tuple(context, items.iter(), self.span, shape, items.len() == 1)
}
ast::TyKind::AnonStruct(..) => Some(context.snippet(self.span).to_owned()),
ast::TyKind::AnonUnion(..) => Some(context.snippet(self.span).to_owned()),
ast::TyKind::AnonStruct(..) => Ok(context.snippet(self.span).to_owned()),
ast::TyKind::AnonUnion(..) => Ok(context.snippet(self.span).to_owned()),
ast::TyKind::Path(ref q_self, ref path) => {
rewrite_path(context, PathContext::Type, q_self, path, shape)
}
@ -855,24 +966,27 @@ impl Rewrite for ast::Ty {
),
ast::TyKind::Infer => {
if shape.width >= 1 {
Some("_".to_owned())
Ok("_".to_owned())
} else {
None
Err(RewriteError::ExceedsMaxWidth {
configured_width: shape.width,
span: self.span(),
})
}
}
ast::TyKind::BareFn(ref bare_fn) => rewrite_bare_fn(bare_fn, self.span, context, shape),
ast::TyKind::Never => Some(String::from("!")),
ast::TyKind::Never => Ok(String::from("!")),
ast::TyKind::MacCall(ref mac) => {
rewrite_macro(mac, None, context, shape, MacroPosition::Expression)
}
ast::TyKind::ImplicitSelf => Some(String::from("")),
ast::TyKind::ImplicitSelf => Ok(String::from("")),
ast::TyKind::ImplTrait(_, ref it) => {
// Empty trait is not a parser error.
if it.is_empty() {
return Some("impl".to_owned());
return Ok("impl".to_owned());
}
let rw = if context.config.version() == Version::One {
it.rewrite(context, shape)
let rw = if context.config.style_edition() <= StyleEdition::Edition2021 {
it.rewrite_result(context, shape)
} else {
join_bounds(context, shape, it, false)
};
@ -881,8 +995,8 @@ impl Rewrite for ast::Ty {
format!("impl{}{}", space, it_str)
})
}
ast::TyKind::CVarArgs => Some("...".to_owned()),
ast::TyKind::Dummy | ast::TyKind::Err(_) => Some(context.snippet(self.span).to_owned()),
ast::TyKind::CVarArgs => Ok("...".to_owned()),
ast::TyKind::Dummy | ast::TyKind::Err(_) => Ok(context.snippet(self.span).to_owned()),
ast::TyKind::Typeof(ref anon_const) => rewrite_call(
context,
"typeof",
@ -891,9 +1005,9 @@ impl Rewrite for ast::Ty {
shape,
),
ast::TyKind::Pat(ref ty, ref pat) => {
let ty = ty.rewrite(context, shape)?;
let pat = pat.rewrite(context, shape)?;
Some(format!("{ty} is {pat}"))
let ty = ty.rewrite_result(context, shape)?;
let pat = pat.rewrite_result(context, shape)?;
Ok(format!("{ty} is {pat}"))
}
}
}
@ -904,7 +1018,7 @@ fn rewrite_bare_fn(
span: Span,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
) -> RewriteResult {
debug!("rewrite_bare_fn {:#?}", shape);
let mut result = String::with_capacity(128);
@ -928,9 +1042,14 @@ fn rewrite_bare_fn(
result.push_str("fn");
let func_ty_shape = if context.use_block_indent() {
shape.offset_left(result.len())?
shape
.offset_left(result.len())
.max_width_error(shape.width, span)?
} else {
shape.visual_indent(result.len()).sub_width(result.len())?
shape
.visual_indent(result.len())
.sub_width(result.len())
.max_width_error(shape.width, span)?
};
let rewrite = format_function_type(
@ -944,7 +1063,7 @@ fn rewrite_bare_fn(
result.push_str(&rewrite);
Some(result)
Ok(result)
}
fn is_generic_bounds_in_order(generic_bounds: &[ast::GenericBound]) -> bool {
@ -968,7 +1087,7 @@ fn join_bounds(
shape: Shape,
items: &[ast::GenericBound],
need_indent: bool,
) -> Option<String> {
) -> RewriteResult {
join_bounds_inner(context, shape, items, need_indent, false)
}
@ -978,7 +1097,7 @@ fn join_bounds_inner(
items: &[ast::GenericBound],
need_indent: bool,
force_newline: bool,
) -> Option<String> {
) -> RewriteResult {
debug_assert!(!items.is_empty());
let generic_bounds_in_order = is_generic_bounds_in_order(items);
@ -1073,10 +1192,10 @@ fn join_bounds_inner(
};
let (extendable, trailing_str) = if i == 0 {
let bound_str = item.rewrite(context, shape)?;
let bound_str = item.rewrite_result(context, shape)?;
(is_bound_extendable(&bound_str, item), bound_str)
} else {
let bound_str = &item.rewrite(context, shape)?;
let bound_str = &item.rewrite_result(context, shape)?;
match leading_span {
Some(ls) if has_leading_comment => (
is_bound_extendable(bound_str, item),
@ -1100,7 +1219,7 @@ fn join_bounds_inner(
true,
)
.map(|v| (v, trailing_span, extendable)),
_ => Some((strs + &trailing_str, trailing_span, extendable)),
_ => Ok((strs + &trailing_str, trailing_span, extendable)),
}
},
)?;
@ -1110,22 +1229,22 @@ fn join_bounds_inner(
// and either there is more than one item;
// or the single item is of type `Trait`,
// and any of the internal arrays contains more than one item;
let retry_with_force_newline = match context.config.version() {
Version::One => {
let retry_with_force_newline = match context.config.style_edition() {
style_edition @ _ if style_edition <= StyleEdition::Edition2021 => {
!force_newline
&& items.len() > 1
&& (result.0.contains('\n') || result.0.len() > shape.width)
}
Version::Two if force_newline => false,
Version::Two if (!result.0.contains('\n') && result.0.len() <= shape.width) => false,
Version::Two if items.len() > 1 => true,
Version::Two => is_item_with_multi_items_array(&items[0]),
_ if force_newline => false,
_ if (!result.0.contains('\n') && result.0.len() <= shape.width) => false,
_ if items.len() > 1 => true,
_ => is_item_with_multi_items_array(&items[0]),
};
if retry_with_force_newline {
join_bounds_inner(context, shape, items, need_indent, true)
} else {
Some(result.0)
Ok(result.0)
}
}

View File

@ -6,11 +6,11 @@ use rustc_ast::ast::{
};
use rustc_ast::ptr;
use rustc_ast_pretty::pprust;
use rustc_span::{sym, symbol, BytePos, LocalExpnId, Span, Symbol, SyntaxContext};
use rustc_span::{BytePos, LocalExpnId, Span, Symbol, SyntaxContext, sym, symbol};
use unicode_width::UnicodeWidthStr;
use crate::comment::{filter_normal_code, CharClasses, FullCodeCharKind, LineClasses};
use crate::config::{Config, Version};
use crate::comment::{CharClasses, FullCodeCharKind, LineClasses, filter_normal_code};
use crate::config::{Config, StyleEdition};
use crate::rewrite::RewriteContext;
use crate::shape::{Indent, Shape};
@ -367,10 +367,10 @@ macro_rules! out_of_file_lines_range {
};
}
macro_rules! skip_out_of_file_lines_range {
macro_rules! skip_out_of_file_lines_range_err {
($self:ident, $span:expr) => {
if out_of_file_lines_range!($self, $span) {
return None;
return Err(RewriteError::SkipFormatting);
}
};
}
@ -596,7 +596,7 @@ pub(crate) fn trim_left_preserve_layout(
// just InString{Commented} in order to allow the start of a string to be indented
let new_veto_trim_value = (kind == FullCodeCharKind::InString
|| (config.version() == Version::Two
|| (config.style_edition() >= StyleEdition::Edition2024
&& kind == FullCodeCharKind::InStringCommented))
&& !line.ends_with('\\');
let line = if veto_trim || new_veto_trim_value {
@ -612,7 +612,7 @@ pub(crate) fn trim_left_preserve_layout(
// such lines should not be taken into account when computing the minimum.
match kind {
FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
if config.version() == Version::Two =>
if config.style_edition() >= StyleEdition::Edition2024 =>
{
None
}
@ -656,7 +656,7 @@ pub(crate) fn indent_next_line(kind: FullCodeCharKind, line: &str, config: &Conf
// formatting the code block, therefore the string's indentation needs
// to be adjusted for the code surrounding the code block.
config.format_strings() && line.ends_with('\\')
} else if config.version() == Version::Two {
} else if config.style_edition() >= StyleEdition::Edition2024 {
!kind.is_commented_string()
} else {
true

View File

@ -11,9 +11,9 @@ use crate::config::lists::*;
use crate::expr::rewrite_field;
use crate::items::{rewrite_struct_field, rewrite_struct_field_prefix};
use crate::lists::{
definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list,
};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::rewrite::{Rewrite, RewriteContext, RewriteResult};
use crate::shape::{Indent, Shape};
use crate::source_map::SpanUtils;
use crate::spanned::Spanned;
@ -24,13 +24,13 @@ use crate::utils::{
pub(crate) trait AlignedItem {
fn skip(&self) -> bool;
fn get_span(&self) -> Span;
fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>;
fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult;
fn rewrite_aligned_item(
&self,
context: &RewriteContext<'_>,
shape: Shape,
prefix_max_width: usize,
) -> Option<String>;
) -> RewriteResult;
}
impl AlignedItem for ast::FieldDef {
@ -42,15 +42,15 @@ impl AlignedItem for ast::FieldDef {
self.span()
}
fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
let attrs_str = self.attrs.rewrite(context, shape)?;
fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
let attrs_str = self.attrs.rewrite_result(context, shape)?;
let missing_span = if self.attrs.is_empty() {
mk_sp(self.span.lo(), self.span.lo())
} else {
mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
};
let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str);
rewrite_struct_field_prefix(context, self).and_then(|field_str| {
let field_str = rewrite_struct_field_prefix(context, self)?;
combine_strs_with_missing_comments(
context,
&attrs_str,
@ -59,7 +59,6 @@ impl AlignedItem for ast::FieldDef {
shape,
attrs_extendable,
)
})
}
fn rewrite_aligned_item(
@ -67,7 +66,7 @@ impl AlignedItem for ast::FieldDef {
context: &RewriteContext<'_>,
shape: Shape,
prefix_max_width: usize,
) -> Option<String> {
) -> RewriteResult {
rewrite_struct_field(context, self, shape, prefix_max_width)
}
}
@ -81,8 +80,8 @@ impl AlignedItem for ast::ExprField {
self.span()
}
fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
let attrs_str = self.attrs.rewrite(context, shape)?;
fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
let attrs_str = self.attrs.rewrite_result(context, shape)?;
let name = rewrite_ident(context, self.ident);
let missing_span = if self.attrs.is_empty() {
mk_sp(self.span.lo(), self.span.lo())
@ -104,7 +103,7 @@ impl AlignedItem for ast::ExprField {
context: &RewriteContext<'_>,
shape: Shape,
prefix_max_width: usize,
) -> Option<String> {
) -> RewriteResult {
rewrite_field(context, self, shape, prefix_max_width)
}
}
@ -199,7 +198,7 @@ fn struct_field_prefix_max_min_width<T: AlignedItem>(
.rewrite_prefix(context, shape)
.map(|field_str| trimmed_last_line_width(&field_str))
})
.fold_options((0, ::std::usize::MAX), |(max_len, min_len), len| {
.fold_ok((0, ::std::usize::MAX), |(max_len, min_len), len| {
(cmp::max(max_len, len), cmp::min(min_len, len))
})
.unwrap_or((0, 0))
@ -246,12 +245,12 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
if tactic == DefinitiveListTactic::Horizontal {
// since the items fits on a line, there is no need to align them
let do_rewrite =
|field: &T| -> Option<String> { field.rewrite_aligned_item(context, item_shape, 0) };
|field: &T| -> RewriteResult { field.rewrite_aligned_item(context, item_shape, 0) };
fields
.iter()
.zip(items.iter_mut())
.for_each(|(field, list_item): (&T, &mut ListItem)| {
if list_item.item.is_some() {
if list_item.item.is_ok() {
list_item.item = do_rewrite(field);
}
});
@ -267,7 +266,7 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
.tactic(tactic)
.trailing_separator(separator_tactic)
.preserve_newline(true);
write_list(&items, &fmt)
write_list(&items, &fmt).ok()
}
/// Returns the index in `fields` up to which a field belongs to the current group.

View File

@ -3,24 +3,23 @@ use std::rc::Rc;
use rustc_ast::{ast, token::Delimiter, visit};
use rustc_data_structures::sync::Lrc;
use rustc_span::{symbol, BytePos, Pos, Span};
use rustc_span::{BytePos, Pos, Span, symbol};
use tracing::debug;
use crate::attr::*;
use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices};
use crate::config::Version;
use crate::config::{BraceStyle, Config, MacroSelector};
use crate::comment::{CodeCharKind, CommentCodeSlices, contains_comment, rewrite_comment};
use crate::config::{BraceStyle, Config, MacroSelector, StyleEdition};
use crate::coverage::transform_missing_snippet;
use crate::items::{
format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, rewrite_extern_crate,
rewrite_type_alias, FnBraceStyle, FnSig, ItemVisitorKind, StaticParts, StructParts,
FnBraceStyle, FnSig, ItemVisitorKind, StaticParts, StructParts, format_impl, format_trait,
format_trait_alias, is_mod_decl, is_use_item, rewrite_extern_crate, rewrite_type_alias,
};
use crate::macros::{macro_style, rewrite_macro, rewrite_macro_def, MacroPosition};
use crate::macros::{MacroPosition, macro_style, rewrite_macro, rewrite_macro_def};
use crate::modules::Module;
use crate::parse::session::ParseSess;
use crate::rewrite::{Rewrite, RewriteContext};
use crate::shape::{Indent, Shape};
use crate::skip::{is_skip_attr, SkipContext};
use crate::skip::{SkipContext, is_skip_attr};
use crate::source_map::{LineRangeUtils, SpanUtils};
use crate::spanned::Spanned;
use crate::stmt::Stmt;
@ -291,7 +290,9 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
let mut comment_shape =
Shape::indented(self.block_indent, config).comment(config);
if self.config.version() == Version::Two && comment_on_same_line {
if self.config.style_edition() >= StyleEdition::Edition2024
&& comment_on_same_line
{
self.push_str(" ");
// put the first line of the comment on the same line as the
// block's last line
@ -312,8 +313,8 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
let comment_str =
rewrite_comment(other_lines, false, comment_shape, config);
match comment_str {
Some(ref s) => self.push_str(s),
None => self.push_str(other_lines),
Ok(ref s) => self.push_str(s),
Err(_) => self.push_str(other_lines),
}
}
}
@ -342,8 +343,8 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
let comment_str = rewrite_comment(&sub_slice, false, comment_shape, config);
match comment_str {
Some(ref s) => self.push_str(s),
None => self.push_str(&sub_slice),
Ok(ref s) => self.push_str(s),
Err(_) => self.push_str(&sub_slice),
}
}
}
@ -561,9 +562,11 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
)
} else {
let indent = self.block_indent;
let rewrite = self.rewrite_required_fn(
let rewrite = self
.rewrite_required_fn(
indent, item.ident, sig, &item.vis, generics, item.span,
);
)
.ok();
self.push_rewrite(item.span, rewrite);
}
}
@ -584,7 +587,8 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
item.ident,
&item.vis,
item.span,
);
)
.ok();
self.push_rewrite(item.span, rewrite);
}
ast::ItemKind::Delegation(..) | ast::ItemKind::DelegationMac(..) => {
@ -609,7 +613,8 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
self.block_indent,
visitor_kind,
span,
);
)
.ok();
self.push_rewrite(span, rewrite);
}
@ -655,8 +660,9 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
);
} else {
let indent = self.block_indent;
let rewrite =
self.rewrite_required_fn(indent, ai.ident, sig, &ai.vis, generics, ai.span);
let rewrite = self
.rewrite_required_fn(indent, ai.ident, sig, &ai.vis, generics, ai.span)
.ok();
self.push_rewrite(ai.span, rewrite);
}
}
@ -683,7 +689,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
// 1 = ;
let shape = self.shape().saturating_sub_width(1);
let rewrite = self.with_context(|ctx| rewrite_macro(mac, ident, ctx, shape, pos));
let rewrite = self.with_context(|ctx| rewrite_macro(mac, ident, ctx, shape, pos).ok());
// As of v638 of the rustc-ap-* crates, the associated span no longer includes
// the trailing semicolon. This determines the correct span to ensure scenarios
// with whitespace between the delimiters and trailing semi (i.e. `foo!(abc) ;`)

View File

@ -0,0 +1 @@
edition = 2019

View File

@ -0,0 +1 @@
style_edition = "2024"

View File

@ -0,0 +1 @@
version = "Two"

View File

@ -0,0 +1,2 @@
style_edition = "2024"
overflow_delimited_expr = false

View File

@ -0,0 +1,2 @@
style_edition = "2021"
edition = "2024"

View File

@ -0,0 +1,2 @@
version = "Two"
edition = "2018"

View File

@ -0,0 +1,3 @@
version = "Two"
edition = "2018"
style_edition = "2021"

View File

@ -0,0 +1,2 @@
version = "Two"
style_edition = "2021"

View File

@ -5,7 +5,7 @@ use std::fs::remove_file;
use std::path::Path;
use std::process::Command;
use rustfmt_config_proc_macro::rustfmt_only_ci_test;
use rustfmt_config_proc_macro::{nightly_only_test, rustfmt_only_ci_test};
/// Run the rustfmt executable and return its output.
fn rustfmt(args: &[&str]) -> (String, String) {
@ -207,3 +207,28 @@ fn rustfmt_emits_error_when_control_brace_style_is_always_next_line() {
let (_stdout, stderr) = rustfmt(&args);
assert!(!stderr.contains("error[internal]: left behind trailing whitespace"))
}
#[nightly_only_test]
#[test]
fn rustfmt_generates_no_error_if_failed_format_code_in_doc_comments() {
// See also https://github.com/rust-lang/rustfmt/issues/6109
let file = "tests/target/issue-6109.rs";
let args = ["--config", "format_code_in_doc_comments=true", file];
let (stdout, stderr) = rustfmt(&args);
assert!(stderr.is_empty());
assert!(stdout.is_empty());
}
#[test]
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 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));
}

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
fn main() {
match a {
_ =>

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
fn main() {
match a {
_ => // comment with =>

View File

@ -1,4 +1,4 @@
// rustfmt-version: One
// rustfmt-style_edition: 2015
// rustfmt-error_on_line_overflow: false
// rustfmt-indent_style: Block

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
// rustfmt-error_on_line_overflow: false
// rustfmt-indent_style: Block

View File

@ -0,0 +1,155 @@
// rustfmt-style_edition: 2015
fn combine_blocklike() {
do_thing(
|param| {
action();
foo(param)
},
);
do_thing(
x,
|param| {
action();
foo(param)
},
);
do_thing(
x,
// I'll be discussing the `action` with your para(m)legal counsel
|param| {
action();
foo(param)
},
);
do_thing(
Bar {
x: value,
y: value2,
},
);
do_thing(
x,
Bar {
x: value,
y: value2,
},
);
do_thing(
x,
// Let me tell you about that one time at the `Bar`
Bar {
x: value,
y: value2,
},
);
do_thing(
&[
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
&[
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
// Just admit it; my list is longer than can be folded on to one line
&[
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
vec![
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
vec![
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
// Just admit it; my list is longer than can be folded on to one line
vec![
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
(
1,
2,
3,
|param| {
action();
foo(param)
},
),
);
}
fn combine_struct_sample() {
let identity = verify(
&ctx,
VerifyLogin {
type_: LoginType::Username,
username: args.username.clone(),
password: Some(args.password.clone()),
domain: None,
},
)?;
}
fn combine_macro_sample() {
rocket::ignite()
.mount(
"/",
routes![
http::auth::login,
http::auth::logout,
http::cors::options,
http::action::dance,
http::action::sleep,
],
)
.launch();
}

View File

@ -0,0 +1,155 @@
// rustfmt-style_edition: 2024
fn combine_blocklike() {
do_thing(
|param| {
action();
foo(param)
},
);
do_thing(
x,
|param| {
action();
foo(param)
},
);
do_thing(
x,
// I'll be discussing the `action` with your para(m)legal counsel
|param| {
action();
foo(param)
},
);
do_thing(
Bar {
x: value,
y: value2,
},
);
do_thing(
x,
Bar {
x: value,
y: value2,
},
);
do_thing(
x,
// Let me tell you about that one time at the `Bar`
Bar {
x: value,
y: value2,
},
);
do_thing(
&[
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
&[
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
// Just admit it; my list is longer than can be folded on to one line
&[
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
vec![
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
vec![
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
// Just admit it; my list is longer than can be folded on to one line
vec![
value_with_longer_name,
value2_with_longer_name,
value3_with_longer_name,
value4_with_longer_name,
],
);
do_thing(
x,
(
1,
2,
3,
|param| {
action();
foo(param)
},
),
);
}
fn combine_struct_sample() {
let identity = verify(
&ctx,
VerifyLogin {
type_: LoginType::Username,
username: args.username.clone(),
password: Some(args.password.clone()),
domain: None,
},
)?;
}
fn combine_macro_sample() {
rocket::ignite()
.mount(
"/",
routes![
http::auth::login,
http::auth::logout,
http::cors::options,
http::action::dance,
http::action::sleep,
],
)
.launch();
}

View File

@ -1,5 +1,5 @@
// rustfmt-fn_single_line: true
// rustfmt-version: One
// rustfmt-style_edition: 2015
// Test single-line functions.
fn foo_expr() {

View File

@ -1,5 +1,5 @@
// rustfmt-fn_single_line: true
// rustfmt-version: Two
// rustfmt-style_edition: 2024
// Test single-line functions.
fn foo_expr() {

View File

@ -1,4 +1,4 @@
// rustfmt-version: One
// rustfmt-style_edition: 2015
// rustfmt-error_on_line_overflow: false
fn issue_2179() {

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
// rustfmt-error_on_line_overflow: false
fn issue_2179() {

View File

@ -1,4 +1,4 @@
// rustfmt-version: One
// rustfmt-style_edition: 2015
fn foo() {
match 0 {

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
fn foo() {
match 0 {

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
fn main() {
thread::spawn(|| {

View File

@ -1,4 +1,4 @@
// rustfmt-version: One
// rustfmt-style_edition: 2015
pub fn main() {
/* let s = String::from(

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
pub fn main() {
/* let s = String::from(

View File

@ -1,4 +1,4 @@
// rustfmt-version: One
// rustfmt-style_edition: 2015
fn main() {
assert!(HAYSTACK

View File

@ -1,4 +1,4 @@
// rustfmt-version: Two
// rustfmt-style_edition: 2024
fn main() {
assert!(HAYSTACK

View File

@ -1,4 +1,4 @@
// rustfmt-version: One
// rustfmt-style_edition: 2015
pub fn parse_conditional<'a, I: 'a>(
) -> impl Parser<Input = I, Output = Expr, PartialState = ()> + 'a

Some files were not shown because too many files have changed in this diff Show More