From 1984b9b4c0badc0c53b17f97635a12a684cf6958 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 4 May 2023 17:24:36 +0200 Subject: Import prettyplease crate Bug: 274187738 Test: m Merged-In: I36d5fa8300f210d8489e79c483859769b4bca31c Change-Id: I36d5fa8300f210d8489e79c483859769b4bca31c (cherry picked from commit dde97c6839f9cfb46e1d2774d51c177bf92e5780) --- .cargo_vcs_info.json | 6 + .gitattributes | 2 + .github/FUNDING.yml | 1 + .github/workflows/ci.yml | 74 +++ .gitignore | 2 + Android.bp | 20 + Cargo.toml | 47 ++ LICENSE | 1 + LICENSE-APACHE | 176 ++++++ LICENSE-MIT | 23 + METADATA | 20 + MODULE_LICENSE_APACHE2 | 0 OWNERS | 1 + README.md | 312 ++++++++++ build.rs | 5 + cargo2android.json | 5 + examples/.tokeignore | 1 + examples/input.rs | 1 + examples/output.prettyplease.rs | 593 +++++++++++++++++++ examples/output.rustc.rs | 508 +++++++++++++++++ examples/output.rustfmt.rs | 552 ++++++++++++++++++ src/algorithm.rs | 376 ++++++++++++ src/attr.rs | 273 +++++++++ src/convenience.rs | 98 ++++ src/data.rs | 95 +++ src/expr.rs | 1205 +++++++++++++++++++++++++++++++++++++++ src/file.rs | 17 + src/generics.rs | 286 ++++++++++ src/item.rs | 836 +++++++++++++++++++++++++++ src/iter.rs | 46 ++ src/lib.rs | 379 ++++++++++++ src/lifetime.rs | 9 + src/lit.rs | 50 ++ src/mac.rs | 220 +++++++ src/pat.rs | 210 +++++++ src/path.rs | 174 ++++++ src/ring.rs | 81 +++ src/stmt.rs | 85 +++ src/token.rs | 80 +++ src/ty.rs | 241 ++++++++ 40 files changed, 7111 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Android.bp create mode 100644 Cargo.toml create mode 120000 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 METADATA create mode 100644 MODULE_LICENSE_APACHE2 create mode 100644 OWNERS create mode 100644 README.md create mode 100644 build.rs create mode 100644 cargo2android.json create mode 100644 examples/.tokeignore create mode 100644 examples/input.rs create mode 100644 examples/output.prettyplease.rs create mode 100644 examples/output.rustc.rs create mode 100644 examples/output.rustfmt.rs create mode 100644 src/algorithm.rs create mode 100644 src/attr.rs create mode 100644 src/convenience.rs create mode 100644 src/data.rs create mode 100644 src/expr.rs create mode 100644 src/file.rs create mode 100644 src/generics.rs create mode 100644 src/item.rs create mode 100644 src/iter.rs create mode 100644 src/lib.rs create mode 100644 src/lifetime.rs create mode 100644 src/lit.rs create mode 100644 src/mac.rs create mode 100644 src/pat.rs create mode 100644 src/path.rs create mode 100644 src/ring.rs create mode 100644 src/stmt.rs create mode 100644 src/token.rs create mode 100644 src/ty.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..3e35b72 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "81780e0ebdb1f8df7c36b39a02e606e60581dce9" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..567c199 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +cargo-expand/*.rs linguist-generated +examples/*.rs linguist-generated diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7507077 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: dtolnay diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ca27e6a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + schedule: [cron: "40 1 * * *"] + +permissions: + contents: read + +env: + RUSTFLAGS: -Dwarnings + +jobs: + pre_ci: + uses: dtolnay/.github/.github/workflows/pre_ci.yml@master + + test: + name: Rust ${{matrix.rust}} + needs: pre_ci + if: needs.pre_ci.outputs.continue + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + rust: [nightly, beta, stable, 1.56.0] + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{matrix.rust}} + - run: cargo check + - run: cargo check --features verbatim + - run: cargo test + env: + RUSTFLAGS: ${{env.RUSTFLAGS}} ${{matrix.rust == 'nightly' && '--cfg exhaustive' || ''}} + + examples: + name: Examples + needs: pre_ci + if: needs.pre_ci.outputs.continue + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + with: + components: llvm-tools, rustc-dev, rustfmt + - run: cargo run --manifest-path examples/update/Cargo.toml + - run: git diff --exit-code + - run: cargo run --manifest-path cargo-expand/update/Cargo.toml + - run: git diff --exit-code + + clippy: + name: Clippy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@clippy + - run: cargo clippy --features verbatim -- -Dclippy::all -Dclippy::pedantic + + outdated: + name: Outdated + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/install@cargo-outdated + - run: cargo outdated --workspace --exit-code 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..3e7383d --- /dev/null +++ b/Android.bp @@ -0,0 +1,20 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library_host { + name: "libprettyplease", + crate_name: "prettyplease", + cargo_env_compat: true, + cargo_pkg_version: "0.1.25", + srcs: ["src/lib.rs"], + edition: "2021", + rustlibs: [ + "libproc_macro2", + "libsyn", + ], + compile_multilib: "first", + product_available: true, + vendor_available: true, +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e77b675 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,47 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.56" +name = "prettyplease" +version = "0.1.25" +authors = ["David Tolnay "] +links = "prettyplease01" +exclude = ["cargo-expand"] +autoexamples = false +description = "A minimal `syn` syntax tree pretty-printer" +documentation = "https://docs.rs/prettyplease" +readme = "README.md" +keywords = ["rustfmt"] +categories = ["development-tools"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/prettyplease" + +[lib] +doc-scrape-examples = false + +[dependencies.proc-macro2] +version = "1.0" +default-features = false + +[dependencies.syn] +version = "1.0.90" +features = ["full"] +default-features = false + +[dev-dependencies.syn] +version = "1.0.90" +features = ["parsing"] +default-features = false + +[features] +verbatim = ["syn/parsing"] diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..6b579aa --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +LICENSE-APACHE \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..b433848 --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "prettyplease" +description: "A minimal `syn` syntax tree pretty-printer" +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/prettyplease" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/prettyplease/prettyplease-0.1.25.crate" + } + version: "0.1.25" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 3 + day: 22 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..45dc4dd --- /dev/null +++ b/OWNERS @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..5695972 --- /dev/null +++ b/README.md @@ -0,0 +1,312 @@ +prettyplease::unparse +===================== + +[github](https://github.com/dtolnay/prettyplease) +[crates.io](https://crates.io/crates/prettyplease) +[docs.rs](https://docs.rs/prettyplease) +[build status](https://github.com/dtolnay/prettyplease/actions?query=branch%3Amaster) + +A minimal `syn` syntax tree pretty-printer. + +
+ +## Overview + +This is a pretty-printer to turn a `syn` syntax tree into a `String` of +well-formatted source code. In contrast to rustfmt, this library is intended to +be suitable for arbitrary generated code. + +Rustfmt prioritizes high-quality output that is impeccable enough that you'd be +comfortable spending your career staring at its output — but that means +some heavyweight algorithms, and it has a tendency to bail out on code that is +hard to format (for example [rustfmt#3697], and there are dozens more issues +like it). That's not necessarily a big deal for human-generated code because +when code gets highly nested, the human will naturally be inclined to refactor +into more easily formattable code. But for generated code, having the formatter +just give up leaves it totally unreadable. + +[rustfmt#3697]: https://github.com/rust-lang/rustfmt/issues/3697 + +This library is designed using the simplest possible algorithm and data +structures that can deliver about 95% of the quality of rustfmt-formatted +output. In my experience testing real-world code, approximately 97-98% of output +lines come out identical between rustfmt's formatting and this crate's. The rest +have slightly different linebreak decisions, but still clearly follow the +dominant modern Rust style. + +The tradeoffs made by this crate are a good fit for generated code that you will +*not* spend your career staring at. For example, the output of `bindgen`, or the +output of `cargo-expand`. In those cases it's more important that the whole +thing be formattable without the formatter giving up, than that it be flawless. + +
+ +## Feature matrix + +Here are a few superficial comparisons of this crate against the AST +pretty-printer built into rustc, and rustfmt. The sections below go into more +detail comparing the output of each of these libraries. + +| | prettyplease | rustc | rustfmt | +|:---|:---:|:---:|:---:| +| non-pathological behavior on big or generated code | 💚 | ❌ | ❌ | +| idiomatic modern formatting ("locally indistinguishable from rustfmt") | 💚 | ❌ | 💚 | +| throughput | 60 MB/s | 39 MB/s | 2.8 MB/s | +| number of dependencies | 3 | 72 | 66 | +| compile time including dependencies | 2.4 sec | 23.1 sec | 29.8 sec | +| buildable using a stable Rust compiler | 💚 | ❌ | ❌ | +| published to crates.io | 💚 | ❌ | ❌ | +| extensively configurable output | ❌ | ❌ | 💚 | +| intended to accommodate hand-maintained source code | ❌ | ❌ | 💚 | + +
+ +## Comparison to rustfmt + +- [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) +- [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) +- [output.rustfmt.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustfmt.rs) + +If you weren't told which output file is which, it would be practically +impossible to tell — **except** for line 435 in the rustfmt output, which +is more than 1000 characters long because rustfmt just gave up formatting that +part of the file: + +```rust + 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[..]; +``` + +This is a pretty typical manifestation of rustfmt bailing out in generated code +— a chunk of the input ends up on one line. The other manifestation is +that you're working on some code, running rustfmt on save like a conscientious +developer, but after a while notice it isn't doing anything. You introduce an +intentional formatting issue, like a stray indent or semicolon, and run rustfmt +to check your suspicion. Nope, it doesn't get cleaned up — rustfmt is just +not formatting the part of the file you are working on. + +The prettyplease library is designed to have no pathological cases that force a +bail out; the entire input you give it will get formatted in some "good enough" +form. + +Separately, rustfmt can be problematic to integrate into projects. It's written +using rustc's internal syntax tree, so it can't be built by a stable compiler. +Its releases are not regularly published to crates.io, so in Cargo builds you'd +need to depend on it as a git dependency, which precludes publishing your crate +to crates.io also. You can shell out to a `rustfmt` binary, but that'll be +whatever rustfmt version is installed on each developer's system (if any), which +can lead to spurious diffs in checked-in generated code formatted by different +versions. In contrast prettyplease is designed to be easy to pull in as a +library, and compiles fast. + +
+ +## Comparison to rustc_ast_pretty + +- [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) +- [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) +- [output.rustc.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustc.rs) + +This is the pretty-printer that gets used when rustc prints source code, such as +`rustc -Zunpretty=expanded`. It's used also by the standard library's +`stringify!` when stringifying an interpolated macro_rules AST fragment, like an +$:expr, and transitively by `dbg!` and many macros in the ecosystem. + +Rustc's formatting is mostly okay, but does not hew closely to the dominant +contemporary style of Rust formatting. Some things wouldn't ever be written on +one line, like this `match` expression, and certainly not with a comma in front +of the closing brace: + +```rust +fn eq(&self, other: &IpAddr) -> bool { + match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } +} +``` + +Some places use non-multiple-of-4 indentation, which is definitely not the norm: + +```rust +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],},} +} +``` + +And although there isn't an egregious example of it in the link because the +input code is pretty tame, in general rustc_ast_pretty has pathological behavior +on generated code. It has a tendency to use excessive horizontal indentation and +rapidly run out of width: + +```rust +::std::io::_print(::core::fmt::Arguments::new_v1(&[""], + &match (&msg,) { + _args => + [::core::fmt::ArgumentV1::new(_args.0, + ::core::fmt::Display::fmt)], + })); +``` + +The snippets above are clearly different from modern rustfmt style. In contrast, +prettyplease is designed to have output that is practically indistinguishable +from rustfmt-formatted code. + +
+ +## Example + +```rust +// [dependencies] +// prettyplease = "0.1" +// syn = { version = "1", default-features = false, features = ["full", "parsing"] } + +const INPUT: &str = stringify! { + use crate::{ + lazy::{Lazy, SyncLazy, SyncOnceCell}, panic, + sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, + mpsc::channel, Mutex, }, + thread, + }; + impl Into for T where U: From { + fn into(self) -> U { U::from(self) } + } +}; + +fn main() { + let syntax_tree = syn::parse_file(INPUT).unwrap(); + let formatted = prettyplease::unparse(&syntax_tree); + print!("{}", formatted); +} +``` + +
+ +## Algorithm notes + +The approach and terminology used in the implementation are derived from [*Derek +C. Oppen, "Pretty Printing" (1979)*][paper], on which rustc_ast_pretty is also +based, and from rustc_ast_pretty's implementation written by Graydon Hoare in +2011 (and modernized over the years by dozens of volunteer maintainers). + +[paper]: http://i.stanford.edu/pub/cstr/reports/cs/tr/79/770/CS-TR-79-770.pdf + +The paper describes two language-agnostic interacting procedures `Scan()` and +`Print()`. Language-specific code decomposes an input data structure into a +stream of `string` and `break` tokens, and `begin` and `end` tokens for +grouping. Each `begin`–`end` range may be identified as either "consistent +breaking" or "inconsistent breaking". If a group is consistently breaking, then +if the whole contents do not fit on the line, *every* `break` token in the group +will receive a linebreak. This is appropriate, for example, for Rust struct +literals, or arguments of a function call. If a group is inconsistently +breaking, then the `string` tokens in the group are greedily placed on the line +until out of space, and linebroken only at those `break` tokens for which the +next string would not fit. For example, this is appropriate for the contents of +a braced `use` statement in Rust. + +Scan's job is to efficiently accumulate sizing information about groups and +breaks. For every `begin` token we compute the distance to the matched `end` +token, and for every `break` we compute the distance to the next `break`. The +algorithm uses a ringbuffer to hold tokens whose size is not yet ascertained. +The maximum size of the ringbuffer is bounded by the target line length and does +not grow indefinitely, regardless of deep nesting in the input stream. That's +because once a group is sufficiently big, the precise size can no longer make a +difference to linebreak decisions and we can effectively treat it as "infinity". + +Print's job is to use the sizing information to efficiently assign a "broken" or +"not broken" status to every `begin` token. At that point the output is easily +constructed by concatenating `string` tokens and breaking at `break` tokens +contained within a broken group. + +Leveraging these primitives (i.e. cleverly placing the all-or-nothing consistent +breaks and greedy inconsistent breaks) to yield rustfmt-compatible formatting +for all of Rust's syntax tree nodes is a fun challenge. + +Here is a visualization of some Rust tokens fed into the pretty printing +algorithm. Consistently breaking `begin`—`end` pairs are represented by +`«`⁠`»`, inconsistently breaking by `‹`⁠`›`, `break` by `·`, and the +rest of the non-whitespace are `string`. + +```text +use crate::«{· +‹ lazy::«{·‹Lazy,· SyncLazy,· SyncOnceCell›·}»,· + panic,· + sync::«{· +‹ atomic::«{·‹AtomicUsize,· Ordering::SeqCst›·}»,· + mpsc::channel,· Mutex›,· + }»,· + thread›,· +}»;· +«‹«impl<«·T‹›,· U‹›·»>» Into<«·U·»>· for T›· +where· + U:‹ From<«·T·»>›,· +{· +« fn into(·«·self·») -> U {· +‹ U::from(«·self·»)›· +» }· +»}· +``` + +The algorithm described in the paper is not quite sufficient for producing +well-formatted Rust code that is locally indistinguishable from rustfmt's style. +The reason is that in the paper, the complete non-whitespace contents are +assumed to be independent of linebreak decisions, with Scan and Print being only +in control of the whitespace (spaces and line breaks). In Rust as idiomatically +formattted by rustfmt, that is not the case. Trailing commas are one example; +the punctuation is only known *after* the broken vs non-broken status of the +surrounding group is known: + +```rust +let _ = Struct { x: 0, y: true }; + +let _ = Struct { + x: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + y: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, //<- trailing comma if the expression wrapped +}; +``` + +The formatting of `match` expressions is another case; we want small arms on the +same line as the pattern, and big arms wrapped in a brace. The presence of the +brace punctuation, comma, and semicolon are all dependent on whether the arm +fits on the line: + +```rust +match total_nanos.checked_add(entry.nanos as u64) { + Some(n) => tmp = n, //<- small arm, inline with comma + None => { + total_secs = total_secs + .checked_add(total_nanos / NANOS_PER_SEC as u64) + .expect("overflow in iter::sum over durations"); + } //<- big arm, needs brace added, and also semicolon^ +} +``` + +The printing algorithm implementation in this crate accommodates all of these +situations with conditional punctuation tokens whose selection can be deferred +and populated after it's known that the group is or is not broken. + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0d798ed --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + println!(concat!("cargo:VERSION=", env!("CARGO_PKG_VERSION"))); +} diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..c4040f0 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,5 @@ +{ + "host-first-multilib": true, + "run": true, + "tests": true +} diff --git a/examples/.tokeignore b/examples/.tokeignore new file mode 100644 index 0000000..6f5f3d1 --- /dev/null +++ b/examples/.tokeignore @@ -0,0 +1 @@ +*.rs diff --git a/examples/input.rs b/examples/input.rs new file mode 100644 index 0000000..ca3d980 --- /dev/null +++ b/examples/input.rs @@ -0,0 +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)) } } diff --git a/examples/output.prettyplease.rs b/examples/output.prettyplease.rs new file mode 100644 index 0000000..45b65d0 --- /dev/null +++ b/examples/output.prettyplease.rs @@ -0,0 +1,593 @@ +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 for IpAddr { + fn from(ipv4: Ipv4Addr) -> IpAddr { + IpAddr::V4(ipv4) + } +} +impl From 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 for IpAddr { + fn eq(&self, other: &Ipv4Addr) -> bool { + match self { + IpAddr::V4(v4) => v4 == other, + IpAddr::V6(_) => false, + } + } +} +impl PartialEq 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(&self, s: &mut H) { + { self.inner.s_addr }.hash(s) + } +} +impl PartialOrd for Ipv4Addr { + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for IpAddr { + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + match self { + IpAddr::V4(v4) => v4.partial_cmp(other), + IpAddr::V6(_) => Some(Ordering::Greater), + } + } +} +impl PartialOrd for Ipv4Addr { + fn partial_cmp(&self, other: &IpAddr) -> Option { + 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 for Ipv4Addr { + fn into_inner(self) -> c::in_addr { + self.inner + } +} +impl From for u32 { + fn from(ip: Ipv4Addr) -> u32 { + let ip = ip.octets(); + u32::from_be_bytes(ip) + } +} +impl From 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 { + 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 { + 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 { + 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 + }; + /// 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 for Ipv6Addr { + fn eq(&self, other: &IpAddr) -> bool { + match other { + IpAddr::V4(_) => false, + IpAddr::V6(v6) => self == v6, + } + } +} +impl PartialEq 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(&self, s: &mut H) { + self.inner.s6_addr.hash(s) + } +} +impl PartialOrd for Ipv6Addr { + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for IpAddr { + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + match self { + IpAddr::V4(_) => Some(Ordering::Less), + IpAddr::V6(v6) => v6.partial_cmp(other), + } + } +} +impl PartialOrd for Ipv6Addr { + fn partial_cmp(&self, other: &IpAddr) -> Option { + 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 for Ipv6Addr { + fn as_inner(&self) -> &c::in6_addr { + &self.inner + } +} +impl FromInner for Ipv6Addr { + fn from_inner(addr: c::in6_addr) -> Ipv6Addr { + Ipv6Addr { inner: addr } + } +} +impl From for u128 { + fn from(ip: Ipv6Addr) -> u128 { + let ip = ip.octets(); + u128::from_be_bytes(ip) + } +} +impl From 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)) + } +} diff --git a/examples/output.rustc.rs b/examples/output.rustc.rs new file mode 100644 index 0000000..609a8c3 --- /dev/null +++ b/examples/output.rustc.rs @@ -0,0 +1,508 @@ +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 for IpAddr { + fn from(ipv4: Ipv4Addr) -> IpAddr { IpAddr::V4(ipv4) } +} +impl From 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 for IpAddr { + fn eq(&self, other: &Ipv4Addr) -> bool { + match self { IpAddr::V4(v4) => v4 == other, IpAddr::V6(_) => false, } + } +} +impl PartialEq 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(&self, s: &mut H) { + { self.inner.s_addr }.hash(s) + } +} +impl PartialOrd for Ipv4Addr { + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for IpAddr { + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + match self { + IpAddr::V4(v4) => v4.partial_cmp(other), + IpAddr::V6(_) => Some(Ordering::Greater), + } + } +} +impl PartialOrd for Ipv4Addr { + fn partial_cmp(&self, other: &IpAddr) -> Option { + 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 for Ipv4Addr { + fn into_inner(self) -> c::in_addr { self.inner } +} +impl From for u32 { + fn from(ip: Ipv4Addr) -> u32 { + let ip = ip.octets(); + u32::from_be_bytes(ip) + } +} +impl From 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 { + 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 { + 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 { + 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 for Ipv6Addr { + fn eq(&self, other: &IpAddr) -> bool { + match other { IpAddr::V4(_) => false, IpAddr::V6(v6) => self == v6, } + } +} +impl PartialEq 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(&self, s: &mut H) { self.inner.s6_addr.hash(s) } +} +impl PartialOrd for Ipv6Addr { + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for IpAddr { + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + match self { + IpAddr::V4(_) => Some(Ordering::Less), + IpAddr::V6(v6) => v6.partial_cmp(other), + } + } +} +impl PartialOrd for Ipv6Addr { + fn partial_cmp(&self, other: &IpAddr) -> Option { + 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 for Ipv6Addr { + fn as_inner(&self) -> &c::in6_addr { &self.inner } +} +impl FromInner for Ipv6Addr { + fn from_inner(addr: c::in6_addr) -> Ipv6Addr { Ipv6Addr { inner: addr } } +} +impl From for u128 { + fn from(ip: Ipv6Addr) -> u128 { + let ip = ip.octets(); + u128::from_be_bytes(ip) + } +} +impl From 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)) + } +} diff --git a/examples/output.rustfmt.rs b/examples/output.rustfmt.rs new file mode 100644 index 0000000..3c7181d --- /dev/null +++ b/examples/output.rustfmt.rs @@ -0,0 +1,552 @@ +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 for IpAddr { + fn from(ipv4: Ipv4Addr) -> IpAddr { + IpAddr::V4(ipv4) + } +} +impl From 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 for IpAddr { + fn eq(&self, other: &Ipv4Addr) -> bool { + match self { + IpAddr::V4(v4) => v4 == other, + IpAddr::V6(_) => false, + } + } +} +impl PartialEq 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(&self, s: &mut H) { + { self.inner.s_addr }.hash(s) + } +} +impl PartialOrd for Ipv4Addr { + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for IpAddr { + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + match self { + IpAddr::V4(v4) => v4.partial_cmp(other), + IpAddr::V6(_) => Some(Ordering::Greater), + } + } +} +impl PartialOrd for Ipv4Addr { + fn partial_cmp(&self, other: &IpAddr) -> Option { + 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 for Ipv4Addr { + fn into_inner(self) -> c::in_addr { + self.inner + } +} +impl From for u32 { + fn from(ip: Ipv4Addr) -> u32 { + let ip = ip.octets(); + u32::from_be_bytes(ip) + } +} +impl From 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 { + 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 { + 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 { + 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 for Ipv6Addr { + fn eq(&self, other: &IpAddr) -> bool { + match other { + IpAddr::V4(_) => false, + IpAddr::V6(v6) => self == v6, + } + } +} +impl PartialEq 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(&self, s: &mut H) { + self.inner.s6_addr.hash(s) + } +} +impl PartialOrd for Ipv6Addr { + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for IpAddr { + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + match self { + IpAddr::V4(_) => Some(Ordering::Less), + IpAddr::V6(v6) => v6.partial_cmp(other), + } + } +} +impl PartialOrd for Ipv6Addr { + fn partial_cmp(&self, other: &IpAddr) -> Option { + 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 for Ipv6Addr { + fn as_inner(&self) -> &c::in6_addr { + &self.inner + } +} +impl FromInner for Ipv6Addr { + fn from_inner(addr: c::in6_addr) -> Ipv6Addr { + Ipv6Addr { inner: addr } + } +} +impl From for u128 { + fn from(ip: Ipv6Addr) -> u128 { + let ip = ip.octets(); + u128::from_be_bytes(ip) + } +} +impl From 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)) + } +} diff --git a/src/algorithm.rs b/src/algorithm.rs new file mode 100644 index 0000000..b6dea57 --- /dev/null +++ b/src/algorithm.rs @@ -0,0 +1,376 @@ +// Adapted from https://github.com/rust-lang/rust/blob/1.57.0/compiler/rustc_ast_pretty/src/pp.rs. +// See "Algorithm notes" in the crate-level rustdoc. + +use crate::ring::RingBuffer; +use crate::{MARGIN, MIN_SPACE}; +use std::borrow::Cow; +use std::cmp; +use std::collections::VecDeque; +use std::iter; + +#[derive(Clone, Copy, PartialEq)] +pub enum Breaks { + Consistent, + Inconsistent, +} + +#[derive(Clone, Copy, Default)] +pub struct BreakToken { + pub offset: isize, + pub blank_space: usize, + pub pre_break: Option, + pub post_break: Option, + pub no_break: Option, + pub if_nonempty: bool, + pub never_break: bool, +} + +#[derive(Clone, Copy)] +pub struct BeginToken { + pub offset: isize, + pub breaks: Breaks, +} + +#[derive(Clone)] +pub enum Token { + String(Cow<'static, str>), + Break(BreakToken), + Begin(BeginToken), + End, +} + +#[derive(Copy, Clone)] +enum PrintFrame { + Fits(Breaks), + Broken(usize, Breaks), +} + +pub const SIZE_INFINITY: isize = 0xffff; + +pub struct Printer { + out: String, + // Number of spaces left on line + space: isize, + // Ring-buffer of tokens and calculated sizes + buf: RingBuffer, + // Total size of tokens already printed + left_total: isize, + // Total size of tokens enqueued, including printed and not yet printed + right_total: isize, + // Holds the ring-buffer index of the Begin that started the current block, + // possibly with the most recent Break after that Begin (if there is any) on + // top of it. Values are pushed and popped on the back of the queue using it + // like stack, and elsewhere old values are popped from the front of the + // queue as they become irrelevant due to the primary ring-buffer advancing. + scan_stack: VecDeque, + // Stack of blocks-in-progress being flushed by print + print_stack: Vec, + // Level of indentation of current line + indent: usize, + // Buffered indentation to avoid writing trailing whitespace + pending_indentation: usize, +} + +#[derive(Clone)] +struct BufEntry { + token: Token, + size: isize, +} + +impl Printer { + pub fn new() -> Self { + Printer { + out: String::new(), + space: MARGIN, + buf: RingBuffer::new(), + left_total: 0, + right_total: 0, + scan_stack: VecDeque::new(), + print_stack: Vec::new(), + indent: 0, + pending_indentation: 0, + } + } + + pub fn eof(mut self) -> String { + if !self.scan_stack.is_empty() { + self.check_stack(0); + self.advance_left(); + } + self.out + } + + pub fn scan_begin(&mut self, token: BeginToken) { + if self.scan_stack.is_empty() { + self.left_total = 1; + self.right_total = 1; + self.buf.clear(); + } + let right = self.buf.push(BufEntry { + token: Token::Begin(token), + size: -self.right_total, + }); + self.scan_stack.push_back(right); + } + + pub fn scan_end(&mut self) { + if self.scan_stack.is_empty() { + self.print_end(); + } else { + if !self.buf.is_empty() { + if let Token::Break(break_token) = self.buf.last().token { + if self.buf.len() >= 2 { + if let Token::Begin(_) = self.buf.second_last().token { + self.buf.pop_last(); + self.buf.pop_last(); + self.scan_stack.pop_back(); + self.scan_stack.pop_back(); + self.right_total -= break_token.blank_space as isize; + return; + } + } + if break_token.if_nonempty { + self.buf.pop_last(); + self.scan_stack.pop_back(); + self.right_total -= break_token.blank_space as isize; + } + } + } + let right = self.buf.push(BufEntry { + token: Token::End, + size: -1, + }); + self.scan_stack.push_back(right); + } + } + + pub fn scan_break(&mut self, token: BreakToken) { + if self.scan_stack.is_empty() { + self.left_total = 1; + self.right_total = 1; + self.buf.clear(); + } else { + self.check_stack(0); + } + let right = self.buf.push(BufEntry { + token: Token::Break(token), + size: -self.right_total, + }); + self.scan_stack.push_back(right); + self.right_total += token.blank_space as isize; + } + + pub fn scan_string(&mut self, string: Cow<'static, str>) { + if self.scan_stack.is_empty() { + self.print_string(string); + } else { + let len = string.len() as isize; + self.buf.push(BufEntry { + token: Token::String(string), + size: len, + }); + self.right_total += len; + self.check_stream(); + } + } + + pub fn offset(&mut self, offset: isize) { + match &mut self.buf.last_mut().token { + Token::Break(token) => token.offset += offset, + Token::Begin(_) => {} + Token::String(_) | Token::End => unreachable!(), + } + } + + pub fn end_with_max_width(&mut self, max: isize) { + let mut depth = 1; + for &index in self.scan_stack.iter().rev() { + let entry = &self.buf[index]; + match entry.token { + Token::Begin(_) => { + depth -= 1; + if depth == 0 { + if entry.size < 0 { + let actual_width = entry.size + self.right_total; + if actual_width > max { + self.buf.push(BufEntry { + token: Token::String(Cow::Borrowed("")), + size: SIZE_INFINITY, + }); + self.right_total += SIZE_INFINITY; + } + } + break; + } + } + Token::End => depth += 1, + Token::Break(_) => {} + Token::String(_) => unreachable!(), + } + } + self.scan_end(); + } + + fn check_stream(&mut self) { + while self.right_total - self.left_total > self.space { + if *self.scan_stack.front().unwrap() == self.buf.index_of_first() { + self.scan_stack.pop_front().unwrap(); + self.buf.first_mut().size = SIZE_INFINITY; + } + + self.advance_left(); + + if self.buf.is_empty() { + break; + } + } + } + + fn advance_left(&mut self) { + while self.buf.first().size >= 0 { + let left = self.buf.pop_first(); + + match left.token { + Token::String(string) => { + self.left_total += left.size; + self.print_string(string); + } + Token::Break(token) => { + self.left_total += token.blank_space as isize; + self.print_break(token, left.size); + } + Token::Begin(token) => self.print_begin(token, left.size), + Token::End => self.print_end(), + } + + if self.buf.is_empty() { + break; + } + } + } + + fn check_stack(&mut self, mut depth: usize) { + while let Some(&index) = self.scan_stack.back() { + let mut entry = &mut self.buf[index]; + match entry.token { + Token::Begin(_) => { + if depth == 0 { + break; + } + self.scan_stack.pop_back().unwrap(); + entry.size += self.right_total; + depth -= 1; + } + Token::End => { + self.scan_stack.pop_back().unwrap(); + entry.size = 1; + depth += 1; + } + Token::Break(_) => { + self.scan_stack.pop_back().unwrap(); + entry.size += self.right_total; + if depth == 0 { + break; + } + } + Token::String(_) => unreachable!(), + } + } + } + + fn get_top(&self) -> PrintFrame { + const OUTER: PrintFrame = PrintFrame::Broken(0, Breaks::Inconsistent); + self.print_stack.last().map_or(OUTER, PrintFrame::clone) + } + + fn print_begin(&mut self, token: BeginToken, size: isize) { + if cfg!(prettyplease_debug) { + self.out.push(match token.breaks { + Breaks::Consistent => '«', + Breaks::Inconsistent => '‹', + }); + if cfg!(prettyplease_debug_indent) { + self.out + .extend(token.offset.to_string().chars().map(|ch| match ch { + '0'..='9' => ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'] + [(ch as u8 - b'0') as usize], + '-' => '₋', + _ => unreachable!(), + })); + } + } + if size > self.space { + self.print_stack + .push(PrintFrame::Broken(self.indent, token.breaks)); + self.indent = usize::try_from(self.indent as isize + token.offset).unwrap(); + } else { + self.print_stack.push(PrintFrame::Fits(token.breaks)); + } + } + + fn print_end(&mut self) { + let breaks = match self.print_stack.pop().unwrap() { + PrintFrame::Broken(indent, breaks) => { + self.indent = indent; + breaks + } + PrintFrame::Fits(breaks) => breaks, + }; + if cfg!(prettyplease_debug) { + self.out.push(match breaks { + Breaks::Consistent => '»', + Breaks::Inconsistent => '›', + }); + } + } + + fn print_break(&mut self, token: BreakToken, size: isize) { + let fits = token.never_break + || match self.get_top() { + PrintFrame::Fits(..) => true, + PrintFrame::Broken(.., Breaks::Consistent) => false, + PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, + }; + if fits { + self.pending_indentation += token.blank_space; + self.space -= token.blank_space as isize; + if let Some(no_break) = token.no_break { + self.out.push(no_break); + self.space -= no_break.len_utf8() as isize; + } + if cfg!(prettyplease_debug) { + self.out.push('·'); + } + } else { + if let Some(pre_break) = token.pre_break { + self.print_indent(); + self.out.push(pre_break); + } + if cfg!(prettyplease_debug) { + self.out.push('·'); + } + self.out.push('\n'); + let indent = self.indent as isize + token.offset; + self.pending_indentation = usize::try_from(indent).unwrap(); + self.space = cmp::max(MARGIN - indent, MIN_SPACE); + if let Some(post_break) = token.post_break { + self.print_indent(); + self.out.push(post_break); + self.space -= post_break.len_utf8() as isize; + } + } + } + + fn print_string(&mut self, string: Cow<'static, str>) { + self.print_indent(); + self.out.push_str(&string); + self.space -= string.len() as isize; + } + + fn print_indent(&mut self) { + self.out.reserve(self.pending_indentation); + self.out + .extend(iter::repeat(' ').take(self.pending_indentation)); + self.pending_indentation = 0; + } +} diff --git a/src/attr.rs b/src/attr.rs new file mode 100644 index 0000000..cd36ce5 --- /dev/null +++ b/src/attr.rs @@ -0,0 +1,273 @@ +use crate::algorithm::Printer; +use crate::INDENT; +use proc_macro2::{Delimiter, TokenStream, TokenTree}; +use syn::{AttrStyle, Attribute, Lit, PathArguments}; + +impl Printer { + pub fn outer_attrs(&mut self, attrs: &[Attribute]) { + for attr in attrs { + if let AttrStyle::Outer = attr.style { + self.attr(attr); + } + } + } + + pub fn inner_attrs(&mut self, attrs: &[Attribute]) { + for attr in attrs { + if let AttrStyle::Inner(_) = attr.style { + self.attr(attr); + } + } + } + + fn attr(&mut self, attr: &Attribute) { + if let Some(mut doc) = value_of_attribute("doc", attr) { + if !doc.contains('\n') + && match attr.style { + AttrStyle::Outer => !doc.starts_with('/'), + AttrStyle::Inner(_) => true, + } + { + trim_trailing_spaces(&mut doc); + self.word(match attr.style { + AttrStyle::Outer => "///", + AttrStyle::Inner(_) => "//!", + }); + self.word(doc); + self.hardbreak(); + return; + } else if can_be_block_comment(&doc) + && match attr.style { + AttrStyle::Outer => !doc.starts_with(&['*', '/'][..]), + AttrStyle::Inner(_) => true, + } + { + trim_interior_trailing_spaces(&mut doc); + self.word(match attr.style { + AttrStyle::Outer => "/**", + AttrStyle::Inner(_) => "/*!", + }); + self.word(doc); + self.word("*/"); + self.hardbreak(); + return; + } + } else if let Some(mut comment) = value_of_attribute("comment", attr) { + if !comment.contains('\n') { + trim_trailing_spaces(&mut comment); + self.word("//"); + self.word(comment); + self.hardbreak(); + return; + } else if can_be_block_comment(&comment) && !comment.starts_with(&['*', '!'][..]) { + trim_interior_trailing_spaces(&mut comment); + self.word("/*"); + self.word(comment); + self.word("*/"); + self.hardbreak(); + return; + } + } + + self.word(match attr.style { + AttrStyle::Outer => "#", + AttrStyle::Inner(_) => "#!", + }); + self.word("["); + self.path(&attr.path); + self.attr_tokens(attr.tokens.clone()); + self.word("]"); + self.space(); + } + + fn attr_tokens(&mut self, tokens: TokenStream) { + let mut stack = Vec::new(); + stack.push((tokens.into_iter().peekable(), Delimiter::None)); + let mut space = Self::nbsp as fn(&mut Self); + + #[derive(PartialEq)] + enum State { + Word, + Punct, + TrailingComma, + } + + use State::*; + let mut state = Word; + + while let Some((tokens, delimiter)) = stack.last_mut() { + match tokens.next() { + Some(TokenTree::Ident(ident)) => { + if let Word = state { + space(self); + } + self.ident(&ident); + state = Word; + } + Some(TokenTree::Punct(punct)) => { + let ch = punct.as_char(); + if let (Word, '=') = (state, ch) { + self.nbsp(); + } + if ch == ',' && tokens.peek().is_none() { + self.trailing_comma(true); + state = TrailingComma; + } else { + self.token_punct(ch); + if ch == '=' { + self.nbsp(); + } else if ch == ',' { + space(self); + } + state = Punct; + } + } + Some(TokenTree::Literal(literal)) => { + if let Word = state { + space(self); + } + self.token_literal(&literal); + state = Word; + } + Some(TokenTree::Group(group)) => { + let delimiter = group.delimiter(); + let stream = group.stream(); + match delimiter { + Delimiter::Parenthesis => { + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + state = Punct; + } + Delimiter::Brace => { + self.word("{"); + state = Punct; + } + Delimiter::Bracket => { + self.word("["); + state = Punct; + } + Delimiter::None => {} + } + stack.push((stream.into_iter().peekable(), delimiter)); + space = Self::space; + } + None => { + match delimiter { + Delimiter::Parenthesis => { + if state != TrailingComma { + self.zerobreak(); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + state = Punct; + } + Delimiter::Brace => { + self.word("}"); + state = Punct; + } + Delimiter::Bracket => { + self.word("]"); + state = Punct; + } + Delimiter::None => {} + } + stack.pop(); + if stack.is_empty() { + space = Self::nbsp; + } + } + } + } + } +} + +fn value_of_attribute(requested: &str, attr: &Attribute) -> Option { + let is_doc = attr.path.leading_colon.is_none() + && attr.path.segments.len() == 1 + && matches!(attr.path.segments[0].arguments, PathArguments::None) + && attr.path.segments[0].ident == requested; + if !is_doc { + return None; + } + let mut tokens = attr.tokens.clone().into_iter(); + match tokens.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => {} + _ => return None, + } + let literal = match tokens.next() { + Some(TokenTree::Literal(literal)) => literal, + _ => return None, + }; + if tokens.next().is_some() { + return None; + } + match Lit::new(literal) { + Lit::Str(string) => Some(string.value()), + _ => None, + } +} + +pub fn has_outer(attrs: &[Attribute]) -> bool { + for attr in attrs { + if let AttrStyle::Outer = attr.style { + return true; + } + } + false +} + +pub fn has_inner(attrs: &[Attribute]) -> bool { + for attr in attrs { + if let AttrStyle::Inner(_) = attr.style { + return true; + } + } + false +} + +fn trim_trailing_spaces(doc: &mut String) { + doc.truncate(doc.trim_end_matches(' ').len()); +} + +fn trim_interior_trailing_spaces(doc: &mut String) { + if !doc.contains(" \n") { + return; + } + let mut trimmed = String::with_capacity(doc.len()); + let mut lines = doc.split('\n').peekable(); + while let Some(line) = lines.next() { + if lines.peek().is_some() { + trimmed.push_str(line.trim_end_matches(' ')); + trimmed.push('\n'); + } else { + trimmed.push_str(line); + } + } + *doc = trimmed; +} + +fn can_be_block_comment(value: &str) -> bool { + let mut depth = 0usize; + let bytes = value.as_bytes(); + let mut i = 0usize; + let upper = bytes.len() - 1; + + while i < upper { + if bytes[i] == b'/' && bytes[i + 1] == b'*' { + depth += 1; + i += 2; + } else if bytes[i] == b'*' && bytes[i + 1] == b'/' { + if depth == 0 { + return false; + } + depth -= 1; + i += 2; + } else { + i += 1; + } + } + + depth == 0 +} diff --git a/src/convenience.rs b/src/convenience.rs new file mode 100644 index 0000000..bc4add6 --- /dev/null +++ b/src/convenience.rs @@ -0,0 +1,98 @@ +use crate::algorithm::{self, BeginToken, BreakToken, Breaks, Printer}; +use std::borrow::Cow; + +impl Printer { + pub fn ibox(&mut self, indent: isize) { + self.scan_begin(BeginToken { + offset: indent, + breaks: Breaks::Inconsistent, + }); + } + + pub fn cbox(&mut self, indent: isize) { + self.scan_begin(BeginToken { + offset: indent, + breaks: Breaks::Consistent, + }); + } + + pub fn end(&mut self) { + self.scan_end(); + } + + pub fn word>>(&mut self, wrd: S) { + let s = wrd.into(); + self.scan_string(s); + } + + fn spaces(&mut self, n: usize) { + self.scan_break(BreakToken { + blank_space: n, + ..BreakToken::default() + }); + } + + pub fn zerobreak(&mut self) { + self.spaces(0); + } + + pub fn space(&mut self) { + self.spaces(1); + } + + pub fn nbsp(&mut self) { + self.word(" "); + } + + pub fn hardbreak(&mut self) { + self.spaces(algorithm::SIZE_INFINITY as usize); + } + + pub fn space_if_nonempty(&mut self) { + self.scan_break(BreakToken { + blank_space: 1, + if_nonempty: true, + ..BreakToken::default() + }); + } + + pub fn hardbreak_if_nonempty(&mut self) { + self.scan_break(BreakToken { + blank_space: algorithm::SIZE_INFINITY as usize, + if_nonempty: true, + ..BreakToken::default() + }); + } + + pub fn trailing_comma(&mut self, is_last: bool) { + if is_last { + self.scan_break(BreakToken { + pre_break: Some(','), + ..BreakToken::default() + }); + } else { + self.word(","); + self.space(); + } + } + + pub fn trailing_comma_or_space(&mut self, is_last: bool) { + if is_last { + self.scan_break(BreakToken { + blank_space: 1, + pre_break: Some(','), + ..BreakToken::default() + }); + } else { + self.word(","); + self.space(); + } + } + + pub fn neverbreak(&mut self) { + self.scan_break(BreakToken { + never_break: true, + ..BreakToken::default() + }); + } +} diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..7767981 --- /dev/null +++ b/src/data.rs @@ -0,0 +1,95 @@ +use crate::algorithm::Printer; +use crate::iter::IterDelimited; +use crate::INDENT; +use syn::{ + Field, Fields, FieldsUnnamed, PathArguments, Variant, VisCrate, VisPublic, VisRestricted, + Visibility, +}; + +impl Printer { + pub fn variant(&mut self, variant: &Variant) { + self.outer_attrs(&variant.attrs); + self.ident(&variant.ident); + match &variant.fields { + Fields::Named(fields) => { + self.nbsp(); + self.word("{"); + self.cbox(INDENT); + self.space(); + for field in fields.named.iter().delimited() { + self.field(&field); + self.trailing_comma_or_space(field.is_last); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + Fields::Unnamed(fields) => { + self.cbox(INDENT); + self.fields_unnamed(fields); + self.end(); + } + Fields::Unit => {} + } + if let Some((_eq_token, discriminant)) = &variant.discriminant { + self.word(" = "); + self.expr(discriminant); + } + } + + pub fn fields_unnamed(&mut self, fields: &FieldsUnnamed) { + self.word("("); + self.zerobreak(); + for field in fields.unnamed.iter().delimited() { + self.field(&field); + self.trailing_comma(field.is_last); + } + self.offset(-INDENT); + self.word(")"); + } + + pub fn field(&mut self, field: &Field) { + self.outer_attrs(&field.attrs); + self.visibility(&field.vis); + if let Some(ident) = &field.ident { + self.ident(ident); + self.word(": "); + } + self.ty(&field.ty); + } + + pub fn visibility(&mut self, vis: &Visibility) { + match vis { + Visibility::Public(vis) => self.vis_public(vis), + Visibility::Crate(vis) => self.vis_crate(vis), + Visibility::Restricted(vis) => self.vis_restricted(vis), + Visibility::Inherited => {} + } + } + + fn vis_public(&mut self, vis: &VisPublic) { + let _ = vis; + self.word("pub "); + } + + fn vis_crate(&mut self, vis: &VisCrate) { + let _ = vis; + self.word("crate "); + } + + fn vis_restricted(&mut self, vis: &VisRestricted) { + self.word("pub("); + let omit_in = vis.path.leading_colon.is_none() + && vis.path.segments.len() == 1 + && matches!(vis.path.segments[0].arguments, PathArguments::None) + && matches!( + vis.path.segments[0].ident.to_string().as_str(), + "self" | "super" | "crate", + ); + if !omit_in { + self.word("in "); + } + self.path(&vis.path); + self.word(") "); + } +} diff --git a/src/expr.rs b/src/expr.rs new file mode 100644 index 0000000..0689595 --- /dev/null +++ b/src/expr.rs @@ -0,0 +1,1205 @@ +use crate::algorithm::{BreakToken, Printer}; +use crate::attr; +use crate::iter::IterDelimited; +use crate::stmt; +use crate::INDENT; +use proc_macro2::TokenStream; +use syn::punctuated::Punctuated; +use syn::{ + token, Arm, Attribute, BinOp, Block, Expr, ExprArray, ExprAssign, ExprAssignOp, ExprAsync, + ExprAwait, ExprBinary, ExprBlock, ExprBox, ExprBreak, ExprCall, ExprCast, ExprClosure, + ExprContinue, ExprField, ExprForLoop, ExprGroup, ExprIf, ExprIndex, ExprLet, ExprLit, ExprLoop, + ExprMacro, ExprMatch, ExprMethodCall, ExprParen, ExprPath, ExprRange, ExprReference, + ExprRepeat, ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprTuple, ExprType, ExprUnary, + ExprUnsafe, ExprWhile, ExprYield, FieldValue, GenericMethodArgument, Index, Label, Member, + MethodTurbofish, PathArguments, QSelf, RangeLimits, ReturnType, Stmt, Token, UnOp, +}; + +impl Printer { + pub fn expr(&mut self, expr: &Expr) { + match expr { + Expr::Array(expr) => self.expr_array(expr), + Expr::Assign(expr) => self.expr_assign(expr), + Expr::AssignOp(expr) => self.expr_assign_op(expr), + Expr::Async(expr) => self.expr_async(expr), + Expr::Await(expr) => self.expr_await(expr, false), + Expr::Binary(expr) => self.expr_binary(expr), + Expr::Block(expr) => self.expr_block(expr), + Expr::Box(expr) => self.expr_box(expr), + Expr::Break(expr) => self.expr_break(expr), + Expr::Call(expr) => self.expr_call(expr, false), + Expr::Cast(expr) => self.expr_cast(expr), + Expr::Closure(expr) => self.expr_closure(expr), + Expr::Continue(expr) => self.expr_continue(expr), + Expr::Field(expr) => self.expr_field(expr, false), + Expr::ForLoop(expr) => self.expr_for_loop(expr), + Expr::Group(expr) => self.expr_group(expr), + Expr::If(expr) => self.expr_if(expr), + Expr::Index(expr) => self.expr_index(expr, false), + Expr::Let(expr) => self.expr_let(expr), + Expr::Lit(expr) => self.expr_lit(expr), + Expr::Loop(expr) => self.expr_loop(expr), + Expr::Macro(expr) => self.expr_macro(expr), + Expr::Match(expr) => self.expr_match(expr), + Expr::MethodCall(expr) => self.expr_method_call(expr, false), + Expr::Paren(expr) => self.expr_paren(expr), + Expr::Path(expr) => self.expr_path(expr), + Expr::Range(expr) => self.expr_range(expr), + Expr::Reference(expr) => self.expr_reference(expr), + Expr::Repeat(expr) => self.expr_repeat(expr), + Expr::Return(expr) => self.expr_return(expr), + Expr::Struct(expr) => self.expr_struct(expr), + Expr::Try(expr) => self.expr_try(expr, false), + Expr::TryBlock(expr) => self.expr_try_block(expr), + Expr::Tuple(expr) => self.expr_tuple(expr), + Expr::Type(expr) => self.expr_type(expr), + Expr::Unary(expr) => self.expr_unary(expr), + Expr::Unsafe(expr) => self.expr_unsafe(expr), + Expr::Verbatim(expr) => self.expr_verbatim(expr), + Expr::While(expr) => self.expr_while(expr), + Expr::Yield(expr) => self.expr_yield(expr), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Expr"), + } + } + + pub fn expr_beginning_of_line(&mut self, expr: &Expr, beginning_of_line: bool) { + match expr { + Expr::Await(expr) => self.expr_await(expr, beginning_of_line), + Expr::Field(expr) => self.expr_field(expr, beginning_of_line), + Expr::Index(expr) => self.expr_index(expr, beginning_of_line), + Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line), + Expr::Try(expr) => self.expr_try(expr, beginning_of_line), + _ => self.expr(expr), + } + } + + fn subexpr(&mut self, expr: &Expr, beginning_of_line: bool) { + match expr { + Expr::Await(expr) => self.subexpr_await(expr, beginning_of_line), + Expr::Call(expr) => self.subexpr_call(expr), + Expr::Field(expr) => self.subexpr_field(expr, beginning_of_line), + Expr::Index(expr) => self.subexpr_index(expr, beginning_of_line), + Expr::MethodCall(expr) => self.subexpr_method_call(expr, beginning_of_line, false), + Expr::Try(expr) => self.subexpr_try(expr, beginning_of_line), + _ => { + self.cbox(-INDENT); + self.expr(expr); + self.end(); + } + } + } + + // If the given expression is a bare `ExprStruct`, wraps it in parenthesis + // before appending it to `TokenStream`. + fn wrap_exterior_struct(&mut self, expr: &Expr) { + let needs_paren = contains_exterior_struct_lit(expr); + if needs_paren { + self.word("("); + } + self.cbox(0); + self.expr(expr); + if needs_paren { + self.word(")"); + } + if needs_newline_if_wrap(expr) { + self.space(); + } else { + self.nbsp(); + } + self.end(); + } + + fn expr_array(&mut self, expr: &ExprArray) { + self.outer_attrs(&expr.attrs); + self.word("["); + self.cbox(INDENT); + self.zerobreak(); + for element in expr.elems.iter().delimited() { + self.expr(&element); + self.trailing_comma(element.is_last); + } + self.offset(-INDENT); + self.end(); + self.word("]"); + } + + fn expr_assign(&mut self, expr: &ExprAssign) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + self.expr(&expr.left); + self.word(" = "); + self.expr(&expr.right); + self.end(); + } + + fn expr_assign_op(&mut self, expr: &ExprAssignOp) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.left); + self.end(); + self.space(); + self.binary_operator(&expr.op); + self.nbsp(); + self.expr(&expr.right); + self.end(); + } + + fn expr_async(&mut self, expr: &ExprAsync) { + self.outer_attrs(&expr.attrs); + self.word("async "); + if expr.capture.is_some() { + self.word("move "); + } + self.cbox(INDENT); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + + fn expr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.subexpr_await(expr, beginning_of_line); + self.end(); + } + + fn subexpr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) { + self.subexpr(&expr.base, beginning_of_line); + self.zerobreak_unless_short_ident(beginning_of_line, &expr.base); + self.word(".await"); + } + + fn expr_binary(&mut self, expr: &ExprBinary) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.left); + self.end(); + self.space(); + self.binary_operator(&expr.op); + self.nbsp(); + self.expr(&expr.right); + self.end(); + } + + pub fn expr_block(&mut self, expr: &ExprBlock) { + self.outer_attrs(&expr.attrs); + if let Some(label) = &expr.label { + self.label(label); + } + self.cbox(INDENT); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + + fn expr_box(&mut self, expr: &ExprBox) { + self.outer_attrs(&expr.attrs); + self.word("box "); + self.expr(&expr.expr); + } + + fn expr_break(&mut self, expr: &ExprBreak) { + self.outer_attrs(&expr.attrs); + self.word("break"); + if let Some(lifetime) = &expr.label { + self.nbsp(); + self.lifetime(lifetime); + } + if let Some(value) = &expr.expr { + self.nbsp(); + self.expr(value); + } + } + + fn expr_call(&mut self, expr: &ExprCall, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.expr_beginning_of_line(&expr.func, beginning_of_line); + self.word("("); + self.call_args(&expr.args); + self.word(")"); + } + + fn subexpr_call(&mut self, expr: &ExprCall) { + self.subexpr(&expr.func, false); + self.word("("); + self.call_args(&expr.args); + self.word(")"); + } + + fn expr_cast(&mut self, expr: &ExprCast) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.expr); + self.end(); + self.space(); + self.word("as "); + self.ty(&expr.ty); + self.end(); + } + + fn expr_closure(&mut self, expr: &ExprClosure) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + if expr.asyncness.is_some() { + self.word("async "); + } + if expr.movability.is_some() { + self.word("static "); + } + if expr.capture.is_some() { + self.word("move "); + } + self.cbox(INDENT); + self.word("|"); + for pat in expr.inputs.iter().delimited() { + if pat.is_first { + self.zerobreak(); + } + self.pat(&pat); + if !pat.is_last { + self.word(","); + self.space(); + } + } + match &expr.output { + ReturnType::Default => { + self.word("|"); + self.space(); + self.offset(-INDENT); + self.end(); + self.neverbreak(); + let wrap_in_brace = match &*expr.body { + Expr::Match(ExprMatch { attrs, .. }) | Expr::Call(ExprCall { attrs, .. }) => { + attr::has_outer(attrs) + } + body => !is_blocklike(body), + }; + if wrap_in_brace { + self.cbox(INDENT); + self.scan_break(BreakToken { + pre_break: Some('{'), + ..BreakToken::default() + }); + self.expr(&expr.body); + self.scan_break(BreakToken { + offset: -INDENT, + pre_break: stmt::add_semi(&expr.body).then(|| ';'), + post_break: Some('}'), + ..BreakToken::default() + }); + self.end(); + } else { + self.expr(&expr.body); + } + } + ReturnType::Type(_arrow, ty) => { + if !expr.inputs.is_empty() { + self.trailing_comma(true); + self.offset(-INDENT); + } + self.word("|"); + self.end(); + self.word(" -> "); + self.ty(ty); + self.nbsp(); + self.neverbreak(); + self.expr(&expr.body); + } + } + self.end(); + } + + fn expr_continue(&mut self, expr: &ExprContinue) { + self.outer_attrs(&expr.attrs); + self.word("continue"); + if let Some(lifetime) = &expr.label { + self.nbsp(); + self.lifetime(lifetime); + } + } + + fn expr_field(&mut self, expr: &ExprField, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.subexpr_field(expr, beginning_of_line); + self.end(); + } + + fn subexpr_field(&mut self, expr: &ExprField, beginning_of_line: bool) { + self.subexpr(&expr.base, beginning_of_line); + self.zerobreak_unless_short_ident(beginning_of_line, &expr.base); + self.word("."); + self.member(&expr.member); + } + + fn expr_for_loop(&mut self, expr: &ExprForLoop) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + if let Some(label) = &expr.label { + self.label(label); + } + self.word("for "); + self.pat(&expr.pat); + self.word(" in "); + self.neverbreak(); + self.wrap_exterior_struct(&expr.expr); + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in &expr.body.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.end(); + } + + fn expr_group(&mut self, expr: &ExprGroup) { + self.outer_attrs(&expr.attrs); + self.expr(&expr.expr); + } + + fn expr_if(&mut self, expr: &ExprIf) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.word("if "); + self.cbox(-INDENT); + self.wrap_exterior_struct(&expr.cond); + self.end(); + if let Some((_else_token, else_branch)) = &expr.else_branch { + let mut else_branch = &**else_branch; + self.small_block(&expr.then_branch, &[]); + loop { + self.word(" else "); + match else_branch { + Expr::If(expr) => { + self.word("if "); + self.cbox(-INDENT); + self.wrap_exterior_struct(&expr.cond); + self.end(); + self.small_block(&expr.then_branch, &[]); + if let Some((_else_token, next)) = &expr.else_branch { + else_branch = next; + continue; + } + } + Expr::Block(expr) => { + self.small_block(&expr.block, &[]); + } + // If not one of the valid expressions to exist in an else + // clause, wrap in a block. + other => { + self.word("{"); + self.space(); + self.ibox(INDENT); + self.expr(other); + self.end(); + self.space(); + self.offset(-INDENT); + self.word("}"); + } + } + break; + } + } else if expr.then_branch.stmts.is_empty() { + self.word("{}"); + } else { + self.word("{"); + self.hardbreak(); + for stmt in &expr.then_branch.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.word("}"); + } + self.end(); + } + + fn expr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.expr_beginning_of_line(&expr.expr, beginning_of_line); + self.word("["); + self.expr(&expr.index); + self.word("]"); + } + + fn subexpr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) { + self.subexpr(&expr.expr, beginning_of_line); + self.word("["); + self.expr(&expr.index); + self.word("]"); + } + + fn expr_let(&mut self, expr: &ExprLet) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.word("let "); + self.ibox(-INDENT); + self.pat(&expr.pat); + self.end(); + self.space(); + self.word("= "); + let needs_paren = contains_exterior_struct_lit(&expr.expr); + if needs_paren { + self.word("("); + } + self.expr(&expr.expr); + if needs_paren { + self.word(")"); + } + self.end(); + } + + pub fn expr_lit(&mut self, expr: &ExprLit) { + self.outer_attrs(&expr.attrs); + self.lit(&expr.lit); + } + + fn expr_loop(&mut self, expr: &ExprLoop) { + self.outer_attrs(&expr.attrs); + if let Some(label) = &expr.label { + self.label(label); + } + self.word("loop {"); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in &expr.body.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn expr_macro(&mut self, expr: &ExprMacro) { + self.outer_attrs(&expr.attrs); + self.mac(&expr.mac, None); + } + + fn expr_match(&mut self, expr: &ExprMatch) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + self.word("match "); + self.wrap_exterior_struct(&expr.expr); + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for arm in &expr.arms { + self.arm(arm); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.end(); + } + + fn expr_method_call(&mut self, expr: &ExprMethodCall, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + let unindent_call_args = beginning_of_line && is_short_ident(&expr.receiver); + self.subexpr_method_call(expr, beginning_of_line, unindent_call_args); + self.end(); + } + + fn subexpr_method_call( + &mut self, + expr: &ExprMethodCall, + beginning_of_line: bool, + unindent_call_args: bool, + ) { + self.subexpr(&expr.receiver, beginning_of_line); + self.zerobreak_unless_short_ident(beginning_of_line, &expr.receiver); + self.word("."); + self.ident(&expr.method); + if let Some(turbofish) = &expr.turbofish { + self.method_turbofish(turbofish); + } + self.cbox(if unindent_call_args { -INDENT } else { 0 }); + self.word("("); + self.call_args(&expr.args); + self.word(")"); + self.end(); + } + + fn expr_paren(&mut self, expr: &ExprParen) { + self.outer_attrs(&expr.attrs); + self.word("("); + self.expr(&expr.expr); + self.word(")"); + } + + fn expr_path(&mut self, expr: &ExprPath) { + self.outer_attrs(&expr.attrs); + self.qpath(&expr.qself, &expr.path); + } + + fn expr_range(&mut self, expr: &ExprRange) { + self.outer_attrs(&expr.attrs); + if let Some(from) = &expr.from { + self.expr(from); + } + self.word(match expr.limits { + RangeLimits::HalfOpen(_) => "..", + RangeLimits::Closed(_) => "..=", + }); + if let Some(to) = &expr.to { + self.expr(to); + } + } + + fn expr_reference(&mut self, expr: &ExprReference) { + self.outer_attrs(&expr.attrs); + self.word("&"); + if expr.mutability.is_some() { + self.word("mut "); + } + self.expr(&expr.expr); + } + + fn expr_repeat(&mut self, expr: &ExprRepeat) { + self.outer_attrs(&expr.attrs); + self.word("["); + self.expr(&expr.expr); + self.word("; "); + self.expr(&expr.len); + self.word("]"); + } + + fn expr_return(&mut self, expr: &ExprReturn) { + self.outer_attrs(&expr.attrs); + self.word("return"); + if let Some(value) = &expr.expr { + self.nbsp(); + self.expr(value); + } + } + + fn expr_struct(&mut self, expr: &ExprStruct) { + self.expr_qualified_struct(&None, expr); + } + + fn expr_qualified_struct(&mut self, qself: &Option, expr: &ExprStruct) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.ibox(-INDENT); + self.qpath(qself, &expr.path); + self.end(); + self.word(" {"); + self.space_if_nonempty(); + for field_value in expr.fields.iter().delimited() { + self.field_value(&field_value); + self.trailing_comma_or_space(field_value.is_last && expr.rest.is_none()); + } + if let Some(rest) = &expr.rest { + self.word(".."); + self.expr(rest); + self.space(); + } + self.offset(-INDENT); + self.end_with_max_width(34); + self.word("}"); + } + + fn expr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.expr_beginning_of_line(&expr.expr, beginning_of_line); + self.word("?"); + } + + fn subexpr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) { + self.subexpr(&expr.expr, beginning_of_line); + self.word("?"); + } + + fn expr_try_block(&mut self, expr: &ExprTryBlock) { + self.outer_attrs(&expr.attrs); + self.word("try "); + self.cbox(INDENT); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + + fn expr_tuple(&mut self, expr: &ExprTuple) { + self.outer_attrs(&expr.attrs); + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in expr.elems.iter().delimited() { + self.expr(&elem); + if expr.elems.len() == 1 { + self.word(","); + self.zerobreak(); + } else { + self.trailing_comma(elem.is_last); + } + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + fn expr_type(&mut self, expr: &ExprType) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.expr); + self.end(); + self.space(); + self.word(": "); + self.ty(&expr.ty); + self.end(); + } + + fn expr_unary(&mut self, expr: &ExprUnary) { + self.outer_attrs(&expr.attrs); + self.unary_operator(&expr.op); + self.expr(&expr.expr); + } + + fn expr_unsafe(&mut self, expr: &ExprUnsafe) { + self.outer_attrs(&expr.attrs); + self.word("unsafe {"); + self.cbox(INDENT); + self.space_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in expr.block.stmts.iter().delimited() { + if stmt.is_first && stmt.is_last { + if let Stmt::Expr(expr) = &*stmt { + self.expr(expr); + self.space(); + continue; + } + } + self.stmt(&stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + #[cfg(not(feature = "verbatim"))] + fn expr_verbatim(&mut self, expr: &TokenStream) { + if !expr.is_empty() { + unimplemented!("Expr::Verbatim `{}`", expr); + } + } + + #[cfg(feature = "verbatim")] + fn expr_verbatim(&mut self, tokens: &TokenStream) { + use syn::parse::{Parse, ParseStream, Result}; + use syn::{braced, BoundLifetimes}; + + enum ExprVerbatim { + Empty, + Infer, + RawReference(RawReference), + ConstBlock(ConstBlock), + ClosureWithLifetimes(ClosureWithLifetimes), + QualifiedStruct(QualifiedStruct), + } + + struct RawReference { + mutable: bool, + expr: Expr, + } + + struct ConstBlock { + attrs: Vec, + block: Block, + } + + struct ClosureWithLifetimes { + lifetimes: BoundLifetimes, + closure: ExprClosure, + } + + struct QualifiedStruct { + qself: QSelf, + strct: ExprStruct, + } + + mod kw { + syn::custom_keyword!(raw); + } + + impl Parse for ExprVerbatim { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if input.is_empty() { + Ok(ExprVerbatim::Empty) + } else if lookahead.peek(Token![_]) { + input.parse::()?; + Ok(ExprVerbatim::Infer) + } else if lookahead.peek(Token![&]) { + input.parse::()?; + input.parse::()?; + let mutable = input.parse::>()?.is_some(); + if !mutable { + input.parse::()?; + } + let expr: Expr = input.parse()?; + Ok(ExprVerbatim::RawReference(RawReference { mutable, expr })) + } else if lookahead.peek(Token![const]) { + input.parse::()?; + let content; + let brace_token = braced!(content in input); + let attrs = content.call(Attribute::parse_inner)?; + let stmts = content.call(Block::parse_within)?; + Ok(ExprVerbatim::ConstBlock(ConstBlock { + attrs, + block: Block { brace_token, stmts }, + })) + } else if lookahead.peek(Token![for]) { + let lifetimes = input.parse()?; + let closure = input.parse()?; + Ok(ExprVerbatim::ClosureWithLifetimes(ClosureWithLifetimes { + lifetimes, + closure, + })) + } else if lookahead.peek(Token![<]) { + let path: ExprPath = input.parse()?; + let content; + let mut expr = QualifiedStruct { + qself: path.qself.unwrap(), + strct: ExprStruct { + attrs: Vec::new(), + brace_token: braced!(content in input), + path: path.path, + fields: Punctuated::new(), + dot2_token: None, + rest: None, + }, + }; + while !content.is_empty() { + if content.peek(Token![..]) { + expr.strct.dot2_token = Some(content.parse()?); + if !content.is_empty() { + expr.strct.rest = Some(Box::new(content.parse()?)); + } + break; + } + expr.strct.fields.push(content.parse()?); + if content.is_empty() { + break; + } + let punct: Token![,] = content.parse()?; + expr.strct.fields.push_punct(punct); + } + Ok(ExprVerbatim::QualifiedStruct(expr)) + } else { + Err(lookahead.error()) + } + } + } + + let expr: ExprVerbatim = match syn::parse2(tokens.clone()) { + Ok(expr) => expr, + Err(_) => unimplemented!("Expr::Verbatim `{}`", tokens), + }; + + match expr { + ExprVerbatim::Empty => {} + ExprVerbatim::Infer => { + self.word("_"); + } + ExprVerbatim::RawReference(expr) => { + self.word("&raw "); + self.word(if expr.mutable { "mut " } else { "const " }); + self.expr(&expr.expr); + } + ExprVerbatim::ConstBlock(expr) => { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.word("const "); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + ExprVerbatim::ClosureWithLifetimes(expr) => { + self.bound_lifetimes(&expr.lifetimes); + self.expr_closure(&expr.closure); + } + ExprVerbatim::QualifiedStruct(expr) => { + self.expr_qualified_struct(&Some(expr.qself), &expr.strct); + } + } + } + + fn expr_while(&mut self, expr: &ExprWhile) { + self.outer_attrs(&expr.attrs); + if let Some(label) = &expr.label { + self.label(label); + } + self.word("while "); + self.wrap_exterior_struct(&expr.cond); + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in &expr.body.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn expr_yield(&mut self, expr: &ExprYield) { + self.outer_attrs(&expr.attrs); + self.word("yield"); + if let Some(value) = &expr.expr { + self.nbsp(); + self.expr(value); + } + } + + fn label(&mut self, label: &Label) { + self.lifetime(&label.name); + self.word(": "); + } + + fn field_value(&mut self, field_value: &FieldValue) { + self.outer_attrs(&field_value.attrs); + self.member(&field_value.member); + if field_value.colon_token.is_some() { + self.word(": "); + self.ibox(0); + self.expr(&field_value.expr); + self.end(); + } + } + + fn arm(&mut self, arm: &Arm) { + self.outer_attrs(&arm.attrs); + self.ibox(0); + self.pat(&arm.pat); + if let Some((_if_token, guard)) = &arm.guard { + self.word(" if "); + self.expr(guard); + } + self.word(" =>"); + let empty_block; + let mut body = &*arm.body; + while let Expr::Block(expr) = body { + if expr.attrs.is_empty() && expr.label.is_none() { + let mut stmts = expr.block.stmts.iter(); + if let (Some(Stmt::Expr(inner)), None) = (stmts.next(), stmts.next()) { + body = inner; + continue; + } + } + break; + } + if let Expr::Tuple(expr) = body { + if expr.elems.is_empty() && expr.attrs.is_empty() { + empty_block = Expr::Block(ExprBlock { + attrs: Vec::new(), + label: None, + block: Block { + brace_token: token::Brace::default(), + stmts: Vec::new(), + }, + }); + body = &empty_block; + } + } + if let Expr::Block(body) = body { + self.nbsp(); + if let Some(label) = &body.label { + self.label(label); + } + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&body.attrs); + for stmt in &body.block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.end(); + } else { + self.nbsp(); + self.neverbreak(); + self.cbox(INDENT); + self.scan_break(BreakToken { + pre_break: Some('{'), + ..BreakToken::default() + }); + self.expr(body); + self.scan_break(BreakToken { + offset: -INDENT, + pre_break: stmt::add_semi(body).then(|| ';'), + post_break: Some('}'), + no_break: requires_terminator(body).then(|| ','), + ..BreakToken::default() + }); + self.end(); + self.end(); + } + } + + fn method_turbofish(&mut self, turbofish: &MethodTurbofish) { + self.word("::<"); + self.cbox(INDENT); + self.zerobreak(); + for arg in turbofish.args.iter().delimited() { + self.generic_method_argument(&arg); + self.trailing_comma(arg.is_last); + } + self.offset(-INDENT); + self.end(); + self.word(">"); + } + + fn generic_method_argument(&mut self, generic: &GenericMethodArgument) { + match generic { + GenericMethodArgument::Type(arg) => self.ty(arg), + GenericMethodArgument::Const(arg) => self.expr(arg), + } + } + + fn call_args(&mut self, args: &Punctuated) { + let mut iter = args.iter(); + match (iter.next(), iter.next()) { + (Some(expr), None) if is_blocklike(expr) => { + self.expr(expr); + } + _ => { + self.cbox(INDENT); + self.zerobreak(); + for arg in args.iter().delimited() { + self.expr(&arg); + self.trailing_comma(arg.is_last); + } + self.offset(-INDENT); + self.end(); + } + } + } + + fn small_block(&mut self, block: &Block, attrs: &[Attribute]) { + self.word("{"); + if attr::has_inner(attrs) || !block.stmts.is_empty() { + self.space(); + self.inner_attrs(attrs); + match (block.stmts.get(0), block.stmts.get(1)) { + (Some(Stmt::Expr(expr)), None) if stmt::break_after(expr) => { + self.ibox(0); + self.expr_beginning_of_line(expr, true); + self.end(); + self.space(); + } + _ => { + for stmt in &block.stmts { + self.stmt(stmt); + } + } + } + self.offset(-INDENT); + } + self.word("}"); + } + + pub fn member(&mut self, member: &Member) { + match member { + Member::Named(ident) => self.ident(ident), + Member::Unnamed(index) => self.index(index), + } + } + + fn index(&mut self, member: &Index) { + self.word(member.index.to_string()); + } + + fn binary_operator(&mut self, op: &BinOp) { + self.word(match op { + BinOp::Add(_) => "+", + BinOp::Sub(_) => "-", + BinOp::Mul(_) => "*", + BinOp::Div(_) => "/", + BinOp::Rem(_) => "%", + BinOp::And(_) => "&&", + BinOp::Or(_) => "||", + BinOp::BitXor(_) => "^", + BinOp::BitAnd(_) => "&", + BinOp::BitOr(_) => "|", + BinOp::Shl(_) => "<<", + BinOp::Shr(_) => ">>", + BinOp::Eq(_) => "==", + BinOp::Lt(_) => "<", + BinOp::Le(_) => "<=", + BinOp::Ne(_) => "!=", + BinOp::Ge(_) => ">=", + BinOp::Gt(_) => ">", + BinOp::AddEq(_) => "+=", + BinOp::SubEq(_) => "-=", + BinOp::MulEq(_) => "*=", + BinOp::DivEq(_) => "/=", + BinOp::RemEq(_) => "%=", + BinOp::BitXorEq(_) => "^=", + BinOp::BitAndEq(_) => "&=", + BinOp::BitOrEq(_) => "|=", + BinOp::ShlEq(_) => "<<=", + BinOp::ShrEq(_) => ">>=", + }); + } + + fn unary_operator(&mut self, op: &UnOp) { + self.word(match op { + UnOp::Deref(_) => "*", + UnOp::Not(_) => "!", + UnOp::Neg(_) => "-", + }); + } + + fn zerobreak_unless_short_ident(&mut self, beginning_of_line: bool, expr: &Expr) { + if beginning_of_line && is_short_ident(expr) { + return; + } + self.zerobreak(); + } +} + +pub fn requires_terminator(expr: &Expr) -> bool { + // see https://github.com/rust-lang/rust/blob/2679c38fc/src/librustc_ast/util/classify.rs#L7-L25 + match expr { + Expr::Unsafe(_) + | Expr::Block(_) + | Expr::If(_) + | Expr::Match(_) + | Expr::While(_) + | Expr::Loop(_) + | Expr::ForLoop(_) + | Expr::Async(_) + | Expr::TryBlock(_) => false, + _ => true, + } +} + +// Expressions that syntactically contain an "exterior" struct literal i.e. not +// surrounded by any parens or other delimiters. For example `X { y: 1 }`, `X { +// y: 1 }.method()`, `foo == X { y: 1 }` and `X { y: 1 } == foo` all do, but `(X +// { y: 1 }) == foo` does not. +fn contains_exterior_struct_lit(expr: &Expr) -> bool { + match expr { + Expr::Struct(_) => true, + + Expr::Assign(ExprAssign { left, right, .. }) + | Expr::AssignOp(ExprAssignOp { left, right, .. }) + | Expr::Binary(ExprBinary { left, right, .. }) => { + // X { y: 1 } + X { y: 2 } + contains_exterior_struct_lit(left) || contains_exterior_struct_lit(right) + } + + Expr::Await(ExprAwait { base: e, .. }) + | Expr::Box(ExprBox { expr: e, .. }) + | Expr::Cast(ExprCast { expr: e, .. }) + | Expr::Field(ExprField { base: e, .. }) + | Expr::Index(ExprIndex { expr: e, .. }) + | Expr::MethodCall(ExprMethodCall { receiver: e, .. }) + | Expr::Reference(ExprReference { expr: e, .. }) + | Expr::Type(ExprType { expr: e, .. }) + | Expr::Unary(ExprUnary { expr: e, .. }) => { + // &X { y: 1 }, X { y: 1 }.y + contains_exterior_struct_lit(e) + } + + _ => false, + } +} + +fn needs_newline_if_wrap(expr: &Expr) -> bool { + match expr { + Expr::Array(_) + | Expr::Async(_) + | Expr::Block(_) + | Expr::Break(ExprBreak { expr: None, .. }) + | Expr::Closure(_) + | Expr::Continue(_) + | Expr::ForLoop(_) + | Expr::If(_) + | Expr::Lit(_) + | Expr::Loop(_) + | Expr::Macro(_) + | Expr::Match(_) + | Expr::Path(_) + | Expr::Range(ExprRange { to: None, .. }) + | Expr::Repeat(_) + | Expr::Return(ExprReturn { expr: None, .. }) + | Expr::Struct(_) + | Expr::TryBlock(_) + | Expr::Tuple(_) + | Expr::Unsafe(_) + | Expr::Verbatim(_) + | Expr::While(_) + | Expr::Yield(ExprYield { expr: None, .. }) => false, + + Expr::Assign(_) + | Expr::AssignOp(_) + | Expr::Await(_) + | Expr::Binary(_) + | Expr::Cast(_) + | Expr::Field(_) + | Expr::Index(_) + | Expr::MethodCall(_) + | Expr::Type(_) => true, + + Expr::Box(ExprBox { expr: e, .. }) + | Expr::Break(ExprBreak { expr: Some(e), .. }) + | Expr::Call(ExprCall { func: e, .. }) + | Expr::Group(ExprGroup { expr: e, .. }) + | Expr::Let(ExprLet { expr: e, .. }) + | Expr::Paren(ExprParen { expr: e, .. }) + | Expr::Range(ExprRange { to: Some(e), .. }) + | Expr::Reference(ExprReference { expr: e, .. }) + | Expr::Return(ExprReturn { expr: Some(e), .. }) + | Expr::Try(ExprTry { expr: e, .. }) + | Expr::Unary(ExprUnary { expr: e, .. }) + | Expr::Yield(ExprYield { expr: Some(e), .. }) => needs_newline_if_wrap(e), + + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => false, + } +} + +fn is_short_ident(expr: &Expr) -> bool { + if let Expr::Path(expr) = expr { + if expr.attrs.is_empty() + && expr.qself.is_none() + && expr.path.leading_colon.is_none() + && expr.path.segments.len() == 1 + && expr.path.segments[0].ident.to_string().len() as isize <= INDENT + { + if let PathArguments::None = expr.path.segments[0].arguments { + return true; + } + } + } + false +} + +fn is_blocklike(expr: &Expr) -> bool { + match expr { + Expr::Array(ExprArray { attrs, .. }) + | Expr::Async(ExprAsync { attrs, .. }) + | Expr::Block(ExprBlock { attrs, .. }) + | Expr::Closure(ExprClosure { attrs, .. }) + | Expr::Struct(ExprStruct { attrs, .. }) + | Expr::TryBlock(ExprTryBlock { attrs, .. }) + | Expr::Tuple(ExprTuple { attrs, .. }) + | Expr::Unsafe(ExprUnsafe { attrs, .. }) => !attr::has_outer(attrs), + _ => false, + } +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..e23bd12 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,17 @@ +use crate::algorithm::Printer; +use syn::File; + +impl Printer { + pub fn file(&mut self, file: &File) { + self.cbox(0); + if let Some(shebang) = &file.shebang { + self.word(shebang.clone()); + self.hardbreak(); + } + self.inner_attrs(&file.attrs); + for item in &file.items { + self.item(item); + } + self.end(); + } +} diff --git a/src/generics.rs b/src/generics.rs new file mode 100644 index 0000000..ede53de --- /dev/null +++ b/src/generics.rs @@ -0,0 +1,286 @@ +use crate::algorithm::Printer; +use crate::iter::IterDelimited; +use crate::INDENT; +use std::ptr; +use syn::{ + BoundLifetimes, ConstParam, GenericParam, Generics, LifetimeDef, PredicateEq, + PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, TypeParam, TypeParamBound, + WhereClause, WherePredicate, +}; + +impl Printer { + pub fn generics(&mut self, generics: &Generics) { + if generics.params.is_empty() { + return; + } + + self.word("<"); + self.cbox(0); + self.zerobreak(); + + // Print lifetimes before types and consts, regardless of their + // order in self.params. + // + // TODO: ordering rules for const parameters vs type parameters have + // not been settled yet. https://github.com/rust-lang/rust/issues/44580 + #[derive(Ord, PartialOrd, Eq, PartialEq)] + enum Group { + First, + Second, + } + fn group(param: &GenericParam) -> Group { + match param { + GenericParam::Lifetime(_) => Group::First, + GenericParam::Type(_) | GenericParam::Const(_) => Group::Second, + } + } + let last = generics.params.iter().max_by_key(|param| group(param)); + for current_group in [Group::First, Group::Second] { + for param in &generics.params { + if group(param) == current_group { + self.generic_param(param); + self.trailing_comma(ptr::eq(param, last.unwrap())); + } + } + } + + self.offset(-INDENT); + self.end(); + self.word(">"); + } + + fn generic_param(&mut self, generic_param: &GenericParam) { + match generic_param { + GenericParam::Type(type_param) => self.type_param(type_param), + GenericParam::Lifetime(lifetime_def) => self.lifetime_def(lifetime_def), + GenericParam::Const(const_param) => self.const_param(const_param), + } + } + + pub fn bound_lifetimes(&mut self, bound_lifetimes: &BoundLifetimes) { + self.word("for<"); + for lifetime_def in bound_lifetimes.lifetimes.iter().delimited() { + self.lifetime_def(&lifetime_def); + if !lifetime_def.is_last { + self.word(", "); + } + } + self.word("> "); + } + + fn lifetime_def(&mut self, lifetime_def: &LifetimeDef) { + self.outer_attrs(&lifetime_def.attrs); + self.lifetime(&lifetime_def.lifetime); + for lifetime in lifetime_def.bounds.iter().delimited() { + if lifetime.is_first { + self.word(": "); + } else { + self.word(" + "); + } + self.lifetime(&lifetime); + } + } + + fn type_param(&mut self, type_param: &TypeParam) { + self.outer_attrs(&type_param.attrs); + self.ident(&type_param.ident); + self.ibox(INDENT); + for type_param_bound in type_param.bounds.iter().delimited() { + if type_param_bound.is_first { + self.word(": "); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&type_param_bound); + } + if let Some(default) = &type_param.default { + self.space(); + self.word("= "); + self.ty(default); + } + self.end(); + } + + pub fn type_param_bound(&mut self, type_param_bound: &TypeParamBound) { + match type_param_bound { + TypeParamBound::Trait(trait_bound) => self.trait_bound(trait_bound), + TypeParamBound::Lifetime(lifetime) => self.lifetime(lifetime), + } + } + + fn trait_bound(&mut self, trait_bound: &TraitBound) { + if trait_bound.paren_token.is_some() { + self.word("("); + } + let skip = match trait_bound.path.segments.first() { + Some(segment) if segment.ident == "const" => { + self.word("~const "); + 1 + } + _ => 0, + }; + self.trait_bound_modifier(&trait_bound.modifier); + if let Some(bound_lifetimes) = &trait_bound.lifetimes { + self.bound_lifetimes(bound_lifetimes); + } + for segment in trait_bound.path.segments.iter().skip(skip).delimited() { + if !segment.is_first || trait_bound.path.leading_colon.is_some() { + self.word("::"); + } + self.path_segment(&segment); + } + if trait_bound.paren_token.is_some() { + self.word(")"); + } + } + + fn trait_bound_modifier(&mut self, trait_bound_modifier: &TraitBoundModifier) { + match trait_bound_modifier { + TraitBoundModifier::None => {} + TraitBoundModifier::Maybe(_question_mark) => self.word("?"), + } + } + + fn const_param(&mut self, const_param: &ConstParam) { + self.outer_attrs(&const_param.attrs); + self.word("const "); + self.ident(&const_param.ident); + self.word(": "); + self.ty(&const_param.ty); + if let Some(default) = &const_param.default { + self.word(" = "); + self.expr(default); + } + } + + pub fn where_clause_for_body(&mut self, where_clause: &Option) { + let hardbreaks = true; + let semi = false; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + pub fn where_clause_semi(&mut self, where_clause: &Option) { + let hardbreaks = true; + let semi = true; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + pub fn where_clause_oneline(&mut self, where_clause: &Option) { + let hardbreaks = false; + let semi = false; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + pub fn where_clause_oneline_semi(&mut self, where_clause: &Option) { + let hardbreaks = false; + let semi = true; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + fn where_clause_impl( + &mut self, + where_clause: &Option, + hardbreaks: bool, + semi: bool, + ) { + let where_clause = match where_clause { + Some(where_clause) if !where_clause.predicates.is_empty() => where_clause, + _ => { + if semi { + self.word(";"); + } else { + self.nbsp(); + } + return; + } + }; + if hardbreaks { + self.hardbreak(); + self.offset(-INDENT); + self.word("where"); + self.hardbreak(); + for predicate in where_clause.predicates.iter().delimited() { + self.where_predicate(&predicate); + if predicate.is_last && semi { + self.word(";"); + } else { + self.word(","); + self.hardbreak(); + } + } + if !semi { + self.offset(-INDENT); + } + } else { + self.space(); + self.offset(-INDENT); + self.word("where"); + self.space(); + for predicate in where_clause.predicates.iter().delimited() { + self.where_predicate(&predicate); + if predicate.is_last && semi { + self.word(";"); + } else { + self.trailing_comma_or_space(predicate.is_last); + } + } + if !semi { + self.offset(-INDENT); + } + } + } + + fn where_predicate(&mut self, predicate: &WherePredicate) { + match predicate { + WherePredicate::Type(predicate) => self.predicate_type(predicate), + WherePredicate::Lifetime(predicate) => self.predicate_lifetime(predicate), + WherePredicate::Eq(predicate) => self.predicate_eq(predicate), + } + } + + fn predicate_type(&mut self, predicate: &PredicateType) { + if let Some(bound_lifetimes) = &predicate.lifetimes { + self.bound_lifetimes(bound_lifetimes); + } + self.ty(&predicate.bounded_ty); + self.word(":"); + if predicate.bounds.len() == 1 { + self.ibox(0); + } else { + self.ibox(INDENT); + } + for type_param_bound in predicate.bounds.iter().delimited() { + if type_param_bound.is_first { + self.nbsp(); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&type_param_bound); + } + self.end(); + } + + fn predicate_lifetime(&mut self, predicate: &PredicateLifetime) { + self.lifetime(&predicate.lifetime); + self.word(":"); + self.ibox(INDENT); + for lifetime in predicate.bounds.iter().delimited() { + if lifetime.is_first { + self.nbsp(); + } else { + self.space(); + self.word("+ "); + } + self.lifetime(&lifetime); + } + self.end(); + } + + fn predicate_eq(&mut self, predicate: &PredicateEq) { + self.ty(&predicate.lhs_ty); + self.word(" = "); + self.ty(&predicate.rhs_ty); + } +} diff --git a/src/item.rs b/src/item.rs new file mode 100644 index 0000000..62252f1 --- /dev/null +++ b/src/item.rs @@ -0,0 +1,836 @@ +use crate::algorithm::Printer; +use crate::iter::IterDelimited; +use crate::INDENT; +use proc_macro2::TokenStream; +use syn::{ + Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemMacro, ForeignItemStatic, + ForeignItemType, ImplItem, ImplItemConst, ImplItemMacro, ImplItemMethod, ImplItemType, Item, + ItemConst, ItemEnum, ItemExternCrate, ItemFn, ItemForeignMod, ItemImpl, ItemMacro, ItemMacro2, + ItemMod, ItemStatic, ItemStruct, ItemTrait, ItemTraitAlias, ItemType, ItemUnion, ItemUse, Pat, + Receiver, Signature, Stmt, TraitItem, TraitItemConst, TraitItemMacro, TraitItemMethod, + TraitItemType, Type, UseGlob, UseGroup, UseName, UsePath, UseRename, UseTree, +}; + +impl Printer { + pub fn item(&mut self, item: &Item) { + match item { + Item::Const(item) => self.item_const(item), + Item::Enum(item) => self.item_enum(item), + Item::ExternCrate(item) => self.item_extern_crate(item), + Item::Fn(item) => self.item_fn(item), + Item::ForeignMod(item) => self.item_foreign_mod(item), + Item::Impl(item) => self.item_impl(item), + Item::Macro(item) => self.item_macro(item), + Item::Macro2(item) => self.item_macro2(item), + Item::Mod(item) => self.item_mod(item), + Item::Static(item) => self.item_static(item), + Item::Struct(item) => self.item_struct(item), + Item::Trait(item) => self.item_trait(item), + Item::TraitAlias(item) => self.item_trait_alias(item), + Item::Type(item) => self.item_type(item), + Item::Union(item) => self.item_union(item), + Item::Use(item) => self.item_use(item), + Item::Verbatim(item) => self.item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Item"), + } + } + + fn item_const(&mut self, item: &ItemConst) { + self.outer_attrs(&item.attrs); + self.cbox(0); + self.visibility(&item.vis); + self.word("const "); + self.ident(&item.ident); + self.word(": "); + self.ty(&item.ty); + self.word(" = "); + self.neverbreak(); + self.expr(&item.expr); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn item_enum(&mut self, item: &ItemEnum) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("enum "); + self.ident(&item.ident); + self.generics(&item.generics); + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + for variant in &item.variants { + self.variant(variant); + self.word(","); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_extern_crate(&mut self, item: &ItemExternCrate) { + self.outer_attrs(&item.attrs); + self.visibility(&item.vis); + self.word("extern crate "); + self.ident(&item.ident); + if let Some((_as_token, rename)) = &item.rename { + self.word(" as "); + self.ident(rename); + } + self.word(";"); + self.hardbreak(); + } + + fn item_fn(&mut self, item: &ItemFn) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.signature(&item.sig); + self.where_clause_for_body(&item.sig.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for stmt in &item.block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_foreign_mod(&mut self, item: &ItemForeignMod) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.abi(&item.abi); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for foreign_item in &item.items { + self.foreign_item(foreign_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_impl(&mut self, item: &ItemImpl) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.ibox(-INDENT); + self.cbox(INDENT); + if item.defaultness.is_some() { + self.word("default "); + } + if item.unsafety.is_some() { + self.word("unsafe "); + } + self.word("impl"); + self.generics(&item.generics); + self.end(); + self.nbsp(); + if let Some((negative_polarity, path, _for_token)) = &item.trait_ { + if negative_polarity.is_some() { + self.word("!"); + } + self.path(path); + self.space(); + self.word("for "); + } + self.ty(&item.self_ty); + self.end(); + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for impl_item in &item.items { + self.impl_item(impl_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_macro(&mut self, item: &ItemMacro) { + self.outer_attrs(&item.attrs); + self.mac(&item.mac, item.ident.as_ref()); + self.mac_semi_if_needed(&item.mac.delimiter); + self.hardbreak(); + } + + fn item_macro2(&mut self, item: &ItemMacro2) { + unimplemented!("Item::Macro2 `macro {} {}`", item.ident, item.rules); + } + + fn item_mod(&mut self, item: &ItemMod) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("mod "); + self.ident(&item.ident); + if let Some((_brace, items)) = &item.content { + self.word(" {"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for item in items { + self.item(item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } else { + self.word(";"); + self.end(); + } + self.hardbreak(); + } + + fn item_static(&mut self, item: &ItemStatic) { + self.outer_attrs(&item.attrs); + self.cbox(0); + self.visibility(&item.vis); + self.word("static "); + if item.mutability.is_some() { + self.word("mut "); + } + self.ident(&item.ident); + self.word(": "); + self.ty(&item.ty); + self.word(" = "); + self.neverbreak(); + self.expr(&item.expr); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn item_struct(&mut self, item: &ItemStruct) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("struct "); + self.ident(&item.ident); + self.generics(&item.generics); + match &item.fields { + Fields::Named(fields) => { + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + for field in &fields.named { + self.field(field); + self.word(","); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + Fields::Unnamed(fields) => { + self.fields_unnamed(fields); + self.where_clause_semi(&item.generics.where_clause); + self.end(); + } + Fields::Unit => { + self.where_clause_semi(&item.generics.where_clause); + self.end(); + } + } + self.hardbreak(); + } + + fn item_trait(&mut self, item: &ItemTrait) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + if item.unsafety.is_some() { + self.word("unsafe "); + } + if item.auto_token.is_some() { + self.word("auto "); + } + self.word("trait "); + self.ident(&item.ident); + self.generics(&item.generics); + for supertrait in item.supertraits.iter().delimited() { + if supertrait.is_first { + self.word(": "); + } else { + self.word(" + "); + } + self.type_param_bound(&supertrait); + } + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for trait_item in &item.items { + self.trait_item(trait_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_trait_alias(&mut self, item: &ItemTraitAlias) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("trait "); + self.ident(&item.ident); + self.generics(&item.generics); + self.word(" = "); + self.neverbreak(); + for bound in item.bounds.iter().delimited() { + if !bound.is_first { + self.space(); + self.word("+ "); + } + self.type_param_bound(&bound); + } + self.where_clause_semi(&item.generics.where_clause); + self.end(); + self.hardbreak(); + } + + fn item_type(&mut self, item: &ItemType) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("type "); + self.ident(&item.ident); + self.generics(&item.generics); + self.where_clause_oneline(&item.generics.where_clause); + self.word("= "); + self.neverbreak(); + self.ibox(-INDENT); + self.ty(&item.ty); + self.end(); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn item_union(&mut self, item: &ItemUnion) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("union "); + self.ident(&item.ident); + self.generics(&item.generics); + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + for field in &item.fields.named { + self.field(field); + self.word(","); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_use(&mut self, item: &ItemUse) { + self.outer_attrs(&item.attrs); + self.visibility(&item.vis); + self.word("use "); + if item.leading_colon.is_some() { + self.word("::"); + } + self.use_tree(&item.tree); + self.word(";"); + self.hardbreak(); + } + + #[cfg(not(feature = "verbatim"))] + fn item_verbatim(&mut self, item: &TokenStream) { + if !item.is_empty() { + unimplemented!("Item::Verbatim `{}`", item); + } + self.hardbreak(); + } + + #[cfg(feature = "verbatim")] + fn item_verbatim(&mut self, tokens: &TokenStream) { + use syn::parse::{Parse, ParseStream, Result}; + use syn::{Attribute, Token}; + + enum ItemVerbatim { + Empty, + UnsafeForeignMod(ItemForeignMod), + } + + impl Parse for ItemVerbatim { + fn parse(input: ParseStream) -> Result { + if input.is_empty() { + Ok(ItemVerbatim::Empty) + } else { + let attrs = input.call(Attribute::parse_outer)?; + input.parse::()?; + let module: ItemForeignMod = input.parse()?; + Ok(ItemVerbatim::UnsafeForeignMod(ItemForeignMod { + attrs, + ..module + })) + } + } + } + + let item: ItemVerbatim = match syn::parse2(tokens.clone()) { + Ok(item) => item, + Err(_) => unimplemented!("Item::Verbatim `{}`", tokens), + }; + + match item { + ItemVerbatim::Empty => {} + ItemVerbatim::UnsafeForeignMod(item) => { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.word("unsafe "); + self.abi(&item.abi); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for foreign_item in &item.items { + self.foreign_item(foreign_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + } + + self.hardbreak(); + } + + fn use_tree(&mut self, use_tree: &UseTree) { + match use_tree { + UseTree::Path(use_path) => self.use_path(use_path), + UseTree::Name(use_name) => self.use_name(use_name), + UseTree::Rename(use_rename) => self.use_rename(use_rename), + UseTree::Glob(use_glob) => self.use_glob(use_glob), + UseTree::Group(use_group) => self.use_group(use_group), + } + } + + fn use_path(&mut self, use_path: &UsePath) { + self.ident(&use_path.ident); + self.word("::"); + self.use_tree(&use_path.tree); + } + + fn use_name(&mut self, use_name: &UseName) { + self.ident(&use_name.ident); + } + + fn use_rename(&mut self, use_rename: &UseRename) { + self.ident(&use_rename.ident); + self.word(" as "); + self.ident(&use_rename.rename); + } + + fn use_glob(&mut self, use_glob: &UseGlob) { + let _ = use_glob; + self.word("*"); + } + + fn use_group(&mut self, use_group: &UseGroup) { + if use_group.items.is_empty() { + self.word("{}"); + } else if use_group.items.len() == 1 { + self.use_tree(&use_group.items[0]); + } else { + self.cbox(INDENT); + self.word("{"); + self.zerobreak(); + self.ibox(0); + for use_tree in use_group.items.iter().delimited() { + self.use_tree(&use_tree); + if !use_tree.is_last { + self.word(","); + let mut use_tree = *use_tree; + while let UseTree::Path(use_path) = use_tree { + use_tree = &use_path.tree; + } + if let UseTree::Group(_) = use_tree { + self.hardbreak(); + } else { + self.space(); + } + } + } + self.end(); + self.trailing_comma(true); + self.offset(-INDENT); + self.word("}"); + self.end(); + } + } + + fn foreign_item(&mut self, foreign_item: &ForeignItem) { + match foreign_item { + ForeignItem::Fn(item) => self.foreign_item_fn(item), + ForeignItem::Static(item) => self.foreign_item_static(item), + ForeignItem::Type(item) => self.foreign_item_type(item), + ForeignItem::Macro(item) => self.foreign_item_macro(item), + ForeignItem::Verbatim(item) => self.foreign_item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown ForeignItem"), + } + } + + fn foreign_item_fn(&mut self, foreign_item: &ForeignItemFn) { + self.outer_attrs(&foreign_item.attrs); + self.cbox(INDENT); + self.visibility(&foreign_item.vis); + self.signature(&foreign_item.sig); + self.where_clause_semi(&foreign_item.sig.generics.where_clause); + self.end(); + self.hardbreak(); + } + + fn foreign_item_static(&mut self, foreign_item: &ForeignItemStatic) { + self.outer_attrs(&foreign_item.attrs); + self.cbox(0); + self.visibility(&foreign_item.vis); + self.word("static "); + if foreign_item.mutability.is_some() { + self.word("mut "); + } + self.ident(&foreign_item.ident); + self.word(": "); + self.ty(&foreign_item.ty); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn foreign_item_type(&mut self, foreign_item: &ForeignItemType) { + self.outer_attrs(&foreign_item.attrs); + self.cbox(0); + self.visibility(&foreign_item.vis); + self.word("type "); + self.ident(&foreign_item.ident); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn foreign_item_macro(&mut self, foreign_item: &ForeignItemMacro) { + self.outer_attrs(&foreign_item.attrs); + self.mac(&foreign_item.mac, None); + self.mac_semi_if_needed(&foreign_item.mac.delimiter); + self.hardbreak(); + } + + #[cfg(not(feature = "verbatim"))] + fn foreign_item_verbatim(&mut self, foreign_item: &TokenStream) { + if !foreign_item.is_empty() { + unimplemented!("ForeignItem::Verbatim `{}`", foreign_item); + } + self.hardbreak(); + } + + #[cfg(feature = "verbatim")] + fn foreign_item_verbatim(&mut self, tokens: &TokenStream) { + use syn::parse::{Parse, ParseStream, Result}; + + enum ForeignItemVerbatim { + TypeAlias(ItemType), + } + + impl Parse for ForeignItemVerbatim { + fn parse(input: ParseStream) -> Result { + input.parse().map(ForeignItemVerbatim::TypeAlias) + } + } + + let foreign_item: ForeignItemVerbatim = match syn::parse2(tokens.clone()) { + Ok(foreign_item) => foreign_item, + Err(_) => unimplemented!("ForeignItem::Verbatim `{}`", tokens), + }; + + match foreign_item { + ForeignItemVerbatim::TypeAlias(item) => self.item_type(&item), + } + } + + fn trait_item(&mut self, trait_item: &TraitItem) { + match trait_item { + TraitItem::Const(item) => self.trait_item_const(item), + TraitItem::Method(item) => self.trait_item_method(item), + TraitItem::Type(item) => self.trait_item_type(item), + TraitItem::Macro(item) => self.trait_item_macro(item), + TraitItem::Verbatim(item) => self.trait_item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown TraitItem"), + } + } + + fn trait_item_const(&mut self, trait_item: &TraitItemConst) { + self.outer_attrs(&trait_item.attrs); + self.cbox(0); + self.word("const "); + self.ident(&trait_item.ident); + self.word(": "); + self.ty(&trait_item.ty); + if let Some((_eq_token, default)) = &trait_item.default { + self.word(" = "); + self.neverbreak(); + self.expr(default); + } + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn trait_item_method(&mut self, trait_item: &TraitItemMethod) { + self.outer_attrs(&trait_item.attrs); + self.cbox(INDENT); + self.signature(&trait_item.sig); + if let Some(block) = &trait_item.default { + self.where_clause_for_body(&trait_item.sig.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&trait_item.attrs); + for stmt in &block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } else { + self.where_clause_semi(&trait_item.sig.generics.where_clause); + self.end(); + } + self.hardbreak(); + } + + fn trait_item_type(&mut self, trait_item: &TraitItemType) { + self.outer_attrs(&trait_item.attrs); + self.cbox(INDENT); + self.word("type "); + self.ident(&trait_item.ident); + self.generics(&trait_item.generics); + for bound in trait_item.bounds.iter().delimited() { + if bound.is_first { + self.word(": "); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&bound); + } + if let Some((_eq_token, default)) = &trait_item.default { + self.word(" = "); + self.neverbreak(); + self.ty(default); + } + self.where_clause_oneline_semi(&trait_item.generics.where_clause); + self.end(); + self.hardbreak(); + } + + fn trait_item_macro(&mut self, trait_item: &TraitItemMacro) { + self.outer_attrs(&trait_item.attrs); + self.mac(&trait_item.mac, None); + self.mac_semi_if_needed(&trait_item.mac.delimiter); + self.hardbreak(); + } + + fn trait_item_verbatim(&mut self, trait_item: &TokenStream) { + if !trait_item.is_empty() { + unimplemented!("TraitItem::Verbatim `{}`", trait_item); + } + self.hardbreak(); + } + + fn impl_item(&mut self, impl_item: &ImplItem) { + match impl_item { + ImplItem::Const(item) => self.impl_item_const(item), + ImplItem::Method(item) => self.impl_item_method(item), + ImplItem::Type(item) => self.impl_item_type(item), + ImplItem::Macro(item) => self.impl_item_macro(item), + ImplItem::Verbatim(item) => self.impl_item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown ImplItem"), + } + } + + fn impl_item_const(&mut self, impl_item: &ImplItemConst) { + self.outer_attrs(&impl_item.attrs); + self.cbox(0); + self.visibility(&impl_item.vis); + if impl_item.defaultness.is_some() { + self.word("default "); + } + self.word("const "); + self.ident(&impl_item.ident); + self.word(": "); + self.ty(&impl_item.ty); + self.word(" = "); + self.neverbreak(); + self.expr(&impl_item.expr); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn impl_item_method(&mut self, impl_item: &ImplItemMethod) { + self.outer_attrs(&impl_item.attrs); + self.cbox(INDENT); + self.visibility(&impl_item.vis); + if impl_item.defaultness.is_some() { + self.word("default "); + } + self.signature(&impl_item.sig); + if impl_item.block.stmts.len() == 1 { + if let Stmt::Item(Item::Verbatim(verbatim)) = &impl_item.block.stmts[0] { + if verbatim.to_string() == ";" { + self.where_clause_semi(&impl_item.sig.generics.where_clause); + self.end(); + self.hardbreak(); + return; + } + } + } + self.where_clause_for_body(&impl_item.sig.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&impl_item.attrs); + for stmt in &impl_item.block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn impl_item_type(&mut self, impl_item: &ImplItemType) { + self.outer_attrs(&impl_item.attrs); + self.cbox(INDENT); + self.visibility(&impl_item.vis); + if impl_item.defaultness.is_some() { + self.word("default "); + } + self.word("type "); + self.ident(&impl_item.ident); + self.generics(&impl_item.generics); + self.word(" = "); + self.neverbreak(); + self.ibox(-INDENT); + self.ty(&impl_item.ty); + self.end(); + self.where_clause_oneline_semi(&impl_item.generics.where_clause); + self.end(); + self.hardbreak(); + } + + fn impl_item_macro(&mut self, impl_item: &ImplItemMacro) { + self.outer_attrs(&impl_item.attrs); + self.mac(&impl_item.mac, None); + self.mac_semi_if_needed(&impl_item.mac.delimiter); + self.hardbreak(); + } + + fn impl_item_verbatim(&mut self, impl_item: &TokenStream) { + if !impl_item.is_empty() { + unimplemented!("ImplItem::Verbatim `{}`", impl_item); + } + self.hardbreak(); + } + + fn maybe_variadic(&mut self, arg: &FnArg) -> bool { + let pat_type = match arg { + FnArg::Typed(pat_type) => pat_type, + FnArg::Receiver(receiver) => { + self.receiver(receiver); + return false; + } + }; + + match pat_type.ty.as_ref() { + Type::Verbatim(ty) if ty.to_string() == "..." => { + match pat_type.pat.as_ref() { + Pat::Verbatim(pat) if pat.to_string() == "..." => { + self.outer_attrs(&pat_type.attrs); + self.word("..."); + } + _ => self.pat_type(pat_type), + } + true + } + _ => { + self.pat_type(pat_type); + false + } + } + } + + fn signature(&mut self, signature: &Signature) { + if signature.constness.is_some() { + self.word("const "); + } + if signature.asyncness.is_some() { + self.word("async "); + } + if signature.unsafety.is_some() { + self.word("unsafe "); + } + if let Some(abi) = &signature.abi { + self.abi(abi); + } + self.word("fn "); + self.ident(&signature.ident); + self.generics(&signature.generics); + self.word("("); + self.neverbreak(); + self.cbox(0); + self.zerobreak(); + let mut last_is_variadic = false; + for input in signature.inputs.iter().delimited() { + last_is_variadic = self.maybe_variadic(&input); + if last_is_variadic { + self.zerobreak(); + } else { + let is_last = input.is_last && signature.variadic.is_none(); + self.trailing_comma(is_last); + } + } + if signature.variadic.is_some() && !last_is_variadic { + self.word("..."); + self.zerobreak(); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + self.cbox(-INDENT); + self.return_type(&signature.output); + self.end(); + } + + fn receiver(&mut self, receiver: &Receiver) { + self.outer_attrs(&receiver.attrs); + if let Some((_ampersand, lifetime)) = &receiver.reference { + self.word("&"); + if let Some(lifetime) = lifetime { + self.lifetime(lifetime); + self.nbsp(); + } + } + if receiver.mutability.is_some() { + self.word("mut "); + } + self.word("self"); + } +} diff --git a/src/iter.rs b/src/iter.rs new file mode 100644 index 0000000..702c653 --- /dev/null +++ b/src/iter.rs @@ -0,0 +1,46 @@ +use std::iter::Peekable; +use std::ops::Deref; + +pub struct Delimited { + is_first: bool, + iter: Peekable, +} + +pub trait IterDelimited: Iterator + Sized { + fn delimited(self) -> Delimited { + Delimited { + is_first: true, + iter: self.peekable(), + } + } +} + +impl IterDelimited for I {} + +pub struct IteratorItem { + value: T, + pub is_first: bool, + pub is_last: bool, +} + +impl Iterator for Delimited { + type Item = IteratorItem; + + fn next(&mut self) -> Option { + let item = IteratorItem { + value: self.iter.next()?, + is_first: self.is_first, + is_last: self.iter.peek().is_none(), + }; + self.is_first = false; + Some(item) + } +} + +impl Deref for IteratorItem { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..15a9e17 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,379 @@ +//! [![github]](https://github.com/dtolnay/prettyplease) [![crates-io]](https://crates.io/crates/prettyplease) [![docs-rs]](https://docs.rs/prettyplease) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! +//!
+//! +//! **prettyplease::unparse** — a minimal `syn` syntax tree pretty-printer +//! +//!
+//! +//! # Overview +//! +//! This is a pretty-printer to turn a `syn` syntax tree into a `String` of +//! well-formatted source code. In contrast to rustfmt, this library is intended +//! to be suitable for arbitrary generated code. +//! +//! Rustfmt prioritizes high-quality output that is impeccable enough that you'd +//! be comfortable spending your career staring at its output — but that +//! means some heavyweight algorithms, and it has a tendency to bail out on code +//! that is hard to format (for example [rustfmt#3697], and there are dozens +//! more issues like it). That's not necessarily a big deal for human-generated +//! code because when code gets highly nested, the human will naturally be +//! inclined to refactor into more easily formattable code. But for generated +//! code, having the formatter just give up leaves it totally unreadable. +//! +//! [rustfmt#3697]: https://github.com/rust-lang/rustfmt/issues/3697 +//! +//! This library is designed using the simplest possible algorithm and data +//! structures that can deliver about 95% of the quality of rustfmt-formatted +//! output. In my experience testing real-world code, approximately 97-98% of +//! output lines come out identical between rustfmt's formatting and this +//! crate's. The rest have slightly different linebreak decisions, but still +//! clearly follow the dominant modern Rust style. +//! +//! The tradeoffs made by this crate are a good fit for generated code that you +//! will *not* spend your career staring at. For example, the output of +//! `bindgen`, or the output of `cargo-expand`. In those cases it's more +//! important that the whole thing be formattable without the formatter giving +//! up, than that it be flawless. +//! +//!
+//! +//! # Feature matrix +//! +//! Here are a few superficial comparisons of this crate against the AST +//! pretty-printer built into rustc, and rustfmt. The sections below go into +//! more detail comparing the output of each of these libraries. +//! +//! | | prettyplease | rustc | rustfmt | +//! |:---|:---:|:---:|:---:| +//! | non-pathological behavior on big or generated code | 💚 | ❌ | ❌ | +//! | idiomatic modern formatting ("locally indistinguishable from rustfmt") | 💚 | ❌ | 💚 | +//! | throughput | 60 MB/s | 39 MB/s | 2.8 MB/s | +//! | number of dependencies | 3 | 72 | 66 | +//! | compile time including dependencies | 2.4 sec | 23.1 sec | 29.8 sec | +//! | buildable using a stable Rust compiler | 💚 | ❌ | ❌ | +//! | published to crates.io | 💚 | ❌ | ❌ | +//! | extensively configurable output | ❌ | ❌ | 💚 | +//! | intended to accommodate hand-maintained source code | ❌ | ❌ | 💚 | +//! +//!
+//! +//! # Comparison to rustfmt +//! +//! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) +//! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) +//! - [output.rustfmt.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustfmt.rs) +//! +//! If you weren't told which output file is which, it would be practically +//! impossible to tell — **except** for line 435 in the rustfmt output, +//! which is more than 1000 characters long because rustfmt just gave up +//! formatting that part of the file: +//! +//! ``` +//! # const _: &str = stringify! {{{ +//! 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[..]; +//! # }}; +//! ``` +//! +//! This is a pretty typical manifestation of rustfmt bailing out in generated +//! code — a chunk of the input ends up on one line. The other +//! manifestation is that you're working on some code, running rustfmt on save +//! like a conscientious developer, but after a while notice it isn't doing +//! anything. You introduce an intentional formatting issue, like a stray indent +//! or semicolon, and run rustfmt to check your suspicion. Nope, it doesn't get +//! cleaned up — rustfmt is just not formatting the part of the file you +//! are working on. +//! +//! The prettyplease library is designed to have no pathological cases that +//! force a bail out; the entire input you give it will get formatted in some +//! "good enough" form. +//! +//! Separately, rustfmt can be problematic to integrate into projects. It's +//! written using rustc's internal syntax tree, so it can't be built by a stable +//! compiler. Its releases are not regularly published to crates.io, so in Cargo +//! builds you'd need to depend on it as a git dependency, which precludes +//! publishing your crate to crates.io also. You can shell out to a `rustfmt` +//! binary, but that'll be whatever rustfmt version is installed on each +//! developer's system (if any), which can lead to spurious diffs in checked-in +//! generated code formatted by different versions. In contrast prettyplease is +//! designed to be easy to pull in as a library, and compiles fast. +//! +//!
+//! +//! # Comparison to rustc_ast_pretty +//! +//! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) +//! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) +//! - [output.rustc.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustc.rs) +//! +//! This is the pretty-printer that gets used when rustc prints source code, +//! such as `rustc -Zunpretty=expanded`. It's used also by the standard +//! library's `stringify!` when stringifying an interpolated macro_rules AST +//! fragment, like an $:expr, and transitively by `dbg!` and many macros in the +//! ecosystem. +//! +//! Rustc's formatting is mostly okay, but does not hew closely to the dominant +//! contemporary style of Rust formatting. Some things wouldn't ever be written +//! on one line, like this `match` expression, and certainly not with a comma in +//! front of the closing brace: +//! +//! ``` +//! # const _: &str = stringify! { +//! fn eq(&self, other: &IpAddr) -> bool { +//! match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } +//! } +//! # }; +//! ``` +//! +//! Some places use non-multiple-of-4 indentation, which is definitely not the +//! norm: +//! +//! ``` +//! # const _: &str = stringify! { +//! 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],},} +//! } +//! # }; +//! ``` +//! +//! And although there isn't an egregious example of it in the link because the +//! input code is pretty tame, in general rustc_ast_pretty has pathological +//! behavior on generated code. It has a tendency to use excessive horizontal +//! indentation and rapidly run out of width: +//! +//! ``` +//! # const _: &str = stringify! { +//! ::std::io::_print(::core::fmt::Arguments::new_v1(&[""], +//! &match (&msg,) { +//! _args => +//! [::core::fmt::ArgumentV1::new(_args.0, +//! ::core::fmt::Display::fmt)], +//! })); +//! # }; +//! ``` +//! +//! The snippets above are clearly different from modern rustfmt style. In +//! contrast, prettyplease is designed to have output that is practically +//! indistinguishable from rustfmt-formatted code. +//! +//!
+//! +//! # Example +//! +//! ``` +//! // [dependencies] +//! // prettyplease = "0.1" +//! // syn = { version = "1", default-features = false, features = ["full", "parsing"] } +//! +//! const INPUT: &str = stringify! { +//! use crate::{ +//! lazy::{Lazy, SyncLazy, SyncOnceCell}, panic, +//! sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, +//! mpsc::channel, Mutex, }, +//! thread, +//! }; +//! impl Into for T where U: From { +//! fn into(self) -> U { U::from(self) } +//! } +//! }; +//! +//! fn main() { +//! let syntax_tree = syn::parse_file(INPUT).unwrap(); +//! let formatted = prettyplease::unparse(&syntax_tree); +//! print!("{}", formatted); +//! } +//! ``` +//! +//!
+//! +//! # Algorithm notes +//! +//! The approach and terminology used in the implementation are derived from +//! [*Derek C. Oppen, "Pretty Printing" (1979)*][paper], on which +//! rustc_ast_pretty is also based, and from rustc_ast_pretty's implementation +//! written by Graydon Hoare in 2011 (and modernized over the years by dozens of +//! volunteer maintainers). +//! +//! [paper]: http://i.stanford.edu/pub/cstr/reports/cs/tr/79/770/CS-TR-79-770.pdf +//! +//! The paper describes two language-agnostic interacting procedures `Scan()` +//! and `Print()`. Language-specific code decomposes an input data structure +//! into a stream of `string` and `break` tokens, and `begin` and `end` tokens +//! for grouping. Each `begin`–`end` range may be identified as either +//! "consistent breaking" or "inconsistent breaking". If a group is consistently +//! breaking, then if the whole contents do not fit on the line, *every* `break` +//! token in the group will receive a linebreak. This is appropriate, for +//! example, for Rust struct literals, or arguments of a function call. If a +//! group is inconsistently breaking, then the `string` tokens in the group are +//! greedily placed on the line until out of space, and linebroken only at those +//! `break` tokens for which the next string would not fit. For example, this is +//! appropriate for the contents of a braced `use` statement in Rust. +//! +//! Scan's job is to efficiently accumulate sizing information about groups and +//! breaks. For every `begin` token we compute the distance to the matched `end` +//! token, and for every `break` we compute the distance to the next `break`. +//! The algorithm uses a ringbuffer to hold tokens whose size is not yet +//! ascertained. The maximum size of the ringbuffer is bounded by the target +//! line length and does not grow indefinitely, regardless of deep nesting in +//! the input stream. That's because once a group is sufficiently big, the +//! precise size can no longer make a difference to linebreak decisions and we +//! can effectively treat it as "infinity". +//! +//! Print's job is to use the sizing information to efficiently assign a +//! "broken" or "not broken" status to every `begin` token. At that point the +//! output is easily constructed by concatenating `string` tokens and breaking +//! at `break` tokens contained within a broken group. +//! +//! Leveraging these primitives (i.e. cleverly placing the all-or-nothing +//! consistent breaks and greedy inconsistent breaks) to yield +//! rustfmt-compatible formatting for all of Rust's syntax tree nodes is a fun +//! challenge. +//! +//! Here is a visualization of some Rust tokens fed into the pretty printing +//! algorithm. Consistently breaking `begin`—`end` pairs are represented +//! by `«`⁠`»`, inconsistently breaking by `‹`⁠`›`, `break` by `·`, +//! and the rest of the non-whitespace are `string`. +//! +//! ```text +//! use crate::«{· +//! ‹ lazy::«{·‹Lazy,· SyncLazy,· SyncOnceCell›·}»,· +//! panic,· +//! sync::«{· +//! ‹ atomic::«{·‹AtomicUsize,· Ordering::SeqCst›·}»,· +//! mpsc::channel,· Mutex›,· +//! }»,· +//! thread›,· +//! }»;· +//! «‹«impl<«·T‹›,· U‹›·»>» Into<«·U·»>· for T›· +//! where· +//! U:‹ From<«·T·»>›,· +//! {· +//! « fn into(·«·self·») -> U {· +//! ‹ U::from(«·self·»)›· +//! » }· +//! »}· +//! ``` +//! +//! The algorithm described in the paper is not quite sufficient for producing +//! well-formatted Rust code that is locally indistinguishable from rustfmt's +//! style. The reason is that in the paper, the complete non-whitespace contents +//! are assumed to be independent of linebreak decisions, with Scan and Print +//! being only in control of the whitespace (spaces and line breaks). In Rust as +//! idiomatically formattted by rustfmt, that is not the case. Trailing commas +//! are one example; the punctuation is only known *after* the broken vs +//! non-broken status of the surrounding group is known: +//! +//! ``` +//! # struct Struct { x: u64, y: bool } +//! # let xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = 0; +//! # let yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy = true; +//! # +//! let _ = Struct { x: 0, y: true }; +//! +//! let _ = Struct { +//! x: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +//! y: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, //<- trailing comma if the expression wrapped +//! }; +//! ``` +//! +//! The formatting of `match` expressions is another case; we want small arms on +//! the same line as the pattern, and big arms wrapped in a brace. The presence +//! of the brace punctuation, comma, and semicolon are all dependent on whether +//! the arm fits on the line: +//! +//! ``` +//! # struct Entry { nanos: u32 } +//! # let total_nanos = 0u64; +//! # let mut total_secs = 0u64; +//! # let tmp; +//! # let entry = Entry { nanos: 0 }; +//! # const NANOS_PER_SEC: u32 = 1_000_000_000; +//! # +//! match total_nanos.checked_add(entry.nanos as u64) { +//! Some(n) => tmp = n, //<- small arm, inline with comma +//! None => { +//! total_secs = total_secs +//! .checked_add(total_nanos / NANOS_PER_SEC as u64) +//! .expect("overflow in iter::sum over durations"); +//! } //<- big arm, needs brace added, and also semicolon^ +//! } +//! ``` +//! +//! The printing algorithm implementation in this crate accommodates all of +//! these situations with conditional punctuation tokens whose selection can be +//! deferred and populated after it's known that the group is or is not broken. + +#![doc(html_root_url = "https://docs.rs/prettyplease/0.1.25")] +#![allow( + clippy::cast_possible_wrap, + clippy::cast_sign_loss, + clippy::derive_partial_eq_without_eq, + clippy::doc_markdown, + clippy::enum_glob_use, + clippy::items_after_statements, + clippy::let_underscore_untyped, + clippy::match_like_matches_macro, + clippy::match_same_arms, + clippy::module_name_repetitions, + clippy::must_use_candidate, + clippy::needless_pass_by_value, + clippy::similar_names, + clippy::too_many_lines, + clippy::unused_self, + clippy::vec_init_then_push +)] +#![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))] + +mod algorithm; +mod attr; +mod convenience; +mod data; +mod expr; +mod file; +mod generics; +mod item; +mod iter; +mod lifetime; +mod lit; +mod mac; +mod pat; +mod path; +mod ring; +mod stmt; +mod token; +mod ty; + +use crate::algorithm::Printer; +use syn::File; + +// Target line width. +const MARGIN: isize = 89; + +// Number of spaces increment at each level of block indentation. +const INDENT: isize = 4; + +// Every line is allowed at least this much space, even if highly indented. +const MIN_SPACE: isize = 60; + +pub fn unparse(file: &File) -> String { + let mut p = Printer::new(); + p.file(file); + p.eof() +} diff --git a/src/lifetime.rs b/src/lifetime.rs new file mode 100644 index 0000000..665caa3 --- /dev/null +++ b/src/lifetime.rs @@ -0,0 +1,9 @@ +use crate::algorithm::Printer; +use syn::Lifetime; + +impl Printer { + pub fn lifetime(&mut self, lifetime: &Lifetime) { + self.word("'"); + self.ident(&lifetime.ident); + } +} diff --git a/src/lit.rs b/src/lit.rs new file mode 100644 index 0000000..c64b8a1 --- /dev/null +++ b/src/lit.rs @@ -0,0 +1,50 @@ +use crate::algorithm::Printer; +use proc_macro2::Literal; +use syn::{Lit, LitBool, LitByte, LitByteStr, LitChar, LitFloat, LitInt, LitStr}; + +impl Printer { + pub fn lit(&mut self, lit: &Lit) { + match lit { + Lit::Str(lit) => self.lit_str(lit), + Lit::ByteStr(lit) => self.lit_byte_str(lit), + Lit::Byte(lit) => self.lit_byte(lit), + Lit::Char(lit) => self.lit_char(lit), + Lit::Int(lit) => self.lit_int(lit), + Lit::Float(lit) => self.lit_float(lit), + Lit::Bool(lit) => self.lit_bool(lit), + Lit::Verbatim(lit) => self.lit_verbatim(lit), + } + } + + pub fn lit_str(&mut self, lit: &LitStr) { + self.word(lit.token().to_string()); + } + + fn lit_byte_str(&mut self, lit: &LitByteStr) { + self.word(lit.token().to_string()); + } + + fn lit_byte(&mut self, lit: &LitByte) { + self.word(lit.token().to_string()); + } + + fn lit_char(&mut self, lit: &LitChar) { + self.word(lit.token().to_string()); + } + + fn lit_int(&mut self, lit: &LitInt) { + self.word(lit.token().to_string()); + } + + fn lit_float(&mut self, lit: &LitFloat) { + self.word(lit.token().to_string()); + } + + fn lit_bool(&mut self, lit: &LitBool) { + self.word(if lit.value { "true" } else { "false" }); + } + + fn lit_verbatim(&mut self, token: &Literal) { + self.word(token.to_string()); + } +} diff --git a/src/mac.rs b/src/mac.rs new file mode 100644 index 0000000..9c4c119 --- /dev/null +++ b/src/mac.rs @@ -0,0 +1,220 @@ +use crate::algorithm::Printer; +use crate::token::Token; +use crate::INDENT; +use proc_macro2::{Delimiter, Spacing, TokenStream}; +use syn::{Ident, Macro, MacroDelimiter, PathArguments}; + +impl Printer { + pub fn mac(&mut self, mac: &Macro, ident: Option<&Ident>) { + let is_macro_rules = mac.path.leading_colon.is_none() + && mac.path.segments.len() == 1 + && matches!(mac.path.segments[0].arguments, PathArguments::None) + && mac.path.segments[0].ident == "macro_rules"; + if is_macro_rules { + if let Some(ident) = ident { + self.macro_rules(ident, &mac.tokens); + return; + } + } + self.path(&mac.path); + self.word("!"); + if let Some(ident) = ident { + self.nbsp(); + self.ident(ident); + } + let (open, close, delimiter_break) = match mac.delimiter { + MacroDelimiter::Paren(_) => ("(", ")", Self::zerobreak as fn(&mut Self)), + MacroDelimiter::Brace(_) => (" {", "}", Self::hardbreak as fn(&mut Self)), + MacroDelimiter::Bracket(_) => ("[", "]", Self::zerobreak as fn(&mut Self)), + }; + self.word(open); + self.cbox(INDENT); + delimiter_break(self); + self.ibox(0); + self.macro_rules_tokens(mac.tokens.clone(), false); + self.end(); + delimiter_break(self); + self.offset(-INDENT); + self.end(); + self.word(close); + } + + pub fn mac_semi_if_needed(&mut self, delimiter: &MacroDelimiter) { + match delimiter { + MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => self.word(";"), + MacroDelimiter::Brace(_) => {} + } + } + + fn macro_rules(&mut self, name: &Ident, rules: &TokenStream) { + enum State { + Start, + Matcher, + Equal, + Greater, + Expander, + } + + use State::*; + + self.word("macro_rules! "); + self.ident(name); + self.word(" {"); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + let mut state = State::Start; + for tt in rules.clone() { + let token = Token::from(tt); + match (state, token) { + (Start, Token::Group(delimiter, stream)) => { + self.delimiter_open(delimiter); + if !stream.is_empty() { + self.cbox(INDENT); + self.zerobreak(); + self.ibox(0); + self.macro_rules_tokens(stream, true); + self.end(); + self.zerobreak(); + self.offset(-INDENT); + self.end(); + } + self.delimiter_close(delimiter); + state = Matcher; + } + (Matcher, Token::Punct('=', Spacing::Joint)) => { + self.word(" ="); + state = Equal; + } + (Equal, Token::Punct('>', Spacing::Alone)) => { + self.word(">"); + state = Greater; + } + (Greater, Token::Group(_delimiter, stream)) => { + self.word(" {"); + self.neverbreak(); + if !stream.is_empty() { + self.cbox(INDENT); + self.hardbreak(); + self.ibox(0); + self.macro_rules_tokens(stream, false); + self.end(); + self.hardbreak(); + self.offset(-INDENT); + self.end(); + } + self.word("}"); + state = Expander; + } + (Expander, Token::Punct(';', Spacing::Alone)) => { + self.word(";"); + self.hardbreak(); + state = Start; + } + _ => unimplemented!("bad macro_rules syntax"), + } + } + match state { + Start => {} + Expander => { + self.word(";"); + self.hardbreak(); + } + _ => self.hardbreak(), + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn macro_rules_tokens(&mut self, stream: TokenStream, matcher: bool) { + #[derive(PartialEq)] + enum State { + Start, + Dollar, + DollarIdent, + DollarIdentColon, + DollarParen, + DollarParenSep, + Pound, + PoundBang, + Dot, + Colon, + Colon2, + Ident, + IdentBang, + Delim, + Other, + } + + use State::*; + + let mut state = Start; + let mut previous_is_joint = true; + for tt in stream { + let token = Token::from(tt); + let (needs_space, next_state) = match (&state, &token) { + (Dollar, Token::Ident(_)) => (false, if matcher { DollarIdent } else { Other }), + (DollarIdent, Token::Punct(':', Spacing::Alone)) => (false, DollarIdentColon), + (DollarIdentColon, Token::Ident(_)) => (false, Other), + (DollarParen, Token::Punct('+' | '*' | '?', Spacing::Alone)) => (false, Other), + (DollarParen, Token::Ident(_) | Token::Literal(_)) => (false, DollarParenSep), + (DollarParen, Token::Punct(_, Spacing::Joint)) => (false, DollarParen), + (DollarParen, Token::Punct(_, Spacing::Alone)) => (false, DollarParenSep), + (DollarParenSep, Token::Punct('+' | '*', _)) => (false, Other), + (Pound, Token::Punct('!', _)) => (false, PoundBang), + (Dollar, Token::Group(Delimiter::Parenthesis, _)) => (false, DollarParen), + (Pound | PoundBang, Token::Group(Delimiter::Bracket, _)) => (false, Other), + (Ident, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => { + (false, Delim) + } + (Ident, Token::Punct('!', Spacing::Alone)) => (false, IdentBang), + (IdentBang, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => { + (false, Other) + } + (Colon, Token::Punct(':', _)) => (false, Colon2), + (_, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => (true, Delim), + (_, Token::Group(Delimiter::Brace | Delimiter::None, _)) => (true, Other), + (_, Token::Ident(ident)) if !is_keyword(ident) => { + (state != Dot && state != Colon2, Ident) + } + (_, Token::Literal(_)) => (state != Dot, Ident), + (_, Token::Punct(',' | ';', _)) => (false, Other), + (_, Token::Punct('.', _)) if !matcher => (state != Ident && state != Delim, Dot), + (_, Token::Punct(':', Spacing::Joint)) => (state != Ident, Colon), + (_, Token::Punct('$', _)) => (true, Dollar), + (_, Token::Punct('#', _)) => (true, Pound), + (_, _) => (true, Other), + }; + if !previous_is_joint { + if needs_space { + self.space(); + } else if let Token::Punct('.', _) = token { + self.zerobreak(); + } + } + previous_is_joint = match token { + Token::Punct(_, Spacing::Joint) | Token::Punct('$', _) => true, + _ => false, + }; + self.single_token( + token, + if matcher { + |printer, stream| printer.macro_rules_tokens(stream, true) + } else { + |printer, stream| printer.macro_rules_tokens(stream, false) + }, + ); + state = next_state; + } + } +} + +fn is_keyword(ident: &Ident) -> bool { + match ident.to_string().as_str() { + "as" | "box" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" + | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" + | "move" | "mut" | "pub" | "ref" | "return" | "static" | "struct" | "trait" | "type" + | "unsafe" | "use" | "where" | "while" | "yield" => true, + _ => false, + } +} diff --git a/src/pat.rs b/src/pat.rs new file mode 100644 index 0000000..4cec22c --- /dev/null +++ b/src/pat.rs @@ -0,0 +1,210 @@ +use crate::algorithm::Printer; +use crate::iter::IterDelimited; +use crate::INDENT; +use proc_macro2::TokenStream; +use syn::{ + FieldPat, Pat, PatBox, PatIdent, PatLit, PatMacro, PatOr, PatPath, PatRange, PatReference, + PatRest, PatSlice, PatStruct, PatTuple, PatTupleStruct, PatType, PatWild, RangeLimits, +}; + +impl Printer { + pub fn pat(&mut self, pat: &Pat) { + match pat { + Pat::Box(pat) => self.pat_box(pat), + Pat::Ident(pat) => self.pat_ident(pat), + Pat::Lit(pat) => self.pat_lit(pat), + Pat::Macro(pat) => self.pat_macro(pat), + Pat::Or(pat) => self.pat_or(pat), + Pat::Path(pat) => self.pat_path(pat), + Pat::Range(pat) => self.pat_range(pat), + Pat::Reference(pat) => self.pat_reference(pat), + Pat::Rest(pat) => self.pat_rest(pat), + Pat::Slice(pat) => self.pat_slice(pat), + Pat::Struct(pat) => self.pat_struct(pat), + Pat::Tuple(pat) => self.pat_tuple(pat), + Pat::TupleStruct(pat) => self.pat_tuple_struct(pat), + Pat::Type(pat) => self.pat_type(pat), + Pat::Verbatim(pat) => self.pat_verbatim(pat), + Pat::Wild(pat) => self.pat_wild(pat), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Pat"), + } + } + + fn pat_box(&mut self, pat: &PatBox) { + self.outer_attrs(&pat.attrs); + self.word("box "); + self.pat(&pat.pat); + } + + fn pat_ident(&mut self, pat: &PatIdent) { + self.outer_attrs(&pat.attrs); + if pat.by_ref.is_some() { + self.word("ref "); + } + if pat.mutability.is_some() { + self.word("mut "); + } + self.ident(&pat.ident); + if let Some((_at_token, subpat)) = &pat.subpat { + self.word(" @ "); + self.pat(subpat); + } + } + + fn pat_lit(&mut self, pat: &PatLit) { + self.outer_attrs(&pat.attrs); + self.expr(&pat.expr); + } + + fn pat_macro(&mut self, pat: &PatMacro) { + self.outer_attrs(&pat.attrs); + self.mac(&pat.mac, None); + } + + fn pat_or(&mut self, pat: &PatOr) { + self.outer_attrs(&pat.attrs); + let mut consistent_break = false; + for case in &pat.cases { + match case { + Pat::Lit(_) | Pat::Wild(_) => {} + _ => { + consistent_break = true; + break; + } + } + } + if consistent_break { + self.cbox(0); + } else { + self.ibox(0); + } + for case in pat.cases.iter().delimited() { + if !case.is_first { + self.space(); + self.word("| "); + } + self.pat(&case); + } + self.end(); + } + + fn pat_path(&mut self, pat: &PatPath) { + self.outer_attrs(&pat.attrs); + self.qpath(&pat.qself, &pat.path); + } + + fn pat_range(&mut self, pat: &PatRange) { + self.outer_attrs(&pat.attrs); + self.expr(&pat.lo); + match &pat.limits { + RangeLimits::HalfOpen(_) => self.word(".."), + RangeLimits::Closed(_) => self.word("..="), + } + self.expr(&pat.hi); + } + + fn pat_reference(&mut self, pat: &PatReference) { + self.outer_attrs(&pat.attrs); + self.word("&"); + if pat.mutability.is_some() { + self.word("mut "); + } + self.pat(&pat.pat); + } + + fn pat_rest(&mut self, pat: &PatRest) { + self.outer_attrs(&pat.attrs); + self.word(".."); + } + + fn pat_slice(&mut self, pat: &PatSlice) { + self.outer_attrs(&pat.attrs); + self.word("["); + for elem in pat.elems.iter().delimited() { + self.pat(&elem); + self.trailing_comma(elem.is_last); + } + self.word("]"); + } + + fn pat_struct(&mut self, pat: &PatStruct) { + self.outer_attrs(&pat.attrs); + self.cbox(INDENT); + self.path(&pat.path); + self.word(" {"); + self.space_if_nonempty(); + for field in pat.fields.iter().delimited() { + self.field_pat(&field); + self.trailing_comma_or_space(field.is_last && pat.dot2_token.is_none()); + } + if pat.dot2_token.is_some() { + self.word(".."); + self.space(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn pat_tuple(&mut self, pat: &PatTuple) { + self.outer_attrs(&pat.attrs); + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in pat.elems.iter().delimited() { + self.pat(&elem); + if pat.elems.len() == 1 { + if pat.elems.trailing_punct() { + self.word(","); + } + self.zerobreak(); + } else { + self.trailing_comma(elem.is_last); + } + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + fn pat_tuple_struct(&mut self, pat: &PatTupleStruct) { + self.outer_attrs(&pat.attrs); + self.path(&pat.path); + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in pat.pat.elems.iter().delimited() { + self.pat(&elem); + self.trailing_comma(elem.is_last); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + pub fn pat_type(&mut self, pat: &PatType) { + self.outer_attrs(&pat.attrs); + self.pat(&pat.pat); + self.word(": "); + self.ty(&pat.ty); + } + + fn pat_verbatim(&mut self, pat: &TokenStream) { + unimplemented!("Pat::Verbatim `{}`", pat); + } + + fn pat_wild(&mut self, pat: &PatWild) { + self.outer_attrs(&pat.attrs); + self.word("_"); + } + + fn field_pat(&mut self, field_pat: &FieldPat) { + self.outer_attrs(&field_pat.attrs); + if field_pat.colon_token.is_some() { + self.member(&field_pat.member); + self.word(": "); + } + self.pat(&field_pat.pat); + } +} diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..53d4b4c --- /dev/null +++ b/src/path.rs @@ -0,0 +1,174 @@ +use crate::algorithm::Printer; +use crate::iter::IterDelimited; +use crate::INDENT; +use std::ptr; +use syn::{ + AngleBracketedGenericArguments, Binding, Constraint, Expr, GenericArgument, + ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf, +}; + +impl Printer { + pub fn path(&mut self, path: &Path) { + assert!(!path.segments.is_empty()); + for segment in path.segments.iter().delimited() { + if !segment.is_first || path.leading_colon.is_some() { + self.word("::"); + } + self.path_segment(&segment); + } + } + + pub fn path_segment(&mut self, segment: &PathSegment) { + self.ident(&segment.ident); + self.path_arguments(&segment.arguments); + } + + fn path_arguments(&mut self, arguments: &PathArguments) { + match arguments { + PathArguments::None => {} + PathArguments::AngleBracketed(arguments) => { + self.angle_bracketed_generic_arguments(arguments); + } + PathArguments::Parenthesized(arguments) => { + self.parenthesized_generic_arguments(arguments); + } + } + } + + fn generic_argument(&mut self, arg: &GenericArgument) { + match arg { + GenericArgument::Lifetime(lifetime) => self.lifetime(lifetime), + GenericArgument::Type(ty) => self.ty(ty), + GenericArgument::Binding(binding) => self.binding(binding), + GenericArgument::Constraint(constraint) => self.constraint(constraint), + GenericArgument::Const(expr) => { + match expr { + Expr::Lit(expr) => self.expr_lit(expr), + Expr::Block(expr) => self.expr_block(expr), + // ERROR CORRECTION: Add braces to make sure that the + // generated code is valid. + _ => { + self.word("{"); + self.expr(expr); + self.word("}"); + } + } + } + } + } + + fn angle_bracketed_generic_arguments(&mut self, generic: &AngleBracketedGenericArguments) { + if generic.args.is_empty() { + return; + } + + if generic.colon2_token.is_some() { + self.word("::"); + } + self.word("<"); + self.cbox(INDENT); + self.zerobreak(); + + // Print lifetimes before types and consts, all before bindings, + // regardless of their order in self.args. + // + // TODO: ordering rules for const arguments vs type arguments have + // not been settled yet. https://github.com/rust-lang/rust/issues/44580 + #[derive(Ord, PartialOrd, Eq, PartialEq)] + enum Group { + First, + Second, + Third, + } + fn group(arg: &GenericArgument) -> Group { + match arg { + GenericArgument::Lifetime(_) => Group::First, + GenericArgument::Type(_) | GenericArgument::Const(_) => Group::Second, + GenericArgument::Binding(_) | GenericArgument::Constraint(_) => Group::Third, + } + } + let last = generic.args.iter().max_by_key(|param| group(param)); + for current_group in [Group::First, Group::Second, Group::Third] { + for arg in &generic.args { + if group(arg) == current_group { + self.generic_argument(arg); + self.trailing_comma(ptr::eq(arg, last.unwrap())); + } + } + } + + self.offset(-INDENT); + self.end(); + self.word(">"); + } + + fn binding(&mut self, binding: &Binding) { + self.ident(&binding.ident); + self.word(" = "); + self.ty(&binding.ty); + } + + fn constraint(&mut self, constraint: &Constraint) { + self.ident(&constraint.ident); + self.ibox(INDENT); + for bound in constraint.bounds.iter().delimited() { + if bound.is_first { + self.word(": "); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&bound); + } + self.end(); + } + + fn parenthesized_generic_arguments(&mut self, arguments: &ParenthesizedGenericArguments) { + self.cbox(INDENT); + self.word("("); + self.zerobreak(); + for ty in arguments.inputs.iter().delimited() { + self.ty(&ty); + self.trailing_comma(ty.is_last); + } + self.offset(-INDENT); + self.word(")"); + self.return_type(&arguments.output); + self.end(); + } + + pub fn qpath(&mut self, qself: &Option, path: &Path) { + let qself = match qself { + Some(qself) => qself, + None => { + self.path(path); + return; + } + }; + + assert!(qself.position < path.segments.len()); + + self.word("<"); + self.ty(&qself.ty); + + let mut segments = path.segments.iter(); + if qself.position > 0 { + self.word(" as "); + for segment in segments.by_ref().take(qself.position).delimited() { + if !segment.is_first || path.leading_colon.is_some() { + self.word("::"); + } + self.path_segment(&segment); + if segment.is_last { + self.word(">"); + } + } + } else { + self.word(">"); + } + for segment in segments { + self.word("::"); + self.path_segment(segment); + } + } +} diff --git a/src/ring.rs b/src/ring.rs new file mode 100644 index 0000000..aff9258 --- /dev/null +++ b/src/ring.rs @@ -0,0 +1,81 @@ +use std::collections::VecDeque; +use std::ops::{Index, IndexMut}; + +pub struct RingBuffer { + data: VecDeque, + // Abstract index of data[0] in infinitely sized queue + offset: usize, +} + +impl RingBuffer { + pub fn new() -> Self { + RingBuffer { + data: VecDeque::new(), + offset: 0, + } + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn push(&mut self, value: T) -> usize { + let index = self.offset + self.data.len(); + self.data.push_back(value); + index + } + + pub fn clear(&mut self) { + self.data.clear(); + } + + pub fn index_of_first(&self) -> usize { + self.offset + } + + pub fn first(&self) -> &T { + &self.data[0] + } + + pub fn first_mut(&mut self) -> &mut T { + &mut self.data[0] + } + + pub fn pop_first(&mut self) -> T { + self.offset += 1; + self.data.pop_front().unwrap() + } + + pub fn last(&self) -> &T { + self.data.back().unwrap() + } + + pub fn last_mut(&mut self) -> &mut T { + self.data.back_mut().unwrap() + } + + pub fn second_last(&self) -> &T { + &self.data[self.data.len() - 2] + } + + pub fn pop_last(&mut self) { + self.data.pop_back().unwrap(); + } +} + +impl Index for RingBuffer { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + &self.data[index.checked_sub(self.offset).unwrap()] + } +} + +impl IndexMut for RingBuffer { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.data[index.checked_sub(self.offset).unwrap()] + } +} diff --git a/src/stmt.rs b/src/stmt.rs new file mode 100644 index 0000000..a127b57 --- /dev/null +++ b/src/stmt.rs @@ -0,0 +1,85 @@ +use crate::algorithm::Printer; +use syn::{Expr, Stmt}; + +impl Printer { + pub fn stmt(&mut self, stmt: &Stmt) { + match stmt { + Stmt::Local(local) => { + self.outer_attrs(&local.attrs); + self.ibox(0); + self.word("let "); + self.pat(&local.pat); + if let Some((_eq, init)) = &local.init { + self.word(" = "); + self.neverbreak(); + self.expr(init); + } + self.word(";"); + self.end(); + self.hardbreak(); + } + Stmt::Item(item) => self.item(item), + Stmt::Expr(expr) => { + if break_after(expr) { + self.ibox(0); + self.expr_beginning_of_line(expr, true); + if add_semi(expr) { + self.word(";"); + } + self.end(); + self.hardbreak(); + } else { + self.expr_beginning_of_line(expr, true); + } + } + Stmt::Semi(expr, _semi) => { + if let Expr::Verbatim(tokens) = expr { + if tokens.is_empty() { + return; + } + } + self.ibox(0); + self.expr_beginning_of_line(expr, true); + if !remove_semi(expr) { + self.word(";"); + } + self.end(); + self.hardbreak(); + } + } + } +} + +pub fn add_semi(expr: &Expr) -> bool { + match expr { + Expr::Assign(_) + | Expr::AssignOp(_) + | Expr::Break(_) + | Expr::Continue(_) + | Expr::Return(_) + | Expr::Yield(_) => true, + Expr::Group(group) => add_semi(&group.expr), + _ => false, + } +} + +pub fn break_after(expr: &Expr) -> bool { + if let Expr::Group(group) = expr { + if let Expr::Verbatim(verbatim) = group.expr.as_ref() { + return !verbatim.is_empty(); + } + } + true +} + +fn remove_semi(expr: &Expr) -> bool { + match expr { + Expr::ForLoop(_) | Expr::While(_) => true, + Expr::Group(group) => remove_semi(&group.expr), + Expr::If(expr) => match &expr.else_branch { + Some((_else_token, else_branch)) => remove_semi(else_branch), + None => true, + }, + _ => false, + } +} diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..e41fd72 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,80 @@ +use crate::algorithm::Printer; +use proc_macro2::{Delimiter, Ident, Literal, Spacing, TokenStream, TokenTree}; + +impl Printer { + pub fn single_token(&mut self, token: Token, group_contents: fn(&mut Self, TokenStream)) { + match token { + Token::Group(delimiter, stream) => self.token_group(delimiter, stream, group_contents), + Token::Ident(ident) => self.ident(&ident), + Token::Punct(ch, _spacing) => self.token_punct(ch), + Token::Literal(literal) => self.token_literal(&literal), + } + } + + fn token_group( + &mut self, + delimiter: Delimiter, + stream: TokenStream, + group_contents: fn(&mut Self, TokenStream), + ) { + self.delimiter_open(delimiter); + if !stream.is_empty() { + if delimiter == Delimiter::Brace { + self.space(); + } + group_contents(self, stream); + if delimiter == Delimiter::Brace { + self.space(); + } + } + self.delimiter_close(delimiter); + } + + pub fn ident(&mut self, ident: &Ident) { + self.word(ident.to_string()); + } + + pub fn token_punct(&mut self, ch: char) { + self.word(ch.to_string()); + } + + pub fn token_literal(&mut self, literal: &Literal) { + self.word(literal.to_string()); + } + + pub fn delimiter_open(&mut self, delimiter: Delimiter) { + self.word(match delimiter { + Delimiter::Parenthesis => "(", + Delimiter::Brace => "{", + Delimiter::Bracket => "[", + Delimiter::None => return, + }); + } + + pub fn delimiter_close(&mut self, delimiter: Delimiter) { + self.word(match delimiter { + Delimiter::Parenthesis => ")", + Delimiter::Brace => "}", + Delimiter::Bracket => "]", + Delimiter::None => return, + }); + } +} + +pub enum Token { + Group(Delimiter, TokenStream), + Ident(Ident), + Punct(char, Spacing), + Literal(Literal), +} + +impl From for Token { + fn from(tt: TokenTree) -> Self { + match tt { + TokenTree::Group(group) => Token::Group(group.delimiter(), group.stream()), + TokenTree::Ident(ident) => Token::Ident(ident), + TokenTree::Punct(punct) => Token::Punct(punct.as_char(), punct.spacing()), + TokenTree::Literal(literal) => Token::Literal(literal), + } + } +} diff --git a/src/ty.rs b/src/ty.rs new file mode 100644 index 0000000..7bbdf46 --- /dev/null +++ b/src/ty.rs @@ -0,0 +1,241 @@ +use crate::algorithm::Printer; +use crate::iter::IterDelimited; +use crate::INDENT; +use proc_macro2::TokenStream; +use syn::{ + Abi, BareFnArg, ReturnType, Type, TypeArray, TypeBareFn, TypeGroup, TypeImplTrait, TypeInfer, + TypeMacro, TypeNever, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTraitObject, + TypeTuple, Variadic, +}; + +impl Printer { + pub fn ty(&mut self, ty: &Type) { + match ty { + Type::Array(ty) => self.type_array(ty), + Type::BareFn(ty) => self.type_bare_fn(ty), + Type::Group(ty) => self.type_group(ty), + Type::ImplTrait(ty) => self.type_impl_trait(ty), + Type::Infer(ty) => self.type_infer(ty), + Type::Macro(ty) => self.type_macro(ty), + Type::Never(ty) => self.type_never(ty), + Type::Paren(ty) => self.type_paren(ty), + Type::Path(ty) => self.type_path(ty), + Type::Ptr(ty) => self.type_ptr(ty), + Type::Reference(ty) => self.type_reference(ty), + Type::Slice(ty) => self.type_slice(ty), + Type::TraitObject(ty) => self.type_trait_object(ty), + Type::Tuple(ty) => self.type_tuple(ty), + Type::Verbatim(ty) => self.type_verbatim(ty), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Type"), + } + } + + fn type_array(&mut self, ty: &TypeArray) { + self.word("["); + self.ty(&ty.elem); + self.word("; "); + self.expr(&ty.len); + self.word("]"); + } + + fn type_bare_fn(&mut self, ty: &TypeBareFn) { + if let Some(bound_lifetimes) = &ty.lifetimes { + self.bound_lifetimes(bound_lifetimes); + } + if ty.unsafety.is_some() { + self.word("unsafe "); + } + if let Some(abi) = &ty.abi { + self.abi(abi); + } + self.word("fn("); + self.cbox(INDENT); + self.zerobreak(); + for bare_fn_arg in ty.inputs.iter().delimited() { + self.bare_fn_arg(&bare_fn_arg); + self.trailing_comma(bare_fn_arg.is_last && ty.variadic.is_none()); + } + if let Some(variadic) = &ty.variadic { + self.variadic(variadic); + self.zerobreak(); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + self.return_type(&ty.output); + } + + fn type_group(&mut self, ty: &TypeGroup) { + self.ty(&ty.elem); + } + + fn type_impl_trait(&mut self, ty: &TypeImplTrait) { + self.word("impl "); + for type_param_bound in ty.bounds.iter().delimited() { + if !type_param_bound.is_first { + self.word(" + "); + } + self.type_param_bound(&type_param_bound); + } + } + + fn type_infer(&mut self, ty: &TypeInfer) { + let _ = ty; + self.word("_"); + } + + fn type_macro(&mut self, ty: &TypeMacro) { + self.mac(&ty.mac, None); + } + + fn type_never(&mut self, ty: &TypeNever) { + let _ = ty; + self.word("!"); + } + + fn type_paren(&mut self, ty: &TypeParen) { + self.word("("); + self.ty(&ty.elem); + self.word(")"); + } + + fn type_path(&mut self, ty: &TypePath) { + self.qpath(&ty.qself, &ty.path); + } + + fn type_ptr(&mut self, ty: &TypePtr) { + self.word("*"); + if ty.mutability.is_some() { + self.word("mut "); + } else { + self.word("const "); + } + self.ty(&ty.elem); + } + + fn type_reference(&mut self, ty: &TypeReference) { + self.word("&"); + if let Some(lifetime) = &ty.lifetime { + self.lifetime(lifetime); + self.nbsp(); + } + if ty.mutability.is_some() { + self.word("mut "); + } + self.ty(&ty.elem); + } + + fn type_slice(&mut self, ty: &TypeSlice) { + self.word("["); + self.ty(&ty.elem); + self.word("]"); + } + + fn type_trait_object(&mut self, ty: &TypeTraitObject) { + self.word("dyn "); + for type_param_bound in ty.bounds.iter().delimited() { + if !type_param_bound.is_first { + self.word(" + "); + } + self.type_param_bound(&type_param_bound); + } + } + + fn type_tuple(&mut self, ty: &TypeTuple) { + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in ty.elems.iter().delimited() { + self.ty(&elem); + if ty.elems.len() == 1 { + self.word(","); + self.zerobreak(); + } else { + self.trailing_comma(elem.is_last); + } + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + #[cfg(not(feature = "verbatim"))] + fn type_verbatim(&mut self, ty: &TokenStream) { + if ty.to_string() == "..." { + self.word("..."); + } else { + unimplemented!("Type::Verbatim `{}`", ty); + } + } + + #[cfg(feature = "verbatim")] + fn type_verbatim(&mut self, tokens: &TokenStream) { + use syn::parse::{Parse, ParseStream, Result}; + use syn::{token, ExprBlock, Lit}; + + enum TypeVerbatim { + Lit(Lit), + Block(ExprBlock), + } + + impl Parse for TypeVerbatim { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Lit) { + input.parse().map(TypeVerbatim::Lit) + } else if lookahead.peek(token::Brace) { + input.parse().map(TypeVerbatim::Block) + } else { + Err(lookahead.error()) + } + } + } + + let ty: TypeVerbatim = match syn::parse2(tokens.clone()) { + Ok(ty) => ty, + Err(_) => unimplemented!("Type::Verbatim `{}`", tokens), + }; + + match ty { + TypeVerbatim::Lit(lit) => { + self.lit(&lit); + } + TypeVerbatim::Block(block) => { + self.expr_block(&block); + } + } + } + + pub fn return_type(&mut self, ty: &ReturnType) { + match ty { + ReturnType::Default => {} + ReturnType::Type(_arrow, ty) => { + self.word(" -> "); + self.ty(ty); + } + } + } + + fn bare_fn_arg(&mut self, bare_fn_arg: &BareFnArg) { + self.outer_attrs(&bare_fn_arg.attrs); + if let Some((name, _colon)) = &bare_fn_arg.name { + self.ident(name); + self.word(": "); + } + self.ty(&bare_fn_arg.ty); + } + + fn variadic(&mut self, variadic: &Variadic) { + self.outer_attrs(&variadic.attrs); + self.word("..."); + } + + pub fn abi(&mut self, abi: &Abi) { + self.word("extern "); + if let Some(name) = &abi.name { + self.lit_str(name); + self.nbsp(); + } + } +} -- cgit v1.2.3