├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── cargo-expand ├── anyhow-1.0.58.rs ├── cargo-tally-1.0.8.rs ├── cxx-1.0.69.rs ├── erased-serde-0.3.21.rs ├── serde-1.0.137.rs ├── serde_derive-1.0.137.rs ├── syn-1.0.97.rs └── update │ ├── Cargo.toml │ └── update.rs ├── examples ├── .tokeignore ├── input.rs ├── output.prettyplease.rs ├── output.rustc.rs ├── output.rustfmt.rs └── update │ ├── Cargo.toml │ └── update-examples.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── round_trip.rs ├── src ├── algorithm.rs ├── attr.rs ├── classify.rs ├── convenience.rs ├── data.rs ├── expr.rs ├── file.rs ├── fixup.rs ├── generics.rs ├── item.rs ├── iter.rs ├── lib.rs ├── lifetime.rs ├── lit.rs ├── mac.rs ├── pat.rs ├── path.rs ├── precedence.rs ├── ring.rs ├── stmt.rs ├── token.rs └── ty.rs └── tests ├── test.rs └── test_precedence.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | cargo-expand/*.rs linguist-generated 2 | examples/*.rs linguist-generated 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Rust ${{matrix.rust}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable, 1.62.0] 28 | timeout-minutes: 45 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: dtolnay/rust-toolchain@master 32 | with: 33 | toolchain: ${{matrix.rust}} 34 | - run: cargo check 35 | - run: cargo check --features verbatim 36 | - run: cargo test 37 | env: 38 | RUSTFLAGS: ${{env.RUSTFLAGS}} ${{matrix.rust == 'nightly' && '--cfg exhaustive' || ''}} 39 | - run: cargo test --release --test test_precedence 40 | env: 41 | RUSTFLAGS: ${{env.RUSTFLAGS}} ${{matrix.rust == 'nightly' && '--cfg exhaustive' || ''}} 42 | if: matrix.rust != '1.62.0' 43 | - uses: actions/upload-artifact@v4 44 | if: matrix.rust == 'nightly' && always() 45 | with: 46 | name: Cargo.lock 47 | path: Cargo.lock 48 | continue-on-error: true 49 | 50 | examples: 51 | name: Examples 52 | needs: pre_ci 53 | if: needs.pre_ci.outputs.continue 54 | runs-on: ubuntu-latest 55 | timeout-minutes: 45 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: dtolnay/rust-toolchain@nightly 59 | with: 60 | components: llvm-tools, rustc-dev, rustfmt 61 | - run: cargo run --manifest-path examples/update/Cargo.toml 62 | - run: git diff --exit-code 63 | - run: cargo run --manifest-path cargo-expand/update/Cargo.toml 64 | - run: git diff --exit-code 65 | 66 | fuzz: 67 | name: Fuzz 68 | needs: pre_ci 69 | if: needs.pre_ci.outputs.continue 70 | runs-on: ubuntu-latest 71 | timeout-minutes: 45 72 | steps: 73 | - uses: actions/checkout@v4 74 | - uses: dtolnay/rust-toolchain@nightly 75 | - uses: dtolnay/install@cargo-fuzz 76 | - run: cargo fuzz check 77 | 78 | minimal: 79 | name: Minimal versions 80 | needs: pre_ci 81 | if: needs.pre_ci.outputs.continue 82 | runs-on: ubuntu-latest 83 | timeout-minutes: 45 84 | steps: 85 | - uses: actions/checkout@v4 86 | - uses: dtolnay/rust-toolchain@nightly 87 | - run: cargo generate-lockfile -Z minimal-versions 88 | - run: cargo check --locked 89 | 90 | doc: 91 | name: Documentation 92 | needs: pre_ci 93 | if: needs.pre_ci.outputs.continue 94 | runs-on: ubuntu-latest 95 | timeout-minutes: 45 96 | env: 97 | RUSTDOCFLAGS: -Dwarnings 98 | steps: 99 | - uses: actions/checkout@v4 100 | - uses: dtolnay/rust-toolchain@nightly 101 | - uses: dtolnay/install@cargo-docs-rs 102 | - run: cargo docs-rs 103 | 104 | clippy: 105 | name: Clippy 106 | runs-on: ubuntu-latest 107 | if: github.event_name != 'pull_request' 108 | timeout-minutes: 45 109 | steps: 110 | - uses: actions/checkout@v4 111 | - uses: dtolnay/rust-toolchain@clippy 112 | - run: cargo clippy --features verbatim -- -Dclippy::all -Dclippy::pedantic 113 | 114 | outdated: 115 | name: Outdated 116 | runs-on: ubuntu-latest 117 | if: github.event_name != 'pull_request' 118 | timeout-minutes: 45 119 | steps: 120 | - uses: actions/checkout@v4 121 | - uses: dtolnay/rust-toolchain@stable 122 | - uses: dtolnay/install@cargo-outdated 123 | - run: cargo outdated --workspace --exit-code 1 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prettyplease" 3 | version = "0.2.35" 4 | authors = ["David Tolnay "] 5 | autoexamples = false 6 | categories = ["development-tools"] 7 | description = "A minimal `syn` syntax tree pretty-printer" 8 | documentation = "https://docs.rs/prettyplease" 9 | edition = "2021" 10 | exclude = ["cargo-expand"] 11 | keywords = ["rustfmt"] 12 | license = "MIT OR Apache-2.0" 13 | links = "prettyplease02" 14 | repository = "https://github.com/dtolnay/prettyplease" 15 | rust-version = "1.62" 16 | 17 | [features] 18 | verbatim = ["syn/parsing"] 19 | 20 | [dependencies] 21 | proc-macro2 = { version = "1.0.80", default-features = false } 22 | syn = { version = "2.0.104", default-features = false, features = ["full"] } 23 | 24 | [dev-dependencies] 25 | indoc = "2" 26 | proc-macro2 = { version = "1.0.80", default-features = false } 27 | quote = { version = "1.0.35", default-features = false } 28 | syn = { version = "2.0.104", default-features = false, features = ["clone-impls", "extra-traits", "parsing", "printing", "visit-mut"] } 29 | 30 | [package.metadata.docs.rs] 31 | targets = ["x86_64-unknown-linux-gnu"] 32 | rustdoc-args = [ 33 | "--generate-link-to-definition", 34 | "--extern-html-root-url=core=https://doc.rust-lang.org", 35 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 36 | "--extern-html-root-url=std=https://doc.rust-lang.org", 37 | ] 38 | 39 | [package.metadata.playground] 40 | features = ["verbatim"] 41 | 42 | [workspace] 43 | members = ["cargo-expand/update", "examples/update"] 44 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | prettyplease::unparse 2 | ===================== 3 | 4 | [github](https://github.com/dtolnay/prettyplease) 5 | [crates.io](https://crates.io/crates/prettyplease) 6 | [docs.rs](https://docs.rs/prettyplease) 7 | [build status](https://github.com/dtolnay/prettyplease/actions?query=branch%3Amaster) 8 | 9 | A minimal `syn` syntax tree pretty-printer. 10 | 11 |
12 | 13 | ## Overview 14 | 15 | This is a pretty-printer to turn a `syn` syntax tree into a `String` of 16 | well-formatted source code. In contrast to rustfmt, this library is intended to 17 | be suitable for arbitrary generated code. 18 | 19 | Rustfmt prioritizes high-quality output that is impeccable enough that you'd be 20 | comfortable spending your career staring at its output — but that means 21 | some heavyweight algorithms, and it has a tendency to bail out on code that is 22 | hard to format (for example [rustfmt#3697], and there are dozens more issues 23 | like it). That's not necessarily a big deal for human-generated code because 24 | when code gets highly nested, the human will naturally be inclined to refactor 25 | into more easily formattable code. But for generated code, having the formatter 26 | just give up leaves it totally unreadable. 27 | 28 | [rustfmt#3697]: https://github.com/rust-lang/rustfmt/issues/3697 29 | 30 | This library is designed using the simplest possible algorithm and data 31 | structures that can deliver about 95% of the quality of rustfmt-formatted 32 | output. In my experience testing real-world code, approximately 97-98% of output 33 | lines come out identical between rustfmt's formatting and this crate's. The rest 34 | have slightly different linebreak decisions, but still clearly follow the 35 | dominant modern Rust style. 36 | 37 | The tradeoffs made by this crate are a good fit for generated code that you will 38 | *not* spend your career staring at. For example, the output of `bindgen`, or the 39 | output of `cargo-expand`. In those cases it's more important that the whole 40 | thing be formattable without the formatter giving up, than that it be flawless. 41 | 42 |
43 | 44 | ## Feature matrix 45 | 46 | Here are a few superficial comparisons of this crate against the AST 47 | pretty-printer built into rustc, and rustfmt. The sections below go into more 48 | detail comparing the output of each of these libraries. 49 | 50 | | | prettyplease | rustc | rustfmt | 51 | |:---|:---:|:---:|:---:| 52 | | non-pathological behavior on big or generated code | 💚 | ❌ | ❌ | 53 | | idiomatic modern formatting ("locally indistinguishable from rustfmt") | 💚 | ❌ | 💚 | 54 | | throughput | 60 MB/s | 39 MB/s | 2.8 MB/s | 55 | | number of dependencies | 3 | 72 | 66 | 56 | | compile time including dependencies | 2.4 sec | 23.1 sec | 29.8 sec | 57 | | buildable using a stable Rust compiler | 💚 | ❌ | ❌ | 58 | | published to crates.io | 💚 | ❌ | ❌ | 59 | | extensively configurable output | ❌ | ❌ | 💚 | 60 | | intended to accommodate hand-maintained source code | ❌ | ❌ | 💚 | 61 | 62 |
63 | 64 | ## Comparison to rustfmt 65 | 66 | - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) 67 | - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) 68 | - [output.rustfmt.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustfmt.rs) 69 | 70 | If you weren't told which output file is which, it would be practically 71 | impossible to tell — **except** for line 435 in the rustfmt output, which 72 | is more than 1000 characters long because rustfmt just gave up formatting that 73 | part of the file: 74 | 75 | ```rust 76 | match segments[5] { 77 | 0 => write!(f, "::{}", ipv4), 78 | 0xffff => write!(f, "::ffff:{}", ipv4), 79 | _ => unreachable!(), 80 | } 81 | } else { # [derive (Copy , Clone , Default)] struct Span { start : usize , len : usize , } let zeroes = { let mut longest = Span :: default () ; let mut current = Span :: default () ; for (i , & segment) in segments . iter () . enumerate () { if segment == 0 { if current . len == 0 { current . start = i ; } current . len += 1 ; if current . len > longest . len { longest = current ; } } else { current = Span :: default () ; } } longest } ; # [doc = " Write a colon-separated part of the address"] # [inline] fn fmt_subslice (f : & mut fmt :: Formatter < '_ > , chunk : & [u16]) -> fmt :: Result { if let Some ((first , tail)) = chunk . split_first () { write ! (f , "{:x}" , first) ? ; for segment in tail { f . write_char (':') ? ; write ! (f , "{:x}" , segment) ? ; } } Ok (()) } if zeroes . len > 1 { fmt_subslice (f , & segments [.. zeroes . start]) ? ; f . write_str ("::") ? ; fmt_subslice (f , & segments [zeroes . start + zeroes . len ..]) } else { fmt_subslice (f , & segments) } } 82 | } else { 83 | const IPV6_BUF_LEN: usize = (4 * 8) + 7; 84 | let mut buf = [0u8; IPV6_BUF_LEN]; 85 | let mut buf_slice = &mut buf[..]; 86 | ``` 87 | 88 | This is a pretty typical manifestation of rustfmt bailing out in generated code 89 | — a chunk of the input ends up on one line. The other manifestation is 90 | that you're working on some code, running rustfmt on save like a conscientious 91 | developer, but after a while notice it isn't doing anything. You introduce an 92 | intentional formatting issue, like a stray indent or semicolon, and run rustfmt 93 | to check your suspicion. Nope, it doesn't get cleaned up — rustfmt is just 94 | not formatting the part of the file you are working on. 95 | 96 | The prettyplease library is designed to have no pathological cases that force a 97 | bail out; the entire input you give it will get formatted in some "good enough" 98 | form. 99 | 100 | Separately, rustfmt can be problematic to integrate into projects. It's written 101 | using rustc's internal syntax tree, so it can't be built by a stable compiler. 102 | Its releases are not regularly published to crates.io, so in Cargo builds you'd 103 | need to depend on it as a git dependency, which precludes publishing your crate 104 | to crates.io also. You can shell out to a `rustfmt` binary, but that'll be 105 | whatever rustfmt version is installed on each developer's system (if any), which 106 | can lead to spurious diffs in checked-in generated code formatted by different 107 | versions. In contrast prettyplease is designed to be easy to pull in as a 108 | library, and compiles fast. 109 | 110 |
111 | 112 | ## Comparison to rustc_ast_pretty 113 | 114 | - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) 115 | - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) 116 | - [output.rustc.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustc.rs) 117 | 118 | This is the pretty-printer that gets used when rustc prints source code, such as 119 | `rustc -Zunpretty=expanded`. It's used also by the standard library's 120 | `stringify!` when stringifying an interpolated macro_rules AST fragment, like an 121 | $:expr, and transitively by `dbg!` and many macros in the ecosystem. 122 | 123 | Rustc's formatting is mostly okay, but does not hew closely to the dominant 124 | contemporary style of Rust formatting. Some things wouldn't ever be written on 125 | one line, like this `match` expression, and certainly not with a comma in front 126 | of the closing brace: 127 | 128 | ```rust 129 | fn eq(&self, other: &IpAddr) -> bool { 130 | match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } 131 | } 132 | ``` 133 | 134 | Some places use non-multiple-of-4 indentation, which is definitely not the norm: 135 | 136 | ```rust 137 | pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { 138 | let [a, b, c, d] = self.octets(); 139 | Ipv6Addr{inner: 140 | c::in6_addr{s6_addr: 141 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 142 | 0xFF, a, b, c, d],},} 143 | } 144 | ``` 145 | 146 | And although there isn't an egregious example of it in the link because the 147 | input code is pretty tame, in general rustc_ast_pretty has pathological behavior 148 | on generated code. It has a tendency to use excessive horizontal indentation and 149 | rapidly run out of width: 150 | 151 | ```rust 152 | ::std::io::_print(::core::fmt::Arguments::new_v1(&[""], 153 | &match (&msg,) { 154 | _args => 155 | [::core::fmt::ArgumentV1::new(_args.0, 156 | ::core::fmt::Display::fmt)], 157 | })); 158 | ``` 159 | 160 | The snippets above are clearly different from modern rustfmt style. In contrast, 161 | prettyplease is designed to have output that is practically indistinguishable 162 | from rustfmt-formatted code. 163 | 164 |
165 | 166 | ## Example 167 | 168 | ```rust 169 | // [dependencies] 170 | // prettyplease = "0.2" 171 | // syn = { version = "2", default-features = false, features = ["full", "parsing"] } 172 | 173 | const INPUT: &str = stringify! { 174 | use crate::{ 175 | lazy::{Lazy, SyncLazy, SyncOnceCell}, panic, 176 | sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, 177 | mpsc::channel, Mutex, }, 178 | thread, 179 | }; 180 | impl Into for T where U: From { 181 | fn into(self) -> U { U::from(self) } 182 | } 183 | }; 184 | 185 | fn main() { 186 | let syntax_tree = syn::parse_file(INPUT).unwrap(); 187 | let formatted = prettyplease::unparse(&syntax_tree); 188 | print!("{}", formatted); 189 | } 190 | ``` 191 | 192 |
193 | 194 | ## Algorithm notes 195 | 196 | The approach and terminology used in the implementation are derived from [*Derek 197 | C. Oppen, "Pretty Printing" (1979)*][paper], on which rustc_ast_pretty is also 198 | based, and from rustc_ast_pretty's implementation written by Graydon Hoare in 199 | 2011 (and modernized over the years by dozens of volunteer maintainers). 200 | 201 | [paper]: http://i.stanford.edu/pub/cstr/reports/cs/tr/79/770/CS-TR-79-770.pdf 202 | 203 | The paper describes two language-agnostic interacting procedures `Scan()` and 204 | `Print()`. Language-specific code decomposes an input data structure into a 205 | stream of `string` and `break` tokens, and `begin` and `end` tokens for 206 | grouping. Each `begin`–`end` range may be identified as either "consistent 207 | breaking" or "inconsistent breaking". If a group is consistently breaking, then 208 | if the whole contents do not fit on the line, *every* `break` token in the group 209 | will receive a linebreak. This is appropriate, for example, for Rust struct 210 | literals, or arguments of a function call. If a group is inconsistently 211 | breaking, then the `string` tokens in the group are greedily placed on the line 212 | until out of space, and linebroken only at those `break` tokens for which the 213 | next string would not fit. For example, this is appropriate for the contents of 214 | a braced `use` statement in Rust. 215 | 216 | Scan's job is to efficiently accumulate sizing information about groups and 217 | breaks. For every `begin` token we compute the distance to the matched `end` 218 | token, and for every `break` we compute the distance to the next `break`. The 219 | algorithm uses a ringbuffer to hold tokens whose size is not yet ascertained. 220 | The maximum size of the ringbuffer is bounded by the target line length and does 221 | not grow indefinitely, regardless of deep nesting in the input stream. That's 222 | because once a group is sufficiently big, the precise size can no longer make a 223 | difference to linebreak decisions and we can effectively treat it as "infinity". 224 | 225 | Print's job is to use the sizing information to efficiently assign a "broken" or 226 | "not broken" status to every `begin` token. At that point the output is easily 227 | constructed by concatenating `string` tokens and breaking at `break` tokens 228 | contained within a broken group. 229 | 230 | Leveraging these primitives (i.e. cleverly placing the all-or-nothing consistent 231 | breaks and greedy inconsistent breaks) to yield rustfmt-compatible formatting 232 | for all of Rust's syntax tree nodes is a fun challenge. 233 | 234 | Here is a visualization of some Rust tokens fed into the pretty printing 235 | algorithm. Consistently breaking `begin`—`end` pairs are represented by 236 | `«`⁠`»`, inconsistently breaking by `‹`⁠`›`, `break` by `·`, and the 237 | rest of the non-whitespace are `string`. 238 | 239 | ```text 240 | use crate::«{· 241 | ‹ lazy::«{·‹Lazy,· SyncLazy,· SyncOnceCell›·}»,· 242 | panic,· 243 | sync::«{· 244 | ‹ atomic::«{·‹AtomicUsize,· Ordering::SeqCst›·}»,· 245 | mpsc::channel,· Mutex›,· 246 | }»,· 247 | thread›,· 248 | }»;· 249 | «‹«impl<«·T‹›,· U‹›·»>» Into<«·U·»>· for T›· 250 | where· 251 | U:‹ From<«·T·»>›,· 252 | {· 253 | « fn into(·«·self·») -> U {· 254 | ‹ U::from(«·self·»)›· 255 | » }· 256 | »}· 257 | ``` 258 | 259 | The algorithm described in the paper is not quite sufficient for producing 260 | well-formatted Rust code that is locally indistinguishable from rustfmt's style. 261 | The reason is that in the paper, the complete non-whitespace contents are 262 | assumed to be independent of linebreak decisions, with Scan and Print being only 263 | in control of the whitespace (spaces and line breaks). In Rust as idiomatically 264 | formatted by rustfmt, that is not the case. Trailing commas are one example; the 265 | punctuation is only known *after* the broken vs non-broken status of the 266 | surrounding group is known: 267 | 268 | ```rust 269 | let _ = Struct { x: 0, y: true }; 270 | 271 | let _ = Struct { 272 | x: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, 273 | y: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, //<- trailing comma if the expression wrapped 274 | }; 275 | ``` 276 | 277 | The formatting of `match` expressions is another case; we want small arms on the 278 | same line as the pattern, and big arms wrapped in a brace. The presence of the 279 | brace punctuation, comma, and semicolon are all dependent on whether the arm 280 | fits on the line: 281 | 282 | ```rust 283 | match total_nanos.checked_add(entry.nanos as u64) { 284 | Some(n) => tmp = n, //<- small arm, inline with comma 285 | None => { 286 | total_secs = total_secs 287 | .checked_add(total_nanos / NANOS_PER_SEC as u64) 288 | .expect("overflow in iter::sum over durations"); 289 | } //<- big arm, needs brace added, and also semicolon^ 290 | } 291 | ``` 292 | 293 | The printing algorithm implementation in this crate accommodates all of these 294 | situations with conditional punctuation tokens whose selection can be deferred 295 | and populated after it's known that the group is or is not broken. 296 | 297 |
298 | 299 | #### License 300 | 301 | 302 | Licensed under either of Apache License, Version 303 | 2.0 or MIT license at your option. 304 | 305 | 306 |
307 | 308 | 309 | Unless you explicitly state otherwise, any contribution intentionally submitted 310 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 311 | be dual licensed as above, without any additional terms or conditions. 312 | 313 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::process; 4 | 5 | fn main() { 6 | println!("cargo:rerun-if-changed=build.rs"); 7 | 8 | println!("cargo:rustc-check-cfg=cfg(exhaustive)"); 9 | println!("cargo:rustc-check-cfg=cfg(prettyplease_debug)"); 10 | println!("cargo:rustc-check-cfg=cfg(prettyplease_debug_indent)"); 11 | 12 | let pkg_version = cargo_env_var("CARGO_PKG_VERSION"); 13 | println!("cargo:VERSION={}", pkg_version.to_str().unwrap()); 14 | } 15 | 16 | fn cargo_env_var(key: &str) -> OsString { 17 | env::var_os(key).unwrap_or_else(|| { 18 | eprintln!("Environment variable ${key} is not set during execution of build script"); 19 | process::exit(1); 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /cargo-expand/update/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prettyplease-update" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [[bin]] 9 | name = "prettyplease-update" 10 | path = "update.rs" 11 | 12 | [dependencies] 13 | anyhow = "1.0" 14 | prettyplease = { path = "../../", features = ["verbatim"] } 15 | proc-macro2 = { version = "1.0", features = ["span-locations"] } 16 | syn = { version = "2.0", default-features = false, features = ["parsing", "printing"] } 17 | -------------------------------------------------------------------------------- /cargo-expand/update/update.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use std::ffi::OsStr; 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | fn main() -> Result<()> { 7 | let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); 8 | let cargo_expand_dir = manifest_dir.join(".."); 9 | 10 | for entry in fs::read_dir(cargo_expand_dir)? { 11 | let entry = entry?; 12 | let file_type = entry.file_type()?; 13 | if !file_type.is_file() { 14 | continue; 15 | } 16 | 17 | let path = entry.path(); 18 | if path.extension() != Some(OsStr::new("rs")) { 19 | continue; 20 | } 21 | 22 | let input_contents = fs::read_to_string(&path)?; 23 | let syntax_tree = match syn::parse_file(&input_contents) { 24 | Ok(syntax_tree) => syntax_tree, 25 | Err(err) => { 26 | let path = path.canonicalize().unwrap_or(path); 27 | let span = err.span().start(); 28 | bail!("{}:{}:{}\n{}", path.display(), span.line, span.column, err); 29 | } 30 | }; 31 | let string = prettyplease::unparse(&syntax_tree); 32 | fs::write(&path, string)?; 33 | } 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /examples/.tokeignore: -------------------------------------------------------------------------------- 1 | *.rs 2 | -------------------------------------------------------------------------------- /examples/input.rs: -------------------------------------------------------------------------------- 1 | use crate :: cmp :: Ordering ; use crate :: fmt :: { self , Write as FmtWrite } ; use crate :: hash ; use crate :: io :: Write as IoWrite ; use crate :: mem :: transmute ; use crate :: sys :: net :: netc as c ; use crate :: sys_common :: { AsInner , FromInner , IntoInner } ; # [derive (Copy , Clone , Eq , PartialEq , Hash , PartialOrd , Ord)] pub enum IpAddr { V4 (Ipv4Addr) , V6 (Ipv6Addr) , } # [derive (Copy)] pub struct Ipv4Addr { inner : c :: in_addr , } # [derive (Copy)] pub struct Ipv6Addr { inner : c :: in6_addr , } # [derive (Copy , PartialEq , Eq , Clone , Hash , Debug)] # [non_exhaustive] pub enum Ipv6MulticastScope { InterfaceLocal , LinkLocal , RealmLocal , AdminLocal , SiteLocal , OrganizationLocal , Global , } impl IpAddr { pub const fn is_unspecified (& self) -> bool { match self { IpAddr :: V4 (ip) => ip . is_unspecified () , IpAddr :: V6 (ip) => ip . is_unspecified () , } } pub const fn is_loopback (& self) -> bool { match self { IpAddr :: V4 (ip) => ip . is_loopback () , IpAddr :: V6 (ip) => ip . is_loopback () , } } pub const fn is_global (& self) -> bool { match self { IpAddr :: V4 (ip) => ip . is_global () , IpAddr :: V6 (ip) => ip . is_global () , } } pub const fn is_multicast (& self) -> bool { match self { IpAddr :: V4 (ip) => ip . is_multicast () , IpAddr :: V6 (ip) => ip . is_multicast () , } } pub const fn is_documentation (& self) -> bool { match self { IpAddr :: V4 (ip) => ip . is_documentation () , IpAddr :: V6 (ip) => ip . is_documentation () , } } pub const fn is_benchmarking (& self) -> bool { match self { IpAddr :: V4 (ip) => ip . is_benchmarking () , IpAddr :: V6 (ip) => ip . is_benchmarking () , } } pub const fn is_ipv4 (& self) -> bool { matches ! (self , IpAddr :: V4 (_)) } pub const fn is_ipv6 (& self) -> bool { matches ! (self , IpAddr :: V6 (_)) } pub const fn to_canonical (& self) -> IpAddr { match self { & v4 @ IpAddr :: V4 (_) => v4 , IpAddr :: V6 (v6) => v6 . to_canonical () , } } } impl Ipv4Addr { pub const fn new (a : u8 , b : u8 , c : u8 , d : u8) -> Ipv4Addr { Ipv4Addr { inner : c :: in_addr { s_addr : u32 :: from_ne_bytes ([a , b , c , d]) } } } pub const LOCALHOST : Self = Ipv4Addr :: new (127 , 0 , 0 , 1) ; # [doc (alias = "INADDR_ANY")] pub const UNSPECIFIED : Self = Ipv4Addr :: new (0 , 0 , 0 , 0) ; pub const BROADCAST : Self = Ipv4Addr :: new (255 , 255 , 255 , 255) ; pub const fn octets (& self) -> [u8 ; 4] { self . inner . s_addr . to_ne_bytes () } pub const fn is_unspecified (& self) -> bool { self . inner . s_addr == 0 } pub const fn is_loopback (& self) -> bool { self . octets () [0] == 127 } pub const fn is_private (& self) -> bool { match self . octets () { [10 , ..] => true , [172 , b , ..] if b >= 16 && b <= 31 => true , [192 , 168 , ..] => true , _ => false , } } pub const fn is_link_local (& self) -> bool { matches ! (self . octets () , [169 , 254 , ..]) } pub const fn is_global (& self) -> bool { if u32 :: from_be_bytes (self . octets ()) == 0xc0000009 || u32 :: from_be_bytes (self . octets ()) == 0xc000000a { return true ; } ! self . is_private () && ! self . is_loopback () && ! self . is_link_local () && ! self . is_broadcast () && ! self . is_documentation () && ! self . is_shared () && ! (self . octets () [0] == 192 && self . octets () [1] == 0 && self . octets () [2] == 0) && ! self . is_reserved () && ! self . is_benchmarking () && self . octets () [0] != 0 } pub const fn is_shared (& self) -> bool { self . octets () [0] == 100 && (self . octets () [1] & 0b1100_0000 == 0b0100_0000) } pub const fn is_benchmarking (& self) -> bool { self . octets () [0] == 198 && (self . octets () [1] & 0xfe) == 18 } pub const fn is_reserved (& self) -> bool { self . octets () [0] & 240 == 240 && ! self . is_broadcast () } pub const fn is_multicast (& self) -> bool { self . octets () [0] >= 224 && self . octets () [0] <= 239 } pub const fn is_broadcast (& self) -> bool { u32 :: from_be_bytes (self . octets ()) == u32 :: from_be_bytes (Self :: BROADCAST . octets ()) } pub const fn is_documentation (& self) -> bool { matches ! (self . octets () , [192 , 0 , 2 , _] | [198 , 51 , 100 , _] | [203 , 0 , 113 , _]) } pub const fn to_ipv6_compatible (& self) -> Ipv6Addr { let [a , b , c , d] = self . octets () ; Ipv6Addr { inner : c :: in6_addr { s6_addr : [0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , a , b , c , d] } , } } pub const fn to_ipv6_mapped (& self) -> Ipv6Addr { let [a , b , c , d] = self . octets () ; Ipv6Addr { inner : c :: in6_addr { s6_addr : [0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0xFF , 0xFF , a , b , c , d] } , } } } impl fmt :: Display for IpAddr { fn fmt (& self , fmt : & mut fmt :: Formatter < '_ >) -> fmt :: Result { match self { IpAddr :: V4 (ip) => ip . fmt (fmt) , IpAddr :: V6 (ip) => ip . fmt (fmt) , } } } impl fmt :: Debug for IpAddr { fn fmt (& self , fmt : & mut fmt :: Formatter < '_ >) -> fmt :: Result { fmt :: Display :: fmt (self , fmt) } } impl From < Ipv4Addr > for IpAddr { fn from (ipv4 : Ipv4Addr) -> IpAddr { IpAddr :: V4 (ipv4) } } impl From < Ipv6Addr > for IpAddr { fn from (ipv6 : Ipv6Addr) -> IpAddr { IpAddr :: V6 (ipv6) } } impl fmt :: Display for Ipv4Addr { fn fmt (& self , fmt : & mut fmt :: Formatter < '_ >) -> fmt :: Result { let octets = self . octets () ; if fmt . precision () . is_none () && fmt . width () . is_none () { write ! (fmt , "{}.{}.{}.{}" , octets [0] , octets [1] , octets [2] , octets [3]) } else { const IPV4_BUF_LEN : usize = 15 ; let mut buf = [0u8 ; IPV4_BUF_LEN] ; let mut buf_slice = & mut buf [..] ; write ! (buf_slice , "{}.{}.{}.{}" , octets [0] , octets [1] , octets [2] , octets [3]) . unwrap () ; let len = IPV4_BUF_LEN - buf_slice . len () ; let buf = unsafe { crate :: str :: from_utf8_unchecked (& buf [.. len]) } ; fmt . pad (buf) } } } impl fmt :: Debug for Ipv4Addr { fn fmt (& self , fmt : & mut fmt :: Formatter < '_ >) -> fmt :: Result { fmt :: Display :: fmt (self , fmt) } } impl Clone for Ipv4Addr { fn clone (& self) -> Ipv4Addr { * self } } impl PartialEq for Ipv4Addr { fn eq (& self , other : & Ipv4Addr) -> bool { self . inner . s_addr == other . inner . s_addr } } impl PartialEq < Ipv4Addr > for IpAddr { fn eq (& self , other : & Ipv4Addr) -> bool { match self { IpAddr :: V4 (v4) => v4 == other , IpAddr :: V6 (_) => false , } } } impl PartialEq < IpAddr > for Ipv4Addr { fn eq (& self , other : & IpAddr) -> bool { match other { IpAddr :: V4 (v4) => self == v4 , IpAddr :: V6 (_) => false , } } } impl Eq for Ipv4Addr { } impl hash :: Hash for Ipv4Addr { fn hash < H : hash :: Hasher > (& self , s : & mut H) { { self . inner . s_addr } . hash (s) } } impl PartialOrd for Ipv4Addr { fn partial_cmp (& self , other : & Ipv4Addr) -> Option < Ordering > { Some (self . cmp (other)) } } impl PartialOrd < Ipv4Addr > for IpAddr { fn partial_cmp (& self , other : & Ipv4Addr) -> Option < Ordering > { match self { IpAddr :: V4 (v4) => v4 . partial_cmp (other) , IpAddr :: V6 (_) => Some (Ordering :: Greater) , } } } impl PartialOrd < IpAddr > for Ipv4Addr { fn partial_cmp (& self , other : & IpAddr) -> Option < Ordering > { match other { IpAddr :: V4 (v4) => self . partial_cmp (v4) , IpAddr :: V6 (_) => Some (Ordering :: Less) , } } } impl Ord for Ipv4Addr { fn cmp (& self , other : & Ipv4Addr) -> Ordering { u32 :: from_be (self . inner . s_addr) . cmp (& u32 :: from_be (other . inner . s_addr)) } } impl IntoInner < c :: in_addr > for Ipv4Addr { fn into_inner (self) -> c :: in_addr { self . inner } } impl From < Ipv4Addr > for u32 { fn from (ip : Ipv4Addr) -> u32 { let ip = ip . octets () ; u32 :: from_be_bytes (ip) } } impl From < u32 > for Ipv4Addr { fn from (ip : u32) -> Ipv4Addr { Ipv4Addr :: from (ip . to_be_bytes ()) } } impl From < [u8 ; 4] > for Ipv4Addr { fn from (octets : [u8 ; 4]) -> Ipv4Addr { Ipv4Addr :: new (octets [0] , octets [1] , octets [2] , octets [3]) } } impl From < [u8 ; 4] > for IpAddr { fn from (octets : [u8 ; 4]) -> IpAddr { IpAddr :: V4 (Ipv4Addr :: from (octets)) } } impl Ipv6Addr { pub const fn new (a : u16 , b : u16 , c : u16 , d : u16 , e : u16 , f : u16 , g : u16 , h : u16) -> Ipv6Addr { let addr16 = [a . to_be () , b . to_be () , c . to_be () , d . to_be () , e . to_be () , f . to_be () , g . to_be () , h . to_be () ,] ; Ipv6Addr { inner : c :: in6_addr { s6_addr : unsafe { transmute :: < _ , [u8 ; 16] > (addr16) } , } , } } pub const LOCALHOST : Self = Ipv6Addr :: new (0 , 0 , 0 , 0 , 0 , 0 , 0 , 1) ; pub const UNSPECIFIED : Self = Ipv6Addr :: new (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0) ; pub const fn segments (& self) -> [u16 ; 8] { let [a , b , c , d , e , f , g , h] = unsafe { transmute :: < _ , [u16 ; 8] > (self . inner . s6_addr) } ; [u16 :: from_be (a) , u16 :: from_be (b) , u16 :: from_be (c) , u16 :: from_be (d) , u16 :: from_be (e) , u16 :: from_be (f) , u16 :: from_be (g) , u16 :: from_be (h) ,] } pub const fn is_unspecified (& self) -> bool { u128 :: from_be_bytes (self . octets ()) == u128 :: from_be_bytes (Ipv6Addr :: UNSPECIFIED . octets ()) } pub const fn is_loopback (& self) -> bool { u128 :: from_be_bytes (self . octets ()) == u128 :: from_be_bytes (Ipv6Addr :: LOCALHOST . octets ()) } pub const fn is_global (& self) -> bool { match self . multicast_scope () { Some (Ipv6MulticastScope :: Global) => true , None => self . is_unicast_global () , _ => false , } } pub const fn is_unique_local (& self) -> bool { (self . segments () [0] & 0xfe00) == 0xfc00 } pub const fn is_unicast (& self) -> bool { ! self . is_multicast () } pub const fn is_unicast_link_local (& self) -> bool { (self . segments () [0] & 0xffc0) == 0xfe80 } pub const fn is_documentation (& self) -> bool { (self . segments () [0] == 0x2001) && (self . segments () [1] == 0xdb8) } pub const fn is_benchmarking (& self) -> bool { (self . segments () [0] == 0x2001) && (self . segments () [1] == 0x2) && (self . segments () [2] == 0) } pub const fn is_unicast_global (& self) -> bool { self . is_unicast () && ! self . is_loopback () && ! self . is_unicast_link_local () && ! self . is_unique_local () && ! self . is_unspecified () && ! self . is_documentation () } pub const fn multicast_scope (& self) -> Option < Ipv6MulticastScope > { if self . is_multicast () { match self . segments () [0] & 0x000f { 1 => Some (Ipv6MulticastScope :: InterfaceLocal) , 2 => Some (Ipv6MulticastScope :: LinkLocal) , 3 => Some (Ipv6MulticastScope :: RealmLocal) , 4 => Some (Ipv6MulticastScope :: AdminLocal) , 5 => Some (Ipv6MulticastScope :: SiteLocal) , 8 => Some (Ipv6MulticastScope :: OrganizationLocal) , 14 => Some (Ipv6MulticastScope :: Global) , _ => None , } } else { None } } pub const fn is_multicast (& self) -> bool { (self . segments () [0] & 0xff00) == 0xff00 } pub const fn to_ipv4_mapped (& self) -> Option < Ipv4Addr > { match self . octets () { [0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0xff , 0xff , a , b , c , d] => { Some (Ipv4Addr :: new (a , b , c , d)) } _ => None , } } pub const fn to_ipv4 (& self) -> Option < Ipv4Addr > { if let [0 , 0 , 0 , 0 , 0 , 0 | 0xffff , ab , cd] = self . segments () { let [a , b] = ab . to_be_bytes () ; let [c , d] = cd . to_be_bytes () ; Some (Ipv4Addr :: new (a , b , c , d)) } else { None } } pub const fn to_canonical (& self) -> IpAddr { if let Some (mapped) = self . to_ipv4_mapped () { return IpAddr :: V4 (mapped) ; } IpAddr :: V6 (* self) } pub const fn octets (& self) -> [u8 ; 16] { self . inner . s6_addr } } impl fmt :: Display for Ipv6Addr { fn fmt (& self , f : & mut fmt :: Formatter < '_ >) -> fmt :: Result { if f . precision () . is_none () && f . width () . is_none () { let segments = self . segments () ; if self . is_unspecified () { f . write_str ("::") } else if self . is_loopback () { f . write_str ("::1") } else if let Some (ipv4) = self . to_ipv4 () { match segments [5] { 0 => write ! (f , "::{}" , ipv4) , 0xffff => write ! (f , "::ffff:{}" , ipv4) , _ => unreachable ! () , } } else { # [derive (Copy , Clone , Default)] struct Span { start : usize , len : usize , } let zeroes = { let mut longest = Span :: default () ; let mut current = Span :: default () ; for (i , & segment) in segments . iter () . enumerate () { if segment == 0 { if current . len == 0 { current . start = i ; } current . len += 1 ; if current . len > longest . len { longest = current ; } } else { current = Span :: default () ; } } longest } ; # [doc = " Write a colon-separated part of the address"] # [inline] fn fmt_subslice (f : & mut fmt :: Formatter < '_ > , chunk : & [u16]) -> fmt :: Result { if let Some ((first , tail)) = chunk . split_first () { write ! (f , "{:x}" , first) ? ; for segment in tail { f . write_char (':') ? ; write ! (f , "{:x}" , segment) ? ; } } Ok (()) } if zeroes . len > 1 { fmt_subslice (f , & segments [.. zeroes . start]) ? ; f . write_str ("::") ? ; fmt_subslice (f , & segments [zeroes . start + zeroes . len ..]) } else { fmt_subslice (f , & segments) } } } else { const IPV6_BUF_LEN : usize = (4 * 8) + 7 ; let mut buf = [0u8 ; IPV6_BUF_LEN] ; let mut buf_slice = & mut buf [..] ; write ! (buf_slice , "{}" , self) . unwrap () ; let len = IPV6_BUF_LEN - buf_slice . len () ; let buf = unsafe { crate :: str :: from_utf8_unchecked (& buf [.. len]) } ; f . pad (buf) } } } impl fmt :: Debug for Ipv6Addr { fn fmt (& self , fmt : & mut fmt :: Formatter < '_ >) -> fmt :: Result { fmt :: Display :: fmt (self , fmt) } } impl Clone for Ipv6Addr { fn clone (& self) -> Ipv6Addr { * self } } impl PartialEq for Ipv6Addr { fn eq (& self , other : & Ipv6Addr) -> bool { self . inner . s6_addr == other . inner . s6_addr } } impl PartialEq < IpAddr > for Ipv6Addr { fn eq (& self , other : & IpAddr) -> bool { match other { IpAddr :: V4 (_) => false , IpAddr :: V6 (v6) => self == v6 , } } } impl PartialEq < Ipv6Addr > for IpAddr { fn eq (& self , other : & Ipv6Addr) -> bool { match self { IpAddr :: V4 (_) => false , IpAddr :: V6 (v6) => v6 == other , } } } impl Eq for Ipv6Addr { } impl hash :: Hash for Ipv6Addr { fn hash < H : hash :: Hasher > (& self , s : & mut H) { self . inner . s6_addr . hash (s) } } impl PartialOrd for Ipv6Addr { fn partial_cmp (& self , other : & Ipv6Addr) -> Option < Ordering > { Some (self . cmp (other)) } } impl PartialOrd < Ipv6Addr > for IpAddr { fn partial_cmp (& self , other : & Ipv6Addr) -> Option < Ordering > { match self { IpAddr :: V4 (_) => Some (Ordering :: Less) , IpAddr :: V6 (v6) => v6 . partial_cmp (other) , } } } impl PartialOrd < IpAddr > for Ipv6Addr { fn partial_cmp (& self , other : & IpAddr) -> Option < Ordering > { match other { IpAddr :: V4 (_) => Some (Ordering :: Greater) , IpAddr :: V6 (v6) => self . partial_cmp (v6) , } } } impl Ord for Ipv6Addr { fn cmp (& self , other : & Ipv6Addr) -> Ordering { self . segments () . cmp (& other . segments ()) } } impl AsInner < c :: in6_addr > for Ipv6Addr { fn as_inner (& self) -> & c :: in6_addr { & self . inner } } impl FromInner < c :: in6_addr > for Ipv6Addr { fn from_inner (addr : c :: in6_addr) -> Ipv6Addr { Ipv6Addr { inner : addr } } } impl From < Ipv6Addr > for u128 { fn from (ip : Ipv6Addr) -> u128 { let ip = ip . octets () ; u128 :: from_be_bytes (ip) } } impl From < u128 > for Ipv6Addr { fn from (ip : u128) -> Ipv6Addr { Ipv6Addr :: from (ip . to_be_bytes ()) } } impl From < [u8 ; 16] > for Ipv6Addr { fn from (octets : [u8 ; 16]) -> Ipv6Addr { let inner = c :: in6_addr { s6_addr : octets } ; Ipv6Addr :: from_inner (inner) } } impl From < [u16 ; 8] > for Ipv6Addr { fn from (segments : [u16 ; 8]) -> Ipv6Addr { let [a , b , c , d , e , f , g , h] = segments ; Ipv6Addr :: new (a , b , c , d , e , f , g , h) } } impl From < [u8 ; 16] > for IpAddr { fn from (octets : [u8 ; 16]) -> IpAddr { IpAddr :: V6 (Ipv6Addr :: from (octets)) } } impl From < [u16 ; 8] > for IpAddr { fn from (segments : [u16 ; 8]) -> IpAddr { IpAddr :: V6 (Ipv6Addr :: from (segments)) } } 2 | -------------------------------------------------------------------------------- /examples/output.rustc.rs: -------------------------------------------------------------------------------- 1 | use crate::cmp::Ordering;use crate::fmt::{self, Write as FmtWrite}; 2 | use crate::hash; 3 | use crate::io::Write as IoWrite; 4 | use crate::mem::transmute; 5 | use crate::sys::net::netc as c; 6 | use crate::sys_common::{AsInner, FromInner, IntoInner}; 7 | #[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] 8 | pub enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), } 9 | #[derive(Copy)] 10 | pub struct Ipv4Addr { 11 | inner: c::in_addr, 12 | } 13 | #[derive(Copy)] 14 | pub struct Ipv6Addr { 15 | inner: c::in6_addr, 16 | } 17 | #[derive(Copy, PartialEq, Eq, Clone, Hash, Debug)] 18 | #[non_exhaustive] 19 | pub enum Ipv6MulticastScope { 20 | InterfaceLocal, 21 | LinkLocal, 22 | RealmLocal, 23 | AdminLocal, 24 | SiteLocal, 25 | OrganizationLocal, 26 | Global, 27 | } 28 | impl IpAddr { 29 | pub const fn is_unspecified(&self) -> bool { 30 | match self { 31 | IpAddr::V4(ip) => ip.is_unspecified(), 32 | IpAddr::V6(ip) => ip.is_unspecified(), 33 | } 34 | } 35 | pub const fn is_loopback(&self) -> bool { 36 | match self { 37 | IpAddr::V4(ip) => ip.is_loopback(), 38 | IpAddr::V6(ip) => ip.is_loopback(), 39 | } 40 | } 41 | pub const fn is_global(&self) -> bool { 42 | match self { 43 | IpAddr::V4(ip) => ip.is_global(), 44 | IpAddr::V6(ip) => ip.is_global(), 45 | } 46 | } 47 | pub const fn is_multicast(&self) -> bool { 48 | match self { 49 | IpAddr::V4(ip) => ip.is_multicast(), 50 | IpAddr::V6(ip) => ip.is_multicast(), 51 | } 52 | } 53 | pub const fn is_documentation(&self) -> bool { 54 | match self { 55 | IpAddr::V4(ip) => ip.is_documentation(), 56 | IpAddr::V6(ip) => ip.is_documentation(), 57 | } 58 | } 59 | pub const fn is_benchmarking(&self) -> bool { 60 | match self { 61 | IpAddr::V4(ip) => ip.is_benchmarking(), 62 | IpAddr::V6(ip) => ip.is_benchmarking(), 63 | } 64 | } 65 | pub const fn is_ipv4(&self) -> bool { matches!(self, IpAddr :: V4(_)) } 66 | pub const fn is_ipv6(&self) -> bool { matches!(self, IpAddr :: V6(_)) } 67 | pub const fn to_canonical(&self) -> IpAddr { 68 | match self { 69 | &v4 @ IpAddr::V4(_) => v4, 70 | IpAddr::V6(v6) => v6.to_canonical(), 71 | } 72 | } 73 | } 74 | impl Ipv4Addr { 75 | pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { 76 | Ipv4Addr { 77 | inner: c::in_addr { s_addr: u32::from_ne_bytes([a, b, c, d]) }, 78 | } 79 | } 80 | pub const LOCALHOST: Self = Ipv4Addr::new(127, 0, 0, 1); 81 | #[doc(alias = "INADDR_ANY")] 82 | pub const UNSPECIFIED: Self = Ipv4Addr::new(0, 0, 0, 0); 83 | pub const BROADCAST: Self = Ipv4Addr::new(255, 255, 255, 255); 84 | pub const fn octets(&self) -> [u8; 4] { self.inner.s_addr.to_ne_bytes() } 85 | pub const fn is_unspecified(&self) -> bool { self.inner.s_addr == 0 } 86 | pub const fn is_loopback(&self) -> bool { self.octets()[0] == 127 } 87 | pub const fn is_private(&self) -> bool { 88 | match self.octets() { 89 | [10, ..] => true, 90 | [172, b, ..] if b >= 16 && b <= 31 => true, 91 | [192, 168, ..] => true, 92 | _ => false, 93 | } 94 | } 95 | pub const fn is_link_local(&self) -> bool { 96 | matches!(self.octets(), [169, 254, ..]) 97 | } 98 | pub const fn is_global(&self) -> bool { 99 | if u32::from_be_bytes(self.octets()) == 0xc0000009 || 100 | u32::from_be_bytes(self.octets()) == 0xc000000a { 101 | return true; 102 | } 103 | !self.is_private() && !self.is_loopback() && !self.is_link_local() && 104 | !self.is_broadcast() && !self.is_documentation() && 105 | !self.is_shared() && 106 | !(self.octets()[0] == 192 && self.octets()[1] == 0 && 107 | self.octets()[2] == 0) && !self.is_reserved() && 108 | !self.is_benchmarking() && self.octets()[0] != 0 109 | } 110 | pub const fn is_shared(&self) -> bool { 111 | self.octets()[0] == 100 && 112 | (self.octets()[1] & 0b1100_0000 == 0b0100_0000) 113 | } 114 | pub const fn is_benchmarking(&self) -> bool { 115 | self.octets()[0] == 198 && (self.octets()[1] & 0xfe) == 18 116 | } 117 | pub const fn is_reserved(&self) -> bool { 118 | self.octets()[0] & 240 == 240 && !self.is_broadcast() 119 | } 120 | pub const fn is_multicast(&self) -> bool { 121 | self.octets()[0] >= 224 && self.octets()[0] <= 239 122 | } 123 | pub const fn is_broadcast(&self) -> bool { 124 | u32::from_be_bytes(self.octets()) == 125 | u32::from_be_bytes(Self::BROADCAST.octets()) 126 | } 127 | pub const fn is_documentation(&self) -> bool { 128 | matches!(self.octets(), [192, 0, 2, _] | [198, 51, 100, _] | 129 | [203, 0, 113, _]) 130 | } 131 | pub const fn to_ipv6_compatible(&self) -> Ipv6Addr { 132 | let [a, b, c, d] = self.octets(); 133 | Ipv6Addr { 134 | inner: c::in6_addr { 135 | s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, a, b, c, d], 136 | }, 137 | } 138 | } 139 | pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { 140 | let [a, b, c, d] = self.octets(); 141 | Ipv6Addr { 142 | inner: c::in6_addr { 143 | s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, a, b, c, 144 | d], 145 | }, 146 | } 147 | } 148 | } 149 | impl fmt::Display for IpAddr { 150 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 151 | match self { 152 | IpAddr::V4(ip) => ip.fmt(fmt), 153 | IpAddr::V6(ip) => ip.fmt(fmt), 154 | } 155 | } 156 | } 157 | impl fmt::Debug for IpAddr { 158 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 159 | fmt::Display::fmt(self, fmt) 160 | } 161 | } 162 | impl From for IpAddr { 163 | fn from(ipv4: Ipv4Addr) -> IpAddr { IpAddr::V4(ipv4) } 164 | } 165 | impl From for IpAddr { 166 | fn from(ipv6: Ipv6Addr) -> IpAddr { IpAddr::V6(ipv6) } 167 | } 168 | impl fmt::Display for Ipv4Addr { 169 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 170 | let octets = self.octets(); 171 | if fmt.precision().is_none() && fmt.width().is_none() { 172 | write!(fmt, "{}.{}.{}.{}", octets [0], octets [1], octets [2], 173 | octets [3]) 174 | } else { 175 | const IPV4_BUF_LEN: usize = 15; 176 | let mut buf = [0u8; IPV4_BUF_LEN]; 177 | let mut buf_slice = &mut buf[..]; 178 | write!(buf_slice, "{}.{}.{}.{}", octets [0], octets [1], octets 179 | [2], octets [3]).unwrap(); 180 | let len = IPV4_BUF_LEN - buf_slice.len(); 181 | let buf = unsafe { crate::str::from_utf8_unchecked(&buf[..len]) }; 182 | fmt.pad(buf) 183 | } 184 | } 185 | } 186 | impl fmt::Debug for Ipv4Addr { 187 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 188 | fmt::Display::fmt(self, fmt) 189 | } 190 | } 191 | impl Clone for Ipv4Addr { 192 | fn clone(&self) -> Ipv4Addr { *self } 193 | } 194 | impl PartialEq for Ipv4Addr { 195 | fn eq(&self, other: &Ipv4Addr) -> bool { 196 | self.inner.s_addr == other.inner.s_addr 197 | } 198 | } 199 | impl PartialEq for IpAddr { 200 | fn eq(&self, other: &Ipv4Addr) -> bool { 201 | match self { IpAddr::V4(v4) => v4 == other, IpAddr::V6(_) => false, } 202 | } 203 | } 204 | impl PartialEq for Ipv4Addr { 205 | fn eq(&self, other: &IpAddr) -> bool { 206 | match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } 207 | } 208 | } 209 | impl Eq for Ipv4Addr {} 210 | impl hash::Hash for Ipv4Addr { 211 | fn hash(&self, s: &mut H) { 212 | { self.inner.s_addr }.hash(s) 213 | } 214 | } 215 | impl PartialOrd for Ipv4Addr { 216 | fn partial_cmp(&self, other: &Ipv4Addr) -> Option { 217 | Some(self.cmp(other)) 218 | } 219 | } 220 | impl PartialOrd for IpAddr { 221 | fn partial_cmp(&self, other: &Ipv4Addr) -> Option { 222 | match self { 223 | IpAddr::V4(v4) => v4.partial_cmp(other), 224 | IpAddr::V6(_) => Some(Ordering::Greater), 225 | } 226 | } 227 | } 228 | impl PartialOrd for Ipv4Addr { 229 | fn partial_cmp(&self, other: &IpAddr) -> Option { 230 | match other { 231 | IpAddr::V4(v4) => self.partial_cmp(v4), 232 | IpAddr::V6(_) => Some(Ordering::Less), 233 | } 234 | } 235 | } 236 | impl Ord for Ipv4Addr { 237 | fn cmp(&self, other: &Ipv4Addr) -> Ordering { 238 | u32::from_be(self.inner.s_addr).cmp(&u32::from_be(other.inner.s_addr)) 239 | } 240 | } 241 | impl IntoInner for Ipv4Addr { 242 | fn into_inner(self) -> c::in_addr { self.inner } 243 | } 244 | impl From for u32 { 245 | fn from(ip: Ipv4Addr) -> u32 { 246 | let ip = ip.octets(); 247 | u32::from_be_bytes(ip) 248 | } 249 | } 250 | impl From for Ipv4Addr { 251 | fn from(ip: u32) -> Ipv4Addr { Ipv4Addr::from(ip.to_be_bytes()) } 252 | } 253 | impl From<[u8; 4]> for Ipv4Addr { 254 | fn from(octets: [u8; 4]) -> Ipv4Addr { 255 | Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]) 256 | } 257 | } 258 | impl From<[u8; 4]> for IpAddr { 259 | fn from(octets: [u8; 4]) -> IpAddr { IpAddr::V4(Ipv4Addr::from(octets)) } 260 | } 261 | impl Ipv6Addr { 262 | pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, 263 | h: u16) -> Ipv6Addr { 264 | let addr16 = 265 | [a.to_be(), b.to_be(), c.to_be(), d.to_be(), e.to_be(), f.to_be(), 266 | g.to_be(), h.to_be()]; 267 | Ipv6Addr { 268 | inner: c::in6_addr { 269 | s6_addr: unsafe { transmute::<_, [u8; 16]>(addr16) }, 270 | }, 271 | } 272 | } 273 | pub const LOCALHOST: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); 274 | pub const UNSPECIFIED: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); 275 | pub const fn segments(&self) -> [u16; 8] { 276 | let [a, b, c, d, e, f, g, h] = 277 | unsafe { transmute::<_, [u16; 8]>(self.inner.s6_addr) }; 278 | [u16::from_be(a), u16::from_be(b), u16::from_be(c), u16::from_be(d), 279 | u16::from_be(e), u16::from_be(f), u16::from_be(g), 280 | u16::from_be(h)] 281 | } 282 | pub const fn is_unspecified(&self) -> bool { 283 | u128::from_be_bytes(self.octets()) == 284 | u128::from_be_bytes(Ipv6Addr::UNSPECIFIED.octets()) 285 | } 286 | pub const fn is_loopback(&self) -> bool { 287 | u128::from_be_bytes(self.octets()) == 288 | u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets()) 289 | } 290 | pub const fn is_global(&self) -> bool { 291 | match self.multicast_scope() { 292 | Some(Ipv6MulticastScope::Global) => true, 293 | None => self.is_unicast_global(), 294 | _ => false, 295 | } 296 | } 297 | pub const fn is_unique_local(&self) -> bool { 298 | (self.segments()[0] & 0xfe00) == 0xfc00 299 | } 300 | pub const fn is_unicast(&self) -> bool { !self.is_multicast() } 301 | pub const fn is_unicast_link_local(&self) -> bool { 302 | (self.segments()[0] & 0xffc0) == 0xfe80 303 | } 304 | pub const fn is_documentation(&self) -> bool { 305 | (self.segments()[0] == 0x2001) && (self.segments()[1] == 0xdb8) 306 | } 307 | pub const fn is_benchmarking(&self) -> bool { 308 | (self.segments()[0] == 0x2001) && (self.segments()[1] == 0x2) && 309 | (self.segments()[2] == 0) 310 | } 311 | pub const fn is_unicast_global(&self) -> bool { 312 | self.is_unicast() && !self.is_loopback() && 313 | !self.is_unicast_link_local() && !self.is_unique_local() && 314 | !self.is_unspecified() && !self.is_documentation() 315 | } 316 | pub const fn multicast_scope(&self) -> Option { 317 | if self.is_multicast() { 318 | match self.segments()[0] & 0x000f { 319 | 1 => Some(Ipv6MulticastScope::InterfaceLocal), 320 | 2 => Some(Ipv6MulticastScope::LinkLocal), 321 | 3 => Some(Ipv6MulticastScope::RealmLocal), 322 | 4 => Some(Ipv6MulticastScope::AdminLocal), 323 | 5 => Some(Ipv6MulticastScope::SiteLocal), 324 | 8 => Some(Ipv6MulticastScope::OrganizationLocal), 325 | 14 => Some(Ipv6MulticastScope::Global), 326 | _ => None, 327 | } 328 | } else { None } 329 | } 330 | pub const fn is_multicast(&self) -> bool { 331 | (self.segments()[0] & 0xff00) == 0xff00 332 | } 333 | pub const fn to_ipv4_mapped(&self) -> Option { 334 | match self.octets() { 335 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => { 336 | Some(Ipv4Addr::new(a, b, c, d)) 337 | } 338 | _ => None, 339 | } 340 | } 341 | pub const fn to_ipv4(&self) -> Option { 342 | if let [0, 0, 0, 0, 0, 0 | 0xffff, ab, cd] = self.segments() { 343 | let [a, b] = ab.to_be_bytes(); 344 | let [c, d] = cd.to_be_bytes(); 345 | Some(Ipv4Addr::new(a, b, c, d)) 346 | } else { None } 347 | } 348 | pub const fn to_canonical(&self) -> IpAddr { 349 | if let Some(mapped) = self.to_ipv4_mapped() { 350 | return IpAddr::V4(mapped); 351 | } 352 | IpAddr::V6(*self) 353 | } 354 | pub const fn octets(&self) -> [u8; 16] { self.inner.s6_addr } 355 | } 356 | impl fmt::Display for Ipv6Addr { 357 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 358 | if f.precision().is_none() && f.width().is_none() { 359 | let segments = self.segments(); 360 | if self.is_unspecified() { 361 | f.write_str("::") 362 | } else if self.is_loopback() { 363 | f.write_str("::1") 364 | } else if let Some(ipv4) = self.to_ipv4() { 365 | match segments[5] { 366 | 0 => write!(f, "::{}", ipv4), 367 | 0xffff => write!(f, "::ffff:{}", ipv4), 368 | _ => unreachable!(), 369 | } 370 | } else { 371 | #[derive(Copy, Clone, Default)] 372 | struct Span { 373 | start: usize, 374 | len: usize, 375 | } 376 | let zeroes = 377 | { 378 | let mut longest = Span::default(); 379 | let mut current = Span::default(); 380 | for (i, &segment) in segments.iter().enumerate() { 381 | if segment == 0 { 382 | if current.len == 0 { current.start = i; } 383 | current.len += 1; 384 | if current.len > longest.len { longest = current; } 385 | } else { current = Span::default(); } 386 | } 387 | longest 388 | }; 389 | #[doc = " Write a colon-separated part of the address"] 390 | #[inline] 391 | fn fmt_subslice(f: &mut fmt::Formatter<'_>, chunk: &[u16]) 392 | -> fmt::Result { 393 | if let Some((first, tail)) = chunk.split_first() { 394 | write!(f, "{:x}", first)?; 395 | for segment in tail { 396 | f.write_char(':')?; 397 | write!(f, "{:x}", segment)?; 398 | } 399 | } 400 | Ok(()) 401 | } 402 | if zeroes.len > 1 { 403 | fmt_subslice(f, &segments[..zeroes.start])?; 404 | f.write_str("::")?; 405 | fmt_subslice(f, &segments[zeroes.start + zeroes.len..]) 406 | } else { fmt_subslice(f, &segments) } 407 | } 408 | } else { 409 | const IPV6_BUF_LEN: usize = (4 * 8) + 7; 410 | let mut buf = [0u8; IPV6_BUF_LEN]; 411 | let mut buf_slice = &mut buf[..]; 412 | write!(buf_slice, "{}", self).unwrap(); 413 | let len = IPV6_BUF_LEN - buf_slice.len(); 414 | let buf = unsafe { crate::str::from_utf8_unchecked(&buf[..len]) }; 415 | f.pad(buf) 416 | } 417 | } 418 | } 419 | impl fmt::Debug for Ipv6Addr { 420 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 421 | fmt::Display::fmt(self, fmt) 422 | } 423 | } 424 | impl Clone for Ipv6Addr { 425 | fn clone(&self) -> Ipv6Addr { *self } 426 | } 427 | impl PartialEq for Ipv6Addr { 428 | fn eq(&self, other: &Ipv6Addr) -> bool { 429 | self.inner.s6_addr == other.inner.s6_addr 430 | } 431 | } 432 | impl PartialEq for Ipv6Addr { 433 | fn eq(&self, other: &IpAddr) -> bool { 434 | match other { IpAddr::V4(_) => false, IpAddr::V6(v6) => self == v6, } 435 | } 436 | } 437 | impl PartialEq for IpAddr { 438 | fn eq(&self, other: &Ipv6Addr) -> bool { 439 | match self { IpAddr::V4(_) => false, IpAddr::V6(v6) => v6 == other, } 440 | } 441 | } 442 | impl Eq for Ipv6Addr {} 443 | impl hash::Hash for Ipv6Addr { 444 | fn hash(&self, s: &mut H) { self.inner.s6_addr.hash(s) } 445 | } 446 | impl PartialOrd for Ipv6Addr { 447 | fn partial_cmp(&self, other: &Ipv6Addr) -> Option { 448 | Some(self.cmp(other)) 449 | } 450 | } 451 | impl PartialOrd for IpAddr { 452 | fn partial_cmp(&self, other: &Ipv6Addr) -> Option { 453 | match self { 454 | IpAddr::V4(_) => Some(Ordering::Less), 455 | IpAddr::V6(v6) => v6.partial_cmp(other), 456 | } 457 | } 458 | } 459 | impl PartialOrd for Ipv6Addr { 460 | fn partial_cmp(&self, other: &IpAddr) -> Option { 461 | match other { 462 | IpAddr::V4(_) => Some(Ordering::Greater), 463 | IpAddr::V6(v6) => self.partial_cmp(v6), 464 | } 465 | } 466 | } 467 | impl Ord for Ipv6Addr { 468 | fn cmp(&self, other: &Ipv6Addr) -> Ordering { 469 | self.segments().cmp(&other.segments()) 470 | } 471 | } 472 | impl AsInner for Ipv6Addr { 473 | fn as_inner(&self) -> &c::in6_addr { &self.inner } 474 | } 475 | impl FromInner for Ipv6Addr { 476 | fn from_inner(addr: c::in6_addr) -> Ipv6Addr { Ipv6Addr { inner: addr } } 477 | } 478 | impl From for u128 { 479 | fn from(ip: Ipv6Addr) -> u128 { 480 | let ip = ip.octets(); 481 | u128::from_be_bytes(ip) 482 | } 483 | } 484 | impl From for Ipv6Addr { 485 | fn from(ip: u128) -> Ipv6Addr { Ipv6Addr::from(ip.to_be_bytes()) } 486 | } 487 | impl From<[u8; 16]> for Ipv6Addr { 488 | fn from(octets: [u8; 16]) -> Ipv6Addr { 489 | let inner = c::in6_addr { s6_addr: octets }; 490 | Ipv6Addr::from_inner(inner) 491 | } 492 | } 493 | impl From<[u16; 8]> for Ipv6Addr { 494 | fn from(segments: [u16; 8]) -> Ipv6Addr { 495 | let [a, b, c, d, e, f, g, h] = segments; 496 | Ipv6Addr::new(a, b, c, d, e, f, g, h) 497 | } 498 | } 499 | impl From<[u8; 16]> for IpAddr { 500 | fn from(octets: [u8; 16]) -> IpAddr { IpAddr::V6(Ipv6Addr::from(octets)) } 501 | } 502 | impl From<[u16; 8]> for IpAddr { 503 | fn from(segments: [u16; 8]) -> IpAddr { 504 | IpAddr::V6(Ipv6Addr::from(segments)) 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /examples/output.rustfmt.rs: -------------------------------------------------------------------------------- 1 | use crate::cmp::Ordering; 2 | use crate::fmt::{self, Write as FmtWrite}; 3 | use crate::hash; 4 | use crate::io::Write as IoWrite; 5 | use crate::mem::transmute; 6 | use crate::sys::net::netc as c; 7 | use crate::sys_common::{AsInner, FromInner, IntoInner}; 8 | #[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] 9 | pub enum IpAddr { 10 | V4(Ipv4Addr), 11 | V6(Ipv6Addr), 12 | } 13 | #[derive(Copy)] 14 | pub struct Ipv4Addr { 15 | inner: c::in_addr, 16 | } 17 | #[derive(Copy)] 18 | pub struct Ipv6Addr { 19 | inner: c::in6_addr, 20 | } 21 | #[derive(Copy, PartialEq, Eq, Clone, Hash, Debug)] 22 | #[non_exhaustive] 23 | pub enum Ipv6MulticastScope { 24 | InterfaceLocal, 25 | LinkLocal, 26 | RealmLocal, 27 | AdminLocal, 28 | SiteLocal, 29 | OrganizationLocal, 30 | Global, 31 | } 32 | impl IpAddr { 33 | pub const fn is_unspecified(&self) -> bool { 34 | match self { 35 | IpAddr::V4(ip) => ip.is_unspecified(), 36 | IpAddr::V6(ip) => ip.is_unspecified(), 37 | } 38 | } 39 | pub const fn is_loopback(&self) -> bool { 40 | match self { 41 | IpAddr::V4(ip) => ip.is_loopback(), 42 | IpAddr::V6(ip) => ip.is_loopback(), 43 | } 44 | } 45 | pub const fn is_global(&self) -> bool { 46 | match self { 47 | IpAddr::V4(ip) => ip.is_global(), 48 | IpAddr::V6(ip) => ip.is_global(), 49 | } 50 | } 51 | pub const fn is_multicast(&self) -> bool { 52 | match self { 53 | IpAddr::V4(ip) => ip.is_multicast(), 54 | IpAddr::V6(ip) => ip.is_multicast(), 55 | } 56 | } 57 | pub const fn is_documentation(&self) -> bool { 58 | match self { 59 | IpAddr::V4(ip) => ip.is_documentation(), 60 | IpAddr::V6(ip) => ip.is_documentation(), 61 | } 62 | } 63 | pub const fn is_benchmarking(&self) -> bool { 64 | match self { 65 | IpAddr::V4(ip) => ip.is_benchmarking(), 66 | IpAddr::V6(ip) => ip.is_benchmarking(), 67 | } 68 | } 69 | pub const fn is_ipv4(&self) -> bool { 70 | matches!(self, IpAddr::V4(_)) 71 | } 72 | pub const fn is_ipv6(&self) -> bool { 73 | matches!(self, IpAddr::V6(_)) 74 | } 75 | pub const fn to_canonical(&self) -> IpAddr { 76 | match self { 77 | &v4 @ IpAddr::V4(_) => v4, 78 | IpAddr::V6(v6) => v6.to_canonical(), 79 | } 80 | } 81 | } 82 | impl Ipv4Addr { 83 | pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { 84 | Ipv4Addr { 85 | inner: c::in_addr { 86 | s_addr: u32::from_ne_bytes([a, b, c, d]), 87 | }, 88 | } 89 | } 90 | pub const LOCALHOST: Self = Ipv4Addr::new(127, 0, 0, 1); 91 | #[doc(alias = "INADDR_ANY")] 92 | pub const UNSPECIFIED: Self = Ipv4Addr::new(0, 0, 0, 0); 93 | pub const BROADCAST: Self = Ipv4Addr::new(255, 255, 255, 255); 94 | pub const fn octets(&self) -> [u8; 4] { 95 | self.inner.s_addr.to_ne_bytes() 96 | } 97 | pub const fn is_unspecified(&self) -> bool { 98 | self.inner.s_addr == 0 99 | } 100 | pub const fn is_loopback(&self) -> bool { 101 | self.octets()[0] == 127 102 | } 103 | pub const fn is_private(&self) -> bool { 104 | match self.octets() { 105 | [10, ..] => true, 106 | [172, b, ..] if b >= 16 && b <= 31 => true, 107 | [192, 168, ..] => true, 108 | _ => false, 109 | } 110 | } 111 | pub const fn is_link_local(&self) -> bool { 112 | matches!(self.octets(), [169, 254, ..]) 113 | } 114 | pub const fn is_global(&self) -> bool { 115 | if u32::from_be_bytes(self.octets()) == 0xc0000009 116 | || u32::from_be_bytes(self.octets()) == 0xc000000a 117 | { 118 | return true; 119 | } 120 | !self.is_private() 121 | && !self.is_loopback() 122 | && !self.is_link_local() 123 | && !self.is_broadcast() 124 | && !self.is_documentation() 125 | && !self.is_shared() 126 | && !(self.octets()[0] == 192 && self.octets()[1] == 0 && self.octets()[2] == 0) 127 | && !self.is_reserved() 128 | && !self.is_benchmarking() 129 | && self.octets()[0] != 0 130 | } 131 | pub const fn is_shared(&self) -> bool { 132 | self.octets()[0] == 100 && (self.octets()[1] & 0b1100_0000 == 0b0100_0000) 133 | } 134 | pub const fn is_benchmarking(&self) -> bool { 135 | self.octets()[0] == 198 && (self.octets()[1] & 0xfe) == 18 136 | } 137 | pub const fn is_reserved(&self) -> bool { 138 | self.octets()[0] & 240 == 240 && !self.is_broadcast() 139 | } 140 | pub const fn is_multicast(&self) -> bool { 141 | self.octets()[0] >= 224 && self.octets()[0] <= 239 142 | } 143 | pub const fn is_broadcast(&self) -> bool { 144 | u32::from_be_bytes(self.octets()) == u32::from_be_bytes(Self::BROADCAST.octets()) 145 | } 146 | pub const fn is_documentation(&self) -> bool { 147 | matches!( 148 | self.octets(), 149 | [192, 0, 2, _] | [198, 51, 100, _] | [203, 0, 113, _] 150 | ) 151 | } 152 | pub const fn to_ipv6_compatible(&self) -> Ipv6Addr { 153 | let [a, b, c, d] = self.octets(); 154 | Ipv6Addr { 155 | inner: c::in6_addr { 156 | s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, a, b, c, d], 157 | }, 158 | } 159 | } 160 | pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { 161 | let [a, b, c, d] = self.octets(); 162 | Ipv6Addr { 163 | inner: c::in6_addr { 164 | s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, a, b, c, d], 165 | }, 166 | } 167 | } 168 | } 169 | impl fmt::Display for IpAddr { 170 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 171 | match self { 172 | IpAddr::V4(ip) => ip.fmt(fmt), 173 | IpAddr::V6(ip) => ip.fmt(fmt), 174 | } 175 | } 176 | } 177 | impl fmt::Debug for IpAddr { 178 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 179 | fmt::Display::fmt(self, fmt) 180 | } 181 | } 182 | impl From for IpAddr { 183 | fn from(ipv4: Ipv4Addr) -> IpAddr { 184 | IpAddr::V4(ipv4) 185 | } 186 | } 187 | impl From for IpAddr { 188 | fn from(ipv6: Ipv6Addr) -> IpAddr { 189 | IpAddr::V6(ipv6) 190 | } 191 | } 192 | impl fmt::Display for Ipv4Addr { 193 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 194 | let octets = self.octets(); 195 | if fmt.precision().is_none() && fmt.width().is_none() { 196 | write!( 197 | fmt, 198 | "{}.{}.{}.{}", 199 | octets[0], octets[1], octets[2], octets[3] 200 | ) 201 | } else { 202 | const IPV4_BUF_LEN: usize = 15; 203 | let mut buf = [0u8; IPV4_BUF_LEN]; 204 | let mut buf_slice = &mut buf[..]; 205 | write!( 206 | buf_slice, 207 | "{}.{}.{}.{}", 208 | octets[0], octets[1], octets[2], octets[3] 209 | ) 210 | .unwrap(); 211 | let len = IPV4_BUF_LEN - buf_slice.len(); 212 | let buf = unsafe { crate::str::from_utf8_unchecked(&buf[..len]) }; 213 | fmt.pad(buf) 214 | } 215 | } 216 | } 217 | impl fmt::Debug for Ipv4Addr { 218 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 219 | fmt::Display::fmt(self, fmt) 220 | } 221 | } 222 | impl Clone for Ipv4Addr { 223 | fn clone(&self) -> Ipv4Addr { 224 | *self 225 | } 226 | } 227 | impl PartialEq for Ipv4Addr { 228 | fn eq(&self, other: &Ipv4Addr) -> bool { 229 | self.inner.s_addr == other.inner.s_addr 230 | } 231 | } 232 | impl PartialEq for IpAddr { 233 | fn eq(&self, other: &Ipv4Addr) -> bool { 234 | match self { 235 | IpAddr::V4(v4) => v4 == other, 236 | IpAddr::V6(_) => false, 237 | } 238 | } 239 | } 240 | impl PartialEq for Ipv4Addr { 241 | fn eq(&self, other: &IpAddr) -> bool { 242 | match other { 243 | IpAddr::V4(v4) => self == v4, 244 | IpAddr::V6(_) => false, 245 | } 246 | } 247 | } 248 | impl Eq for Ipv4Addr {} 249 | impl hash::Hash for Ipv4Addr { 250 | fn hash(&self, s: &mut H) { 251 | { self.inner.s_addr }.hash(s) 252 | } 253 | } 254 | impl PartialOrd for Ipv4Addr { 255 | fn partial_cmp(&self, other: &Ipv4Addr) -> Option { 256 | Some(self.cmp(other)) 257 | } 258 | } 259 | impl PartialOrd for IpAddr { 260 | fn partial_cmp(&self, other: &Ipv4Addr) -> Option { 261 | match self { 262 | IpAddr::V4(v4) => v4.partial_cmp(other), 263 | IpAddr::V6(_) => Some(Ordering::Greater), 264 | } 265 | } 266 | } 267 | impl PartialOrd for Ipv4Addr { 268 | fn partial_cmp(&self, other: &IpAddr) -> Option { 269 | match other { 270 | IpAddr::V4(v4) => self.partial_cmp(v4), 271 | IpAddr::V6(_) => Some(Ordering::Less), 272 | } 273 | } 274 | } 275 | impl Ord for Ipv4Addr { 276 | fn cmp(&self, other: &Ipv4Addr) -> Ordering { 277 | u32::from_be(self.inner.s_addr).cmp(&u32::from_be(other.inner.s_addr)) 278 | } 279 | } 280 | impl IntoInner for Ipv4Addr { 281 | fn into_inner(self) -> c::in_addr { 282 | self.inner 283 | } 284 | } 285 | impl From for u32 { 286 | fn from(ip: Ipv4Addr) -> u32 { 287 | let ip = ip.octets(); 288 | u32::from_be_bytes(ip) 289 | } 290 | } 291 | impl From for Ipv4Addr { 292 | fn from(ip: u32) -> Ipv4Addr { 293 | Ipv4Addr::from(ip.to_be_bytes()) 294 | } 295 | } 296 | impl From<[u8; 4]> for Ipv4Addr { 297 | fn from(octets: [u8; 4]) -> Ipv4Addr { 298 | Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]) 299 | } 300 | } 301 | impl From<[u8; 4]> for IpAddr { 302 | fn from(octets: [u8; 4]) -> IpAddr { 303 | IpAddr::V4(Ipv4Addr::from(octets)) 304 | } 305 | } 306 | impl Ipv6Addr { 307 | pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { 308 | let addr16 = [ 309 | a.to_be(), 310 | b.to_be(), 311 | c.to_be(), 312 | d.to_be(), 313 | e.to_be(), 314 | f.to_be(), 315 | g.to_be(), 316 | h.to_be(), 317 | ]; 318 | Ipv6Addr { 319 | inner: c::in6_addr { 320 | s6_addr: unsafe { transmute::<_, [u8; 16]>(addr16) }, 321 | }, 322 | } 323 | } 324 | pub const LOCALHOST: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); 325 | pub const UNSPECIFIED: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); 326 | pub const fn segments(&self) -> [u16; 8] { 327 | let [a, b, c, d, e, f, g, h] = unsafe { transmute::<_, [u16; 8]>(self.inner.s6_addr) }; 328 | [ 329 | u16::from_be(a), 330 | u16::from_be(b), 331 | u16::from_be(c), 332 | u16::from_be(d), 333 | u16::from_be(e), 334 | u16::from_be(f), 335 | u16::from_be(g), 336 | u16::from_be(h), 337 | ] 338 | } 339 | pub const fn is_unspecified(&self) -> bool { 340 | u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::UNSPECIFIED.octets()) 341 | } 342 | pub const fn is_loopback(&self) -> bool { 343 | u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets()) 344 | } 345 | pub const fn is_global(&self) -> bool { 346 | match self.multicast_scope() { 347 | Some(Ipv6MulticastScope::Global) => true, 348 | None => self.is_unicast_global(), 349 | _ => false, 350 | } 351 | } 352 | pub const fn is_unique_local(&self) -> bool { 353 | (self.segments()[0] & 0xfe00) == 0xfc00 354 | } 355 | pub const fn is_unicast(&self) -> bool { 356 | !self.is_multicast() 357 | } 358 | pub const fn is_unicast_link_local(&self) -> bool { 359 | (self.segments()[0] & 0xffc0) == 0xfe80 360 | } 361 | pub const fn is_documentation(&self) -> bool { 362 | (self.segments()[0] == 0x2001) && (self.segments()[1] == 0xdb8) 363 | } 364 | pub const fn is_benchmarking(&self) -> bool { 365 | (self.segments()[0] == 0x2001) && (self.segments()[1] == 0x2) && (self.segments()[2] == 0) 366 | } 367 | pub const fn is_unicast_global(&self) -> bool { 368 | self.is_unicast() 369 | && !self.is_loopback() 370 | && !self.is_unicast_link_local() 371 | && !self.is_unique_local() 372 | && !self.is_unspecified() 373 | && !self.is_documentation() 374 | } 375 | pub const fn multicast_scope(&self) -> Option { 376 | if self.is_multicast() { 377 | match self.segments()[0] & 0x000f { 378 | 1 => Some(Ipv6MulticastScope::InterfaceLocal), 379 | 2 => Some(Ipv6MulticastScope::LinkLocal), 380 | 3 => Some(Ipv6MulticastScope::RealmLocal), 381 | 4 => Some(Ipv6MulticastScope::AdminLocal), 382 | 5 => Some(Ipv6MulticastScope::SiteLocal), 383 | 8 => Some(Ipv6MulticastScope::OrganizationLocal), 384 | 14 => Some(Ipv6MulticastScope::Global), 385 | _ => None, 386 | } 387 | } else { 388 | None 389 | } 390 | } 391 | pub const fn is_multicast(&self) -> bool { 392 | (self.segments()[0] & 0xff00) == 0xff00 393 | } 394 | pub const fn to_ipv4_mapped(&self) -> Option { 395 | match self.octets() { 396 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => { 397 | Some(Ipv4Addr::new(a, b, c, d)) 398 | } 399 | _ => None, 400 | } 401 | } 402 | pub const fn to_ipv4(&self) -> Option { 403 | if let [0, 0, 0, 0, 0, 0 | 0xffff, ab, cd] = self.segments() { 404 | let [a, b] = ab.to_be_bytes(); 405 | let [c, d] = cd.to_be_bytes(); 406 | Some(Ipv4Addr::new(a, b, c, d)) 407 | } else { 408 | None 409 | } 410 | } 411 | pub const fn to_canonical(&self) -> IpAddr { 412 | if let Some(mapped) = self.to_ipv4_mapped() { 413 | return IpAddr::V4(mapped); 414 | } 415 | IpAddr::V6(*self) 416 | } 417 | pub const fn octets(&self) -> [u8; 16] { 418 | self.inner.s6_addr 419 | } 420 | } 421 | impl fmt::Display for Ipv6Addr { 422 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 423 | if f.precision().is_none() && f.width().is_none() { 424 | let segments = self.segments(); 425 | if self.is_unspecified() { 426 | f.write_str("::") 427 | } else if self.is_loopback() { 428 | f.write_str("::1") 429 | } else if let Some(ipv4) = self.to_ipv4() { 430 | match segments[5] { 431 | 0 => write!(f, "::{}", ipv4), 432 | 0xffff => write!(f, "::ffff:{}", ipv4), 433 | _ => unreachable!(), 434 | } 435 | } else { # [derive (Copy , Clone , Default)] struct Span { start : usize , len : usize , } let zeroes = { let mut longest = Span :: default () ; let mut current = Span :: default () ; for (i , & segment) in segments . iter () . enumerate () { if segment == 0 { if current . len == 0 { current . start = i ; } current . len += 1 ; if current . len > longest . len { longest = current ; } } else { current = Span :: default () ; } } longest } ; # [doc = " Write a colon-separated part of the address"] # [inline] fn fmt_subslice (f : & mut fmt :: Formatter < '_ > , chunk : & [u16]) -> fmt :: Result { if let Some ((first , tail)) = chunk . split_first () { write ! (f , "{:x}" , first) ? ; for segment in tail { f . write_char (':') ? ; write ! (f , "{:x}" , segment) ? ; } } Ok (()) } if zeroes . len > 1 { fmt_subslice (f , & segments [.. zeroes . start]) ? ; f . write_str ("::") ? ; fmt_subslice (f , & segments [zeroes . start + zeroes . len ..]) } else { fmt_subslice (f , & segments) } } 436 | } else { 437 | const IPV6_BUF_LEN: usize = (4 * 8) + 7; 438 | let mut buf = [0u8; IPV6_BUF_LEN]; 439 | let mut buf_slice = &mut buf[..]; 440 | write!(buf_slice, "{}", self).unwrap(); 441 | let len = IPV6_BUF_LEN - buf_slice.len(); 442 | let buf = unsafe { crate::str::from_utf8_unchecked(&buf[..len]) }; 443 | f.pad(buf) 444 | } 445 | } 446 | } 447 | impl fmt::Debug for Ipv6Addr { 448 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 449 | fmt::Display::fmt(self, fmt) 450 | } 451 | } 452 | impl Clone for Ipv6Addr { 453 | fn clone(&self) -> Ipv6Addr { 454 | *self 455 | } 456 | } 457 | impl PartialEq for Ipv6Addr { 458 | fn eq(&self, other: &Ipv6Addr) -> bool { 459 | self.inner.s6_addr == other.inner.s6_addr 460 | } 461 | } 462 | impl PartialEq for Ipv6Addr { 463 | fn eq(&self, other: &IpAddr) -> bool { 464 | match other { 465 | IpAddr::V4(_) => false, 466 | IpAddr::V6(v6) => self == v6, 467 | } 468 | } 469 | } 470 | impl PartialEq for IpAddr { 471 | fn eq(&self, other: &Ipv6Addr) -> bool { 472 | match self { 473 | IpAddr::V4(_) => false, 474 | IpAddr::V6(v6) => v6 == other, 475 | } 476 | } 477 | } 478 | impl Eq for Ipv6Addr {} 479 | impl hash::Hash for Ipv6Addr { 480 | fn hash(&self, s: &mut H) { 481 | self.inner.s6_addr.hash(s) 482 | } 483 | } 484 | impl PartialOrd for Ipv6Addr { 485 | fn partial_cmp(&self, other: &Ipv6Addr) -> Option { 486 | Some(self.cmp(other)) 487 | } 488 | } 489 | impl PartialOrd for IpAddr { 490 | fn partial_cmp(&self, other: &Ipv6Addr) -> Option { 491 | match self { 492 | IpAddr::V4(_) => Some(Ordering::Less), 493 | IpAddr::V6(v6) => v6.partial_cmp(other), 494 | } 495 | } 496 | } 497 | impl PartialOrd for Ipv6Addr { 498 | fn partial_cmp(&self, other: &IpAddr) -> Option { 499 | match other { 500 | IpAddr::V4(_) => Some(Ordering::Greater), 501 | IpAddr::V6(v6) => self.partial_cmp(v6), 502 | } 503 | } 504 | } 505 | impl Ord for Ipv6Addr { 506 | fn cmp(&self, other: &Ipv6Addr) -> Ordering { 507 | self.segments().cmp(&other.segments()) 508 | } 509 | } 510 | impl AsInner for Ipv6Addr { 511 | fn as_inner(&self) -> &c::in6_addr { 512 | &self.inner 513 | } 514 | } 515 | impl FromInner for Ipv6Addr { 516 | fn from_inner(addr: c::in6_addr) -> Ipv6Addr { 517 | Ipv6Addr { inner: addr } 518 | } 519 | } 520 | impl From for u128 { 521 | fn from(ip: Ipv6Addr) -> u128 { 522 | let ip = ip.octets(); 523 | u128::from_be_bytes(ip) 524 | } 525 | } 526 | impl From for Ipv6Addr { 527 | fn from(ip: u128) -> Ipv6Addr { 528 | Ipv6Addr::from(ip.to_be_bytes()) 529 | } 530 | } 531 | impl From<[u8; 16]> for Ipv6Addr { 532 | fn from(octets: [u8; 16]) -> Ipv6Addr { 533 | let inner = c::in6_addr { s6_addr: octets }; 534 | Ipv6Addr::from_inner(inner) 535 | } 536 | } 537 | impl From<[u16; 8]> for Ipv6Addr { 538 | fn from(segments: [u16; 8]) -> Ipv6Addr { 539 | let [a, b, c, d, e, f, g, h] = segments; 540 | Ipv6Addr::new(a, b, c, d, e, f, g, h) 541 | } 542 | } 543 | impl From<[u8; 16]> for IpAddr { 544 | fn from(octets: [u8; 16]) -> IpAddr { 545 | IpAddr::V6(Ipv6Addr::from(octets)) 546 | } 547 | } 548 | impl From<[u16; 8]> for IpAddr { 549 | fn from(segments: [u16; 8]) -> IpAddr { 550 | IpAddr::V6(Ipv6Addr::from(segments)) 551 | } 552 | } 553 | -------------------------------------------------------------------------------- /examples/update/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prettyplease-update-examples" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [[bin]] 9 | name = "prettyplease-update-examples" 10 | path = "update-examples.rs" 11 | 12 | [dependencies] 13 | anyhow = "1.0" 14 | prettyplease = { path = "../../" } 15 | quote = { version = "1.0", default-features = false } 16 | syn = { version = "2.0", default-features = false, features = ["parsing", "printing"] } 17 | -------------------------------------------------------------------------------- /examples/update/update-examples.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate rustc_ast_pretty; 4 | extern crate rustc_driver; 5 | extern crate rustc_parse; 6 | extern crate rustc_session; 7 | extern crate rustc_span; 8 | 9 | use anyhow::Result; 10 | use quote::quote; 11 | use rustc_session::parse::ParseSess; 12 | use rustc_span::edition::Edition::Edition2021; 13 | use std::fs::{self, File}; 14 | use std::path::Path; 15 | use std::process::{Command, Stdio}; 16 | 17 | fn main() -> Result<()> { 18 | let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); 19 | 20 | // Read and parse input.rs 21 | let input_path = manifest_dir.join("..").join("input.rs"); 22 | let input_contents = fs::read_to_string(&input_path)?; 23 | let syntax_tree = syn::parse_file(&input_contents)?; 24 | 25 | // Write input.rs 26 | let tokens = quote!(#syntax_tree); 27 | let mut string = tokens.to_string(); 28 | string.push('\n'); 29 | fs::write(&input_path, string)?; 30 | 31 | // Write output.prettyplease.rs 32 | let output_path = manifest_dir.join("..").join("output.prettyplease.rs"); 33 | let string = prettyplease::unparse(&syntax_tree); 34 | fs::write(&output_path, string)?; 35 | 36 | // Write output.rustc.rs 37 | let output_path = manifest_dir.join("..").join("output.rustc.rs"); 38 | let mut string = rustc_span::create_session_globals_then(Edition2021, &[], None, || { 39 | let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(); 40 | let sess = ParseSess::new(locale_resources); 41 | let mut parser = rustc_parse::new_parser_from_file(&sess, &input_path, None).unwrap(); 42 | let krate = parser.parse_crate_mod().unwrap(); 43 | rustc_ast_pretty::pprust::crate_to_string_for_macros(&krate) 44 | }); 45 | string.push('\n'); 46 | fs::write(&output_path, string)?; 47 | 48 | // Write output.rustfmt.rs 49 | let output_path = manifest_dir.join("..").join("output.rustfmt.rs"); 50 | let output_file = File::create(output_path)?; 51 | Command::new("rustfmt") 52 | .arg("--edition=2021") 53 | .arg("--config=reorder_imports=false") 54 | .arg("--config=normalize_doc_attributes=true") 55 | .arg("--emit=stdout") 56 | .arg("--quiet") 57 | .arg("--unstable-features") 58 | .arg("--skip-children") 59 | .arg(&input_path) 60 | .stdin(Stdio::null()) 61 | .stdout(output_file) 62 | .stderr(Stdio::inherit()) 63 | .spawn()? 64 | .wait()?; 65 | 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | /artifacts/ 2 | /corpus/ 3 | /coverage/ 4 | /target/ 5 | /Cargo.lock 6 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prettyplease-fuzz" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | prettyplease = { path = "..", features = ["verbatim"] } 14 | syn = { version = "2", default-features = false, features = ["full", "parsing"] } 15 | 16 | [[bin]] 17 | name = "round_trip" 18 | path = "fuzz_targets/round_trip.rs" 19 | test = false 20 | doc = false 21 | 22 | [workspace] 23 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/round_trip.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use std::str; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let ..=299 = data.len() else { return }; 8 | let Ok(string) = str::from_utf8(data) else { 9 | return; 10 | }; 11 | let Ok(syntax_tree) = syn::parse_file(string) else { 12 | return; 13 | }; 14 | let _ = prettyplease::unparse(&syntax_tree); 15 | }); 16 | -------------------------------------------------------------------------------- /src/algorithm.rs: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/rust-lang/rust/blob/1.57.0/compiler/rustc_ast_pretty/src/pp.rs. 2 | // See "Algorithm notes" in the crate-level rustdoc. 3 | 4 | use crate::ring::RingBuffer; 5 | use crate::{MARGIN, MIN_SPACE}; 6 | use std::borrow::Cow; 7 | use std::cmp; 8 | use std::collections::VecDeque; 9 | use std::iter; 10 | 11 | #[derive(Clone, Copy, PartialEq)] 12 | pub enum Breaks { 13 | Consistent, 14 | Inconsistent, 15 | } 16 | 17 | #[derive(Clone, Copy, Default)] 18 | pub struct BreakToken { 19 | pub offset: isize, 20 | pub blank_space: usize, 21 | pub pre_break: Option, 22 | pub post_break: &'static str, 23 | pub no_break: Option, 24 | pub if_nonempty: bool, 25 | pub never_break: bool, 26 | } 27 | 28 | #[derive(Clone, Copy)] 29 | pub struct BeginToken { 30 | pub offset: isize, 31 | pub breaks: Breaks, 32 | } 33 | 34 | #[derive(Clone)] 35 | pub enum Token { 36 | String(Cow<'static, str>), 37 | Break(BreakToken), 38 | Begin(BeginToken), 39 | End, 40 | } 41 | 42 | #[derive(Copy, Clone)] 43 | enum PrintFrame { 44 | Fits(Breaks), 45 | Broken(usize, Breaks), 46 | } 47 | 48 | pub const SIZE_INFINITY: isize = 0xffff; 49 | 50 | pub struct Printer { 51 | out: String, 52 | // Number of spaces left on line 53 | space: isize, 54 | // Ring-buffer of tokens and calculated sizes 55 | buf: RingBuffer, 56 | // Total size of tokens already printed 57 | left_total: isize, 58 | // Total size of tokens enqueued, including printed and not yet printed 59 | right_total: isize, 60 | // Holds the ring-buffer index of the Begin that started the current block, 61 | // possibly with the most recent Break after that Begin (if there is any) on 62 | // top of it. Values are pushed and popped on the back of the queue using it 63 | // like stack, and elsewhere old values are popped from the front of the 64 | // queue as they become irrelevant due to the primary ring-buffer advancing. 65 | scan_stack: VecDeque, 66 | // Stack of blocks-in-progress being flushed by print 67 | print_stack: Vec, 68 | // Level of indentation of current line 69 | indent: usize, 70 | // Buffered indentation to avoid writing trailing whitespace 71 | pending_indentation: usize, 72 | } 73 | 74 | #[derive(Clone)] 75 | struct BufEntry { 76 | token: Token, 77 | size: isize, 78 | } 79 | 80 | impl Printer { 81 | pub fn new() -> Self { 82 | Printer { 83 | out: String::new(), 84 | space: MARGIN, 85 | buf: RingBuffer::new(), 86 | left_total: 0, 87 | right_total: 0, 88 | scan_stack: VecDeque::new(), 89 | print_stack: Vec::new(), 90 | indent: 0, 91 | pending_indentation: 0, 92 | } 93 | } 94 | 95 | pub fn eof(mut self) -> String { 96 | if !self.scan_stack.is_empty() { 97 | self.check_stack(0); 98 | self.advance_left(); 99 | } 100 | self.out 101 | } 102 | 103 | pub fn scan_begin(&mut self, token: BeginToken) { 104 | if self.scan_stack.is_empty() { 105 | self.left_total = 1; 106 | self.right_total = 1; 107 | self.buf.clear(); 108 | } 109 | let right = self.buf.push(BufEntry { 110 | token: Token::Begin(token), 111 | size: -self.right_total, 112 | }); 113 | self.scan_stack.push_back(right); 114 | } 115 | 116 | pub fn scan_end(&mut self) { 117 | if self.scan_stack.is_empty() { 118 | self.print_end(); 119 | } else { 120 | if !self.buf.is_empty() { 121 | if let Token::Break(break_token) = self.buf.last().token { 122 | if self.buf.len() >= 2 { 123 | if let Token::Begin(_) = self.buf.second_last().token { 124 | self.buf.pop_last(); 125 | self.buf.pop_last(); 126 | self.scan_stack.pop_back(); 127 | self.scan_stack.pop_back(); 128 | self.right_total -= break_token.blank_space as isize; 129 | return; 130 | } 131 | } 132 | if break_token.if_nonempty { 133 | self.buf.pop_last(); 134 | self.scan_stack.pop_back(); 135 | self.right_total -= break_token.blank_space as isize; 136 | } 137 | } 138 | } 139 | let right = self.buf.push(BufEntry { 140 | token: Token::End, 141 | size: -1, 142 | }); 143 | self.scan_stack.push_back(right); 144 | } 145 | } 146 | 147 | pub fn scan_break(&mut self, token: BreakToken) { 148 | if self.scan_stack.is_empty() { 149 | self.left_total = 1; 150 | self.right_total = 1; 151 | self.buf.clear(); 152 | } else { 153 | self.check_stack(0); 154 | } 155 | let right = self.buf.push(BufEntry { 156 | token: Token::Break(token), 157 | size: -self.right_total, 158 | }); 159 | self.scan_stack.push_back(right); 160 | self.right_total += token.blank_space as isize; 161 | } 162 | 163 | pub fn scan_string(&mut self, string: Cow<'static, str>) { 164 | if self.scan_stack.is_empty() { 165 | self.print_string(string); 166 | } else { 167 | let len = string.len() as isize; 168 | self.buf.push(BufEntry { 169 | token: Token::String(string), 170 | size: len, 171 | }); 172 | self.right_total += len; 173 | self.check_stream(); 174 | } 175 | } 176 | 177 | #[track_caller] 178 | pub fn offset(&mut self, offset: isize) { 179 | match &mut self.buf.last_mut().token { 180 | Token::Break(token) => token.offset += offset, 181 | Token::Begin(_) => {} 182 | Token::String(_) | Token::End => unreachable!(), 183 | } 184 | } 185 | 186 | pub fn end_with_max_width(&mut self, max: isize) { 187 | let mut depth = 1; 188 | for &index in self.scan_stack.iter().rev() { 189 | let entry = &self.buf[index]; 190 | match entry.token { 191 | Token::Begin(_) => { 192 | depth -= 1; 193 | if depth == 0 { 194 | if entry.size < 0 { 195 | let actual_width = entry.size + self.right_total; 196 | if actual_width > max { 197 | self.buf.push(BufEntry { 198 | token: Token::String(Cow::Borrowed("")), 199 | size: SIZE_INFINITY, 200 | }); 201 | self.right_total += SIZE_INFINITY; 202 | } 203 | } 204 | break; 205 | } 206 | } 207 | Token::End => depth += 1, 208 | Token::Break(_) => {} 209 | Token::String(_) => unreachable!(), 210 | } 211 | } 212 | self.scan_end(); 213 | } 214 | 215 | pub fn ends_with(&self, ch: char) -> bool { 216 | for i in self.buf.index_range().rev() { 217 | if let Token::String(token) = &self.buf[i].token { 218 | return token.ends_with(ch); 219 | } 220 | } 221 | self.out.ends_with(ch) 222 | } 223 | 224 | fn check_stream(&mut self) { 225 | while self.right_total - self.left_total > self.space { 226 | if *self.scan_stack.front().unwrap() == self.buf.index_range().start { 227 | self.scan_stack.pop_front().unwrap(); 228 | self.buf.first_mut().size = SIZE_INFINITY; 229 | } 230 | 231 | self.advance_left(); 232 | 233 | if self.buf.is_empty() { 234 | break; 235 | } 236 | } 237 | } 238 | 239 | fn advance_left(&mut self) { 240 | while self.buf.first().size >= 0 { 241 | let left = self.buf.pop_first(); 242 | 243 | match left.token { 244 | Token::String(string) => { 245 | self.left_total += left.size; 246 | self.print_string(string); 247 | } 248 | Token::Break(token) => { 249 | self.left_total += token.blank_space as isize; 250 | self.print_break(token, left.size); 251 | } 252 | Token::Begin(token) => self.print_begin(token, left.size), 253 | Token::End => self.print_end(), 254 | } 255 | 256 | if self.buf.is_empty() { 257 | break; 258 | } 259 | } 260 | } 261 | 262 | fn check_stack(&mut self, mut depth: usize) { 263 | while let Some(&index) = self.scan_stack.back() { 264 | let entry = &mut self.buf[index]; 265 | match entry.token { 266 | Token::Begin(_) => { 267 | if depth == 0 { 268 | break; 269 | } 270 | self.scan_stack.pop_back().unwrap(); 271 | entry.size += self.right_total; 272 | depth -= 1; 273 | } 274 | Token::End => { 275 | self.scan_stack.pop_back().unwrap(); 276 | entry.size = 1; 277 | depth += 1; 278 | } 279 | Token::Break(_) => { 280 | self.scan_stack.pop_back().unwrap(); 281 | entry.size += self.right_total; 282 | if depth == 0 { 283 | break; 284 | } 285 | } 286 | Token::String(_) => unreachable!(), 287 | } 288 | } 289 | } 290 | 291 | fn get_top(&self) -> PrintFrame { 292 | const OUTER: PrintFrame = PrintFrame::Broken(0, Breaks::Inconsistent); 293 | self.print_stack.last().map_or(OUTER, PrintFrame::clone) 294 | } 295 | 296 | fn print_begin(&mut self, token: BeginToken, size: isize) { 297 | if cfg!(prettyplease_debug) { 298 | self.out.push(match token.breaks { 299 | Breaks::Consistent => '«', 300 | Breaks::Inconsistent => '‹', 301 | }); 302 | if cfg!(prettyplease_debug_indent) { 303 | self.out 304 | .extend(token.offset.to_string().chars().map(|ch| match ch { 305 | '0'..='9' => ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'] 306 | [(ch as u8 - b'0') as usize], 307 | '-' => '₋', 308 | _ => unreachable!(), 309 | })); 310 | } 311 | } 312 | if size > self.space { 313 | self.print_stack 314 | .push(PrintFrame::Broken(self.indent, token.breaks)); 315 | self.indent = usize::try_from(self.indent as isize + token.offset).unwrap(); 316 | } else { 317 | self.print_stack.push(PrintFrame::Fits(token.breaks)); 318 | } 319 | } 320 | 321 | fn print_end(&mut self) { 322 | let breaks = match self.print_stack.pop().unwrap() { 323 | PrintFrame::Broken(indent, breaks) => { 324 | self.indent = indent; 325 | breaks 326 | } 327 | PrintFrame::Fits(breaks) => breaks, 328 | }; 329 | if cfg!(prettyplease_debug) { 330 | self.out.push(match breaks { 331 | Breaks::Consistent => '»', 332 | Breaks::Inconsistent => '›', 333 | }); 334 | } 335 | } 336 | 337 | fn print_break(&mut self, token: BreakToken, size: isize) { 338 | let fits = token.never_break 339 | || match self.get_top() { 340 | PrintFrame::Fits(..) => true, 341 | PrintFrame::Broken(.., Breaks::Consistent) => false, 342 | PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, 343 | }; 344 | if fits { 345 | self.pending_indentation += token.blank_space; 346 | self.space -= token.blank_space as isize; 347 | if let Some(no_break) = token.no_break { 348 | self.out.push(no_break); 349 | self.space -= no_break.len_utf8() as isize; 350 | } 351 | if cfg!(prettyplease_debug) { 352 | self.out.push('·'); 353 | } 354 | } else { 355 | if let Some(pre_break) = token.pre_break { 356 | self.print_indent(); 357 | self.out.push(pre_break); 358 | } 359 | if cfg!(prettyplease_debug) { 360 | self.out.push('·'); 361 | } 362 | self.out.push('\n'); 363 | let indent = self.indent as isize + token.offset; 364 | self.pending_indentation = usize::try_from(indent).unwrap(); 365 | self.space = cmp::max(MARGIN - indent, MIN_SPACE); 366 | if !token.post_break.is_empty() { 367 | self.print_indent(); 368 | self.out.push_str(token.post_break); 369 | self.space -= token.post_break.len() as isize; 370 | } 371 | } 372 | } 373 | 374 | fn print_string(&mut self, string: Cow<'static, str>) { 375 | self.print_indent(); 376 | self.out.push_str(&string); 377 | self.space -= string.len() as isize; 378 | } 379 | 380 | fn print_indent(&mut self) { 381 | self.out.reserve(self.pending_indentation); 382 | self.out 383 | .extend(iter::repeat(' ').take(self.pending_indentation)); 384 | self.pending_indentation = 0; 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/attr.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::fixup::FixupContext; 3 | use crate::path::PathKind; 4 | use crate::INDENT; 5 | use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; 6 | use syn::{AttrStyle, Attribute, Expr, Lit, MacroDelimiter, Meta, MetaList, MetaNameValue}; 7 | 8 | impl Printer { 9 | pub fn outer_attrs(&mut self, attrs: &[Attribute]) { 10 | for attr in attrs { 11 | if let AttrStyle::Outer = attr.style { 12 | self.attr(attr); 13 | } 14 | } 15 | } 16 | 17 | pub fn inner_attrs(&mut self, attrs: &[Attribute]) { 18 | for attr in attrs { 19 | if let AttrStyle::Inner(_) = attr.style { 20 | self.attr(attr); 21 | } 22 | } 23 | } 24 | 25 | fn attr(&mut self, attr: &Attribute) { 26 | if let Some(mut doc) = value_of_attribute("doc", attr) { 27 | if !doc.contains('\n') 28 | && match attr.style { 29 | AttrStyle::Outer => !doc.starts_with('/'), 30 | AttrStyle::Inner(_) => true, 31 | } 32 | { 33 | trim_trailing_spaces(&mut doc); 34 | self.word(match attr.style { 35 | AttrStyle::Outer => "///", 36 | AttrStyle::Inner(_) => "//!", 37 | }); 38 | self.word(doc); 39 | self.hardbreak(); 40 | return; 41 | } else if can_be_block_comment(&doc) 42 | && match attr.style { 43 | AttrStyle::Outer => !doc.starts_with(&['*', '/'][..]), 44 | AttrStyle::Inner(_) => true, 45 | } 46 | { 47 | trim_interior_trailing_spaces(&mut doc); 48 | self.word(match attr.style { 49 | AttrStyle::Outer => "/**", 50 | AttrStyle::Inner(_) => "/*!", 51 | }); 52 | self.word(doc); 53 | self.word("*/"); 54 | self.hardbreak(); 55 | return; 56 | } 57 | } else if let Some(mut comment) = value_of_attribute("comment", attr) { 58 | if !comment.contains('\n') { 59 | trim_trailing_spaces(&mut comment); 60 | self.word("//"); 61 | self.word(comment); 62 | self.hardbreak(); 63 | return; 64 | } else if can_be_block_comment(&comment) && !comment.starts_with(&['*', '!'][..]) { 65 | trim_interior_trailing_spaces(&mut comment); 66 | self.word("/*"); 67 | self.word(comment); 68 | self.word("*/"); 69 | self.hardbreak(); 70 | return; 71 | } 72 | } 73 | 74 | self.word(match attr.style { 75 | AttrStyle::Outer => "#", 76 | AttrStyle::Inner(_) => "#!", 77 | }); 78 | self.word("["); 79 | self.meta(&attr.meta); 80 | self.word("]"); 81 | self.space(); 82 | } 83 | 84 | fn meta(&mut self, meta: &Meta) { 85 | match meta { 86 | Meta::Path(path) => self.path(path, PathKind::Simple), 87 | Meta::List(meta) => self.meta_list(meta), 88 | Meta::NameValue(meta) => self.meta_name_value(meta), 89 | } 90 | } 91 | 92 | fn meta_list(&mut self, meta: &MetaList) { 93 | self.path(&meta.path, PathKind::Simple); 94 | let delimiter = match meta.delimiter { 95 | MacroDelimiter::Paren(_) => Delimiter::Parenthesis, 96 | MacroDelimiter::Brace(_) => Delimiter::Brace, 97 | MacroDelimiter::Bracket(_) => Delimiter::Bracket, 98 | }; 99 | let group = Group::new(delimiter, meta.tokens.clone()); 100 | self.attr_tokens(TokenStream::from(TokenTree::Group(group))); 101 | } 102 | 103 | fn meta_name_value(&mut self, meta: &MetaNameValue) { 104 | self.path(&meta.path, PathKind::Simple); 105 | self.word(" = "); 106 | self.expr(&meta.value, FixupContext::NONE); 107 | } 108 | 109 | fn attr_tokens(&mut self, tokens: TokenStream) { 110 | let mut stack = Vec::new(); 111 | stack.push((tokens.into_iter().peekable(), Delimiter::None)); 112 | let mut space = Self::nbsp as fn(&mut Self); 113 | 114 | #[derive(PartialEq)] 115 | enum State { 116 | Word, 117 | Punct, 118 | TrailingComma, 119 | } 120 | 121 | use State::*; 122 | let mut state = Word; 123 | 124 | while let Some((tokens, delimiter)) = stack.last_mut() { 125 | match tokens.next() { 126 | Some(TokenTree::Ident(ident)) => { 127 | if let Word = state { 128 | space(self); 129 | } 130 | self.ident(&ident); 131 | state = Word; 132 | } 133 | Some(TokenTree::Punct(punct)) => { 134 | let ch = punct.as_char(); 135 | if let (Word, '=') = (state, ch) { 136 | self.nbsp(); 137 | } 138 | if ch == ',' && tokens.peek().is_none() { 139 | self.trailing_comma(true); 140 | state = TrailingComma; 141 | } else { 142 | self.token_punct(ch); 143 | if ch == '=' { 144 | self.nbsp(); 145 | } else if ch == ',' { 146 | space(self); 147 | } 148 | state = Punct; 149 | } 150 | } 151 | Some(TokenTree::Literal(literal)) => { 152 | if let Word = state { 153 | space(self); 154 | } 155 | self.token_literal(&literal); 156 | state = Word; 157 | } 158 | Some(TokenTree::Group(group)) => { 159 | let delimiter = group.delimiter(); 160 | let stream = group.stream(); 161 | match delimiter { 162 | Delimiter::Parenthesis => { 163 | self.word("("); 164 | self.cbox(INDENT); 165 | self.zerobreak(); 166 | state = Punct; 167 | } 168 | Delimiter::Brace => { 169 | self.word("{"); 170 | state = Punct; 171 | } 172 | Delimiter::Bracket => { 173 | self.word("["); 174 | state = Punct; 175 | } 176 | Delimiter::None => {} 177 | } 178 | stack.push((stream.into_iter().peekable(), delimiter)); 179 | space = Self::space; 180 | } 181 | None => { 182 | match delimiter { 183 | Delimiter::Parenthesis => { 184 | if state != TrailingComma { 185 | self.zerobreak(); 186 | } 187 | self.offset(-INDENT); 188 | self.end(); 189 | self.word(")"); 190 | state = Punct; 191 | } 192 | Delimiter::Brace => { 193 | self.word("}"); 194 | state = Punct; 195 | } 196 | Delimiter::Bracket => { 197 | self.word("]"); 198 | state = Punct; 199 | } 200 | Delimiter::None => {} 201 | } 202 | stack.pop(); 203 | if stack.is_empty() { 204 | space = Self::nbsp; 205 | } 206 | } 207 | } 208 | } 209 | } 210 | } 211 | 212 | fn value_of_attribute(requested: &str, attr: &Attribute) -> Option { 213 | let value = match &attr.meta { 214 | Meta::NameValue(meta) if meta.path.is_ident(requested) => &meta.value, 215 | _ => return None, 216 | }; 217 | let lit = match value { 218 | Expr::Lit(expr) if expr.attrs.is_empty() => &expr.lit, 219 | _ => return None, 220 | }; 221 | match lit { 222 | Lit::Str(string) => Some(string.value()), 223 | _ => None, 224 | } 225 | } 226 | 227 | pub fn has_outer(attrs: &[Attribute]) -> bool { 228 | for attr in attrs { 229 | if let AttrStyle::Outer = attr.style { 230 | return true; 231 | } 232 | } 233 | false 234 | } 235 | 236 | pub fn has_inner(attrs: &[Attribute]) -> bool { 237 | for attr in attrs { 238 | if let AttrStyle::Inner(_) = attr.style { 239 | return true; 240 | } 241 | } 242 | false 243 | } 244 | 245 | fn trim_trailing_spaces(doc: &mut String) { 246 | doc.truncate(doc.trim_end_matches(' ').len()); 247 | } 248 | 249 | fn trim_interior_trailing_spaces(doc: &mut String) { 250 | if !doc.contains(" \n") { 251 | return; 252 | } 253 | let mut trimmed = String::with_capacity(doc.len()); 254 | let mut lines = doc.split('\n').peekable(); 255 | while let Some(line) = lines.next() { 256 | if lines.peek().is_some() { 257 | trimmed.push_str(line.trim_end_matches(' ')); 258 | trimmed.push('\n'); 259 | } else { 260 | trimmed.push_str(line); 261 | } 262 | } 263 | *doc = trimmed; 264 | } 265 | 266 | fn can_be_block_comment(value: &str) -> bool { 267 | let mut depth = 0usize; 268 | let bytes = value.as_bytes(); 269 | let mut i = 0usize; 270 | let upper = bytes.len() - 1; 271 | 272 | while i < upper { 273 | if bytes[i] == b'/' && bytes[i + 1] == b'*' { 274 | depth += 1; 275 | i += 2; 276 | } else if bytes[i] == b'*' && bytes[i + 1] == b'/' { 277 | if depth == 0 { 278 | return false; 279 | } 280 | depth -= 1; 281 | i += 2; 282 | } else { 283 | i += 1; 284 | } 285 | } 286 | 287 | depth == 0 && !value.ends_with('/') 288 | } 289 | -------------------------------------------------------------------------------- /src/classify.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Delimiter, TokenStream, TokenTree}; 2 | use std::ops::ControlFlow; 3 | use syn::punctuated::Punctuated; 4 | use syn::{Expr, MacroDelimiter, Path, PathArguments, ReturnType, Token, Type, TypeParamBound}; 5 | 6 | pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool { 7 | match expr { 8 | Expr::Macro(expr) => !matches!(expr.mac.delimiter, MacroDelimiter::Brace(_)), 9 | _ => requires_comma_to_be_match_arm(expr), 10 | } 11 | } 12 | 13 | pub(crate) fn requires_comma_to_be_match_arm(mut expr: &Expr) -> bool { 14 | loop { 15 | match expr { 16 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 17 | Expr::If(_) 18 | | Expr::Match(_) 19 | | Expr::Block(_) | Expr::Unsafe(_) // both under ExprKind::Block in rustc 20 | | Expr::While(_) 21 | | Expr::Loop(_) 22 | | Expr::ForLoop(_) 23 | | Expr::TryBlock(_) 24 | | Expr::Const(_) => return false, 25 | 26 | Expr::Array(_) 27 | | Expr::Assign(_) 28 | | Expr::Async(_) 29 | | Expr::Await(_) 30 | | Expr::Binary(_) 31 | | Expr::Break(_) 32 | | Expr::Call(_) 33 | | Expr::Cast(_) 34 | | Expr::Closure(_) 35 | | Expr::Continue(_) 36 | | Expr::Field(_) 37 | | Expr::Index(_) 38 | | Expr::Infer(_) 39 | | Expr::Let(_) 40 | | Expr::Lit(_) 41 | | Expr::Macro(_) 42 | | Expr::MethodCall(_) 43 | | Expr::Paren(_) 44 | | Expr::Path(_) 45 | | Expr::Range(_) 46 | | Expr::RawAddr(_) 47 | | Expr::Reference(_) 48 | | Expr::Repeat(_) 49 | | Expr::Return(_) 50 | | Expr::Struct(_) 51 | | Expr::Try(_) 52 | | Expr::Tuple(_) 53 | | Expr::Unary(_) 54 | | Expr::Yield(_) 55 | | Expr::Verbatim(_) => return true, 56 | 57 | Expr::Group(group) => expr = &group.expr, 58 | 59 | _ => return true, 60 | } 61 | } 62 | } 63 | 64 | pub(crate) fn trailing_unparameterized_path(mut ty: &Type) -> bool { 65 | loop { 66 | match ty { 67 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 68 | Type::BareFn(t) => match &t.output { 69 | ReturnType::Default => return false, 70 | ReturnType::Type(_, ret) => ty = ret, 71 | }, 72 | Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) { 73 | ControlFlow::Break(trailing_path) => return trailing_path, 74 | ControlFlow::Continue(t) => ty = t, 75 | }, 76 | Type::Path(t) => match last_type_in_path(&t.path) { 77 | ControlFlow::Break(trailing_path) => return trailing_path, 78 | ControlFlow::Continue(t) => ty = t, 79 | }, 80 | Type::Ptr(t) => ty = &t.elem, 81 | Type::Reference(t) => ty = &t.elem, 82 | Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) { 83 | ControlFlow::Break(trailing_path) => return trailing_path, 84 | ControlFlow::Continue(t) => ty = t, 85 | }, 86 | 87 | Type::Array(_) 88 | | Type::Group(_) 89 | | Type::Infer(_) 90 | | Type::Macro(_) 91 | | Type::Never(_) 92 | | Type::Paren(_) 93 | | Type::Slice(_) 94 | | Type::Tuple(_) 95 | | Type::Verbatim(_) => return false, 96 | 97 | _ => return false, 98 | } 99 | } 100 | 101 | fn last_type_in_path(path: &Path) -> ControlFlow { 102 | match &path.segments.last().unwrap().arguments { 103 | PathArguments::None => ControlFlow::Break(true), 104 | PathArguments::AngleBracketed(_) => ControlFlow::Break(false), 105 | PathArguments::Parenthesized(arg) => match &arg.output { 106 | ReturnType::Default => ControlFlow::Break(false), 107 | ReturnType::Type(_, ret) => ControlFlow::Continue(ret), 108 | }, 109 | } 110 | } 111 | 112 | fn last_type_in_bounds( 113 | bounds: &Punctuated, 114 | ) -> ControlFlow { 115 | match bounds.last().unwrap() { 116 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 117 | TypeParamBound::Trait(t) => last_type_in_path(&t.path), 118 | TypeParamBound::Lifetime(_) 119 | | TypeParamBound::PreciseCapture(_) 120 | | TypeParamBound::Verbatim(_) => ControlFlow::Break(false), 121 | _ => ControlFlow::Break(false), 122 | } 123 | } 124 | } 125 | 126 | /// Whether the expression's first token is the label of a loop/block. 127 | pub(crate) fn expr_leading_label(mut expr: &Expr) -> bool { 128 | loop { 129 | match expr { 130 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 131 | Expr::Block(e) => return e.label.is_some(), 132 | Expr::ForLoop(e) => return e.label.is_some(), 133 | Expr::Loop(e) => return e.label.is_some(), 134 | Expr::While(e) => return e.label.is_some(), 135 | 136 | Expr::Assign(e) => expr = &e.left, 137 | Expr::Await(e) => expr = &e.base, 138 | Expr::Binary(e) => expr = &e.left, 139 | Expr::Call(e) => expr = &e.func, 140 | Expr::Cast(e) => expr = &e.expr, 141 | Expr::Field(e) => expr = &e.base, 142 | Expr::Index(e) => expr = &e.expr, 143 | Expr::MethodCall(e) => expr = &e.receiver, 144 | Expr::Range(e) => match &e.start { 145 | Some(start) => expr = start, 146 | None => return false, 147 | }, 148 | Expr::Try(e) => expr = &e.expr, 149 | 150 | Expr::Array(_) 151 | | Expr::Async(_) 152 | | Expr::Break(_) 153 | | Expr::Closure(_) 154 | | Expr::Const(_) 155 | | Expr::Continue(_) 156 | | Expr::If(_) 157 | | Expr::Infer(_) 158 | | Expr::Let(_) 159 | | Expr::Lit(_) 160 | | Expr::Macro(_) 161 | | Expr::Match(_) 162 | | Expr::Paren(_) 163 | | Expr::Path(_) 164 | | Expr::RawAddr(_) 165 | | Expr::Reference(_) 166 | | Expr::Repeat(_) 167 | | Expr::Return(_) 168 | | Expr::Struct(_) 169 | | Expr::TryBlock(_) 170 | | Expr::Tuple(_) 171 | | Expr::Unary(_) 172 | | Expr::Unsafe(_) 173 | | Expr::Verbatim(_) 174 | | Expr::Yield(_) => return false, 175 | 176 | Expr::Group(e) => { 177 | if !e.attrs.is_empty() { 178 | return false; 179 | } 180 | expr = &e.expr; 181 | } 182 | 183 | _ => return false, 184 | } 185 | } 186 | } 187 | 188 | /// Whether the expression's last token is `}`. 189 | pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool { 190 | loop { 191 | match expr { 192 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 193 | Expr::Async(_) 194 | | Expr::Block(_) 195 | | Expr::Const(_) 196 | | Expr::ForLoop(_) 197 | | Expr::If(_) 198 | | Expr::Loop(_) 199 | | Expr::Match(_) 200 | | Expr::Struct(_) 201 | | Expr::TryBlock(_) 202 | | Expr::Unsafe(_) 203 | | Expr::While(_) => return true, 204 | 205 | Expr::Assign(e) => expr = &e.right, 206 | Expr::Binary(e) => expr = &e.right, 207 | Expr::Break(e) => match &e.expr { 208 | Some(e) => expr = e, 209 | None => return false, 210 | }, 211 | Expr::Cast(e) => return type_trailing_brace(&e.ty), 212 | Expr::Closure(e) => expr = &e.body, 213 | Expr::Group(e) => expr = &e.expr, 214 | Expr::Let(e) => expr = &e.expr, 215 | Expr::Macro(e) => return matches!(e.mac.delimiter, MacroDelimiter::Brace(_)), 216 | Expr::Range(e) => match &e.end { 217 | Some(end) => expr = end, 218 | None => return false, 219 | }, 220 | Expr::RawAddr(e) => expr = &e.expr, 221 | Expr::Reference(e) => expr = &e.expr, 222 | Expr::Return(e) => match &e.expr { 223 | Some(e) => expr = e, 224 | None => return false, 225 | }, 226 | Expr::Unary(e) => expr = &e.expr, 227 | Expr::Verbatim(e) => return tokens_trailing_brace(e), 228 | Expr::Yield(e) => match &e.expr { 229 | Some(e) => expr = e, 230 | None => return false, 231 | }, 232 | 233 | Expr::Array(_) 234 | | Expr::Await(_) 235 | | Expr::Call(_) 236 | | Expr::Continue(_) 237 | | Expr::Field(_) 238 | | Expr::Index(_) 239 | | Expr::Infer(_) 240 | | Expr::Lit(_) 241 | | Expr::MethodCall(_) 242 | | Expr::Paren(_) 243 | | Expr::Path(_) 244 | | Expr::Repeat(_) 245 | | Expr::Try(_) 246 | | Expr::Tuple(_) => return false, 247 | 248 | _ => return false, 249 | } 250 | } 251 | 252 | fn type_trailing_brace(mut ty: &Type) -> bool { 253 | loop { 254 | match ty { 255 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 256 | Type::BareFn(t) => match &t.output { 257 | ReturnType::Default => return false, 258 | ReturnType::Type(_, ret) => ty = ret, 259 | }, 260 | Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) { 261 | ControlFlow::Break(trailing_brace) => return trailing_brace, 262 | ControlFlow::Continue(t) => ty = t, 263 | }, 264 | Type::Macro(t) => return matches!(t.mac.delimiter, MacroDelimiter::Brace(_)), 265 | Type::Path(t) => match last_type_in_path(&t.path) { 266 | Some(t) => ty = t, 267 | None => return false, 268 | }, 269 | Type::Ptr(t) => ty = &t.elem, 270 | Type::Reference(t) => ty = &t.elem, 271 | Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) { 272 | ControlFlow::Break(trailing_brace) => return trailing_brace, 273 | ControlFlow::Continue(t) => ty = t, 274 | }, 275 | Type::Verbatim(t) => return tokens_trailing_brace(t), 276 | 277 | Type::Array(_) 278 | | Type::Group(_) 279 | | Type::Infer(_) 280 | | Type::Never(_) 281 | | Type::Paren(_) 282 | | Type::Slice(_) 283 | | Type::Tuple(_) => return false, 284 | 285 | _ => return false, 286 | } 287 | } 288 | } 289 | 290 | fn last_type_in_path(path: &Path) -> Option<&Type> { 291 | match &path.segments.last().unwrap().arguments { 292 | PathArguments::None | PathArguments::AngleBracketed(_) => None, 293 | PathArguments::Parenthesized(arg) => match &arg.output { 294 | ReturnType::Default => None, 295 | ReturnType::Type(_, ret) => Some(ret), 296 | }, 297 | } 298 | } 299 | 300 | fn last_type_in_bounds( 301 | bounds: &Punctuated, 302 | ) -> ControlFlow { 303 | match bounds.last().unwrap() { 304 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 305 | TypeParamBound::Trait(t) => match last_type_in_path(&t.path) { 306 | Some(t) => ControlFlow::Continue(t), 307 | None => ControlFlow::Break(false), 308 | }, 309 | TypeParamBound::Lifetime(_) | TypeParamBound::PreciseCapture(_) => { 310 | ControlFlow::Break(false) 311 | } 312 | TypeParamBound::Verbatim(t) => ControlFlow::Break(tokens_trailing_brace(t)), 313 | _ => ControlFlow::Break(false), 314 | } 315 | } 316 | 317 | fn tokens_trailing_brace(tokens: &TokenStream) -> bool { 318 | if let Some(TokenTree::Group(last)) = tokens.clone().into_iter().last() { 319 | last.delimiter() == Delimiter::Brace 320 | } else { 321 | false 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/convenience.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::{self, BeginToken, BreakToken, Breaks, Printer}; 2 | use std::borrow::Cow; 3 | 4 | impl Printer { 5 | pub fn ibox(&mut self, indent: isize) { 6 | self.scan_begin(BeginToken { 7 | offset: indent, 8 | breaks: Breaks::Inconsistent, 9 | }); 10 | } 11 | 12 | pub fn cbox(&mut self, indent: isize) { 13 | self.scan_begin(BeginToken { 14 | offset: indent, 15 | breaks: Breaks::Consistent, 16 | }); 17 | } 18 | 19 | pub fn end(&mut self) { 20 | self.scan_end(); 21 | } 22 | 23 | pub fn word>>(&mut self, wrd: S) { 24 | let s = wrd.into(); 25 | self.scan_string(s); 26 | } 27 | 28 | fn spaces(&mut self, n: usize) { 29 | self.scan_break(BreakToken { 30 | blank_space: n, 31 | ..BreakToken::default() 32 | }); 33 | } 34 | 35 | pub fn zerobreak(&mut self) { 36 | self.spaces(0); 37 | } 38 | 39 | pub fn space(&mut self) { 40 | self.spaces(1); 41 | } 42 | 43 | pub fn nbsp(&mut self) { 44 | self.word(" "); 45 | } 46 | 47 | pub fn hardbreak(&mut self) { 48 | self.spaces(algorithm::SIZE_INFINITY as usize); 49 | } 50 | 51 | pub fn space_if_nonempty(&mut self) { 52 | self.scan_break(BreakToken { 53 | blank_space: 1, 54 | if_nonempty: true, 55 | ..BreakToken::default() 56 | }); 57 | } 58 | 59 | pub fn hardbreak_if_nonempty(&mut self) { 60 | self.scan_break(BreakToken { 61 | blank_space: algorithm::SIZE_INFINITY as usize, 62 | if_nonempty: true, 63 | ..BreakToken::default() 64 | }); 65 | } 66 | 67 | pub fn trailing_comma(&mut self, is_last: bool) { 68 | if is_last { 69 | self.scan_break(BreakToken { 70 | pre_break: Some(','), 71 | ..BreakToken::default() 72 | }); 73 | } else { 74 | self.word(","); 75 | self.space(); 76 | } 77 | } 78 | 79 | pub fn trailing_comma_or_space(&mut self, is_last: bool) { 80 | if is_last { 81 | self.scan_break(BreakToken { 82 | blank_space: 1, 83 | pre_break: Some(','), 84 | ..BreakToken::default() 85 | }); 86 | } else { 87 | self.word(","); 88 | self.space(); 89 | } 90 | } 91 | 92 | pub fn neverbreak(&mut self) { 93 | self.scan_break(BreakToken { 94 | never_break: true, 95 | ..BreakToken::default() 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::fixup::FixupContext; 3 | use crate::iter::IterDelimited; 4 | use crate::path::PathKind; 5 | use crate::INDENT; 6 | use syn::{Field, Fields, FieldsUnnamed, Variant, VisRestricted, Visibility}; 7 | 8 | impl Printer { 9 | pub fn variant(&mut self, variant: &Variant) { 10 | self.outer_attrs(&variant.attrs); 11 | self.ident(&variant.ident); 12 | match &variant.fields { 13 | Fields::Named(fields) => { 14 | self.nbsp(); 15 | self.word("{"); 16 | self.cbox(INDENT); 17 | self.space(); 18 | for field in fields.named.iter().delimited() { 19 | self.field(&field); 20 | self.trailing_comma_or_space(field.is_last); 21 | } 22 | self.offset(-INDENT); 23 | self.end(); 24 | self.word("}"); 25 | } 26 | Fields::Unnamed(fields) => { 27 | self.cbox(INDENT); 28 | self.fields_unnamed(fields); 29 | self.end(); 30 | } 31 | Fields::Unit => {} 32 | } 33 | if let Some((_eq_token, discriminant)) = &variant.discriminant { 34 | self.word(" = "); 35 | self.expr(discriminant, FixupContext::NONE); 36 | } 37 | } 38 | 39 | pub fn fields_unnamed(&mut self, fields: &FieldsUnnamed) { 40 | self.word("("); 41 | self.zerobreak(); 42 | for field in fields.unnamed.iter().delimited() { 43 | self.field(&field); 44 | self.trailing_comma(field.is_last); 45 | } 46 | self.offset(-INDENT); 47 | self.word(")"); 48 | } 49 | 50 | pub fn field(&mut self, field: &Field) { 51 | self.outer_attrs(&field.attrs); 52 | self.visibility(&field.vis); 53 | if let Some(ident) = &field.ident { 54 | self.ident(ident); 55 | self.word(": "); 56 | } 57 | self.ty(&field.ty); 58 | } 59 | 60 | pub fn visibility(&mut self, vis: &Visibility) { 61 | match vis { 62 | Visibility::Public(_) => self.word("pub "), 63 | Visibility::Restricted(vis) => self.vis_restricted(vis), 64 | Visibility::Inherited => {} 65 | } 66 | } 67 | 68 | fn vis_restricted(&mut self, vis: &VisRestricted) { 69 | self.word("pub("); 70 | let omit_in = vis.path.get_ident().map_or(false, |ident| { 71 | matches!(ident.to_string().as_str(), "self" | "super" | "crate") 72 | }); 73 | if !omit_in { 74 | self.word("in "); 75 | } 76 | self.path(&vis.path, PathKind::Simple); 77 | self.word(") "); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use syn::File; 3 | 4 | impl Printer { 5 | pub fn file(&mut self, file: &File) { 6 | self.cbox(0); 7 | if let Some(shebang) = &file.shebang { 8 | self.word(shebang.clone()); 9 | self.hardbreak(); 10 | } 11 | self.inner_attrs(&file.attrs); 12 | for item in &file.items { 13 | self.item(item); 14 | } 15 | self.end(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/generics.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::iter::IterDelimited; 3 | use crate::path::PathKind; 4 | use crate::INDENT; 5 | use proc_macro2::TokenStream; 6 | use std::ptr; 7 | use syn::{ 8 | BoundLifetimes, CapturedParam, ConstParam, Expr, GenericParam, Generics, LifetimeParam, 9 | PreciseCapture, PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, TypeParam, 10 | TypeParamBound, WhereClause, WherePredicate, 11 | }; 12 | 13 | impl Printer { 14 | pub fn generics(&mut self, generics: &Generics) { 15 | if generics.params.is_empty() { 16 | return; 17 | } 18 | 19 | self.word("<"); 20 | self.cbox(0); 21 | self.zerobreak(); 22 | 23 | // Print lifetimes before types and consts, regardless of their 24 | // order in self.params. 25 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 26 | enum Group { 27 | First, 28 | Second, 29 | } 30 | fn group(param: &GenericParam) -> Group { 31 | match param { 32 | GenericParam::Lifetime(_) => Group::First, 33 | GenericParam::Type(_) | GenericParam::Const(_) => Group::Second, 34 | } 35 | } 36 | let last = generics.params.iter().max_by_key(|param| group(param)); 37 | for current_group in [Group::First, Group::Second] { 38 | for param in &generics.params { 39 | if group(param) == current_group { 40 | self.generic_param(param); 41 | self.trailing_comma(ptr::eq(param, last.unwrap())); 42 | } 43 | } 44 | } 45 | 46 | self.offset(-INDENT); 47 | self.end(); 48 | self.word(">"); 49 | } 50 | 51 | fn generic_param(&mut self, generic_param: &GenericParam) { 52 | match generic_param { 53 | GenericParam::Type(type_param) => self.type_param(type_param), 54 | GenericParam::Lifetime(lifetime_param) => self.lifetime_param(lifetime_param), 55 | GenericParam::Const(const_param) => self.const_param(const_param), 56 | } 57 | } 58 | 59 | pub fn bound_lifetimes(&mut self, bound_lifetimes: &BoundLifetimes) { 60 | self.word("for<"); 61 | for param in bound_lifetimes.lifetimes.iter().delimited() { 62 | self.generic_param(¶m); 63 | if !param.is_last { 64 | self.word(", "); 65 | } 66 | } 67 | self.word("> "); 68 | } 69 | 70 | fn lifetime_param(&mut self, lifetime_param: &LifetimeParam) { 71 | self.outer_attrs(&lifetime_param.attrs); 72 | self.lifetime(&lifetime_param.lifetime); 73 | for lifetime in lifetime_param.bounds.iter().delimited() { 74 | if lifetime.is_first { 75 | self.word(": "); 76 | } else { 77 | self.word(" + "); 78 | } 79 | self.lifetime(&lifetime); 80 | } 81 | } 82 | 83 | fn type_param(&mut self, type_param: &TypeParam) { 84 | self.outer_attrs(&type_param.attrs); 85 | self.ident(&type_param.ident); 86 | self.ibox(INDENT); 87 | for type_param_bound in type_param.bounds.iter().delimited() { 88 | if type_param_bound.is_first { 89 | self.word(": "); 90 | } else { 91 | self.space(); 92 | self.word("+ "); 93 | } 94 | self.type_param_bound(&type_param_bound); 95 | } 96 | if let Some(default) = &type_param.default { 97 | self.space(); 98 | self.word("= "); 99 | self.ty(default); 100 | } 101 | self.end(); 102 | } 103 | 104 | pub fn type_param_bound(&mut self, type_param_bound: &TypeParamBound) { 105 | match type_param_bound { 106 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 107 | TypeParamBound::Trait(trait_bound) => { 108 | let tilde_const = false; 109 | self.trait_bound(trait_bound, tilde_const); 110 | } 111 | TypeParamBound::Lifetime(lifetime) => self.lifetime(lifetime), 112 | TypeParamBound::PreciseCapture(precise_capture) => { 113 | self.precise_capture(precise_capture); 114 | } 115 | TypeParamBound::Verbatim(bound) => self.type_param_bound_verbatim(bound), 116 | _ => unimplemented!("unknown TypeParamBound"), 117 | } 118 | } 119 | 120 | fn trait_bound(&mut self, trait_bound: &TraitBound, tilde_const: bool) { 121 | if trait_bound.paren_token.is_some() { 122 | self.word("("); 123 | } 124 | if tilde_const { 125 | self.word("~const "); 126 | } 127 | self.trait_bound_modifier(&trait_bound.modifier); 128 | if let Some(bound_lifetimes) = &trait_bound.lifetimes { 129 | self.bound_lifetimes(bound_lifetimes); 130 | } 131 | for segment in trait_bound.path.segments.iter().delimited() { 132 | if !segment.is_first || trait_bound.path.leading_colon.is_some() { 133 | self.word("::"); 134 | } 135 | self.path_segment(&segment, PathKind::Type); 136 | } 137 | if trait_bound.paren_token.is_some() { 138 | self.word(")"); 139 | } 140 | } 141 | 142 | fn trait_bound_modifier(&mut self, trait_bound_modifier: &TraitBoundModifier) { 143 | match trait_bound_modifier { 144 | TraitBoundModifier::None => {} 145 | TraitBoundModifier::Maybe(_question_mark) => self.word("?"), 146 | } 147 | } 148 | 149 | #[cfg(not(feature = "verbatim"))] 150 | fn type_param_bound_verbatim(&mut self, bound: &TokenStream) { 151 | unimplemented!("TypeParamBound::Verbatim `{}`", bound); 152 | } 153 | 154 | #[cfg(feature = "verbatim")] 155 | fn type_param_bound_verbatim(&mut self, tokens: &TokenStream) { 156 | use syn::parse::{Parse, ParseStream, Result}; 157 | use syn::{parenthesized, token, Token}; 158 | 159 | enum TypeParamBoundVerbatim { 160 | Ellipsis, 161 | TildeConst(TraitBound), 162 | } 163 | 164 | impl Parse for TypeParamBoundVerbatim { 165 | fn parse(input: ParseStream) -> Result { 166 | let content; 167 | let (paren_token, content) = if input.peek(token::Paren) { 168 | (Some(parenthesized!(content in input)), &content) 169 | } else { 170 | (None, input) 171 | }; 172 | let lookahead = content.lookahead1(); 173 | if lookahead.peek(Token![~]) { 174 | content.parse::()?; 175 | content.parse::()?; 176 | let mut bound: TraitBound = content.parse()?; 177 | bound.paren_token = paren_token; 178 | Ok(TypeParamBoundVerbatim::TildeConst(bound)) 179 | } else if lookahead.peek(Token![...]) { 180 | content.parse::()?; 181 | Ok(TypeParamBoundVerbatim::Ellipsis) 182 | } else { 183 | Err(lookahead.error()) 184 | } 185 | } 186 | } 187 | 188 | let bound: TypeParamBoundVerbatim = match syn::parse2(tokens.clone()) { 189 | Ok(bound) => bound, 190 | Err(_) => unimplemented!("TypeParamBound::Verbatim `{}`", tokens), 191 | }; 192 | 193 | match bound { 194 | TypeParamBoundVerbatim::Ellipsis => { 195 | self.word("..."); 196 | } 197 | TypeParamBoundVerbatim::TildeConst(trait_bound) => { 198 | let tilde_const = true; 199 | self.trait_bound(&trait_bound, tilde_const); 200 | } 201 | } 202 | } 203 | 204 | fn const_param(&mut self, const_param: &ConstParam) { 205 | self.outer_attrs(&const_param.attrs); 206 | self.word("const "); 207 | self.ident(&const_param.ident); 208 | self.word(": "); 209 | self.ty(&const_param.ty); 210 | if let Some(default) = &const_param.default { 211 | self.word(" = "); 212 | self.const_argument(default); 213 | } 214 | } 215 | 216 | pub fn where_clause_for_body(&mut self, where_clause: &Option) { 217 | let hardbreaks = true; 218 | let semi = false; 219 | self.where_clause_impl(where_clause, hardbreaks, semi); 220 | } 221 | 222 | pub fn where_clause_semi(&mut self, where_clause: &Option) { 223 | let hardbreaks = true; 224 | let semi = true; 225 | self.where_clause_impl(where_clause, hardbreaks, semi); 226 | } 227 | 228 | pub fn where_clause_oneline(&mut self, where_clause: &Option) { 229 | let hardbreaks = false; 230 | let semi = false; 231 | self.where_clause_impl(where_clause, hardbreaks, semi); 232 | } 233 | 234 | pub fn where_clause_oneline_semi(&mut self, where_clause: &Option) { 235 | let hardbreaks = false; 236 | let semi = true; 237 | self.where_clause_impl(where_clause, hardbreaks, semi); 238 | } 239 | 240 | fn where_clause_impl( 241 | &mut self, 242 | where_clause: &Option, 243 | hardbreaks: bool, 244 | semi: bool, 245 | ) { 246 | let where_clause = match where_clause { 247 | Some(where_clause) if !where_clause.predicates.is_empty() => where_clause, 248 | _ => { 249 | if semi { 250 | self.word(";"); 251 | } else { 252 | self.nbsp(); 253 | } 254 | return; 255 | } 256 | }; 257 | if hardbreaks { 258 | self.hardbreak(); 259 | self.offset(-INDENT); 260 | self.word("where"); 261 | self.hardbreak(); 262 | for predicate in where_clause.predicates.iter().delimited() { 263 | self.where_predicate(&predicate); 264 | if predicate.is_last && semi { 265 | self.word(";"); 266 | } else { 267 | self.word(","); 268 | self.hardbreak(); 269 | } 270 | } 271 | if !semi { 272 | self.offset(-INDENT); 273 | } 274 | } else { 275 | self.space(); 276 | self.offset(-INDENT); 277 | self.word("where"); 278 | self.space(); 279 | for predicate in where_clause.predicates.iter().delimited() { 280 | self.where_predicate(&predicate); 281 | if predicate.is_last && semi { 282 | self.word(";"); 283 | } else { 284 | self.trailing_comma_or_space(predicate.is_last); 285 | } 286 | } 287 | if !semi { 288 | self.offset(-INDENT); 289 | } 290 | } 291 | } 292 | 293 | fn where_predicate(&mut self, predicate: &WherePredicate) { 294 | match predicate { 295 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 296 | WherePredicate::Type(predicate) => self.predicate_type(predicate), 297 | WherePredicate::Lifetime(predicate) => self.predicate_lifetime(predicate), 298 | _ => unimplemented!("unknown WherePredicate"), 299 | } 300 | } 301 | 302 | fn predicate_type(&mut self, predicate: &PredicateType) { 303 | if let Some(bound_lifetimes) = &predicate.lifetimes { 304 | self.bound_lifetimes(bound_lifetimes); 305 | } 306 | self.ty(&predicate.bounded_ty); 307 | self.word(":"); 308 | if predicate.bounds.len() == 1 { 309 | self.ibox(0); 310 | } else { 311 | self.ibox(INDENT); 312 | } 313 | for type_param_bound in predicate.bounds.iter().delimited() { 314 | if type_param_bound.is_first { 315 | self.nbsp(); 316 | } else { 317 | self.space(); 318 | self.word("+ "); 319 | } 320 | self.type_param_bound(&type_param_bound); 321 | } 322 | self.end(); 323 | } 324 | 325 | fn predicate_lifetime(&mut self, predicate: &PredicateLifetime) { 326 | self.lifetime(&predicate.lifetime); 327 | self.word(":"); 328 | self.ibox(INDENT); 329 | for lifetime in predicate.bounds.iter().delimited() { 330 | if lifetime.is_first { 331 | self.nbsp(); 332 | } else { 333 | self.space(); 334 | self.word("+ "); 335 | } 336 | self.lifetime(&lifetime); 337 | } 338 | self.end(); 339 | } 340 | 341 | fn precise_capture(&mut self, precise_capture: &PreciseCapture) { 342 | self.word("use<"); 343 | for capture in precise_capture.params.iter().delimited() { 344 | self.captured_param(&capture); 345 | if !capture.is_last { 346 | self.word(", "); 347 | } 348 | } 349 | self.word(">"); 350 | } 351 | 352 | fn captured_param(&mut self, capture: &CapturedParam) { 353 | match capture { 354 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 355 | CapturedParam::Lifetime(lifetime) => self.lifetime(lifetime), 356 | CapturedParam::Ident(ident) => self.ident(ident), 357 | _ => unimplemented!("unknown CapturedParam"), 358 | } 359 | } 360 | 361 | pub fn const_argument(&mut self, expr: &Expr) { 362 | match expr { 363 | #![cfg_attr(all(test, exhaustive), allow(non_exhaustive_omitted_patterns))] 364 | Expr::Lit(expr) => self.expr_lit(expr), 365 | 366 | Expr::Path(expr) 367 | if expr.attrs.is_empty() 368 | && expr.qself.is_none() 369 | && expr.path.get_ident().is_some() => 370 | { 371 | self.expr_path(expr); 372 | } 373 | 374 | Expr::Block(expr) => self.expr_block(expr), 375 | 376 | _ => { 377 | self.cbox(INDENT); 378 | self.expr_as_small_block(expr, 0); 379 | self.end(); 380 | } 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | use std::ops::Deref; 3 | 4 | pub struct Delimited { 5 | is_first: bool, 6 | iter: Peekable, 7 | } 8 | 9 | pub trait IterDelimited: Iterator + Sized { 10 | fn delimited(self) -> Delimited { 11 | Delimited { 12 | is_first: true, 13 | iter: self.peekable(), 14 | } 15 | } 16 | } 17 | 18 | impl IterDelimited for I {} 19 | 20 | pub struct IteratorItem { 21 | value: T, 22 | pub is_first: bool, 23 | pub is_last: bool, 24 | } 25 | 26 | impl Iterator for Delimited { 27 | type Item = IteratorItem; 28 | 29 | fn next(&mut self) -> Option { 30 | let item = IteratorItem { 31 | value: self.iter.next()?, 32 | is_first: self.is_first, 33 | is_last: self.iter.peek().is_none(), 34 | }; 35 | self.is_first = false; 36 | Some(item) 37 | } 38 | } 39 | 40 | impl Deref for IteratorItem { 41 | type Target = T; 42 | 43 | fn deref(&self) -> &Self::Target { 44 | &self.value 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/prettyplease) [![crates-io]](https://crates.io/crates/prettyplease) [![docs-rs]](https://docs.rs/prettyplease) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //! **prettyplease::unparse** — a minimal `syn` syntax tree pretty-printer 10 | //! 11 | //!
12 | //! 13 | //! # Overview 14 | //! 15 | //! This is a pretty-printer to turn a `syn` syntax tree into a `String` of 16 | //! well-formatted source code. In contrast to rustfmt, this library is intended 17 | //! to be suitable for arbitrary generated code. 18 | //! 19 | //! Rustfmt prioritizes high-quality output that is impeccable enough that you'd 20 | //! be comfortable spending your career staring at its output — but that 21 | //! means some heavyweight algorithms, and it has a tendency to bail out on code 22 | //! that is hard to format (for example [rustfmt#3697], and there are dozens 23 | //! more issues like it). That's not necessarily a big deal for human-generated 24 | //! code because when code gets highly nested, the human will naturally be 25 | //! inclined to refactor into more easily formattable code. But for generated 26 | //! code, having the formatter just give up leaves it totally unreadable. 27 | //! 28 | //! [rustfmt#3697]: https://github.com/rust-lang/rustfmt/issues/3697 29 | //! 30 | //! This library is designed using the simplest possible algorithm and data 31 | //! structures that can deliver about 95% of the quality of rustfmt-formatted 32 | //! output. In my experience testing real-world code, approximately 97-98% of 33 | //! output lines come out identical between rustfmt's formatting and this 34 | //! crate's. The rest have slightly different linebreak decisions, but still 35 | //! clearly follow the dominant modern Rust style. 36 | //! 37 | //! The tradeoffs made by this crate are a good fit for generated code that you 38 | //! will *not* spend your career staring at. For example, the output of 39 | //! `bindgen`, or the output of `cargo-expand`. In those cases it's more 40 | //! important that the whole thing be formattable without the formatter giving 41 | //! up, than that it be flawless. 42 | //! 43 | //!
44 | //! 45 | //! # Feature matrix 46 | //! 47 | //! Here are a few superficial comparisons of this crate against the AST 48 | //! pretty-printer built into rustc, and rustfmt. The sections below go into 49 | //! more detail comparing the output of each of these libraries. 50 | //! 51 | //! | | prettyplease | rustc | rustfmt | 52 | //! |:---|:---:|:---:|:---:| 53 | //! | non-pathological behavior on big or generated code | 💚 | ❌ | ❌ | 54 | //! | idiomatic modern formatting ("locally indistinguishable from rustfmt") | 💚 | ❌ | 💚 | 55 | //! | throughput | 60 MB/s | 39 MB/s | 2.8 MB/s | 56 | //! | number of dependencies | 3 | 72 | 66 | 57 | //! | compile time including dependencies | 2.4 sec | 23.1 sec | 29.8 sec | 58 | //! | buildable using a stable Rust compiler | 💚 | ❌ | ❌ | 59 | //! | published to crates.io | 💚 | ❌ | ❌ | 60 | //! | extensively configurable output | ❌ | ❌ | 💚 | 61 | //! | intended to accommodate hand-maintained source code | ❌ | ❌ | 💚 | 62 | //! 63 | //!
64 | //! 65 | //! # Comparison to rustfmt 66 | //! 67 | //! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) 68 | //! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) 69 | //! - [output.rustfmt.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustfmt.rs) 70 | //! 71 | //! If you weren't told which output file is which, it would be practically 72 | //! impossible to tell — **except** for line 435 in the rustfmt output, 73 | //! which is more than 1000 characters long because rustfmt just gave up 74 | //! formatting that part of the file: 75 | //! 76 | //! ``` 77 | //! # const _: &str = stringify! {{{ 78 | //! match segments[5] { 79 | //! 0 => write!(f, "::{}", ipv4), 80 | //! 0xffff => write!(f, "::ffff:{}", ipv4), 81 | //! _ => unreachable!(), 82 | //! } 83 | //! } else { # [derive (Copy , Clone , Default)] struct Span { start : usize , len : usize , } let zeroes = { let mut longest = Span :: default () ; let mut current = Span :: default () ; for (i , & segment) in segments . iter () . enumerate () { if segment == 0 { if current . len == 0 { current . start = i ; } current . len += 1 ; if current . len > longest . len { longest = current ; } } else { current = Span :: default () ; } } longest } ; # [doc = " Write a colon-separated part of the address"] # [inline] fn fmt_subslice (f : & mut fmt :: Formatter < '_ > , chunk : & [u16]) -> fmt :: Result { if let Some ((first , tail)) = chunk . split_first () { write ! (f , "{:x}" , first) ? ; for segment in tail { f . write_char (':') ? ; write ! (f , "{:x}" , segment) ? ; } } Ok (()) } if zeroes . len > 1 { fmt_subslice (f , & segments [.. zeroes . start]) ? ; f . write_str ("::") ? ; fmt_subslice (f , & segments [zeroes . start + zeroes . len ..]) } else { fmt_subslice (f , & segments) } } 84 | //! } else { 85 | //! const IPV6_BUF_LEN: usize = (4 * 8) + 7; 86 | //! let mut buf = [0u8; IPV6_BUF_LEN]; 87 | //! let mut buf_slice = &mut buf[..]; 88 | //! # }}; 89 | //! ``` 90 | //! 91 | //! This is a pretty typical manifestation of rustfmt bailing out in generated 92 | //! code — a chunk of the input ends up on one line. The other 93 | //! manifestation is that you're working on some code, running rustfmt on save 94 | //! like a conscientious developer, but after a while notice it isn't doing 95 | //! anything. You introduce an intentional formatting issue, like a stray indent 96 | //! or semicolon, and run rustfmt to check your suspicion. Nope, it doesn't get 97 | //! cleaned up — rustfmt is just not formatting the part of the file you 98 | //! are working on. 99 | //! 100 | //! The prettyplease library is designed to have no pathological cases that 101 | //! force a bail out; the entire input you give it will get formatted in some 102 | //! "good enough" form. 103 | //! 104 | //! Separately, rustfmt can be problematic to integrate into projects. It's 105 | //! written using rustc's internal syntax tree, so it can't be built by a stable 106 | //! compiler. Its releases are not regularly published to crates.io, so in Cargo 107 | //! builds you'd need to depend on it as a git dependency, which precludes 108 | //! publishing your crate to crates.io also. You can shell out to a `rustfmt` 109 | //! binary, but that'll be whatever rustfmt version is installed on each 110 | //! developer's system (if any), which can lead to spurious diffs in checked-in 111 | //! generated code formatted by different versions. In contrast prettyplease is 112 | //! designed to be easy to pull in as a library, and compiles fast. 113 | //! 114 | //!
115 | //! 116 | //! # Comparison to rustc_ast_pretty 117 | //! 118 | //! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) 119 | //! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) 120 | //! - [output.rustc.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustc.rs) 121 | //! 122 | //! This is the pretty-printer that gets used when rustc prints source code, 123 | //! such as `rustc -Zunpretty=expanded`. It's used also by the standard 124 | //! library's `stringify!` when stringifying an interpolated macro_rules AST 125 | //! fragment, like an $:expr, and transitively by `dbg!` and many macros in the 126 | //! ecosystem. 127 | //! 128 | //! Rustc's formatting is mostly okay, but does not hew closely to the dominant 129 | //! contemporary style of Rust formatting. Some things wouldn't ever be written 130 | //! on one line, like this `match` expression, and certainly not with a comma in 131 | //! front of the closing brace: 132 | //! 133 | //! ``` 134 | //! # const _: &str = stringify! { 135 | //! fn eq(&self, other: &IpAddr) -> bool { 136 | //! match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } 137 | //! } 138 | //! # }; 139 | //! ``` 140 | //! 141 | //! Some places use non-multiple-of-4 indentation, which is definitely not the 142 | //! norm: 143 | //! 144 | //! ``` 145 | //! # const _: &str = stringify! { 146 | //! pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { 147 | //! let [a, b, c, d] = self.octets(); 148 | //! Ipv6Addr{inner: 149 | //! c::in6_addr{s6_addr: 150 | //! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 151 | //! 0xFF, a, b, c, d],},} 152 | //! } 153 | //! # }; 154 | //! ``` 155 | //! 156 | //! And although there isn't an egregious example of it in the link because the 157 | //! input code is pretty tame, in general rustc_ast_pretty has pathological 158 | //! behavior on generated code. It has a tendency to use excessive horizontal 159 | //! indentation and rapidly run out of width: 160 | //! 161 | //! ``` 162 | //! # const _: &str = stringify! { 163 | //! ::std::io::_print(::core::fmt::Arguments::new_v1(&[""], 164 | //! &match (&msg,) { 165 | //! _args => 166 | //! [::core::fmt::ArgumentV1::new(_args.0, 167 | //! ::core::fmt::Display::fmt)], 168 | //! })); 169 | //! # }; 170 | //! ``` 171 | //! 172 | //! The snippets above are clearly different from modern rustfmt style. In 173 | //! contrast, prettyplease is designed to have output that is practically 174 | //! indistinguishable from rustfmt-formatted code. 175 | //! 176 | //!
177 | //! 178 | //! # Example 179 | //! 180 | //! ``` 181 | //! // [dependencies] 182 | //! // prettyplease = "0.2" 183 | //! // syn = { version = "2", default-features = false, features = ["full", "parsing"] } 184 | //! 185 | //! const INPUT: &str = stringify! { 186 | //! use crate::{ 187 | //! lazy::{Lazy, SyncLazy, SyncOnceCell}, panic, 188 | //! sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, 189 | //! mpsc::channel, Mutex, }, 190 | //! thread, 191 | //! }; 192 | //! impl Into for T where U: From { 193 | //! fn into(self) -> U { U::from(self) } 194 | //! } 195 | //! }; 196 | //! 197 | //! fn main() { 198 | //! let syntax_tree = syn::parse_file(INPUT).unwrap(); 199 | //! let formatted = prettyplease::unparse(&syntax_tree); 200 | //! print!("{}", formatted); 201 | //! } 202 | //! ``` 203 | //! 204 | //!
205 | //! 206 | //! # Algorithm notes 207 | //! 208 | //! The approach and terminology used in the implementation are derived from 209 | //! [*Derek C. Oppen, "Pretty Printing" (1979)*][paper], on which 210 | //! rustc_ast_pretty is also based, and from rustc_ast_pretty's implementation 211 | //! written by Graydon Hoare in 2011 (and modernized over the years by dozens of 212 | //! volunteer maintainers). 213 | //! 214 | //! [paper]: http://i.stanford.edu/pub/cstr/reports/cs/tr/79/770/CS-TR-79-770.pdf 215 | //! 216 | //! The paper describes two language-agnostic interacting procedures `Scan()` 217 | //! and `Print()`. Language-specific code decomposes an input data structure 218 | //! into a stream of `string` and `break` tokens, and `begin` and `end` tokens 219 | //! for grouping. Each `begin`–`end` range may be identified as either 220 | //! "consistent breaking" or "inconsistent breaking". If a group is consistently 221 | //! breaking, then if the whole contents do not fit on the line, *every* `break` 222 | //! token in the group will receive a linebreak. This is appropriate, for 223 | //! example, for Rust struct literals, or arguments of a function call. If a 224 | //! group is inconsistently breaking, then the `string` tokens in the group are 225 | //! greedily placed on the line until out of space, and linebroken only at those 226 | //! `break` tokens for which the next string would not fit. For example, this is 227 | //! appropriate for the contents of a braced `use` statement in Rust. 228 | //! 229 | //! Scan's job is to efficiently accumulate sizing information about groups and 230 | //! breaks. For every `begin` token we compute the distance to the matched `end` 231 | //! token, and for every `break` we compute the distance to the next `break`. 232 | //! The algorithm uses a ringbuffer to hold tokens whose size is not yet 233 | //! ascertained. The maximum size of the ringbuffer is bounded by the target 234 | //! line length and does not grow indefinitely, regardless of deep nesting in 235 | //! the input stream. That's because once a group is sufficiently big, the 236 | //! precise size can no longer make a difference to linebreak decisions and we 237 | //! can effectively treat it as "infinity". 238 | //! 239 | //! Print's job is to use the sizing information to efficiently assign a 240 | //! "broken" or "not broken" status to every `begin` token. At that point the 241 | //! output is easily constructed by concatenating `string` tokens and breaking 242 | //! at `break` tokens contained within a broken group. 243 | //! 244 | //! Leveraging these primitives (i.e. cleverly placing the all-or-nothing 245 | //! consistent breaks and greedy inconsistent breaks) to yield 246 | //! rustfmt-compatible formatting for all of Rust's syntax tree nodes is a fun 247 | //! challenge. 248 | //! 249 | //! Here is a visualization of some Rust tokens fed into the pretty printing 250 | //! algorithm. Consistently breaking `begin`—`end` pairs are represented 251 | //! by `«`⁠`»`, inconsistently breaking by `‹`⁠`›`, `break` by `·`, 252 | //! and the rest of the non-whitespace are `string`. 253 | //! 254 | //! ```text 255 | //! use crate::«{· 256 | //! ‹ lazy::«{·‹Lazy,· SyncLazy,· SyncOnceCell›·}»,· 257 | //! panic,· 258 | //! sync::«{· 259 | //! ‹ atomic::«{·‹AtomicUsize,· Ordering::SeqCst›·}»,· 260 | //! mpsc::channel,· Mutex›,· 261 | //! }»,· 262 | //! thread›,· 263 | //! }»;· 264 | //! «‹«impl<«·T‹›,· U‹›·»>» Into<«·U·»>· for T›· 265 | //! where· 266 | //! U:‹ From<«·T·»>›,· 267 | //! {· 268 | //! « fn into(·«·self·») -> U {· 269 | //! ‹ U::from(«·self·»)›· 270 | //! » }· 271 | //! »}· 272 | //! ``` 273 | //! 274 | //! The algorithm described in the paper is not quite sufficient for producing 275 | //! well-formatted Rust code that is locally indistinguishable from rustfmt's 276 | //! style. The reason is that in the paper, the complete non-whitespace contents 277 | //! are assumed to be independent of linebreak decisions, with Scan and Print 278 | //! being only in control of the whitespace (spaces and line breaks). In Rust as 279 | //! idiomatically formatted by rustfmt, that is not the case. Trailing commas 280 | //! are one example; the punctuation is only known *after* the broken vs 281 | //! non-broken status of the surrounding group is known: 282 | //! 283 | //! ``` 284 | //! # struct Struct { x: u64, y: bool } 285 | //! # let xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = 0; 286 | //! # let yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy = true; 287 | //! # 288 | //! let _ = Struct { x: 0, y: true }; 289 | //! 290 | //! let _ = Struct { 291 | //! x: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, 292 | //! y: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, //<- trailing comma if the expression wrapped 293 | //! }; 294 | //! ``` 295 | //! 296 | //! The formatting of `match` expressions is another case; we want small arms on 297 | //! the same line as the pattern, and big arms wrapped in a brace. The presence 298 | //! of the brace punctuation, comma, and semicolon are all dependent on whether 299 | //! the arm fits on the line: 300 | //! 301 | //! ``` 302 | //! # struct Entry { nanos: u32 } 303 | //! # let total_nanos = 0u64; 304 | //! # let mut total_secs = 0u64; 305 | //! # let tmp; 306 | //! # let entry = Entry { nanos: 0 }; 307 | //! # const NANOS_PER_SEC: u32 = 1_000_000_000; 308 | //! # 309 | //! match total_nanos.checked_add(entry.nanos as u64) { 310 | //! Some(n) => tmp = n, //<- small arm, inline with comma 311 | //! None => { 312 | //! total_secs = total_secs 313 | //! .checked_add(total_nanos / NANOS_PER_SEC as u64) 314 | //! .expect("overflow in iter::sum over durations"); 315 | //! } //<- big arm, needs brace added, and also semicolon^ 316 | //! } 317 | //! ``` 318 | //! 319 | //! The printing algorithm implementation in this crate accommodates all of 320 | //! these situations with conditional punctuation tokens whose selection can be 321 | //! deferred and populated after it's known that the group is or is not broken. 322 | 323 | #![doc(html_root_url = "https://docs.rs/prettyplease/0.2.35")] 324 | #![allow( 325 | clippy::bool_to_int_with_if, 326 | clippy::cast_possible_wrap, 327 | clippy::cast_sign_loss, 328 | clippy::derive_partial_eq_without_eq, 329 | clippy::doc_markdown, 330 | clippy::enum_glob_use, 331 | clippy::items_after_statements, 332 | clippy::let_underscore_untyped, 333 | clippy::match_like_matches_macro, 334 | clippy::match_same_arms, 335 | clippy::module_name_repetitions, 336 | clippy::must_use_candidate, 337 | clippy::needless_pass_by_value, 338 | clippy::ref_option, 339 | clippy::similar_names, 340 | clippy::struct_excessive_bools, 341 | clippy::too_many_lines, 342 | clippy::unused_self, 343 | clippy::vec_init_then_push 344 | )] 345 | #![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))] 346 | 347 | mod algorithm; 348 | mod attr; 349 | mod classify; 350 | mod convenience; 351 | mod data; 352 | mod expr; 353 | mod file; 354 | mod fixup; 355 | mod generics; 356 | mod item; 357 | mod iter; 358 | mod lifetime; 359 | mod lit; 360 | mod mac; 361 | mod pat; 362 | mod path; 363 | mod precedence; 364 | mod ring; 365 | mod stmt; 366 | mod token; 367 | mod ty; 368 | 369 | use crate::algorithm::Printer; 370 | use syn::File; 371 | 372 | // Target line width. 373 | const MARGIN: isize = 89; 374 | 375 | // Number of spaces increment at each level of block indentation. 376 | const INDENT: isize = 4; 377 | 378 | // Every line is allowed at least this much space, even if highly indented. 379 | const MIN_SPACE: isize = 60; 380 | 381 | pub fn unparse(file: &File) -> String { 382 | let mut p = Printer::new(); 383 | p.file(file); 384 | p.eof() 385 | } 386 | -------------------------------------------------------------------------------- /src/lifetime.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use syn::Lifetime; 3 | 4 | impl Printer { 5 | pub fn lifetime(&mut self, lifetime: &Lifetime) { 6 | self.word("'"); 7 | self.ident(&lifetime.ident); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/lit.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use proc_macro2::Literal; 3 | use syn::{Lit, LitBool, LitByte, LitByteStr, LitCStr, LitChar, LitFloat, LitInt, LitStr}; 4 | 5 | impl Printer { 6 | pub fn lit(&mut self, lit: &Lit) { 7 | match lit { 8 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 9 | Lit::Str(lit) => self.lit_str(lit), 10 | Lit::ByteStr(lit) => self.lit_byte_str(lit), 11 | Lit::CStr(lit) => self.lit_c_str(lit), 12 | Lit::Byte(lit) => self.lit_byte(lit), 13 | Lit::Char(lit) => self.lit_char(lit), 14 | Lit::Int(lit) => self.lit_int(lit), 15 | Lit::Float(lit) => self.lit_float(lit), 16 | Lit::Bool(lit) => self.lit_bool(lit), 17 | Lit::Verbatim(lit) => self.lit_verbatim(lit), 18 | _ => unimplemented!("unknown Lit"), 19 | } 20 | } 21 | 22 | pub fn lit_str(&mut self, lit: &LitStr) { 23 | self.word(lit.token().to_string()); 24 | } 25 | 26 | fn lit_byte_str(&mut self, lit: &LitByteStr) { 27 | self.word(lit.token().to_string()); 28 | } 29 | 30 | fn lit_c_str(&mut self, lit: &LitCStr) { 31 | self.word(lit.token().to_string()); 32 | } 33 | 34 | fn lit_byte(&mut self, lit: &LitByte) { 35 | self.word(lit.token().to_string()); 36 | } 37 | 38 | fn lit_char(&mut self, lit: &LitChar) { 39 | self.word(lit.token().to_string()); 40 | } 41 | 42 | fn lit_int(&mut self, lit: &LitInt) { 43 | self.word(lit.token().to_string()); 44 | } 45 | 46 | fn lit_float(&mut self, lit: &LitFloat) { 47 | self.word(lit.token().to_string()); 48 | } 49 | 50 | fn lit_bool(&mut self, lit: &LitBool) { 51 | self.word(if lit.value { "true" } else { "false" }); 52 | } 53 | 54 | fn lit_verbatim(&mut self, token: &Literal) { 55 | self.word(token.to_string()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/pat.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::fixup::FixupContext; 3 | use crate::iter::IterDelimited; 4 | use crate::path::PathKind; 5 | use crate::INDENT; 6 | use proc_macro2::TokenStream; 7 | use syn::{ 8 | FieldPat, Pat, PatIdent, PatOr, PatParen, PatReference, PatRest, PatSlice, PatStruct, PatTuple, 9 | PatTupleStruct, PatType, PatWild, 10 | }; 11 | 12 | impl Printer { 13 | pub fn pat(&mut self, pat: &Pat) { 14 | match pat { 15 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 16 | Pat::Const(pat) => self.expr_const(pat), 17 | Pat::Ident(pat) => self.pat_ident(pat), 18 | Pat::Lit(pat) => self.expr_lit(pat), 19 | Pat::Macro(pat) => self.expr_macro(pat), 20 | Pat::Or(pat) => self.pat_or(pat), 21 | Pat::Paren(pat) => self.pat_paren(pat), 22 | Pat::Path(pat) => self.expr_path(pat), 23 | Pat::Range(pat) => self.expr_range(pat, FixupContext::NONE), 24 | Pat::Reference(pat) => self.pat_reference(pat), 25 | Pat::Rest(pat) => self.pat_rest(pat), 26 | Pat::Slice(pat) => self.pat_slice(pat), 27 | Pat::Struct(pat) => self.pat_struct(pat), 28 | Pat::Tuple(pat) => self.pat_tuple(pat), 29 | Pat::TupleStruct(pat) => self.pat_tuple_struct(pat), 30 | Pat::Type(pat) => self.pat_type(pat), 31 | Pat::Verbatim(pat) => self.pat_verbatim(pat), 32 | Pat::Wild(pat) => self.pat_wild(pat), 33 | _ => unimplemented!("unknown Pat"), 34 | } 35 | } 36 | 37 | fn pat_ident(&mut self, pat: &PatIdent) { 38 | self.outer_attrs(&pat.attrs); 39 | if pat.by_ref.is_some() { 40 | self.word("ref "); 41 | } 42 | if pat.mutability.is_some() { 43 | self.word("mut "); 44 | } 45 | self.ident(&pat.ident); 46 | if let Some((_at_token, subpat)) = &pat.subpat { 47 | self.word(" @ "); 48 | self.pat(subpat); 49 | } 50 | } 51 | 52 | fn pat_or(&mut self, pat: &PatOr) { 53 | self.outer_attrs(&pat.attrs); 54 | let mut consistent_break = false; 55 | for case in &pat.cases { 56 | match case { 57 | Pat::Lit(_) | Pat::Wild(_) => {} 58 | _ => { 59 | consistent_break = true; 60 | break; 61 | } 62 | } 63 | } 64 | if consistent_break { 65 | self.cbox(0); 66 | } else { 67 | self.ibox(0); 68 | } 69 | for case in pat.cases.iter().delimited() { 70 | if !case.is_first { 71 | self.space(); 72 | self.word("| "); 73 | } 74 | self.pat(&case); 75 | } 76 | self.end(); 77 | } 78 | 79 | fn pat_paren(&mut self, pat: &PatParen) { 80 | self.outer_attrs(&pat.attrs); 81 | self.word("("); 82 | self.pat(&pat.pat); 83 | self.word(")"); 84 | } 85 | 86 | fn pat_reference(&mut self, pat: &PatReference) { 87 | self.outer_attrs(&pat.attrs); 88 | self.word("&"); 89 | if pat.mutability.is_some() { 90 | self.word("mut "); 91 | } 92 | self.pat(&pat.pat); 93 | } 94 | 95 | fn pat_rest(&mut self, pat: &PatRest) { 96 | self.outer_attrs(&pat.attrs); 97 | self.word(".."); 98 | } 99 | 100 | fn pat_slice(&mut self, pat: &PatSlice) { 101 | self.outer_attrs(&pat.attrs); 102 | self.word("["); 103 | for elem in pat.elems.iter().delimited() { 104 | self.pat(&elem); 105 | self.trailing_comma(elem.is_last); 106 | } 107 | self.word("]"); 108 | } 109 | 110 | fn pat_struct(&mut self, pat: &PatStruct) { 111 | self.outer_attrs(&pat.attrs); 112 | self.cbox(INDENT); 113 | self.path(&pat.path, PathKind::Expr); 114 | self.word(" {"); 115 | self.space_if_nonempty(); 116 | for field in pat.fields.iter().delimited() { 117 | self.field_pat(&field); 118 | self.trailing_comma_or_space(field.is_last && pat.rest.is_none()); 119 | } 120 | if let Some(rest) = &pat.rest { 121 | self.pat_rest(rest); 122 | self.space(); 123 | } 124 | self.offset(-INDENT); 125 | self.end(); 126 | self.word("}"); 127 | } 128 | 129 | fn pat_tuple(&mut self, pat: &PatTuple) { 130 | self.outer_attrs(&pat.attrs); 131 | self.word("("); 132 | self.cbox(INDENT); 133 | self.zerobreak(); 134 | for elem in pat.elems.iter().delimited() { 135 | self.pat(&elem); 136 | if pat.elems.len() == 1 { 137 | if pat.elems.trailing_punct() { 138 | self.word(","); 139 | } 140 | self.zerobreak(); 141 | } else { 142 | self.trailing_comma(elem.is_last); 143 | } 144 | } 145 | self.offset(-INDENT); 146 | self.end(); 147 | self.word(")"); 148 | } 149 | 150 | fn pat_tuple_struct(&mut self, pat: &PatTupleStruct) { 151 | self.outer_attrs(&pat.attrs); 152 | self.path(&pat.path, PathKind::Expr); 153 | self.word("("); 154 | self.cbox(INDENT); 155 | self.zerobreak(); 156 | for elem in pat.elems.iter().delimited() { 157 | self.pat(&elem); 158 | self.trailing_comma(elem.is_last); 159 | } 160 | self.offset(-INDENT); 161 | self.end(); 162 | self.word(")"); 163 | } 164 | 165 | pub fn pat_type(&mut self, pat: &PatType) { 166 | self.outer_attrs(&pat.attrs); 167 | self.pat(&pat.pat); 168 | self.word(": "); 169 | self.ty(&pat.ty); 170 | } 171 | 172 | #[cfg(not(feature = "verbatim"))] 173 | fn pat_verbatim(&mut self, pat: &TokenStream) { 174 | unimplemented!("Pat::Verbatim `{}`", pat); 175 | } 176 | 177 | #[cfg(feature = "verbatim")] 178 | fn pat_verbatim(&mut self, tokens: &TokenStream) { 179 | use syn::parse::{Parse, ParseStream, Result}; 180 | use syn::{braced, Attribute, Block, Token}; 181 | 182 | enum PatVerbatim { 183 | Ellipsis, 184 | Box(Pat), 185 | Const(PatConst), 186 | } 187 | 188 | struct PatConst { 189 | attrs: Vec, 190 | block: Block, 191 | } 192 | 193 | impl Parse for PatVerbatim { 194 | fn parse(input: ParseStream) -> Result { 195 | let lookahead = input.lookahead1(); 196 | if lookahead.peek(Token![box]) { 197 | input.parse::()?; 198 | let inner = Pat::parse_single(input)?; 199 | Ok(PatVerbatim::Box(inner)) 200 | } else if lookahead.peek(Token![const]) { 201 | input.parse::()?; 202 | let content; 203 | let brace_token = braced!(content in input); 204 | let attrs = content.call(Attribute::parse_inner)?; 205 | let stmts = content.call(Block::parse_within)?; 206 | Ok(PatVerbatim::Const(PatConst { 207 | attrs, 208 | block: Block { brace_token, stmts }, 209 | })) 210 | } else if lookahead.peek(Token![...]) { 211 | input.parse::()?; 212 | Ok(PatVerbatim::Ellipsis) 213 | } else { 214 | Err(lookahead.error()) 215 | } 216 | } 217 | } 218 | 219 | let pat: PatVerbatim = match syn::parse2(tokens.clone()) { 220 | Ok(pat) => pat, 221 | Err(_) => unimplemented!("Pat::Verbatim `{}`", tokens), 222 | }; 223 | 224 | match pat { 225 | PatVerbatim::Ellipsis => { 226 | self.word("..."); 227 | } 228 | PatVerbatim::Box(pat) => { 229 | self.word("box "); 230 | self.pat(&pat); 231 | } 232 | PatVerbatim::Const(pat) => { 233 | self.word("const "); 234 | self.cbox(INDENT); 235 | self.small_block(&pat.block, &pat.attrs); 236 | self.end(); 237 | } 238 | } 239 | } 240 | 241 | fn pat_wild(&mut self, pat: &PatWild) { 242 | self.outer_attrs(&pat.attrs); 243 | self.word("_"); 244 | } 245 | 246 | fn field_pat(&mut self, field_pat: &FieldPat) { 247 | self.outer_attrs(&field_pat.attrs); 248 | if field_pat.colon_token.is_some() { 249 | self.member(&field_pat.member); 250 | self.word(": "); 251 | } 252 | self.pat(&field_pat.pat); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::iter::IterDelimited; 3 | use crate::INDENT; 4 | use std::ptr; 5 | use syn::{ 6 | AngleBracketedGenericArguments, AssocConst, AssocType, Constraint, GenericArgument, 7 | ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf, 8 | }; 9 | 10 | #[derive(Copy, Clone, PartialEq)] 11 | pub enum PathKind { 12 | // a::B 13 | Simple, 14 | // a::B 15 | Type, 16 | // a::B:: 17 | Expr, 18 | } 19 | 20 | impl Printer { 21 | pub fn path(&mut self, path: &Path, kind: PathKind) { 22 | assert!(!path.segments.is_empty()); 23 | for segment in path.segments.iter().delimited() { 24 | if !segment.is_first || path.leading_colon.is_some() { 25 | self.word("::"); 26 | } 27 | self.path_segment(&segment, kind); 28 | } 29 | } 30 | 31 | pub fn path_segment(&mut self, segment: &PathSegment, kind: PathKind) { 32 | self.ident(&segment.ident); 33 | self.path_arguments(&segment.arguments, kind); 34 | } 35 | 36 | fn path_arguments(&mut self, arguments: &PathArguments, kind: PathKind) { 37 | match arguments { 38 | PathArguments::None => {} 39 | PathArguments::AngleBracketed(arguments) => { 40 | self.angle_bracketed_generic_arguments(arguments, kind); 41 | } 42 | PathArguments::Parenthesized(arguments) => { 43 | self.parenthesized_generic_arguments(arguments); 44 | } 45 | } 46 | } 47 | 48 | fn generic_argument(&mut self, arg: &GenericArgument) { 49 | match arg { 50 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 51 | GenericArgument::Lifetime(lifetime) => self.lifetime(lifetime), 52 | GenericArgument::Type(ty) => self.ty(ty), 53 | GenericArgument::Const(expr) => self.const_argument(expr), 54 | GenericArgument::AssocType(assoc) => self.assoc_type(assoc), 55 | GenericArgument::AssocConst(assoc) => self.assoc_const(assoc), 56 | GenericArgument::Constraint(constraint) => self.constraint(constraint), 57 | _ => unimplemented!("unknown GenericArgument"), 58 | } 59 | } 60 | 61 | pub fn angle_bracketed_generic_arguments( 62 | &mut self, 63 | generic: &AngleBracketedGenericArguments, 64 | path_kind: PathKind, 65 | ) { 66 | if generic.args.is_empty() || path_kind == PathKind::Simple { 67 | return; 68 | } 69 | 70 | if path_kind == PathKind::Expr { 71 | self.word("::"); 72 | } 73 | self.word("<"); 74 | self.cbox(INDENT); 75 | self.zerobreak(); 76 | 77 | // Print lifetimes before types/consts/bindings, regardless of their 78 | // order in self.args. 79 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 80 | enum Group { 81 | First, 82 | Second, 83 | } 84 | fn group(arg: &GenericArgument) -> Group { 85 | match arg { 86 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 87 | GenericArgument::Lifetime(_) => Group::First, 88 | GenericArgument::Type(_) 89 | | GenericArgument::Const(_) 90 | | GenericArgument::AssocType(_) 91 | | GenericArgument::AssocConst(_) 92 | | GenericArgument::Constraint(_) => Group::Second, 93 | _ => Group::Second, 94 | } 95 | } 96 | let last = generic.args.iter().max_by_key(|param| group(param)); 97 | for current_group in [Group::First, Group::Second] { 98 | for arg in &generic.args { 99 | if group(arg) == current_group { 100 | self.generic_argument(arg); 101 | self.trailing_comma(ptr::eq(arg, last.unwrap())); 102 | } 103 | } 104 | } 105 | 106 | self.offset(-INDENT); 107 | self.end(); 108 | self.word(">"); 109 | } 110 | 111 | fn assoc_type(&mut self, assoc: &AssocType) { 112 | self.ident(&assoc.ident); 113 | if let Some(generics) = &assoc.generics { 114 | self.angle_bracketed_generic_arguments(generics, PathKind::Type); 115 | } 116 | self.word(" = "); 117 | self.ty(&assoc.ty); 118 | } 119 | 120 | fn assoc_const(&mut self, assoc: &AssocConst) { 121 | self.ident(&assoc.ident); 122 | if let Some(generics) = &assoc.generics { 123 | self.angle_bracketed_generic_arguments(generics, PathKind::Type); 124 | } 125 | self.word(" = "); 126 | self.const_argument(&assoc.value); 127 | } 128 | 129 | fn constraint(&mut self, constraint: &Constraint) { 130 | self.ident(&constraint.ident); 131 | if let Some(generics) = &constraint.generics { 132 | self.angle_bracketed_generic_arguments(generics, PathKind::Type); 133 | } 134 | self.ibox(INDENT); 135 | for bound in constraint.bounds.iter().delimited() { 136 | if bound.is_first { 137 | self.word(": "); 138 | } else { 139 | self.space(); 140 | self.word("+ "); 141 | } 142 | self.type_param_bound(&bound); 143 | } 144 | self.end(); 145 | } 146 | 147 | fn parenthesized_generic_arguments(&mut self, arguments: &ParenthesizedGenericArguments) { 148 | self.cbox(INDENT); 149 | self.word("("); 150 | self.zerobreak(); 151 | for ty in arguments.inputs.iter().delimited() { 152 | self.ty(&ty); 153 | self.trailing_comma(ty.is_last); 154 | } 155 | self.offset(-INDENT); 156 | self.word(")"); 157 | self.return_type(&arguments.output); 158 | self.end(); 159 | } 160 | 161 | pub fn qpath(&mut self, qself: &Option, path: &Path, kind: PathKind) { 162 | let qself = if let Some(qself) = qself { 163 | qself 164 | } else { 165 | self.path(path, kind); 166 | return; 167 | }; 168 | 169 | assert!(qself.position < path.segments.len()); 170 | 171 | self.word("<"); 172 | self.ty(&qself.ty); 173 | 174 | let mut segments = path.segments.iter(); 175 | if qself.position > 0 { 176 | self.word(" as "); 177 | for segment in segments.by_ref().take(qself.position).delimited() { 178 | if !segment.is_first || path.leading_colon.is_some() { 179 | self.word("::"); 180 | } 181 | self.path_segment(&segment, PathKind::Type); 182 | if segment.is_last { 183 | self.word(">"); 184 | } 185 | } 186 | } else { 187 | self.word(">"); 188 | } 189 | for segment in segments { 190 | self.word("::"); 191 | self.path_segment(segment, kind); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/precedence.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | AttrStyle, Attribute, BinOp, Expr, ExprArray, ExprAsync, ExprAwait, ExprBlock, ExprBreak, 3 | ExprCall, ExprConst, ExprContinue, ExprField, ExprForLoop, ExprIf, ExprIndex, ExprInfer, 4 | ExprLit, ExprLoop, ExprMacro, ExprMatch, ExprMethodCall, ExprParen, ExprPath, ExprRepeat, 5 | ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprTuple, ExprUnsafe, ExprWhile, ExprYield, 6 | ReturnType, 7 | }; 8 | 9 | // Reference: https://doc.rust-lang.org/reference/expressions.html#expression-precedence 10 | #[derive(Copy, Clone, PartialEq, PartialOrd)] 11 | pub enum Precedence { 12 | // return, break, closures 13 | Jump, 14 | // = += -= *= /= %= &= |= ^= <<= >>= 15 | Assign, 16 | // .. ..= 17 | Range, 18 | // || 19 | Or, 20 | // && 21 | And, 22 | // let 23 | Let, 24 | // == != < > <= >= 25 | Compare, 26 | // | 27 | BitOr, 28 | // ^ 29 | BitXor, 30 | // & 31 | BitAnd, 32 | // << >> 33 | Shift, 34 | // + - 35 | Sum, 36 | // * / % 37 | Product, 38 | // as 39 | Cast, 40 | // unary - * ! & &mut 41 | Prefix, 42 | // paths, loops, function calls, array indexing, field expressions, method calls 43 | Unambiguous, 44 | } 45 | 46 | impl Precedence { 47 | pub(crate) const MIN: Self = Precedence::Jump; 48 | 49 | pub(crate) fn of_binop(op: &BinOp) -> Self { 50 | match op { 51 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 52 | BinOp::Add(_) | BinOp::Sub(_) => Precedence::Sum, 53 | BinOp::Mul(_) | BinOp::Div(_) | BinOp::Rem(_) => Precedence::Product, 54 | BinOp::And(_) => Precedence::And, 55 | BinOp::Or(_) => Precedence::Or, 56 | BinOp::BitXor(_) => Precedence::BitXor, 57 | BinOp::BitAnd(_) => Precedence::BitAnd, 58 | BinOp::BitOr(_) => Precedence::BitOr, 59 | BinOp::Shl(_) | BinOp::Shr(_) => Precedence::Shift, 60 | 61 | BinOp::Eq(_) 62 | | BinOp::Lt(_) 63 | | BinOp::Le(_) 64 | | BinOp::Ne(_) 65 | | BinOp::Ge(_) 66 | | BinOp::Gt(_) => Precedence::Compare, 67 | 68 | BinOp::AddAssign(_) 69 | | BinOp::SubAssign(_) 70 | | BinOp::MulAssign(_) 71 | | BinOp::DivAssign(_) 72 | | BinOp::RemAssign(_) 73 | | BinOp::BitXorAssign(_) 74 | | BinOp::BitAndAssign(_) 75 | | BinOp::BitOrAssign(_) 76 | | BinOp::ShlAssign(_) 77 | | BinOp::ShrAssign(_) => Precedence::Assign, 78 | 79 | _ => Precedence::MIN, 80 | } 81 | } 82 | 83 | pub(crate) fn of(e: &Expr) -> Self { 84 | fn prefix_attrs(attrs: &[Attribute]) -> Precedence { 85 | for attr in attrs { 86 | if let AttrStyle::Outer = attr.style { 87 | return Precedence::Prefix; 88 | } 89 | } 90 | Precedence::Unambiguous 91 | } 92 | 93 | match e { 94 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 95 | Expr::Closure(e) => match e.output { 96 | ReturnType::Default => Precedence::Jump, 97 | ReturnType::Type(..) => prefix_attrs(&e.attrs), 98 | }, 99 | 100 | Expr::Break(ExprBreak { expr, .. }) 101 | | Expr::Return(ExprReturn { expr, .. }) 102 | | Expr::Yield(ExprYield { expr, .. }) => match expr { 103 | Some(_) => Precedence::Jump, 104 | None => Precedence::Unambiguous, 105 | }, 106 | 107 | Expr::Assign(_) => Precedence::Assign, 108 | Expr::Range(_) => Precedence::Range, 109 | Expr::Binary(e) => Precedence::of_binop(&e.op), 110 | Expr::Let(_) => Precedence::Let, 111 | Expr::Cast(_) => Precedence::Cast, 112 | Expr::RawAddr(_) | Expr::Reference(_) | Expr::Unary(_) => Precedence::Prefix, 113 | 114 | Expr::Array(ExprArray { attrs, .. }) 115 | | Expr::Async(ExprAsync { attrs, .. }) 116 | | Expr::Await(ExprAwait { attrs, .. }) 117 | | Expr::Block(ExprBlock { attrs, .. }) 118 | | Expr::Call(ExprCall { attrs, .. }) 119 | | Expr::Const(ExprConst { attrs, .. }) 120 | | Expr::Continue(ExprContinue { attrs, .. }) 121 | | Expr::Field(ExprField { attrs, .. }) 122 | | Expr::ForLoop(ExprForLoop { attrs, .. }) 123 | | Expr::If(ExprIf { attrs, .. }) 124 | | Expr::Index(ExprIndex { attrs, .. }) 125 | | Expr::Infer(ExprInfer { attrs, .. }) 126 | | Expr::Lit(ExprLit { attrs, .. }) 127 | | Expr::Loop(ExprLoop { attrs, .. }) 128 | | Expr::Macro(ExprMacro { attrs, .. }) 129 | | Expr::Match(ExprMatch { attrs, .. }) 130 | | Expr::MethodCall(ExprMethodCall { attrs, .. }) 131 | | Expr::Paren(ExprParen { attrs, .. }) 132 | | Expr::Path(ExprPath { attrs, .. }) 133 | | Expr::Repeat(ExprRepeat { attrs, .. }) 134 | | Expr::Struct(ExprStruct { attrs, .. }) 135 | | Expr::Try(ExprTry { attrs, .. }) 136 | | Expr::TryBlock(ExprTryBlock { attrs, .. }) 137 | | Expr::Tuple(ExprTuple { attrs, .. }) 138 | | Expr::Unsafe(ExprUnsafe { attrs, .. }) 139 | | Expr::While(ExprWhile { attrs, .. }) => prefix_attrs(attrs), 140 | 141 | Expr::Group(e) => Precedence::of(&e.expr), 142 | 143 | Expr::Verbatim(_) => Precedence::Unambiguous, 144 | 145 | _ => Precedence::Unambiguous, 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/ring.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::ops::{Index, IndexMut, Range}; 3 | 4 | pub struct RingBuffer { 5 | data: VecDeque, 6 | // Abstract index of data[0] in infinitely sized queue 7 | offset: usize, 8 | } 9 | 10 | impl RingBuffer { 11 | pub fn new() -> Self { 12 | RingBuffer { 13 | data: VecDeque::new(), 14 | offset: 0, 15 | } 16 | } 17 | 18 | pub fn is_empty(&self) -> bool { 19 | self.data.is_empty() 20 | } 21 | 22 | pub fn len(&self) -> usize { 23 | self.data.len() 24 | } 25 | 26 | pub fn push(&mut self, value: T) -> usize { 27 | let index = self.offset + self.data.len(); 28 | self.data.push_back(value); 29 | index 30 | } 31 | 32 | pub fn clear(&mut self) { 33 | self.data.clear(); 34 | } 35 | 36 | pub fn index_range(&self) -> Range { 37 | self.offset..self.offset + self.data.len() 38 | } 39 | 40 | pub fn first(&self) -> &T { 41 | &self.data[0] 42 | } 43 | 44 | pub fn first_mut(&mut self) -> &mut T { 45 | &mut self.data[0] 46 | } 47 | 48 | pub fn pop_first(&mut self) -> T { 49 | self.offset += 1; 50 | self.data.pop_front().unwrap() 51 | } 52 | 53 | pub fn last(&self) -> &T { 54 | self.data.back().unwrap() 55 | } 56 | 57 | pub fn last_mut(&mut self) -> &mut T { 58 | self.data.back_mut().unwrap() 59 | } 60 | 61 | pub fn second_last(&self) -> &T { 62 | &self.data[self.data.len() - 2] 63 | } 64 | 65 | pub fn pop_last(&mut self) { 66 | self.data.pop_back().unwrap(); 67 | } 68 | } 69 | 70 | impl Index for RingBuffer { 71 | type Output = T; 72 | fn index(&self, index: usize) -> &Self::Output { 73 | &self.data[index.checked_sub(self.offset).unwrap()] 74 | } 75 | } 76 | 77 | impl IndexMut for RingBuffer { 78 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 79 | &mut self.data[index.checked_sub(self.offset).unwrap()] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/stmt.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::classify; 3 | use crate::expr; 4 | use crate::fixup::FixupContext; 5 | use crate::mac; 6 | use crate::INDENT; 7 | use syn::{BinOp, Expr, Stmt}; 8 | 9 | impl Printer { 10 | pub fn stmt(&mut self, stmt: &Stmt, is_last: bool) { 11 | match stmt { 12 | Stmt::Local(local) => { 13 | self.outer_attrs(&local.attrs); 14 | self.ibox(0); 15 | self.word("let "); 16 | self.pat(&local.pat); 17 | if let Some(local_init) = &local.init { 18 | self.word(" = "); 19 | self.neverbreak(); 20 | self.subexpr( 21 | &local_init.expr, 22 | local_init.diverge.is_some() 23 | && classify::expr_trailing_brace(&local_init.expr), 24 | FixupContext::NONE, 25 | ); 26 | if let Some((_else, diverge)) = &local_init.diverge { 27 | self.space(); 28 | self.word("else "); 29 | self.end(); 30 | self.neverbreak(); 31 | self.cbox(INDENT); 32 | if let Some(expr) = expr::simple_block(diverge) { 33 | self.small_block(&expr.block, &[]); 34 | } else { 35 | self.expr_as_small_block(diverge, INDENT); 36 | } 37 | } 38 | } 39 | self.end(); 40 | self.word(";"); 41 | self.hardbreak(); 42 | } 43 | Stmt::Item(item) => self.item(item), 44 | Stmt::Expr(expr, None) => { 45 | if break_after(expr) { 46 | self.ibox(0); 47 | self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt()); 48 | if add_semi(expr) { 49 | self.word(";"); 50 | } 51 | self.end(); 52 | self.hardbreak(); 53 | } else { 54 | self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt()); 55 | } 56 | } 57 | Stmt::Expr(expr, Some(_semi)) => { 58 | if let Expr::Verbatim(tokens) = expr { 59 | if tokens.is_empty() { 60 | return; 61 | } 62 | } 63 | self.ibox(0); 64 | self.expr_beginning_of_line(expr, false, true, FixupContext::new_stmt()); 65 | if !remove_semi(expr) { 66 | self.word(";"); 67 | } 68 | self.end(); 69 | self.hardbreak(); 70 | } 71 | Stmt::Macro(stmt) => { 72 | self.outer_attrs(&stmt.attrs); 73 | let semicolon = stmt.semi_token.is_some() 74 | || !is_last && mac::requires_semi(&stmt.mac.delimiter); 75 | self.mac(&stmt.mac, None, semicolon); 76 | self.hardbreak(); 77 | } 78 | } 79 | } 80 | } 81 | 82 | pub fn add_semi(expr: &Expr) -> bool { 83 | match expr { 84 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 85 | Expr::Assign(_) | Expr::Break(_) | Expr::Continue(_) | Expr::Return(_) | Expr::Yield(_) => { 86 | true 87 | } 88 | Expr::Binary(expr) => 89 | { 90 | match expr.op { 91 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 92 | BinOp::AddAssign(_) 93 | | BinOp::SubAssign(_) 94 | | BinOp::MulAssign(_) 95 | | BinOp::DivAssign(_) 96 | | BinOp::RemAssign(_) 97 | | BinOp::BitXorAssign(_) 98 | | BinOp::BitAndAssign(_) 99 | | BinOp::BitOrAssign(_) 100 | | BinOp::ShlAssign(_) 101 | | BinOp::ShrAssign(_) => true, 102 | BinOp::Add(_) 103 | | BinOp::Sub(_) 104 | | BinOp::Mul(_) 105 | | BinOp::Div(_) 106 | | BinOp::Rem(_) 107 | | BinOp::And(_) 108 | | BinOp::Or(_) 109 | | BinOp::BitXor(_) 110 | | BinOp::BitAnd(_) 111 | | BinOp::BitOr(_) 112 | | BinOp::Shl(_) 113 | | BinOp::Shr(_) 114 | | BinOp::Eq(_) 115 | | BinOp::Lt(_) 116 | | BinOp::Le(_) 117 | | BinOp::Ne(_) 118 | | BinOp::Ge(_) 119 | | BinOp::Gt(_) => false, 120 | _ => unimplemented!("unknown BinOp"), 121 | } 122 | } 123 | Expr::Group(group) => add_semi(&group.expr), 124 | 125 | Expr::Array(_) 126 | | Expr::Async(_) 127 | | Expr::Await(_) 128 | | Expr::Block(_) 129 | | Expr::Call(_) 130 | | Expr::Cast(_) 131 | | Expr::Closure(_) 132 | | Expr::Const(_) 133 | | Expr::Field(_) 134 | | Expr::ForLoop(_) 135 | | Expr::If(_) 136 | | Expr::Index(_) 137 | | Expr::Infer(_) 138 | | Expr::Let(_) 139 | | Expr::Lit(_) 140 | | Expr::Loop(_) 141 | | Expr::Macro(_) 142 | | Expr::Match(_) 143 | | Expr::MethodCall(_) 144 | | Expr::Paren(_) 145 | | Expr::Path(_) 146 | | Expr::Range(_) 147 | | Expr::RawAddr(_) 148 | | Expr::Reference(_) 149 | | Expr::Repeat(_) 150 | | Expr::Struct(_) 151 | | Expr::Try(_) 152 | | Expr::TryBlock(_) 153 | | Expr::Tuple(_) 154 | | Expr::Unary(_) 155 | | Expr::Unsafe(_) 156 | | Expr::Verbatim(_) 157 | | Expr::While(_) => false, 158 | 159 | _ => false, 160 | } 161 | } 162 | 163 | pub fn break_after(expr: &Expr) -> bool { 164 | if let Expr::Group(group) = expr { 165 | if let Expr::Verbatim(verbatim) = group.expr.as_ref() { 166 | return !verbatim.is_empty(); 167 | } 168 | } 169 | true 170 | } 171 | 172 | fn remove_semi(expr: &Expr) -> bool { 173 | match expr { 174 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 175 | Expr::ForLoop(_) | Expr::While(_) => true, 176 | Expr::Group(group) => remove_semi(&group.expr), 177 | Expr::If(expr) => match &expr.else_branch { 178 | Some((_else_token, else_branch)) => remove_semi(else_branch), 179 | None => true, 180 | }, 181 | 182 | Expr::Array(_) 183 | | Expr::Assign(_) 184 | | Expr::Async(_) 185 | | Expr::Await(_) 186 | | Expr::Binary(_) 187 | | Expr::Block(_) 188 | | Expr::Break(_) 189 | | Expr::Call(_) 190 | | Expr::Cast(_) 191 | | Expr::Closure(_) 192 | | Expr::Continue(_) 193 | | Expr::Const(_) 194 | | Expr::Field(_) 195 | | Expr::Index(_) 196 | | Expr::Infer(_) 197 | | Expr::Let(_) 198 | | Expr::Lit(_) 199 | | Expr::Loop(_) 200 | | Expr::Macro(_) 201 | | Expr::Match(_) 202 | | Expr::MethodCall(_) 203 | | Expr::Paren(_) 204 | | Expr::Path(_) 205 | | Expr::Range(_) 206 | | Expr::RawAddr(_) 207 | | Expr::Reference(_) 208 | | Expr::Repeat(_) 209 | | Expr::Return(_) 210 | | Expr::Struct(_) 211 | | Expr::Try(_) 212 | | Expr::TryBlock(_) 213 | | Expr::Tuple(_) 214 | | Expr::Unary(_) 215 | | Expr::Unsafe(_) 216 | | Expr::Verbatim(_) 217 | | Expr::Yield(_) => false, 218 | 219 | _ => false, 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use proc_macro2::{Delimiter, Ident, Literal, Spacing, TokenStream, TokenTree}; 3 | 4 | impl Printer { 5 | pub fn single_token(&mut self, token: Token, group_contents: fn(&mut Self, TokenStream)) { 6 | match token { 7 | Token::Group(delimiter, stream) => self.token_group(delimiter, stream, group_contents), 8 | Token::Ident(ident) => self.ident(&ident), 9 | Token::Punct(ch, _spacing) => self.token_punct(ch), 10 | Token::Literal(literal) => self.token_literal(&literal), 11 | } 12 | } 13 | 14 | fn token_group( 15 | &mut self, 16 | delimiter: Delimiter, 17 | stream: TokenStream, 18 | group_contents: fn(&mut Self, TokenStream), 19 | ) { 20 | self.delimiter_open(delimiter); 21 | if !stream.is_empty() { 22 | if delimiter == Delimiter::Brace { 23 | self.space(); 24 | } 25 | group_contents(self, stream); 26 | if delimiter == Delimiter::Brace { 27 | self.space(); 28 | } 29 | } 30 | self.delimiter_close(delimiter); 31 | } 32 | 33 | pub fn ident(&mut self, ident: &Ident) { 34 | self.word(ident.to_string()); 35 | } 36 | 37 | pub fn token_punct(&mut self, ch: char) { 38 | self.word(ch.to_string()); 39 | } 40 | 41 | pub fn token_literal(&mut self, literal: &Literal) { 42 | self.word(literal.to_string()); 43 | } 44 | 45 | pub fn delimiter_open(&mut self, delimiter: Delimiter) { 46 | self.word(match delimiter { 47 | Delimiter::Parenthesis => "(", 48 | Delimiter::Brace => "{", 49 | Delimiter::Bracket => "[", 50 | Delimiter::None => return, 51 | }); 52 | } 53 | 54 | pub fn delimiter_close(&mut self, delimiter: Delimiter) { 55 | self.word(match delimiter { 56 | Delimiter::Parenthesis => ")", 57 | Delimiter::Brace => "}", 58 | Delimiter::Bracket => "]", 59 | Delimiter::None => return, 60 | }); 61 | } 62 | } 63 | 64 | pub enum Token { 65 | Group(Delimiter, TokenStream), 66 | Ident(Ident), 67 | Punct(char, Spacing), 68 | Literal(Literal), 69 | } 70 | 71 | impl From for Token { 72 | fn from(tt: TokenTree) -> Self { 73 | match tt { 74 | TokenTree::Group(group) => Token::Group(group.delimiter(), group.stream()), 75 | TokenTree::Ident(ident) => Token::Ident(ident), 76 | TokenTree::Punct(punct) => Token::Punct(punct.as_char(), punct.spacing()), 77 | TokenTree::Literal(literal) => Token::Literal(literal), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ty.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithm::Printer; 2 | use crate::fixup::FixupContext; 3 | use crate::iter::IterDelimited; 4 | use crate::path::PathKind; 5 | use crate::INDENT; 6 | use proc_macro2::TokenStream; 7 | use syn::{ 8 | Abi, BareFnArg, BareVariadic, ReturnType, Type, TypeArray, TypeBareFn, TypeGroup, 9 | TypeImplTrait, TypeInfer, TypeMacro, TypeNever, TypeParen, TypePath, TypePtr, TypeReference, 10 | TypeSlice, TypeTraitObject, TypeTuple, 11 | }; 12 | 13 | impl Printer { 14 | pub fn ty(&mut self, ty: &Type) { 15 | match ty { 16 | #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] 17 | Type::Array(ty) => self.type_array(ty), 18 | Type::BareFn(ty) => self.type_bare_fn(ty), 19 | Type::Group(ty) => self.type_group(ty), 20 | Type::ImplTrait(ty) => self.type_impl_trait(ty), 21 | Type::Infer(ty) => self.type_infer(ty), 22 | Type::Macro(ty) => self.type_macro(ty), 23 | Type::Never(ty) => self.type_never(ty), 24 | Type::Paren(ty) => self.type_paren(ty), 25 | Type::Path(ty) => self.type_path(ty), 26 | Type::Ptr(ty) => self.type_ptr(ty), 27 | Type::Reference(ty) => self.type_reference(ty), 28 | Type::Slice(ty) => self.type_slice(ty), 29 | Type::TraitObject(ty) => self.type_trait_object(ty), 30 | Type::Tuple(ty) => self.type_tuple(ty), 31 | Type::Verbatim(ty) => self.type_verbatim(ty), 32 | _ => unimplemented!("unknown Type"), 33 | } 34 | } 35 | 36 | fn type_array(&mut self, ty: &TypeArray) { 37 | self.word("["); 38 | self.ty(&ty.elem); 39 | self.word("; "); 40 | self.expr(&ty.len, FixupContext::NONE); 41 | self.word("]"); 42 | } 43 | 44 | fn type_bare_fn(&mut self, ty: &TypeBareFn) { 45 | if let Some(bound_lifetimes) = &ty.lifetimes { 46 | self.bound_lifetimes(bound_lifetimes); 47 | } 48 | if ty.unsafety.is_some() { 49 | self.word("unsafe "); 50 | } 51 | if let Some(abi) = &ty.abi { 52 | self.abi(abi); 53 | } 54 | self.word("fn("); 55 | self.cbox(INDENT); 56 | self.zerobreak(); 57 | for bare_fn_arg in ty.inputs.iter().delimited() { 58 | self.bare_fn_arg(&bare_fn_arg); 59 | self.trailing_comma(bare_fn_arg.is_last && ty.variadic.is_none()); 60 | } 61 | if let Some(variadic) = &ty.variadic { 62 | self.bare_variadic(variadic); 63 | self.zerobreak(); 64 | } 65 | self.offset(-INDENT); 66 | self.end(); 67 | self.word(")"); 68 | self.return_type(&ty.output); 69 | } 70 | 71 | fn type_group(&mut self, ty: &TypeGroup) { 72 | self.ty(&ty.elem); 73 | } 74 | 75 | fn type_impl_trait(&mut self, ty: &TypeImplTrait) { 76 | self.word("impl "); 77 | for type_param_bound in ty.bounds.iter().delimited() { 78 | if !type_param_bound.is_first { 79 | self.word(" + "); 80 | } 81 | self.type_param_bound(&type_param_bound); 82 | } 83 | } 84 | 85 | fn type_infer(&mut self, ty: &TypeInfer) { 86 | let _ = ty; 87 | self.word("_"); 88 | } 89 | 90 | fn type_macro(&mut self, ty: &TypeMacro) { 91 | let semicolon = false; 92 | self.mac(&ty.mac, None, semicolon); 93 | } 94 | 95 | fn type_never(&mut self, ty: &TypeNever) { 96 | let _ = ty; 97 | self.word("!"); 98 | } 99 | 100 | fn type_paren(&mut self, ty: &TypeParen) { 101 | self.word("("); 102 | self.ty(&ty.elem); 103 | self.word(")"); 104 | } 105 | 106 | fn type_path(&mut self, ty: &TypePath) { 107 | self.qpath(&ty.qself, &ty.path, PathKind::Type); 108 | } 109 | 110 | fn type_ptr(&mut self, ty: &TypePtr) { 111 | self.word("*"); 112 | if ty.mutability.is_some() { 113 | self.word("mut "); 114 | } else { 115 | self.word("const "); 116 | } 117 | self.ty(&ty.elem); 118 | } 119 | 120 | fn type_reference(&mut self, ty: &TypeReference) { 121 | self.word("&"); 122 | if let Some(lifetime) = &ty.lifetime { 123 | self.lifetime(lifetime); 124 | self.nbsp(); 125 | } 126 | if ty.mutability.is_some() { 127 | self.word("mut "); 128 | } 129 | self.ty(&ty.elem); 130 | } 131 | 132 | fn type_slice(&mut self, ty: &TypeSlice) { 133 | self.word("["); 134 | self.ty(&ty.elem); 135 | self.word("]"); 136 | } 137 | 138 | fn type_trait_object(&mut self, ty: &TypeTraitObject) { 139 | self.word("dyn "); 140 | for type_param_bound in ty.bounds.iter().delimited() { 141 | if !type_param_bound.is_first { 142 | self.word(" + "); 143 | } 144 | self.type_param_bound(&type_param_bound); 145 | } 146 | } 147 | 148 | fn type_tuple(&mut self, ty: &TypeTuple) { 149 | self.word("("); 150 | self.cbox(INDENT); 151 | self.zerobreak(); 152 | for elem in ty.elems.iter().delimited() { 153 | self.ty(&elem); 154 | if ty.elems.len() == 1 { 155 | self.word(","); 156 | self.zerobreak(); 157 | } else { 158 | self.trailing_comma(elem.is_last); 159 | } 160 | } 161 | self.offset(-INDENT); 162 | self.end(); 163 | self.word(")"); 164 | } 165 | 166 | #[cfg(not(feature = "verbatim"))] 167 | fn type_verbatim(&mut self, ty: &TokenStream) { 168 | unimplemented!("Type::Verbatim `{}`", ty); 169 | } 170 | 171 | #[cfg(feature = "verbatim")] 172 | fn type_verbatim(&mut self, tokens: &TokenStream) { 173 | use syn::parse::{Parse, ParseStream, Result}; 174 | use syn::punctuated::Punctuated; 175 | use syn::{token, FieldsNamed, Token, TypeParamBound}; 176 | 177 | enum TypeVerbatim { 178 | Ellipsis, 179 | AnonStruct(AnonStruct), 180 | AnonUnion(AnonUnion), 181 | DynStar(DynStar), 182 | MutSelf(MutSelf), 183 | NotType(NotType), 184 | } 185 | 186 | struct AnonStruct { 187 | fields: FieldsNamed, 188 | } 189 | 190 | struct AnonUnion { 191 | fields: FieldsNamed, 192 | } 193 | 194 | struct DynStar { 195 | bounds: Punctuated, 196 | } 197 | 198 | struct MutSelf { 199 | ty: Option, 200 | } 201 | 202 | struct NotType { 203 | inner: Type, 204 | } 205 | 206 | impl Parse for TypeVerbatim { 207 | fn parse(input: ParseStream) -> Result { 208 | let lookahead = input.lookahead1(); 209 | if lookahead.peek(Token![struct]) { 210 | input.parse::()?; 211 | let fields: FieldsNamed = input.parse()?; 212 | Ok(TypeVerbatim::AnonStruct(AnonStruct { fields })) 213 | } else if lookahead.peek(Token![union]) && input.peek2(token::Brace) { 214 | input.parse::()?; 215 | let fields: FieldsNamed = input.parse()?; 216 | Ok(TypeVerbatim::AnonUnion(AnonUnion { fields })) 217 | } else if lookahead.peek(Token![dyn]) { 218 | input.parse::()?; 219 | input.parse::()?; 220 | let bounds = input.parse_terminated(TypeParamBound::parse, Token![+])?; 221 | Ok(TypeVerbatim::DynStar(DynStar { bounds })) 222 | } else if lookahead.peek(Token![mut]) { 223 | input.parse::()?; 224 | input.parse::()?; 225 | let ty = if input.is_empty() { 226 | None 227 | } else { 228 | input.parse::()?; 229 | let ty: Type = input.parse()?; 230 | Some(ty) 231 | }; 232 | Ok(TypeVerbatim::MutSelf(MutSelf { ty })) 233 | } else if lookahead.peek(Token![!]) { 234 | input.parse::()?; 235 | let inner: Type = input.parse()?; 236 | Ok(TypeVerbatim::NotType(NotType { inner })) 237 | } else if lookahead.peek(Token![...]) { 238 | input.parse::()?; 239 | Ok(TypeVerbatim::Ellipsis) 240 | } else { 241 | Err(lookahead.error()) 242 | } 243 | } 244 | } 245 | 246 | let ty: TypeVerbatim = match syn::parse2(tokens.clone()) { 247 | Ok(ty) => ty, 248 | Err(_) => unimplemented!("Type::Verbatim `{}`", tokens), 249 | }; 250 | 251 | match ty { 252 | TypeVerbatim::Ellipsis => { 253 | self.word("..."); 254 | } 255 | TypeVerbatim::AnonStruct(ty) => { 256 | self.cbox(INDENT); 257 | self.word("struct {"); 258 | self.hardbreak_if_nonempty(); 259 | for field in &ty.fields.named { 260 | self.field(field); 261 | self.word(","); 262 | self.hardbreak(); 263 | } 264 | self.offset(-INDENT); 265 | self.end(); 266 | self.word("}"); 267 | } 268 | TypeVerbatim::AnonUnion(ty) => { 269 | self.cbox(INDENT); 270 | self.word("union {"); 271 | self.hardbreak_if_nonempty(); 272 | for field in &ty.fields.named { 273 | self.field(field); 274 | self.word(","); 275 | self.hardbreak(); 276 | } 277 | self.offset(-INDENT); 278 | self.end(); 279 | self.word("}"); 280 | } 281 | TypeVerbatim::DynStar(ty) => { 282 | self.word("dyn* "); 283 | for type_param_bound in ty.bounds.iter().delimited() { 284 | if !type_param_bound.is_first { 285 | self.word(" + "); 286 | } 287 | self.type_param_bound(&type_param_bound); 288 | } 289 | } 290 | TypeVerbatim::MutSelf(bare_fn_arg) => { 291 | self.word("mut self"); 292 | if let Some(ty) = &bare_fn_arg.ty { 293 | self.word(": "); 294 | self.ty(ty); 295 | } 296 | } 297 | TypeVerbatim::NotType(ty) => { 298 | self.word("!"); 299 | self.ty(&ty.inner); 300 | } 301 | } 302 | } 303 | 304 | pub fn return_type(&mut self, ty: &ReturnType) { 305 | match ty { 306 | ReturnType::Default => {} 307 | ReturnType::Type(_arrow, ty) => { 308 | self.word(" -> "); 309 | self.ty(ty); 310 | } 311 | } 312 | } 313 | 314 | fn bare_fn_arg(&mut self, bare_fn_arg: &BareFnArg) { 315 | self.outer_attrs(&bare_fn_arg.attrs); 316 | if let Some((name, _colon)) = &bare_fn_arg.name { 317 | self.ident(name); 318 | self.word(": "); 319 | } 320 | self.ty(&bare_fn_arg.ty); 321 | } 322 | 323 | fn bare_variadic(&mut self, variadic: &BareVariadic) { 324 | self.outer_attrs(&variadic.attrs); 325 | if let Some((name, _colon)) = &variadic.name { 326 | self.ident(name); 327 | self.word(": "); 328 | } 329 | self.word("..."); 330 | } 331 | 332 | pub fn abi(&mut self, abi: &Abi) { 333 | self.word("extern "); 334 | if let Some(name) = &abi.name { 335 | self.lit_str(name); 336 | self.nbsp(); 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use indoc::indoc; 2 | use proc_macro2::{Delimiter, Group, TokenStream}; 3 | use quote::quote; 4 | 5 | #[track_caller] 6 | fn test(tokens: TokenStream, expected: &str) { 7 | let syntax_tree: syn::File = syn::parse2(tokens).unwrap(); 8 | let pretty = prettyplease::unparse(&syntax_tree); 9 | assert_eq!(pretty, expected); 10 | } 11 | 12 | #[test] 13 | fn test_parenthesize_cond() { 14 | let s = Group::new(Delimiter::None, quote!(Struct {})); 15 | test( 16 | quote! { 17 | fn main() { 18 | if #s == #s {} 19 | } 20 | }, 21 | indoc! {" 22 | fn main() { 23 | if (Struct {}) == (Struct {}) {} 24 | } 25 | "}, 26 | ); 27 | } 28 | 29 | #[test] 30 | fn test_parenthesize_match_guard() { 31 | let expr_struct = Group::new(Delimiter::None, quote!(Struct {})); 32 | let expr_binary = Group::new(Delimiter::None, quote!(true && false)); 33 | test( 34 | quote! { 35 | fn main() { 36 | match () { 37 | () if let _ = #expr_struct => {} 38 | () if let _ = #expr_binary => {} 39 | } 40 | } 41 | }, 42 | indoc! {" 43 | fn main() { 44 | match () { 45 | () if let _ = Struct {} => {} 46 | () if let _ = (true && false) => {} 47 | } 48 | } 49 | "}, 50 | ); 51 | } 52 | --------------------------------------------------------------------------------