From 1453885ed2c3a5159431bb41398b9b8bea4d49f5 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 17 Feb 2020 06:09:45 -0600 Subject: [PATCH] woops --- syn-serde | 1 - syn-serde/.editorconfig | 22 + syn-serde/.gitattributes | 4 + syn-serde/.github/CODEOWNERS | 1 + syn-serde/.github/bors.toml | 10 + syn-serde/.github/workflows/ci.yml | 95 + syn-serde/.gitignore | 7 + syn-serde/CHANGELOG.md | 21 + syn-serde/Cargo.toml | 36 + syn-serde/LICENSE-APACHE | 201 + syn-serde/LICENSE-MIT | 23 + syn-serde/README.md | 130 + syn-serde/ci/install-component.sh | 29 + syn-serde/ci/install-rust.sh | 20 + syn-serde/codegen/Cargo.toml | 17 + syn-serde/codegen/README.md | 12 + syn-serde/codegen/src/file.rs | 40 + syn-serde/codegen/src/gen.rs | 20 + syn-serde/codegen/src/main.rs | 32 + syn-serde/codegen/src/serde.rs | 293 + syn-serde/examples/README.md | 9 + syn-serde/examples/json2rust/Cargo.toml | 12 + syn-serde/examples/json2rust/README.md | 7 + .../examples/json2rust/json2rust_main.json | 1147 ++++ syn-serde/examples/json2rust/src/main.rs | 54 + syn-serde/examples/rust2json/Cargo.toml | 10 + syn-serde/examples/rust2json/README.md | 7 + .../examples/rust2json/rust2json_main.json | 782 +++ syn-serde/examples/rust2json/src/main.rs | 39 + syn-serde/src/attr.rs | 115 + syn-serde/src/data.rs | 185 + syn-serde/src/expr.rs | 779 +++ syn-serde/src/file.rs | 12 + syn-serde/src/gen/mod.rs | 3398 ++++++++++ syn-serde/src/generics.rs | 223 + syn-serde/src/item.rs | 770 +++ syn-serde/src/json.rs | 210 + syn-serde/src/lib.rs | 350 + syn-serde/src/lifetime.rs | 19 + syn-serde/src/lit.rs | 525 ++ syn-serde/src/mac.rs | 19 + syn-serde/src/macros.rs | 57 + syn-serde/src/op.rs | 104 + syn-serde/src/pat.rs | 250 + syn-serde/src/path.rs | 136 + syn-serde/src/stmt.rs | 39 + syn-serde/src/token_stream.rs | 345 + syn-serde/src/ty.rs | 291 + syn-serde/syn.json | 5616 +++++++++++++++++ syn-serde/tests/test_item.rs | 740 +++ 50 files changed, 17263 insertions(+), 1 deletion(-) delete mode 160000 syn-serde create mode 100644 syn-serde/.editorconfig create mode 100644 syn-serde/.gitattributes create mode 100644 syn-serde/.github/CODEOWNERS create mode 100644 syn-serde/.github/bors.toml create mode 100644 syn-serde/.github/workflows/ci.yml create mode 100644 syn-serde/.gitignore create mode 100644 syn-serde/CHANGELOG.md create mode 100644 syn-serde/Cargo.toml create mode 100644 syn-serde/LICENSE-APACHE create mode 100644 syn-serde/LICENSE-MIT create mode 100644 syn-serde/README.md create mode 100644 syn-serde/ci/install-component.sh create mode 100644 syn-serde/ci/install-rust.sh create mode 100644 syn-serde/codegen/Cargo.toml create mode 100644 syn-serde/codegen/README.md create mode 100644 syn-serde/codegen/src/file.rs create mode 100644 syn-serde/codegen/src/gen.rs create mode 100644 syn-serde/codegen/src/main.rs create mode 100644 syn-serde/codegen/src/serde.rs create mode 100644 syn-serde/examples/README.md create mode 100644 syn-serde/examples/json2rust/Cargo.toml create mode 100644 syn-serde/examples/json2rust/README.md create mode 100644 syn-serde/examples/json2rust/json2rust_main.json create mode 100644 syn-serde/examples/json2rust/src/main.rs create mode 100644 syn-serde/examples/rust2json/Cargo.toml create mode 100644 syn-serde/examples/rust2json/README.md create mode 100644 syn-serde/examples/rust2json/rust2json_main.json create mode 100644 syn-serde/examples/rust2json/src/main.rs create mode 100644 syn-serde/src/attr.rs create mode 100644 syn-serde/src/data.rs create mode 100644 syn-serde/src/expr.rs create mode 100644 syn-serde/src/file.rs create mode 100644 syn-serde/src/gen/mod.rs create mode 100644 syn-serde/src/generics.rs create mode 100644 syn-serde/src/item.rs create mode 100644 syn-serde/src/json.rs create mode 100644 syn-serde/src/lib.rs create mode 100644 syn-serde/src/lifetime.rs create mode 100644 syn-serde/src/lit.rs create mode 100644 syn-serde/src/mac.rs create mode 100644 syn-serde/src/macros.rs create mode 100644 syn-serde/src/op.rs create mode 100644 syn-serde/src/pat.rs create mode 100644 syn-serde/src/path.rs create mode 100644 syn-serde/src/stmt.rs create mode 100644 syn-serde/src/token_stream.rs create mode 100644 syn-serde/src/ty.rs create mode 100644 syn-serde/syn.json create mode 100644 syn-serde/tests/test_item.rs diff --git a/syn-serde b/syn-serde deleted file mode 160000 index dff506b..0000000 --- a/syn-serde +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dff506bb8a83702e2dc82b17177dda43e6de0f3a diff --git a/syn-serde/.editorconfig b/syn-serde/.editorconfig new file mode 100644 index 0000000..2d145b1 --- /dev/null +++ b/syn-serde/.editorconfig @@ -0,0 +1,22 @@ +# EditorConfig configuration +# https://editorconfig.org + +# Top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file, utf-8 charset +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +# Match rust/toml, set 4 space indentation +[*.{rs,toml}] +indent_style = space +indent_size = 4 + +# Match json/yaml/markdown, set 2 space indentation +[*.{json,yml,md}] +indent_style = space +indent_size = 2 diff --git a/syn-serde/.gitattributes b/syn-serde/.gitattributes new file mode 100644 index 0000000..45bca84 --- /dev/null +++ b/syn-serde/.gitattributes @@ -0,0 +1,4 @@ +[attr]rust text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4 + +* text=auto eol=lf +*.rs rust diff --git a/syn-serde/.github/CODEOWNERS b/syn-serde/.github/CODEOWNERS new file mode 100644 index 0000000..2fdc28f --- /dev/null +++ b/syn-serde/.github/CODEOWNERS @@ -0,0 +1 @@ +* @taiki-e diff --git a/syn-serde/.github/bors.toml b/syn-serde/.github/bors.toml new file mode 100644 index 0000000..b44dd8c --- /dev/null +++ b/syn-serde/.github/bors.toml @@ -0,0 +1,10 @@ +status = [ + "test (1.31.0)", + "test (stable)", + "test (beta)", + "test (nightly)", + "style (clippy)", + "style (rustfmt)", + "style (rustdoc)", +] +delete_merged_branches = true diff --git a/syn-serde/.github/workflows/ci.yml b/syn-serde/.github/workflows/ci.yml new file mode 100644 index 0000000..fa21e87 --- /dev/null +++ b/syn-serde/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +name: ci + +on: + pull_request: + push: + branches: + - master + - staging + - trying + schedule: + - cron: '00 01 * * *' + +env: + RUSTFLAGS: -Dwarnings + +jobs: + test: + name: test + runs-on: ubuntu-latest + strategy: + matrix: + rust: + # This is the minimum supported Rust version of this crate. + # When updating this, the reminder to update the minimum supported + # Rust version in README.md. + # + # Tests are not run as tests may require newer versions of Rust. + - 1.31.0 + - stable + - beta + - nightly + steps: + - uses: actions/checkout@master + - name: Install Rust + shell: bash + run: | + . ./ci/install-rust.sh ${{ matrix.rust }} + - name: Install cargo-hack + if: matrix.rust != '1.31.0' + run: | + cargo install cargo-hack + - name: cargo check + if: matrix.rust == '1.31.0' + run: | + cargo check --all-features + - name: cargo test + if: matrix.rust != '1.31.0' + run: | + cargo test --all-features + - name: cargo hack check --each-feature + if: matrix.rust != '1.31.0' + run: | + cargo hack check --all --each-feature --no-dev-deps + # Refs: https://github.com/rust-lang/cargo/issues/5657 + - name: cargo check -Zminimal-versions + if: matrix.rust == 'nightly' + run: | + cargo update -Zminimal-versions + cargo hack check --all --all-features --no-dev-deps --ignore-private + + style: + name: style + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + component: + - clippy + - rustfmt + - rustdoc + steps: + - uses: actions/checkout@master + - name: Install Rust + shell: bash + run: | + . ./ci/install-rust.sh + - name: Install component + if: matrix.component != 'rustdoc' + shell: bash + run: | + . ./ci/install-component.sh ${{ matrix.component }} + - name: cargo clippy + if: matrix.component == 'clippy' + run: | + cargo clippy --all --all-features --all-targets + - name: cargo fmt -- --check + if: matrix.component == 'rustfmt' + run: | + cargo fmt --all -- --check + - name: cargo doc + if: matrix.component == 'rustdoc' + env: + RUSTDOCFLAGS: -Dwarnings + run: | + cargo doc --no-deps --all --all-features diff --git a/syn-serde/.gitignore b/syn-serde/.gitignore new file mode 100644 index 0000000..6a57018 --- /dev/null +++ b/syn-serde/.gitignore @@ -0,0 +1,7 @@ +target +**/*.rs.bk +Cargo.lock + +# For platform and editor specific settings, it is recommended to add to +# a global .gitignore file. +# Refs: https://help.github.com/en/articles/ignoring-files#create-a-global-gitignore diff --git a/syn-serde/CHANGELOG.md b/syn-serde/CHANGELOG.md new file mode 100644 index 0000000..dc803fa --- /dev/null +++ b/syn-serde/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +This project adheres to [Semantic Versioning](https://semver.org). + +## [Unreleased] + +# [0.2.0] - 2019-09-16 + +* [Removed error from `to_string` / `to_vec`.][e949263] + +[e949263]: https://github.com/taiki-e/syn-serde/commit/e9492636eb7d58565fc415e55fd824b06b37f3d3 + +# [0.1.0] - 2019-09-16 + +Initial release + +[Unreleased]: https://github.com/taiki-e/syn-serde/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/taiki-e/syn-serde/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/taiki-e/syn-serde/releases/tag/v0.1.0 diff --git a/syn-serde/Cargo.toml b/syn-serde/Cargo.toml new file mode 100644 index 0000000..48b06fc --- /dev/null +++ b/syn-serde/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "syn-serde" +version = "0.2.0" +authors = ["David Tolnay ", "Taiki Endo "] +edition = "2018" +license = "Apache-2.0 OR MIT" +repository = "https://github.com/taiki-e/syn-serde" +homepage = "https://github.com/taiki-e/syn-serde" +documentation = "https://docs.rs/syn-serde" +keywords = ["serde", "serialization", "syn"] +categories = ["development-tools::procedural-macro-helpers", "encoding"] +readme = "README.md" +description = """ +Library to serialize and deserialize Syn syntax trees. +""" + +[features] +json = ["serde_json"] + +[dependencies] +proc-macro2 = { version = "1.0", default-features = false } +serde = { version = "1.0.99", features = ["derive"] } +serde_derive = "1.0.99" # This is necessary to make `-Z minimal-versions` build successful. +serde_json = { version = "1.0", optional = true } +syn = { version = "1.0.5", default-features = false, features = ["full"] } + +[dev-dependencies] +quote = "1.0" +serde_json = "1.0" +syn = { version = "1.0", features = ["full", "extra-traits"] } + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.playground] +all-features = true diff --git a/syn-serde/LICENSE-APACHE b/syn-serde/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/syn-serde/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/syn-serde/LICENSE-MIT b/syn-serde/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/syn-serde/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/syn-serde/README.md b/syn-serde/README.md new file mode 100644 index 0000000..7b34204 --- /dev/null +++ b/syn-serde/README.md @@ -0,0 +1,130 @@ +# syn-serde + +[![crates-badge]][crates-url] +[![docs-badge]][docs-url] +[![license-badge]][license] +[![rustc-badge]][rustc-url] + +[crates-badge]: https://img.shields.io/crates/v/syn-serde.svg +[crates-url]: https://crates.io/crates/syn-serde +[docs-badge]: https://docs.rs/syn-serde/badge.svg +[docs-url]: https://docs.rs/syn-serde +[license-badge]: https://img.shields.io/crates/l/syn-serde.svg +[license]: #license +[rustc-badge]: https://img.shields.io/badge/rustc-1.31+-lightgray.svg +[rustc-url]: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html + +Library to serialize and deserialize [Syn] syntax trees. + +[**Documentation**][docs-url] + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +syn-serde = "0.2" +``` + +The current syn-serde requires Rust 1.31 or later. + +## Examples + +```toml +[dependencies] +syn-serde = { version = "0.2", features = ["json"] } +syn = { version = "1", features = ["full"] } +``` + +```rust +use syn_serde::json; + +let syn_file: syn::File = syn::parse_quote! { + fn main() { + println!("Hello, world!"); + } +}; + +println!("{}", json::to_string_pretty(&syn_file)); +``` + +This prints the following JSON: + +```json +{ + "items": [ + { + "fn": { + "ident": "main", + "inputs": [], + "output": null, + "stmts": [ + { + "semi": { + "macro": { + "path": { + "segments": [ + { + "ident": "println" + } + ] + }, + "delimiter": "paren", + "tokens": [ + { + "lit": "\"Hello, world!\"" + } + ] + } + } + } + ] + } + } + ] +} +``` + +### Rust source file -> JSON representation of the syntax tree + +The [`rust2json`] example parse a Rust source file into a `syn_serde::File` +and print out a JSON representation of the syntax tree. + +[`rust2json`]: examples/rust2json + +### JSON file -> Rust syntax tree + +The [`json2rust`] example parse a JSON file into a `syn_serde::File` and +print out a Rust syntax tree. + +[`json2rust`]: examples/json2rust + +## Optional features + +* **`json`** — Provides functions for JSON <-> Rust serializing and + deserializing. + +## Relationship to Syn + +syn-serde is a fork of [Syn], and syn-serde provides a set of data structures +similar but not identical to [Syn]. All data structures provided by syn-serde +can be converted to the data structures of [Syn] and [proc-macro2]. + +The data structures of syn-serde 0.2 is compatible with the data structures of [Syn] 1.0. + +[Syn]: https://github.com/dtolnay/syn +[proc-macro2]: https://github.com/alexcrichton/proc-macro2 + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work 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/syn-serde/ci/install-component.sh b/syn-serde/ci/install-component.sh new file mode 100644 index 0000000..943755c --- /dev/null +++ b/syn-serde/ci/install-component.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -euo pipefail + +component="${1}" + +if ! rustup component add "${component}" 2>/dev/null; then + # If the component is unavailable on the latest nightly, + # use the latest toolchain with the component available. + # Refs: https://github.com/rust-lang/rustup-components-history#the-web-part + target=$(curl -sSf "https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/${component}") + echo "'${component}' is unavailable on the default toolchain, use the toolchain 'nightly-${target}' instead" + + rustup update "nightly-${target}" --no-self-update + rustup default "nightly-${target}" + + echo "Query rust and cargo versions:" + rustup -V + rustc -V + cargo -V + + rustup component add "${component}" +fi + +echo "Query component versions:" +case "${component}" in + clippy | miri) cargo "${component}" -V ;; + rustfmt) "${component}" -V ;; +esac diff --git a/syn-serde/ci/install-rust.sh b/syn-serde/ci/install-rust.sh new file mode 100644 index 0000000..3e0b27a --- /dev/null +++ b/syn-serde/ci/install-rust.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -euo pipefail + +toolchain="${1:-nightly}" + +if rustup -V 2>/dev/null; then + rustup set profile minimal + rustup update "${toolchain}" --no-self-update + rustup default "${toolchain}" +else + curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain "${toolchain}" + export PATH=${PATH}:${HOME}/.cargo/bin + echo "##[add-path]${HOME}/.cargo/bin" +fi + +echo "Query rust and cargo versions:" +rustup -V +rustc -V +cargo -V diff --git a/syn-serde/codegen/Cargo.toml b/syn-serde/codegen/Cargo.toml new file mode 100644 index 0000000..4cf3d00 --- /dev/null +++ b/syn-serde/codegen/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "syn-serde-internal-codegen" +version = "0.0.0" +authors = ["David Tolnay ", "Nika Layzell ", "Taiki Endo "] +edition = "2018" +publish = false # this is an internal crate which should never be published + +[workspace] +# Prefer that `cargo clean` in syn-serde's directory does not require a rebuild of +# rustfmt in the codegen directory. + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +rustfmt = { package = "rustfmt-nightly", git = "https://github.com/rust-lang-nursery/rustfmt" } +serde_json = "1.0" +syn-codegen = { git = "https://github.com/dtolnay/syn" } diff --git a/syn-serde/codegen/README.md b/syn-serde/codegen/README.md new file mode 100644 index 0000000..68ffef9 --- /dev/null +++ b/syn-serde/codegen/README.md @@ -0,0 +1,12 @@ +# syn_serde_codegen + +This is an internal (not published on crates.io) crate which is used to generate +the files in the `gen/` directory of `syn-serde`. It is used to ensure that the +implementations for `Syn` remain in sync with the +actual AST. + +To run this program, run `cargo run` in this directory, and the `gen/` folder +will be re-generated. + +This program is slow, and is therefore not run when building `syn-serde` as part of +the build script to save on compile time. diff --git a/syn-serde/codegen/src/file.rs b/syn-serde/codegen/src/file.rs new file mode 100644 index 0000000..7dda878 --- /dev/null +++ b/syn-serde/codegen/src/file.rs @@ -0,0 +1,40 @@ +use crate::Result; +use proc_macro2::TokenStream; +use std::{fmt, fs, io::Write, path::Path}; + +pub(crate) fn write>(path: P, content: TokenStream) -> Result<()> { + let mut formatted = Vec::new(); + writeln!( + formatted, + "// This file is @generated by syn-serde-internal-codegen.\n\ + // It is not intended for manual editing.\n" + )?; + + let mut config = rustfmt::Config::default(); + config.set().emit_mode(rustfmt::EmitMode::Stdout); + config.set().verbose(rustfmt::Verbosity::Quiet); + config.set().format_macro_matchers(true); + config.set().normalize_doc_attributes(true); + + let mut session = rustfmt::Session::new(config, Some(&mut formatted)); + session.format(rustfmt::Input::Text(content.to_string())).map_err(RustfmtError)?; + drop(session); + + if path.as_ref().is_file() && fs::read(&path)? == formatted { + return Ok(()); + } + + fs::write(path, formatted)?; + Ok(()) +} + +#[derive(Debug)] +struct RustfmtError(rustfmt::ErrorKind); + +impl fmt::Display for RustfmtError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for RustfmtError {} diff --git a/syn-serde/codegen/src/gen.rs b/syn-serde/codegen/src/gen.rs new file mode 100644 index 0000000..e13647b --- /dev/null +++ b/syn-serde/codegen/src/gen.rs @@ -0,0 +1,20 @@ +use proc_macro2::TokenStream; +use syn_codegen::{Definitions, Node}; + +pub(crate) fn traverse( + defs: &Definitions, + node: fn(&mut TokenStream, &Node, &Definitions), +) -> TokenStream { + let mut types = defs.types.clone(); + types.sort_by(|a, b| a.ident.cmp(&b.ident)); + + let mut traits = TokenStream::new(); + for s in types { + if s.ident == "Reserved" { + continue; + } + node(&mut traits, &s, defs); + } + + traits +} diff --git a/syn-serde/codegen/src/main.rs b/syn-serde/codegen/src/main.rs new file mode 100644 index 0000000..d504483 --- /dev/null +++ b/syn-serde/codegen/src/main.rs @@ -0,0 +1,32 @@ +// Based on https://github.com/dtolnay/syn/tree/1.0.5/codegen. +// +// This crate generates the Syn trait in syn-serde programmatically from +// the syntax tree description. + +#![recursion_limit = "128"] +#![warn(rust_2018_idioms, unreachable_pub)] + +const SYN_JSON: &str = "../syn.json"; + +mod file; +mod gen; +mod serde; + +use std::fs; + +type Result = std::result::Result>; + +fn main() { + if let Err(e) = try_main() { + eprintln!("error: {}", e); + std::process::exit(1); + } +} + +fn try_main() -> Result<()> { + let defs = fs::read_to_string(SYN_JSON)?; + let defs = serde_json::from_str(&defs)?; + + serde::generate(&defs)?; + Ok(()) +} diff --git a/syn-serde/codegen/src/serde.rs b/syn-serde/codegen/src/serde.rs new file mode 100644 index 0000000..0b83e6e --- /dev/null +++ b/syn-serde/codegen/src/serde.rs @@ -0,0 +1,293 @@ +use crate::{file, gen, Result}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn_codegen::{Data, Definitions, Node, Type}; + +const SERDE_SRC: &str = "../src/gen/mod.rs"; + +const IGNORED_TYPES: &[&str] = &[ + /* we don't have them */ + "DeriveInput", + "Data", + "DataStruct", + "DataEnum", + "DataUnion", + /* private */ + "LitByte", + "LitByteStr", + "LitChar", + "LitFloat", + "LitInt", + "LitStr", + /* cannot be implemented by codegen */ + "Type", + "UseTree", + "Visibility", + "Receiver", + /* optimize */ + "Generics", + "ExprMatch", + "Arm", + "TraitItemMethod", + "ItemStruct", + "ReturnType", +]; +const EMPTY_STRUCTS: &[&str] = &["TypeInfer", "TypeNever", "UseGlob", "VisCrate", "VisPublic"]; + +#[allow(clippy::cognitive_complexity)] +fn visit(ty: &Type, name: &TokenStream) -> (Option, TokenStream) { + match ty { + Type::Box(_) | Type::Vec(_) | Type::Punctuated(_) => { + let from = Some(quote!(#name.map_into())); + let into = quote!(#name.map_into()); + (from, into) + } + Type::Option(t) => match &**t { + Type::Token(_) | Type::Group(_) => { + let from = Some(quote!(#name.is_some())); + let into = quote!(default_or_none(#name)); + (from, into) + } + Type::Tuple(t) => { + let mut from_expr = Vec::new(); + let mut from_pat = Vec::new(); + let mut into_expr = Vec::new(); + let mut into_pat = Vec::new(); + + for (i, t) in t.iter().enumerate() { + let id = format_ident!("_{}", i); + let (from, into) = visit(t, "e!((*#id))); + + from_pat.push(id.clone()); + into_expr.push(into); + if from.is_some() { + into_pat.push(id); + from_expr.push(from); + } + } + assert_eq!(from_pat.len(), into_expr.len()); + assert_eq!(into_pat.len(), from_expr.len()); + assert_ne!(into_pat.len(), 0); + + if into_pat.len() == 1 { + let from = Some(quote!(#name.ref_map(|(#(#from_pat),*)| #(#from_expr),*))); + let into = quote!(#name.ref_map(|#(#into_pat),*| (#(#into_expr),*))); + (from, into) + } else { + let from = Some(quote!(#name.ref_map(|(#(#from_pat),*)| (#(#from_expr),*)))); + let into = quote!(#name.ref_map(|(#(#into_pat),*)| (#(#into_expr),*))); + (from, into) + } + } + Type::Box(_) | Type::Vec(_) | Type::Punctuated(_) => { + let from = Some(quote!(#name.ref_map(MapInto::map_into))); + let into = quote!(#name.ref_map(MapInto::map_into)); + (from, into) + } + Type::Std(t) if t == "String" => { + // `From<&String> for String` requires Rust 1.36 or later. + // Refs: https://github.com/rust-lang/rust/pull/59825 + let from = Some(quote!(#name.ref_map(ToString::to_string))); + let into = quote!(#name.ref_map(ToString::to_string)); + (from, into) + } + _ => { + let from = Some(quote!(#name.map_into())); + let into = quote!(#name.map_into()); + (from, into) + } + }, + Type::Token(_) | Type::Group(_) => { + let from = None; + let into = quote!(default()); + (from, into) + } + Type::Syn(t) if t == "Reserved" => { + let from = None; + let into = quote!(default()); + (from, into) + } + Type::Ext(t) if t == "Span" => { + let from = None; + let into = quote!(proc_macro2::Span::call_site()); + (from, into) + } + Type::Syn(_) | Type::Ext(_) => { + let from = Some(quote!(#name.ref_into())); + let into = quote!(#name.ref_into()); + (from, into) + } + Type::Std(_) => { + let from = Some(quote!(#name.into())); + let into = quote!(#name.into()); + (from, into) + } + Type::Tuple(t) => unreachable!("Type::Tuple: {:?}", t), + } +} + +#[allow(clippy::cognitive_complexity)] +fn node(traits: &mut TokenStream, node: &Node, _defs: &Definitions) { + if IGNORED_TYPES.contains(&&*node.ident) { + return; + } + + let ty = format_ident!("{}", &node.ident); + + let mut from_impl = TokenStream::new(); + let mut into_impl = TokenStream::new(); + + match &node.data { + Data::Enum(variants) => { + let mut from_variants = TokenStream::new(); + let mut into_variants = TokenStream::new(); + + for (variant, fields) in variants { + let variant_ident = format_ident!("{}", variant); + + if fields.is_empty() { + from_variants.extend(quote! { + syn::#ty::#variant_ident => { + #ty::#variant_ident + } + }); + into_variants.extend(quote! { + #ty::#variant_ident => { + syn::#ty::#variant_ident + } + }); + } else { + let mut from_expr = Vec::new(); + let mut from_pat = Vec::new(); + let mut into_expr = Vec::new(); + let mut into_pat = Vec::new(); + + for (i, t) in fields.iter().enumerate() { + let id = format_ident!("_{}", i); + let (from, into) = visit(t, "e!((*#id))); + + from_pat.push(id.clone()); + into_expr.push(into); + if from.is_some() { + into_pat.push(id); + from_expr.push(from); + } + } + + if from_expr.is_empty() { + from_variants.extend(quote! { + syn::#ty::#variant_ident(..) => { + #ty::#variant_ident + } + }); + into_variants.extend(quote! { + #ty::#variant_ident => { + syn::#ty::#variant_ident(#(#into_expr),*) + } + }); + } else { + from_variants.extend(quote! { + syn::#ty::#variant_ident(#(#from_pat),*) => { + #ty::#variant_ident(#(#from_expr),*) + } + }); + into_variants.extend(quote! { + #ty::#variant_ident(#(#into_pat),*) => { + syn::#ty::#variant_ident(#(#into_expr),*) + } + }); + } + } + } + + let nonexhaustive = + if node.exhaustive { None } else { Some(quote!(_ => unreachable!())) }; + + from_impl.extend(quote! { + match node { + #from_variants + #nonexhaustive + } + }); + into_impl.extend(quote! { + match node { + #into_variants + #nonexhaustive + } + }); + } + Data::Struct(fields) => { + let mut from_fields = TokenStream::new(); + let mut into_fields = TokenStream::new(); + + for (field, ty) in fields { + let id = format_ident!("{}", field); + let ref_toks = quote!(node.#id); + + let (from, into) = visit(&ty, &ref_toks); + + if from.is_some() { + from_fields.extend(quote! { + #id: #from, + }); + } + into_fields.extend(quote! { + #id: #into, + }); + } + + assert!(!fields.is_empty(), "fields.is_empty: {}", ty); + if !from_fields.is_empty() { + from_impl.extend(quote! { + #ty { + #from_fields + } + }); + into_impl.extend(quote! { + syn::#ty { + #into_fields + } + }); + } else { + assert!(EMPTY_STRUCTS.contains(&&*node.ident), "from_fields.is_empty(): {}", ty); + return; + } + } + Data::Private => unreachable!("Data::Private: {}", ty), + } + + traits.extend(quote! { + syn_trait_impl!(syn::#ty); + impl From<&syn::#ty> for #ty { + fn from(node: &syn::#ty) -> Self { + #from_impl + } + } + impl From<&#ty> for syn::#ty { + fn from(node: &#ty) -> Self { + #into_impl + } + } + }); +} + +pub(crate) fn generate(defs: &Definitions) -> Result<()> { + let traits = gen::traverse(defs, node); + file::write( + SERDE_SRC, + quote! { + // Unreachable code is generated sometimes without the full feature. + #![allow(unreachable_code, unused_variables, unused_parens)] + #![allow( + clippy::double_parens, + clippy::identity_conversion, + clippy::just_underscores_and_digits, + )] + + use crate::*; + + #traits + }, + )?; + Ok(()) +} diff --git a/syn-serde/examples/README.md b/syn-serde/examples/README.md new file mode 100644 index 0000000..932506f --- /dev/null +++ b/syn-serde/examples/README.md @@ -0,0 +1,9 @@ +### [`rust2json`](rust2json) + +**Rust -> JSON**. +Little utility to parse a Rust source file into a `syn_serde::File` and print out a JSON representation of the syntax tree. + +### [`json2rust`](json2rust) + +**JSON -> Rust**. +Little utility to parse a JSON file into a `syn_serde::File` and print out a Rust syntax tree. diff --git a/syn-serde/examples/json2rust/Cargo.toml b/syn-serde/examples/json2rust/Cargo.toml new file mode 100644 index 0000000..c7b4e3e --- /dev/null +++ b/syn-serde/examples/json2rust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "json2rust" +version = "0.0.0" +authors = ["Taiki Endo "] +edition = "2018" +publish = false + +[dependencies] +quote = "1.0" +syn-serde = { path = "../..", features = ["json"] } +syn = { version = "1.0", features = ["full"] } +tempfile = "3.1" diff --git a/syn-serde/examples/json2rust/README.md b/syn-serde/examples/json2rust/README.md new file mode 100644 index 0000000..c5c1b55 --- /dev/null +++ b/syn-serde/examples/json2rust/README.md @@ -0,0 +1,7 @@ +Parse a JSON file into a `syn_serde::File` and print out a Rust syntax tree. + +```text +cargo run -- json2rust_main.json +``` + +The output is the same as which deleted blank line and comment from [src/main.rs](src/main.rs). diff --git a/syn-serde/examples/json2rust/json2rust_main.json b/syn-serde/examples/json2rust/json2rust_main.json new file mode 100644 index 0000000..513b95b --- /dev/null +++ b/syn-serde/examples/json2rust/json2rust_main.json @@ -0,0 +1,1147 @@ +{ + "attrs": [ + { + "style": "inner", + "path": { + "segments": [ + { + "ident": "warn" + } + ] + }, + "tokens": [ + { + "group": { + "delimiter": "parenthesis", + "stream": [ + { + "ident": "rust_2018_idioms" + } + ] + } + } + ] + } + ], + "items": [ + { + "use": { + "tree": { + "path": { + "ident": "quote", + "tree": { + "ident": "ToTokens" + } + } + } + } + }, + { + "use": { + "tree": { + "path": { + "ident": "std", + "tree": { + "group": [ + { + "ident": "env" + }, + { + "ident": "fs" + }, + { + "path": { + "ident": "io", + "tree": { + "group": [ + { + "ident": "self" + }, + { + "ident": "BufWriter" + }, + { + "ident": "Write" + } + ] + } + } + }, + { + "path": { + "ident": "path", + "tree": { + "ident": "PathBuf" + } + } + }, + { + "path": { + "ident": "process", + "tree": { + "group": [ + { + "ident": "Command" + }, + { + "ident": "Stdio" + } + ] + } + } + } + ] + } + } + } + } + }, + { + "use": { + "tree": { + "path": { + "ident": "syn_serde", + "tree": { + "ident": "json" + } + } + } + } + }, + { + "use": { + "tree": { + "path": { + "ident": "tempfile", + "tree": { + "ident": "Builder" + } + } + } + } + }, + { + "type": { + "ident": "Result", + "generics": { + "params": [ + { + "type": { + "ident": "T" + } + } + ] + }, + "ty": { + "path": { + "segments": [ + { + "ident": "std" + }, + { + "ident": "result" + }, + { + "ident": "Result", + "arguments": { + "angle_bracketed": { + "args": [ + { + "type": { + "path": { + "segments": [ + { + "ident": "T" + } + ] + } + } + }, + { + "type": { + "path": { + "segments": [ + { + "ident": "Box", + "arguments": { + "angle_bracketed": { + "args": [ + { + "type": { + "trait_object": { + "dyn": true, + "bounds": [ + { + "trait": { + "path": { + "segments": [ + { + "ident": "std" + }, + { + "ident": "error" + }, + { + "ident": "Error" + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + }, + { + "fn": { + "ident": "main", + "inputs": [], + "output": null, + "stmts": [ + { + "expr": { + "if": { + "cond": { + "let": { + "pat": { + "tuple_struct": { + "path": { + "segments": [ + { + "ident": "Err" + } + ] + }, + "pat": { + "elems": [ + { + "ident": { + "ident": "e" + } + } + ] + } + } + }, + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "try_main" + } + ] + } + }, + "args": [] + } + } + } + }, + "then_branch": [ + { + "semi": { + "macro": { + "path": { + "segments": [ + { + "ident": "eprintln" + } + ] + }, + "delimiter": "paren", + "tokens": [ + { + "lit": "\"{}\"" + }, + { + "punct": { + "op": ",", + "spacing": "alone" + } + }, + { + "ident": "e" + } + ] + } + } + }, + { + "semi": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "std" + }, + { + "ident": "process" + }, + { + "ident": "exit" + } + ] + } + }, + "args": [ + { + "lit": { + "int": "1" + } + } + ] + } + } + } + ] + } + } + } + ] + } + }, + { + "fn": { + "ident": "try_main", + "inputs": [], + "output": { + "path": { + "segments": [ + { + "ident": "Result", + "arguments": { + "angle_bracketed": { + "args": [ + { + "type": { + "tuple": { + "elems": [] + } + } + } + ] + } + } + } + ] + } + }, + "stmts": [ + { + "let": { + "pat": { + "ident": { + "mut": true, + "ident": "args" + } + }, + "init": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "env" + }, + { + "ident": "args_os" + } + ] + } + }, + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "_": {} + }, + "init": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "args" + } + ] + } + }, + "method": "next", + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "filepath" + } + }, + "init": { + "match": { + "expr": { + "tuple": { + "elems": [ + { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "args" + } + ] + } + }, + "method": "next", + "args": [] + } + }, + { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "args" + } + ] + } + }, + "method": "next", + "args": [] + } + } + ] + } + }, + "arms": [ + { + "pat": { + "tuple": { + "elems": [ + { + "tuple_struct": { + "path": { + "segments": [ + { + "ident": "Some" + } + ] + }, + "pat": { + "elems": [ + { + "ident": { + "ident": "arg1" + } + } + ] + } + } + }, + { + "ident": { + "ident": "None" + } + } + ] + } + }, + "body": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "PathBuf" + }, + { + "ident": "from" + } + ] + } + }, + "args": [ + { + "path": { + "segments": [ + { + "ident": "arg1" + } + ] + } + } + ] + } + } + }, + { + "pat": { + "_": {} + }, + "body": { + "block": { + "stmts": [ + { + "semi": { + "macro": { + "path": { + "segments": [ + { + "ident": "println" + } + ] + }, + "delimiter": "paren", + "tokens": [ + { + "lit": "\"Usage: rust2json path/to/filename.rs\"" + } + ] + } + } + }, + { + "semi": { + "return": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Ok" + } + ] + } + }, + "args": [ + { + "tuple": { + "elems": [] + } + } + ] + } + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "json" + } + }, + "init": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "fs" + }, + { + "ident": "read_to_string" + } + ] + } + }, + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "filepath" + } + ] + } + } + } + } + ] + } + } + } + } + } + }, + { + "let": { + "pat": { + "type": { + "pat": { + "ident": { + "ident": "syntax" + } + }, + "ty": { + "path": { + "segments": [ + { + "ident": "syn" + }, + { + "ident": "File" + } + ] + } + } + } + }, + "init": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "json" + }, + { + "ident": "from_str" + } + ] + } + }, + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "json" + } + ] + } + } + } + } + ] + } + } + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "outdir" + } + }, + "init": { + "try": { + "expr": { + "method_call": { + "receiver": { + "method_call": { + "receiver": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Builder" + }, + { + "ident": "new" + } + ] + } + }, + "args": [] + } + }, + "method": "prefix", + "args": [ + { + "lit": { + "str": "\"json2rust\"" + } + } + ] + } + }, + "method": "tempdir", + "args": [] + } + } + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "outfile_path" + } + }, + "init": { + "method_call": { + "receiver": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "outdir" + } + ] + } + }, + "method": "path", + "args": [] + } + }, + "method": "join", + "args": [ + { + "lit": { + "str": "\"expanded\"" + } + } + ] + } + } + } + }, + { + "semi": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "fs" + }, + { + "ident": "write" + } + ] + } + }, + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "outfile_path" + } + ] + } + } + } + }, + { + "method_call": { + "receiver": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "syntax" + } + ] + } + }, + "method": "into_token_stream", + "args": [] + } + }, + "method": "to_string", + "args": [] + } + } + ] + } + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "rustfmt_config_path" + } + }, + "init": { + "method_call": { + "receiver": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "outdir" + } + ] + } + }, + "method": "path", + "args": [] + } + }, + "method": "join", + "args": [ + { + "lit": { + "str": "\"rustfmt.toml\"" + } + } + ] + } + } + } + }, + { + "semi": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "fs" + }, + { + "ident": "write" + } + ] + } + }, + "args": [ + { + "path": { + "segments": [ + { + "ident": "rustfmt_config_path" + } + ] + } + }, + { + "lit": { + "str": "\"normalize_doc_attributes = true\\n\"" + } + } + ] + } + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "_status" + } + }, + "init": { + "method_call": { + "receiver": { + "method_call": { + "receiver": { + "method_call": { + "receiver": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Command" + }, + { + "ident": "new" + } + ] + } + }, + "args": [ + { + "lit": { + "str": "\"rustfmt\"" + } + } + ] + } + }, + "method": "arg", + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "outfile_path" + } + ] + } + } + } + } + ] + } + }, + "method": "stderr", + "args": [ + { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Stdio" + }, + { + "ident": "null" + } + ] + } + }, + "args": [] + } + } + ] + } + }, + "method": "status", + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "writer" + } + }, + "init": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "io" + }, + { + "ident": "stdout" + } + ] + } + }, + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "mut": true, + "ident": "writer" + } + }, + "init": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "BufWriter" + }, + { + "ident": "new" + } + ] + } + }, + "args": [ + { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "writer" + } + ] + } + }, + "method": "lock", + "args": [] + } + } + ] + } + } + } + }, + { + "semi": { + "try": { + "expr": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "writer" + } + ] + } + }, + "method": "write_all", + "args": [ + { + "method_call": { + "receiver": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "fs" + }, + { + "ident": "read_to_string" + } + ] + } + }, + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "outfile_path" + } + ] + } + } + } + } + ] + } + } + } + }, + "method": "as_bytes", + "args": [] + } + } + ] + } + } + } + } + }, + { + "semi": { + "try": { + "expr": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "writer" + } + ] + } + }, + "method": "flush", + "args": [] + } + } + } + } + }, + { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Ok" + } + ] + } + }, + "args": [ + { + "tuple": { + "elems": [] + } + } + ] + } + } + } + ] + } + } + ] +} diff --git a/syn-serde/examples/json2rust/src/main.rs b/syn-serde/examples/json2rust/src/main.rs new file mode 100644 index 0000000..3667130 --- /dev/null +++ b/syn-serde/examples/json2rust/src/main.rs @@ -0,0 +1,54 @@ +#![warn(rust_2018_idioms)] + +use quote::ToTokens; +use std::{ + env, fs, + io::{self, BufWriter, Write}, + path::PathBuf, + process::{Command, Stdio}, +}; +use syn_serde::json; +use tempfile::Builder; + +type Result = std::result::Result>; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{}", e); + std::process::exit(1); + } +} + +fn try_main() -> Result<()> { + let mut args = env::args_os(); + let _ = args.next(); // executable name + + let filepath = match (args.next(), args.next()) { + (Some(arg1), None) => PathBuf::from(arg1), + _ => { + println!("Usage: rust2json path/to/filename.rs"); + return Ok(()); + } + }; + + let json = fs::read_to_string(&filepath)?; + let syntax: syn::File = json::from_str(&json)?; + + let outdir = Builder::new().prefix("json2rust").tempdir()?; + let outfile_path = outdir.path().join("expanded"); + fs::write(&outfile_path, syntax.into_token_stream().to_string())?; + + // Run rustfmt + // https://github.com/dtolnay/cargo-expand/blob/0.4.9/src/main.rs#L181-L182 + let rustfmt_config_path = outdir.path().join("rustfmt.toml"); + fs::write(rustfmt_config_path, "normalize_doc_attributes = true\n")?; + + // Ignore any errors. + let _status = Command::new("rustfmt").arg(&outfile_path).stderr(Stdio::null()).status(); + + let writer = io::stdout(); + let mut writer = BufWriter::new(writer.lock()); + writer.write_all(fs::read_to_string(&outfile_path)?.as_bytes())?; + writer.flush()?; + Ok(()) +} diff --git a/syn-serde/examples/rust2json/Cargo.toml b/syn-serde/examples/rust2json/Cargo.toml new file mode 100644 index 0000000..da16494 --- /dev/null +++ b/syn-serde/examples/rust2json/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rust2json" +version = "0.0.0" +authors = ["Taiki Endo "] +edition = "2018" +publish = false + +[dependencies] +syn-serde = { path = "../..", features = ["json"] } +syn = { version = "1.0", features = ["full"] } diff --git a/syn-serde/examples/rust2json/README.md b/syn-serde/examples/rust2json/README.md new file mode 100644 index 0000000..1558e60 --- /dev/null +++ b/syn-serde/examples/rust2json/README.md @@ -0,0 +1,7 @@ +Parse a Rust source file into a `syn_serde::File` and print out a JSON representation of the syntax tree. + +```text +cargo run -- src/main.rs +``` + +The output is the same as [rust2json_main.json](rust2json_main.json). diff --git a/syn-serde/examples/rust2json/rust2json_main.json b/syn-serde/examples/rust2json/rust2json_main.json new file mode 100644 index 0000000..0008fba --- /dev/null +++ b/syn-serde/examples/rust2json/rust2json_main.json @@ -0,0 +1,782 @@ +{ + "attrs": [ + { + "style": "inner", + "path": { + "segments": [ + { + "ident": "warn" + } + ] + }, + "tokens": [ + { + "group": { + "delimiter": "parenthesis", + "stream": [ + { + "ident": "rust_2018_idioms" + } + ] + } + } + ] + } + ], + "items": [ + { + "use": { + "tree": { + "path": { + "ident": "std", + "tree": { + "group": [ + { + "ident": "env" + }, + { + "ident": "fs" + }, + { + "path": { + "ident": "io", + "tree": { + "group": [ + { + "ident": "self" + }, + { + "ident": "BufWriter" + }, + { + "ident": "Write" + } + ] + } + } + }, + { + "path": { + "ident": "path", + "tree": { + "ident": "PathBuf" + } + } + } + ] + } + } + } + } + }, + { + "use": { + "tree": { + "path": { + "ident": "syn_serde", + "tree": { + "ident": "json" + } + } + } + } + }, + { + "type": { + "ident": "Result", + "generics": { + "params": [ + { + "type": { + "ident": "T" + } + } + ] + }, + "ty": { + "path": { + "segments": [ + { + "ident": "std" + }, + { + "ident": "result" + }, + { + "ident": "Result", + "arguments": { + "angle_bracketed": { + "args": [ + { + "type": { + "path": { + "segments": [ + { + "ident": "T" + } + ] + } + } + }, + { + "type": { + "path": { + "segments": [ + { + "ident": "Box", + "arguments": { + "angle_bracketed": { + "args": [ + { + "type": { + "trait_object": { + "dyn": true, + "bounds": [ + { + "trait": { + "path": { + "segments": [ + { + "ident": "std" + }, + { + "ident": "error" + }, + { + "ident": "Error" + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + }, + { + "fn": { + "ident": "main", + "inputs": [], + "output": null, + "stmts": [ + { + "expr": { + "if": { + "cond": { + "let": { + "pat": { + "tuple_struct": { + "path": { + "segments": [ + { + "ident": "Err" + } + ] + }, + "pat": { + "elems": [ + { + "ident": { + "ident": "e" + } + } + ] + } + } + }, + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "try_main" + } + ] + } + }, + "args": [] + } + } + } + }, + "then_branch": [ + { + "semi": { + "macro": { + "path": { + "segments": [ + { + "ident": "eprintln" + } + ] + }, + "delimiter": "paren", + "tokens": [ + { + "lit": "\"{}\"" + }, + { + "punct": { + "op": ",", + "spacing": "alone" + } + }, + { + "ident": "e" + } + ] + } + } + }, + { + "semi": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "std" + }, + { + "ident": "process" + }, + { + "ident": "exit" + } + ] + } + }, + "args": [ + { + "lit": { + "int": "1" + } + } + ] + } + } + } + ] + } + } + } + ] + } + }, + { + "fn": { + "ident": "try_main", + "inputs": [], + "output": { + "path": { + "segments": [ + { + "ident": "Result", + "arguments": { + "angle_bracketed": { + "args": [ + { + "type": { + "tuple": { + "elems": [] + } + } + } + ] + } + } + } + ] + } + }, + "stmts": [ + { + "let": { + "pat": { + "ident": { + "mut": true, + "ident": "args" + } + }, + "init": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "env" + }, + { + "ident": "args_os" + } + ] + } + }, + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "_": {} + }, + "init": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "args" + } + ] + } + }, + "method": "next", + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "filepath" + } + }, + "init": { + "match": { + "expr": { + "tuple": { + "elems": [ + { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "args" + } + ] + } + }, + "method": "next", + "args": [] + } + }, + { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "args" + } + ] + } + }, + "method": "next", + "args": [] + } + } + ] + } + }, + "arms": [ + { + "pat": { + "tuple": { + "elems": [ + { + "tuple_struct": { + "path": { + "segments": [ + { + "ident": "Some" + } + ] + }, + "pat": { + "elems": [ + { + "ident": { + "ident": "arg" + } + } + ] + } + } + }, + { + "ident": { + "ident": "None" + } + } + ] + } + }, + "body": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "PathBuf" + }, + { + "ident": "from" + } + ] + } + }, + "args": [ + { + "path": { + "segments": [ + { + "ident": "arg" + } + ] + } + } + ] + } + } + }, + { + "pat": { + "_": {} + }, + "body": { + "block": { + "stmts": [ + { + "semi": { + "macro": { + "path": { + "segments": [ + { + "ident": "println" + } + ] + }, + "delimiter": "paren", + "tokens": [ + { + "lit": "\"Usage: rust2json path/to/filename.rs\"" + } + ] + } + } + }, + { + "semi": { + "return": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Ok" + } + ] + } + }, + "args": [ + { + "tuple": { + "elems": [] + } + } + ] + } + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "code" + } + }, + "init": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "fs" + }, + { + "ident": "read_to_string" + } + ] + } + }, + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "filepath" + } + ] + } + } + } + } + ] + } + } + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "syntax" + } + }, + "init": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "syn" + }, + { + "ident": "parse_file" + } + ] + } + }, + "args": [ + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "code" + } + ] + } + } + } + } + ] + } + } + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "ident": "writer" + } + }, + "init": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "io" + }, + { + "ident": "stdout" + } + ] + } + }, + "args": [] + } + } + } + }, + { + "let": { + "pat": { + "ident": { + "mut": true, + "ident": "writer" + } + }, + "init": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "BufWriter" + }, + { + "ident": "new" + } + ] + } + }, + "args": [ + { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "writer" + } + ] + } + }, + "method": "lock", + "args": [] + } + } + ] + } + } + } + }, + { + "semi": { + "try": { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "json" + }, + { + "ident": "to_writer_pretty" + } + ] + } + }, + "args": [ + { + "reference": { + "mut": true, + "expr": { + "path": { + "segments": [ + { + "ident": "writer" + } + ] + } + } + } + }, + { + "reference": { + "expr": { + "path": { + "segments": [ + { + "ident": "syntax" + } + ] + } + } + } + } + ] + } + } + } + } + }, + { + "semi": { + "try": { + "expr": { + "method_call": { + "receiver": { + "path": { + "segments": [ + { + "ident": "writer" + } + ] + } + }, + "method": "flush", + "args": [] + } + } + } + } + }, + { + "expr": { + "call": { + "func": { + "path": { + "segments": [ + { + "ident": "Ok" + } + ] + } + }, + "args": [ + { + "tuple": { + "elems": [] + } + } + ] + } + } + } + ] + } + } + ] +} diff --git a/syn-serde/examples/rust2json/src/main.rs b/syn-serde/examples/rust2json/src/main.rs new file mode 100644 index 0000000..45a7224 --- /dev/null +++ b/syn-serde/examples/rust2json/src/main.rs @@ -0,0 +1,39 @@ +#![warn(rust_2018_idioms)] + +use std::{ + env, fs, + io::{self, BufWriter, Write}, + path::PathBuf, +}; +use syn_serde::json; + +type Result = std::result::Result>; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{}", e); + std::process::exit(1); + } +} + +fn try_main() -> Result<()> { + let mut args = env::args_os(); + let _ = args.next(); // executable name + + let filepath = match (args.next(), args.next()) { + (Some(arg), None) => PathBuf::from(arg), + _ => { + println!("Usage: rust2json path/to/filename.rs"); + return Ok(()); + } + }; + + let code = fs::read_to_string(&filepath)?; + let syntax = syn::parse_file(&code)?; + + let writer = io::stdout(); + let mut writer = BufWriter::new(writer.lock()); + json::to_writer_pretty(&mut writer, &syntax)?; + writer.flush()?; + Ok(()) +} diff --git a/syn-serde/src/attr.rs b/syn-serde/src/attr.rs new file mode 100644 index 0000000..fd42d5f --- /dev/null +++ b/syn-serde/src/attr.rs @@ -0,0 +1,115 @@ +use super::*; + +ast_struct! { + /// An attribute like `#[repr(transparent)]`. + /// + /// # Syntax + /// + /// Rust has six types of attributes. + /// + /// - Outer attributes like `#[repr(transparent)]`. These appear outside or + /// in front of the item they describe. + /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside + /// of the item they describe, usually a module. + /// - Outer doc comments like `/// # Example`. + /// - Inner doc comments like `//! Please file an issue`. + /// - Outer block comments `/** # Example */`. + /// - Inner block comments `/*! Please file an issue */`. + /// + /// The `style` field of type `AttrStyle` distinguishes whether an attribute + /// is outer or inner. Doc comments and block comments are promoted to + /// attributes, as this is how they are processed by the compiler and by + /// `macro_rules!` macros. + /// + /// The `path` field gives the possibly colon-delimited path against which + /// the attribute is resolved. It is equal to `"doc"` for desugared doc + /// comments. The `tokens` field contains the rest of the attribute body as + /// tokens. + /// + /// ```text + /// #[derive(Copy)] #[crate::precondition x < 5] + /// ^^^^^^~~~~~~ ^^^^^^^^^^^^^^^^^^^ ~~~~~ + /// path tokens path tokens + /// ``` + pub struct Attribute { + pub(crate) style: AttrStyle, + pub(crate) path: Path, + #[serde(default, skip_serializing_if = "TokenStream::is_empty")] + pub(crate) tokens: TokenStream, + } +} + +ast_enum! { + /// Distinguishes between attributes that decorate an item and attributes + /// that are contained within an item. + /// + /// # Outer attributes + /// + /// - `#[repr(transparent)]` + /// - `/// # Example` + /// - `/** Please file an issue */` + /// + /// # Inner attributes + /// + /// - `#![feature(proc_macro)]` + /// - `//! # Example` + /// - `/*! Please file an issue */` + pub enum AttrStyle { + Outer, + Inner, + } +} + +ast_enum! { + /// Content of a compile-time structured attribute. + /// + /// ## Path + /// + /// A meta path is like the `test` in `#[test]`. + /// + /// ## List + /// + /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`. + /// + /// ## NameValue + /// + /// A name-value meta is like the `path = "..."` in `#[path = + /// "sys/windows.rs"]`. + pub enum Meta { + Path(Path), + + /// A structured list within an attribute, like `derive(Copy, Clone)`. + List(MetaList), + + /// A name-value pair within an attribute, like `feature = "nightly"`. + NameValue(MetaNameValue), + } +} + +ast_struct! { + /// A structured list within an attribute, like `derive(Copy, Clone)`. + pub struct MetaList { + pub(crate) path: Path, + pub(crate) nested: Punctuated, + } +} + +ast_struct! { + /// A name-value pair within an attribute, like `feature = "nightly"`. + pub struct MetaNameValue { + pub(crate) path: Path, + pub(crate) lit: Lit, + } +} + +ast_enum! { + /// Element of a compile-time attribute list. + pub enum NestedMeta { + /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which + /// would be a nested `Meta::Word`. + Meta(Meta), + + /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`. + Lit(Lit), + } +} diff --git a/syn-serde/src/data.rs b/syn-serde/src/data.rs new file mode 100644 index 0000000..0a30206 --- /dev/null +++ b/syn-serde/src/data.rs @@ -0,0 +1,185 @@ +use super::*; + +ast_struct! { + /// An enum variant. + pub struct Variant { + /// Attributes tagged on the variant. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + + /// Name of the variant. + pub(crate) ident: Ident, + + /// Content stored in the variant. + pub(crate) fields: Fields, + + /// Explicit discriminant: `Variant = 1` + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) discriminant: Option, + } +} + +ast_enum! { + /// Data stored within an enum variant or struct. + pub enum Fields { + /// Named fields of a struct or struct variant such as `Point { x: f64, + /// y: f64 }`. + Named(FieldsNamed), + + /// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`. + Unnamed(FieldsUnnamed), + + /// Unit struct or unit variant such as `None`. + Unit, + } +} + +ast_struct! { + /// Named fields of a struct or struct variant such as `Point { x: f64, + /// y: f64 }`. + #[serde(transparent)] + pub struct FieldsNamed { + pub(crate) named: Punctuated, + } +} + +ast_struct! { + /// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`. + #[serde(transparent)] + pub struct FieldsUnnamed { + pub(crate) unnamed: Punctuated, + } +} + +impl Fields { + pub(crate) fn is_named(&self) -> bool { + match self { + Fields::Named(_) => true, + Fields::Unnamed(_) | Fields::Unit => false, + } + } +} + +// assertions +// `syn::perse*` functions will detect these, but there is a possibility to +// generate incorrect code by subsequent operations. +pub(crate) fn assert_struct_semi(fields: &Fields, semi_token: bool) { + match fields { + // struct foo {}; + Fields::Named(_) => assert!(!semi_token, "unexpected token: `;`"), + // struct foo () + Fields::Unnamed(_) => assert!( + semi_token, + "unexpected end of input, expected `where` or `;`" + ), + // struct foo + Fields::Unit => assert!( + semi_token, + "unexpected end of input, expected one of: `where`, parentheses, curly braces, `;`" + ), + } +} + +ast_struct! { + /// A field of a struct or enum variant. + pub struct Field { + /// Attributes tagged on the field. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + + /// Visibility of the field. + #[serde(default, skip_serializing_if = "Visibility::is_inherited")] + pub(crate) vis: Visibility, + + /// Name of the field, if any. + /// + /// Fields of tuple structs have no names. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) ident: Option, + + #[serde(default, skip_serializing_if = "not")] + pub(crate) colon_token: bool, + + /// Type of the field. + pub(crate) ty: Type, + } +} + +ast_enum! { + /// The visibility level of an item: inherited or `pub` or + /// `pub(restricted)`. + pub enum Visibility { + /// A public visibility level: `pub`. + #[serde(rename = "pub")] + Public, + + /// A crate-level visibility: `crate`. + Crate, + + /// A visibility level restricted to some path: `pub(self)` or + /// `pub(super)` or `pub(crate)` or `pub(in some::module)`. + Restricted(VisRestricted), + + /// An inherited visibility, which usually means private. + Inherited, + } +} + +impl Visibility { + pub(crate) fn is_inherited(&self) -> bool { + match self { + Visibility::Inherited => true, + _ => false, + } + } +} + +impl Default for Visibility { + fn default() -> Self { + Visibility::Inherited + } +} + +ast_struct! { + /// A visibility level restricted to some path: `pub(self)` or + /// `pub(super)` or `pub(crate)` or `pub(in some::module)`. + pub struct VisRestricted { + #[serde(default, skip_serializing_if = "not")] + pub(crate) in_token: bool, + pub(crate) path: Box, + } +} + +mod convert { + use super::*; + + // Visibility + syn_trait_impl!(syn::Visibility); + impl From<&syn::Visibility> for Visibility { + fn from(other: &syn::Visibility) -> Self { + use super::Visibility::*; + use syn::Visibility; + match other { + Visibility::Public(_) => Public, + Visibility::Crate(_) => Crate, + Visibility::Restricted(x) => Restricted(x.ref_into()), + Visibility::Inherited => Inherited, + } + } + } + impl From<&Visibility> for syn::Visibility { + fn from(other: &Visibility) -> Self { + use syn::Visibility::*; + match other { + Visibility::Public => Public(syn::VisPublic { + pub_token: default(), + }), + Visibility::Crate => Crate(syn::VisCrate { + crate_token: default(), + }), + Visibility::Restricted(x) => Restricted(x.into()), + Visibility::Inherited => Inherited, + } + } + } +} diff --git a/syn-serde/src/expr.rs b/syn-serde/src/expr.rs new file mode 100644 index 0000000..5e6bdef --- /dev/null +++ b/syn-serde/src/expr.rs @@ -0,0 +1,779 @@ +use super::*; + +ast_enum! { + /// A Rust expression. + pub enum Expr { + /// A slice literal expression: `[a, b, c, d]`. + Array(ExprArray), + + /// An assignment expression: `a = compute()`. + Assign(ExprAssign), + + /// A compound assignment expression: `counter += 1`. + AssignOp(ExprAssignOp), + + /// An async block: `async { ... }`. + Async(ExprAsync), + + /// An await expression: `fut.await`. + Await(ExprAwait), + + /// A binary operation: `a + b`, `a * b`. + Binary(ExprBinary), + + /// A blocked scope: `{ ... }`. + Block(ExprBlock), + + /// A box expression: `box f`. + Box(ExprBox), + + /// A `break`, with an optional label to break and an optional + /// expression. + Break(ExprBreak), + + /// A function call expression: `invoke(a, b)`. + Call(ExprCall), + + /// A cast expression: `foo as f64`. + Cast(ExprCast), + + /// A closure expression: `|a, b| a + b`. + Closure(ExprClosure), + + /// A `continue`, with an optional label. + Continue(ExprContinue), + + /// Access of a named struct field (`obj.k`) or unnamed tuple struct + /// field (`obj.0`). + Field(ExprField), + + /// A for loop: `for pat in expr { ... }`. + ForLoop(ExprForLoop), + + /// An expression contained within invisible delimiters. + /// + /// This variant is important for faithfully representing the precedence + /// of expressions and is related to `None`-delimited spans in a + /// `TokenStream`. + Group(ExprGroup), + + /// An `if` expression with an optional `else` block: `if expr { ... } + /// else { ... }`. + /// + /// The `else` branch expression may only be an `If` or `Block` + /// expression, not any of the other types of expression. + If(ExprIf), + + /// A square bracketed indexing expression: `vector[2]`. + Index(ExprIndex), + + /// A `let` guard: `let Some(x) = opt`. + Let(ExprLet), + + /// A literal in place of an expression: `1`, `"foo"`. + Lit(ExprLit), + + /// Conditionless loop: `loop { ... }`. + Loop(ExprLoop), + + /// A macro invocation expression: `format!("{}", q)`. + Macro(ExprMacro), + + /// A `match` expression: `match n { Some(n) => {}, None => {} }`. + Match(ExprMatch), + + /// A method call expression: `x.foo::(a, b)`. + MethodCall(ExprMethodCall), + + /// A parenthesized expression: `(a + b)`. + Paren(ExprParen), + + /// A path like `std::mem::replace` possibly containing generic + /// parameters and a qualified self-type. + /// + /// A plain identifier like `x` is a path of length 1. + Path(ExprPath), + + /// A range expression: `1..2`, `1..`, `..2`, `1..=2`, `..=2`. + Range(ExprRange), + + /// A referencing operation: `&a` or `&mut a`. + Reference(ExprReference), + + /// An array literal constructed from one repeated element: `[0u8; N]`. + Repeat(ExprRepeat), + + /// A `return`, with an optional value to be returned. + Return(ExprReturn), + + /// A struct literal expression: `Point { x: 1, y: 1 }`. + /// + /// The `rest` provides the value of the remaining fields as in `S { a: + /// 1, b: 1, ..rest }`. + Struct(ExprStruct), + + /// A try-expression: `expr?`. + Try(ExprTry), + + /// A try block: `try { ... }`. + TryBlock(ExprTryBlock), + + /// A tuple expression: `(a, b, c, d)`. + Tuple(ExprTuple), + + /// A type ascription expression: `foo: f64`. + Type(ExprType), + + /// A unary operation: `!x`, `*x`. + Unary(ExprUnary), + + /// An unsafe block: `unsafe { ... }`. + Unsafe(ExprUnsafe), + + /// Tokens in expression position not interpreted by Syn. + Verbatim(TokenStream), + + /// A while loop: `while expr { ... }`. + While(ExprWhile), + + /// A yield expression: `yield expr`. + Yield(ExprYield), + + #[doc(hidden)] + __Nonexhaustive, + } +} + +ast_struct! { + /// A slice literal expression: `[a, b, c, d]`. + pub struct ExprArray { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + pub(crate) elems: Punctuated, + } +} + +ast_struct! { + /// An assignment expression: `a = compute()`. + pub struct ExprAssign { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + pub(crate) left: Box, + pub(crate) right: Box, + } +} + +ast_struct! { + /// A compound assignment expression: `counter += 1`. + pub struct ExprAssignOp { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + pub(crate) left: Box, + pub(crate) op: BinOp, + pub(crate) right: Box, + } +} + +ast_struct! { + /// An async block: `async { ... }`. + pub struct ExprAsync { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + #[serde(rename = "move")] + #[serde(default, skip_serializing_if = "not")] + pub(crate) capture: bool, + #[serde(rename = "stmts")] + pub(crate) block: Block, + } +} + +ast_struct! { + /// An await expression: `fut.await`. + pub struct ExprAwait { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + pub(crate) base: Box, + } +} + +ast_struct! { + /// A binary operation: `a + b`, `a * b`. + pub struct ExprBinary { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + pub(crate) left: Box, + pub(crate) op: BinOp, + pub(crate) right: Box, + } +} + +ast_struct! { + /// A blocked scope: `{ ... }`. + pub struct ExprBlock { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) attrs: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) label: Option