This commit is contained in:
Michael Zhang 2020-02-17 06:09:45 -06:00
parent 51be5dcf55
commit 1453885ed2
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
50 changed files with 17263 additions and 1 deletions

@ -1 +0,0 @@
Subproject commit dff506bb8a83702e2dc82b17177dda43e6de0f3a

22
syn-serde/.editorconfig Normal file
View file

@ -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

4
syn-serde/.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
[attr]rust text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4
* text=auto eol=lf
*.rs rust

1
syn-serde/.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
* @taiki-e

10
syn-serde/.github/bors.toml vendored Normal file
View file

@ -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

95
syn-serde/.github/workflows/ci.yml vendored Normal file
View file

@ -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

7
syn-serde/.gitignore vendored Normal file
View file

@ -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

21
syn-serde/CHANGELOG.md Normal file
View file

@ -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

36
syn-serde/Cargo.toml Normal file
View file

@ -0,0 +1,36 @@
[package]
name = "syn-serde"
version = "0.2.0"
authors = ["David Tolnay <dtolnay@gmail.com>", "Taiki Endo <te316e89@gmail.com>"]
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

201
syn-serde/LICENSE-APACHE Normal file
View file

@ -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.

23
syn-serde/LICENSE-MIT Normal file
View file

@ -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.

130
syn-serde/README.md Normal file
View file

@ -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 <http://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
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.

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,17 @@
[package]
name = "syn-serde-internal-codegen"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>", "Nika Layzell <nika@thelayzells.com>", "Taiki Endo <te316e89@gmail.com>"]
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" }

View file

@ -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.

View file

@ -0,0 +1,40 @@
use crate::Result;
use proc_macro2::TokenStream;
use std::{fmt, fs, io::Write, path::Path};
pub(crate) fn write<P: AsRef<Path>>(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 {}

View file

@ -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
}

View file

@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
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(())
}

View file

@ -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>, 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, &quote!((*#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, &quote!((*#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(())
}

View file

@ -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.

View file

@ -0,0 +1,12 @@
[package]
name = "json2rust"
version = "0.0.0"
authors = ["Taiki Endo <te316e89@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
quote = "1.0"
syn-serde = { path = "../..", features = ["json"] }
syn = { version = "1.0", features = ["full"] }
tempfile = "3.1"

View file

@ -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).

File diff suppressed because it is too large Load diff

View file

@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
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(())
}

View file

@ -0,0 +1,10 @@
[package]
name = "rust2json"
version = "0.0.0"
authors = ["Taiki Endo <te316e89@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
syn-serde = { path = "../..", features = ["json"] }
syn = { version = "1.0", features = ["full"] }

View file

@ -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).

View file

@ -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": []
}
}
]
}
}
}
]
}
}
]
}

View file

@ -0,0 +1,39 @@
#![warn(rust_2018_idioms)]
use std::{
env, fs,
io::{self, BufWriter, Write},
path::PathBuf,
};
use syn_serde::json;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
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(())
}

115
syn-serde/src/attr.rs Normal file
View file

@ -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<NestedMeta>,
}
}
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),
}
}

185
syn-serde/src/data.rs Normal file
View file

@ -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<Attribute>,
/// 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<Expr>,
}
}
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<Field>,
}
}
ast_struct! {
/// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`.
#[serde(transparent)]
pub struct FieldsUnnamed {
pub(crate) unnamed: Punctuated<Field>,
}
}
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<Attribute>,
/// 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<Ident>,
#[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<Path>,
}
}
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,
}
}
}
}

779
syn-serde/src/expr.rs Normal file
View file

@ -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::<T>(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<Attribute>,
pub(crate) elems: Punctuated<Expr>,
}
}
ast_struct! {
/// An assignment expression: `a = compute()`.
pub struct ExprAssign {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) left: Box<Expr>,
pub(crate) right: Box<Expr>,
}
}
ast_struct! {
/// A compound assignment expression: `counter += 1`.
pub struct ExprAssignOp {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) left: Box<Expr>,
pub(crate) op: BinOp,
pub(crate) right: Box<Expr>,
}
}
ast_struct! {
/// An async block: `async { ... }`.
pub struct ExprAsync {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[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<Attribute>,
pub(crate) base: Box<Expr>,
}
}
ast_struct! {
/// A binary operation: `a + b`, `a * b`.
pub struct ExprBinary {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) left: Box<Expr>,
pub(crate) op: BinOp,
pub(crate) right: Box<Expr>,
}
}
ast_struct! {
/// A blocked scope: `{ ... }`.
pub struct ExprBlock {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) label: Option<Label>,
#[serde(rename = "stmts")]
pub(crate) block: Block,
}
}
ast_struct! {
/// A box expression: `box f`.
pub struct ExprBox {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// A `break`, with an optional label to break and an optional
/// expression.
pub struct ExprBreak {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) label: Option<Lifetime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) expr: Option<Box<Expr>>,
}
}
ast_struct! {
/// A function call expression: `invoke(a, b)`.
pub struct ExprCall {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) func: Box<Expr>,
pub(crate) args: Punctuated<Expr>,
}
}
ast_struct! {
/// A cast expression: `foo as f64`.
pub struct ExprCast {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
pub(crate) ty: Box<Type>,
}
}
ast_struct! {
/// A closure expression: `|a, b| a + b`.
pub struct ExprClosure {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "async")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) asyncness: bool,
#[serde(rename = "static")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) movability: bool,
#[serde(rename = "move")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) capture: bool,
pub(crate) inputs: Punctuated<Pat>,
#[serde(default)]
pub(crate) output: ReturnType,
pub(crate) body: Box<Expr>,
}
}
ast_struct! {
/// A `continue`, with an optional label.
pub struct ExprContinue {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) label: Option<Lifetime>,
}
}
ast_struct! {
/// Access of a named struct field (`obj.k`) or unnamed tuple struct
/// field (`obj.0`).
pub struct ExprField {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) base: Box<Expr>,
#[serde(flatten)]
pub(crate) member: Member,
}
}
ast_struct! {
/// A for loop: `for pat in expr { ... }`.
pub struct ExprForLoop {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) label: Option<Label>,
pub(crate) pat: Pat,
pub(crate) expr: Box<Expr>,
pub(crate) body: Block,
}
}
ast_struct! {
/// 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`.
pub struct ExprGroup {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// 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.
pub struct ExprIf {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) cond: Box<Expr>,
pub(crate) then_branch: Block,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) else_branch: Option<Box<Expr>>,
}
}
ast_struct! {
/// A square bracketed indexing expression: `vector[2]`.
pub struct ExprIndex {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
pub(crate) index: Box<Expr>,
}
}
ast_struct! {
/// A `let` guard: `let Some(x) = opt`.
pub struct ExprLet {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) pat: Pat,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// A literal in place of an expression: `1`, `"foo"`.
pub struct ExprLit {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) lit: Lit,
}
}
ast_struct! {
/// Conditionless loop: `loop { ... }`.
pub struct ExprLoop {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) label: Option<Label>,
pub(crate) body: Block,
}
}
ast_struct! {
/// A macro invocation expression: `format!("{}", q)`.
pub struct ExprMacro {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) mac: Macro,
}
}
ast_struct! {
/// A `match` expression: `match n { Some(n) => {}, None => {} }`.
pub struct ExprMatch {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
pub(crate) arms: Vec<Arm>,
}
}
ast_struct! {
/// A method call expression: `x.foo::<T>(a, b)`.
pub struct ExprMethodCall {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) receiver: Box<Expr>,
pub(crate) method: Ident,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) turbofish: Option<MethodTurbofish>,
pub(crate) args: Punctuated<Expr>,
}
}
ast_struct! {
/// A parenthesized expression: `(a + b)`.
pub struct ExprParen {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// 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.
pub struct ExprPath {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) qself: Option<QSelf>,
#[serde(flatten)]
pub(crate) path: Path,
}
}
ast_struct! {
/// A range expression: `1..2`, `1..`, `..2`, `1..=2`, `..=2`.
pub struct ExprRange {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) from: Option<Box<Expr>>,
pub(crate) limits: RangeLimits,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) to: Option<Box<Expr>>,
}
}
ast_struct! {
/// A referencing operation: `&a` or `&mut a`.
pub struct ExprReference {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
// #[serde(default, skip_serializing_if = "Reserved::is_default")]
// pub(crate) raw: Reserved,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// An array literal constructed from one repeated element: `[0u8; N]`.
pub struct ExprRepeat {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
pub(crate) len: Box<Expr>,
}
}
ast_struct! {
/// A `return`, with an optional value to be returned.
pub struct ExprReturn {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) expr: Option<Box<Expr>>,
}
}
ast_struct! {
/// 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 }`.
pub struct ExprStruct {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) path: Path,
pub(crate) fields: Punctuated<FieldValue>,
#[serde(default, skip_serializing_if = "not")]
pub(crate) dot2_token: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) rest: Option<Box<Expr>>,
}
}
ast_struct! {
/// A try-expression: `expr?`.
pub struct ExprTry {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// A try block: `try { ... }`.
pub struct ExprTryBlock {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "stmts")]
pub(crate) block: Block,
}
}
ast_struct! {
/// A tuple expression: `(a, b, c, d)`.
pub struct ExprTuple {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) elems: Punctuated<Expr>,
}
}
ast_struct! {
/// A type ascription expression: `foo: f64`.
pub struct ExprType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
pub(crate) ty: Box<Type>,
}
}
ast_struct! {
/// A unary operation: `!x`, `*x`.
pub struct ExprUnary {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) op: UnOp,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// An unsafe block: `unsafe { ... }`.
pub struct ExprUnsafe {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "stmts")]
pub(crate) block: Block,
}
}
ast_struct! {
/// A while loop: `while expr { ... }`.
pub struct ExprWhile {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) label: Option<Label>,
pub(crate) cond: Box<Expr>,
pub(crate) body: Block,
}
}
ast_struct! {
/// A yield expression: `yield expr`.
pub struct ExprYield {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) expr: Option<Box<Expr>>,
}
}
ast_enum! {
/// A struct or tuple struct field accessed in a struct literal or field
/// expression.
pub enum Member {
/// A named field like `self.x`.
#[serde(rename = "ident")]
Named(Ident),
/// An unnamed field like `self.0`.
#[serde(rename = "index")]
Unnamed(Index),
}
}
ast_struct! {
/// The index of an unnamed tuple struct field.
#[serde(transparent)]
pub struct Index {
pub(crate) index: u32,
}
}
ast_struct! {
/// The `::<>` explicit type parameters passed to a method call:
/// `parse::<u64>()`.
pub struct MethodTurbofish {
pub(crate) args: Punctuated<GenericMethodArgument>,
}
}
ast_enum! {
/// An individual generic argument to a method, like `T`.
pub enum GenericMethodArgument {
/// A type argument.
Type(Type),
/// A const expression. Must be inside of a block.
///
/// NOTE: Identity expressions are represented as Type arguments, as
/// they are indistinguishable syntactically.
Const(Expr),
}
}
ast_struct! {
/// A field-value pair in a struct literal.
pub struct FieldValue {
/// Attributes tagged on the field.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
/// Name or index of the field.
#[serde(flatten)]
pub(crate) member: Member,
/// The colon in `Struct { x: x }`. If written in shorthand like
/// `Struct { x }`, there is no colon.
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon_token: bool,
/// Value of the field.
pub(crate) expr: Expr,
}
}
ast_struct! {
/// A lifetime labeling a `for`, `while`, or `loop`.
#[serde(transparent)]
pub struct Label {
pub(crate) name: Lifetime,
}
}
ast_struct! {
/// One arm of a `match` expression: `0...10 => { return true; }`.
pub struct Arm {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) pat: Pat,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) guard: Option<Box<Expr>>,
pub(crate) body: Box<Expr>,
// #[serde(default, skip_serializing_if = "not")]
// pub(crate) comma: bool,
}
}
ast_enum! {
/// Limit types of a range, inclusive or exclusive.
pub enum RangeLimits {
/// Inclusive at the beginning, exclusive at the end.
#[serde(rename = "..")]
HalfOpen,
/// Inclusive at the beginning and end.
#[serde(rename = "..=")]
Closed,
}
}
pub(crate) fn requires_terminator(expr: &Expr) -> bool {
// see https://github.com/rust-lang/rust/blob/eb8f2586e/src/libsyntax/parse/classify.rs#L17-L37
match expr {
Expr::Unsafe(..)
| Expr::Block(..)
| Expr::If(..)
| Expr::Match(..)
| Expr::While(..)
| Expr::Loop(..)
| Expr::ForLoop(..)
| Expr::Async(..)
| Expr::TryBlock(..) => false,
_ => true,
}
}
mod convert {
use super::*;
// ExprMatch
syn_trait_impl!(syn::ExprMatch);
fn from_syn_arms(other: &[syn::Arm]) -> Vec<Arm> {
let last = other.len().saturating_sub(1);
other
.iter()
.enumerate()
.map(|(i, other)| {
let body = other.body.map_into();
if i < last && requires_terminator(&*body) {
assert!(other.comma.is_some(), "expected `,`");
}
Arm {
attrs: other.attrs.map_into(),
pat: other.pat.ref_into(),
guard: other.guard.ref_map(|(_, x)| x.map_into()),
body,
}
})
.collect()
}
impl From<&syn::ExprMatch> for ExprMatch {
fn from(other: &syn::ExprMatch) -> Self {
Self {
attrs: other.attrs.map_into(),
expr: other.expr.map_into(),
arms: from_syn_arms(&other.arms),
}
}
}
impl From<&ExprMatch> for syn::ExprMatch {
fn from(other: &ExprMatch) -> Self {
Self {
attrs: other.attrs.map_into(),
match_token: default(),
expr: other.expr.map_into(),
brace_token: default(),
arms: other.arms.map_into(),
}
}
}
// Arm
syn_trait_impl!(syn::Arm);
impl From<&syn::Arm> for Arm {
fn from(other: &syn::Arm) -> Self {
let body = other.body.map_into();
if requires_terminator(&*body) {
assert!(other.comma.is_some(), "expected `,`");
}
Self {
attrs: other.attrs.map_into(),
pat: other.pat.ref_into(),
guard: other.guard.ref_map(|(_, x)| x.map_into()),
body,
}
}
}
impl From<&Arm> for syn::Arm {
fn from(other: &Arm) -> Self {
Self {
attrs: other.attrs.map_into(),
pat: other.pat.ref_into(),
guard: other.guard.ref_map(|x| (default(), x.map_into())),
fat_arrow_token: default(),
body: other.body.map_into(),
comma: default_or_none(requires_terminator(&*other.body)),
}
}
}
}

12
syn-serde/src/file.rs Normal file
View file

@ -0,0 +1,12 @@
use super::*;
ast_struct! {
/// A complete file of Rust source code.
pub struct File {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) shebang: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) items: Vec<Item>,
}
}

3398
syn-serde/src/gen/mod.rs Normal file

File diff suppressed because it is too large Load diff

223
syn-serde/src/generics.rs Normal file
View file

@ -0,0 +1,223 @@
use super::*;
ast_struct! {
/// Lifetimes and type parameters attached to a declaration of a function,
/// enum, trait, etc.
#[derive(Default)]
pub struct Generics {
// #[serde(default, skip_serializing_if = "not")]
// pub(crate) lt_token: bool,
#[serde(default, skip_serializing_if = "Punctuated::is_empty")]
pub(crate) params: Punctuated<GenericParam>,
// #[serde(default, skip_serializing_if = "not")]
// pub(crate) gt_token: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) where_clause: Option<WhereClause>,
}
}
impl Generics {
pub(crate) fn is_none(&self) -> bool {
self.params.is_empty() && self.where_clause.is_none() // && !self.lt_token && !self.gt_token
}
}
ast_enum! {
/// A generic type parameter, lifetime, or const generic: `T: Into<String>`,
/// `'a: 'b`, `const LEN: usize`.
pub enum GenericParam {
/// A generic type parameter: `T: Into<String>`.
Type(TypeParam),
/// A lifetime definition: `'a: 'b + 'c + 'd`.
Lifetime(LifetimeDef),
/// A const generic parameter: `const LENGTH: usize`.
Const(ConstParam),
}
}
ast_struct! {
/// A generic type parameter: `T: Into<String>`.
pub struct TypeParam {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon_token: bool,
#[serde(default, skip_serializing_if = "Punctuated::is_empty")]
pub(crate) bounds: Punctuated<TypeParamBound>,
#[serde(default, skip_serializing_if = "not")]
pub(crate) eq_token: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) default: Option<Type>,
}
}
ast_struct! {
/// A lifetime definition: `'a: 'b + 'c + 'd`.
pub struct LifetimeDef {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) lifetime: Lifetime,
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon_token: bool,
#[serde(default, skip_serializing_if = "Punctuated::is_empty")]
pub(crate) bounds: Punctuated<Lifetime>,
}
}
ast_struct! {
/// A const generic parameter: `const LENGTH: usize`.
pub struct ConstParam {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) ident: Ident,
pub(crate) ty: Type,
#[serde(default, skip_serializing_if = "not")]
pub(crate) eq_token: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) default: Option<Expr>,
}
}
ast_struct! {
/// A set of bound lifetimes: `for<'a, 'b, 'c>`.
#[derive(Default)]
#[serde(transparent)]
pub struct BoundLifetimes {
pub(crate) lifetimes: Punctuated<LifetimeDef>,
}
}
ast_enum! {
/// A trait or lifetime used as a bound on a type parameter.
pub enum TypeParamBound {
Trait(TraitBound),
Lifetime(Lifetime),
}
}
ast_struct! {
/// A trait used as a bound on a type parameter.
pub struct TraitBound {
#[serde(default, skip_serializing_if = "not")]
pub(crate) paren_token: bool,
#[serde(default, skip_serializing_if = "TraitBoundModifier::is_none")]
pub(crate) modifier: TraitBoundModifier,
/// The `for<'a>` in `for<'a> Foo<&'a T>`
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) lifetimes: Option<BoundLifetimes>,
/// The `Foo<&'a T>` in `for<'a> Foo<&'a T>`
pub(crate) path: Path,
}
}
ast_enum! {
/// A modifier on a trait bound, currently only used for the `?` in
/// `?Sized`.
pub enum TraitBoundModifier {
None,
Maybe,
}
}
impl TraitBoundModifier {
fn is_none(&self) -> bool {
match self {
TraitBoundModifier::None => true,
TraitBoundModifier::Maybe => false,
}
}
}
impl Default for TraitBoundModifier {
fn default() -> Self {
TraitBoundModifier::None
}
}
ast_struct! {
/// A `where` clause in a definition: `where T: Deserialize<'de>, D:
/// 'static`.
#[serde(transparent)]
pub struct WhereClause {
pub(crate) predicates: Punctuated<WherePredicate>,
}
}
ast_enum! {
/// A single predicate in a `where` clause: `T: Deserialize<'de>`.
pub enum WherePredicate {
/// A type predicate in a `where` clause: `for<'c> Foo<'c>: Trait<'c>`.
Type(PredicateType),
/// A lifetime predicate in a `where` clause: `'a: 'b + 'c`.
Lifetime(PredicateLifetime),
/// An equality predicate in a `where` clause (unsupported).
Eq(PredicateEq),
}
}
ast_struct! {
/// A type predicate in a `where` clause: `for<'c> Foo<'c>: Trait<'c>`.
pub struct PredicateType {
/// Any lifetimes from a `for` binding
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) lifetimes: Option<BoundLifetimes>,
/// The type being bounded
pub(crate) bounded_ty: Type,
/// Trait and lifetime bounds (`Clone+Send+'static`)
pub(crate) bounds: Punctuated<TypeParamBound>,
}
}
ast_struct! {
/// A lifetime predicate in a `where` clause: `'a: 'b + 'c`.
pub struct PredicateLifetime {
pub(crate) lifetime: Lifetime,
pub(crate) bounds: Punctuated<Lifetime>,
}
}
ast_struct! {
/// An equality predicate in a `where` clause (unsupported).
pub struct PredicateEq {
pub(crate) lhs_ty: Type,
pub(crate) rhs_ty: Type,
}
}
mod convert {
use super::*;
// Generics
syn_trait_impl!(syn::Generics);
impl From<&syn::Generics> for Generics {
fn from(other: &syn::Generics) -> Self {
// `ident ..>` or `ident <..`
assert_eq!(other.lt_token.is_some(), other.gt_token.is_some());
// `ident T`
assert!(
other.params.is_empty() || other.lt_token.is_some(),
"expected `<`"
);
Self {
params: other.params.map_into(),
where_clause: other.where_clause.map_into(),
}
}
}
impl From<&Generics> for syn::Generics {
fn from(other: &Generics) -> Self {
Self {
lt_token: default_or_none(!other.params.is_empty()),
params: other.params.map_into(),
gt_token: default_or_none(!other.params.is_empty()),
where_clause: other.where_clause.map_into(),
}
}
}
}

770
syn-serde/src/item.rs Normal file
View file

@ -0,0 +1,770 @@
use super::*;
ast_enum! {
/// Things that can appear directly inside of a module or scope.
pub enum Item {
/// A constant item: `const MAX: u16 = 65535`.
Const(ItemConst),
/// An enum definition: `enum Foo<A, B> { A(A), B(B) }`.
Enum(ItemEnum),
/// An `extern crate` item: `extern crate serde`.
ExternCrate(ItemExternCrate),
/// A free-standing function: `fn process(n: usize) -> Result<()> { ...
/// }`.
Fn(ItemFn),
/// A block of foreign items: `extern "C" { ... }`.
ForeignMod(ItemForeignMod),
/// An impl block providing trait or associated items: `impl<A> Trait
/// for Data<A> { ... }`.
Impl(ItemImpl),
/// A macro invocation, which includes `macro_rules!` definitions.
Macro(ItemMacro),
/// A 2.0-style declarative macro introduced by the `macro` keyword.
Macro2(ItemMacro2),
/// A module or module declaration: `mod m` or `mod m { ... }`.
Mod(ItemMod),
/// A static item: `static BIKE: Shed = Shed(42)`.
Static(ItemStatic),
/// A struct definition: `struct Foo<A> { x: A }`.
Struct(ItemStruct),
/// A trait definition: `pub trait Iterator { ... }`.
Trait(ItemTrait),
/// A trait alias: `pub trait SharableIterator = Iterator + Sync`.
TraitAlias(ItemTraitAlias),
/// A type alias: `type Result<T> = std::result::Result<T, MyError>`.
Type(ItemType),
/// A union definition: `union Foo<A, B> { x: A, y: B }`.
Union(ItemUnion),
/// A use declaration: `use std::collections::HashMap`.
Use(ItemUse),
/// Tokens forming an item not interpreted by Syn.
Verbatim(TokenStream),
#[doc(hidden)]
__Nonexhaustive,
}
}
ast_struct! {
/// A constant item: `const MAX: u16 = 65535`.
pub struct ItemConst {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
pub(crate) ty: Box<Type>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// An enum definition: `enum Foo<A, B> { A(A), B(B) }`.
pub struct ItemEnum {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) variants: Punctuated<Variant>,
}
}
ast_struct! {
/// An `extern crate` item: `extern crate serde`.
pub struct ItemExternCrate {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) rename: Option<Ident>,
}
}
ast_struct! {
/// A free-standing function: `fn process(n: usize) -> Result<()> { ...
/// }`.
pub struct ItemFn {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(flatten)]
pub(crate) sig: Signature,
#[serde(rename = "stmts")]
pub(crate) block: Box<Block>,
}
}
ast_struct! {
/// A block of foreign items: `extern "C" { ... }`.
pub struct ItemForeignMod {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) abi: Abi,
pub(crate) items: Vec<ForeignItem>,
}
}
ast_struct! {
/// An impl block providing trait or associated items: `impl<A> Trait
/// for Data<A> { ... }`.
pub struct ItemImpl {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "default")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) defaultness: bool,
#[serde(rename = "unsafe")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) unsafety: bool,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
/// Trait this impl implements.
#[serde(rename = "trait")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) trait_: Option<(bool, Path)>,
/// The Self type of the impl.
pub(crate) self_ty: Box<Type>,
pub(crate) items: Vec<ImplItem>,
}
}
ast_struct! {
/// A macro invocation, which includes `macro_rules!` definitions.
pub struct ItemMacro {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
/// The `example` in `macro_rules! example { ... }`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) ident: Option<Ident>,
#[serde(flatten)]
pub(crate) mac: Macro,
#[serde(default, skip_serializing_if = "not")]
pub(crate) semi_token: bool,
}
}
ast_struct! {
/// A 2.0-style declarative macro introduced by the `macro` keyword.
pub struct ItemMacro2 {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
pub(crate) rules: TokenStream,
}
}
ast_struct! {
/// A module or module declaration: `mod m` or `mod m { ... }`.
pub struct ItemMod {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) content: Option<Vec<Item>>,
#[serde(default, skip_serializing_if = "not")]
pub(crate) semi: bool,
}
}
ast_struct! {
/// A static item: `static BIKE: Shed = Shed(42)`.
pub struct ItemStatic {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) ident: Ident,
pub(crate) ty: Box<Type>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// A struct definition: `struct Foo<A> { x: A }`.
pub struct ItemStruct {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) fields: Fields,
// #[serde(default, skip_serializing_if = "not")]
// pub(crate) semi_token: bool,
}
}
ast_struct! {
/// A trait definition: `pub trait Iterator { ... }`.
pub struct ItemTrait {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(rename = "unsafe")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) unsafety: bool,
#[serde(rename = "auto")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) auto_token: bool,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon_token: bool,
#[serde(default, skip_serializing_if = "Punctuated::is_empty")]
pub(crate) supertraits: Punctuated<TypeParamBound>,
pub(crate) items: Vec<TraitItem>,
}
}
ast_struct! {
/// A trait alias: `pub trait SharableIterator = Iterator + Sync`.
pub struct ItemTraitAlias {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) bounds: Punctuated<TypeParamBound>,
}
}
ast_struct! {
/// A type alias: `type Result<T> = std::result::Result<T, MyError>`.
pub struct ItemType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) ty: Box<Type>,
}
}
ast_struct! {
/// A union definition: `union Foo<A, B> { x: A, y: B }`.
pub struct ItemUnion {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) fields: FieldsNamed,
}
}
ast_struct! {
/// A use declaration: `use std::collections::HashMap`.
pub struct ItemUse {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(default, skip_serializing_if = "not")]
pub(crate) leading_colon: bool,
pub(crate) tree: UseTree,
}
}
ast_enum! {
/// A suffix of an import tree in a `use` item: `Type as Renamed` or `*`.
pub enum UseTree {
/// A path prefix of imports in a `use` item: `std::...`.
Path(UsePath),
/// An identifier imported by a `use` item: `HashMap`.
#[serde(rename = "ident")]
Name(UseName),
/// An renamed identifier imported by a `use` item: `HashMap as Map`.
Rename(UseRename),
/// A glob import in a `use` item: `*`.
#[serde(rename = "*")]
Glob,
/// A braced group of imports in a `use` item: `{A, B, C}`.
Group(UseGroup),
}
}
ast_struct! {
/// A path prefix of imports in a `use` item: `std::...`.
pub struct UsePath {
pub(crate) ident: Ident,
pub(crate) tree: Box<UseTree>,
}
}
ast_struct! {
/// An identifier imported by a `use` item: `HashMap`.
#[serde(transparent)]
pub struct UseName {
pub(crate) ident: Ident,
}
}
ast_struct! {
/// An renamed identifier imported by a `use` item: `HashMap as Map`.
pub struct UseRename {
pub(crate) ident: Ident,
pub(crate) rename: Ident,
}
}
ast_struct! {
/// A braced group of imports in a `use` item: `{A, B, C}`.
#[serde(transparent)]
pub struct UseGroup {
pub(crate) items: Punctuated<UseTree>,
}
}
ast_enum! {
/// An item within an `extern` block.
pub enum ForeignItem {
/// A foreign function in an `extern` block.
Fn(ForeignItemFn),
/// A foreign static item in an `extern` block: `static ext: u8`.
Static(ForeignItemStatic),
/// A foreign type in an `extern` block: `type void`.
Type(ForeignItemType),
/// A macro invocation within an extern block.
Macro(ForeignItemMacro),
/// Tokens in an `extern` block not interpreted by Syn.
Verbatim(TokenStream),
#[doc(hidden)]
__Nonexhaustive,
}
}
ast_struct! {
/// A foreign function in an `extern` block.
pub struct ForeignItemFn {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(flatten)]
pub(crate) sig: Signature,
}
}
ast_struct! {
/// A foreign static item in an `extern` block: `static ext: u8`.
pub struct ForeignItemStatic {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) ident: Ident,
pub(crate) ty: Box<Type>,
}
}
ast_struct! {
/// A foreign type in an `extern` block: `type void`.
pub struct ForeignItemType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
pub(crate) ident: Ident,
}
}
ast_struct! {
/// A macro invocation within an extern block.
pub struct ForeignItemMacro {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) mac: Macro,
#[serde(default, skip_serializing_if = "not")]
pub(crate) semi_token: bool,
}
}
ast_enum! {
/// An item declaration within the definition of a trait.
pub enum TraitItem {
/// An associated constant within the definition of a trait.
Const(TraitItemConst),
/// A trait method within the definition of a trait.
Method(TraitItemMethod),
/// An associated type within the definition of a trait.
Type(TraitItemType),
/// A macro invocation within the definition of a trait.
Macro(TraitItemMacro),
/// Tokens within the definition of a trait not interpreted by Syn.
Verbatim(TokenStream),
#[doc(hidden)]
__Nonexhaustive,
}
}
ast_struct! {
/// An associated constant within the definition of a trait.
pub struct TraitItemConst {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) ident: Ident,
pub(crate) ty: Type,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) default: Option<Expr>,
}
}
ast_struct! {
/// A trait method within the definition of a trait.
pub struct TraitItemMethod {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) sig: Signature,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) default: Option<Block>,
// #[serde(default, skip_serializing_if = "not")]
// pub(crate) semi_token: bool,
}
}
ast_struct! {
/// An associated type within the definition of a trait.
pub struct TraitItemType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon_token: bool,
#[serde(default, skip_serializing_if = "Punctuated::is_empty")]
pub(crate) bounds: Punctuated<TypeParamBound>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) default: Option<Type>,
}
}
ast_struct! {
/// A macro invocation within the definition of a trait.
pub struct TraitItemMacro {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) mac: Macro,
#[serde(default, skip_serializing_if = "not")]
pub(crate) semi_token: bool,
}
}
ast_enum! {
/// An item within an impl block.
pub enum ImplItem {
/// An associated constant within an impl block.
Const(ImplItemConst),
/// A method within an impl block.
Method(ImplItemMethod),
/// An associated type within an impl block.
Type(ImplItemType),
/// A macro invocation within an impl block.
Macro(ImplItemMacro),
/// Tokens within an impl block not interpreted by Syn.
Verbatim(TokenStream),
#[doc(hidden)]
__Nonexhaustive,
}
}
ast_struct! {
/// An associated constant within an impl block.
pub struct ImplItemConst {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(rename = "default")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) defaultness: bool,
pub(crate) ident: Ident,
pub(crate) ty: Type,
pub(crate) expr: Expr,
}
}
ast_struct! {
/// A method within an impl block.
pub struct ImplItemMethod {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(rename = "default")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) defaultness: bool,
#[serde(flatten)]
pub(crate) sig: Signature,
#[serde(rename = "stmts")]
pub(crate) block: Block,
}
}
ast_struct! {
/// An associated type within an impl block.
pub struct ImplItemType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Visibility::is_inherited")]
pub(crate) vis: Visibility,
#[serde(rename = "default")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) defaultness: bool,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) ty: Type,
}
}
ast_struct! {
/// A macro invocation within an impl block.
pub struct ImplItemMacro {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) mac: Macro,
#[serde(default, skip_serializing_if = "not")]
pub(crate) semi_token: bool,
}
}
ast_struct! {
/// A function signature in a trait or implementation: `unsafe fn
/// initialize(&self)`.
pub struct Signature {
#[serde(rename = "const")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) constness: bool,
#[serde(rename = "async")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) asyncness: bool,
#[serde(rename = "unsafe")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) unsafety: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) abi: Option<Abi>,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Generics::is_none")]
pub(crate) generics: Generics,
pub(crate) inputs: Punctuated<FnArg>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) variadic: Option<Variadic>,
#[serde(default)]
pub(crate) output: ReturnType,
}
}
ast_enum! {
/// An argument in a function signature: the `n: usize` in `fn f(n: usize)`.
pub enum FnArg {
/// The `self` argument of an associated method, whether taken by value
/// or by reference.
Receiver(Receiver),
/// A function argument accepted by pattern and type.
Typed(PatType),
}
}
ast_struct! {
/// The `self` argument of an associated method, whether taken by value
/// or by reference.
pub struct Receiver {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "ref")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) reference: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) lifetime: Option<Lifetime>,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
}
}
mod convert {
use super::*;
// ItemStruct
syn_trait_impl!(syn::ItemStruct);
impl From<&syn::ItemStruct> for ItemStruct {
fn from(other: &syn::ItemStruct) -> Self {
let fields: Fields = other.fields.ref_into();
assert_struct_semi(&fields, other.semi_token.is_some());
Self {
attrs: other.attrs.map_into(),
vis: other.vis.ref_into(),
ident: other.ident.ref_into(),
generics: other.generics.ref_into(),
fields,
}
}
}
impl From<&ItemStruct> for syn::ItemStruct {
fn from(other: &ItemStruct) -> Self {
Self {
attrs: other.attrs.map_into(),
vis: other.vis.ref_into(),
struct_token: default(),
ident: other.ident.ref_into(),
generics: other.generics.ref_into(),
fields: other.fields.ref_into(),
semi_token: default_or_none(!other.fields.is_named()),
}
}
}
// TraitItemMethod
syn_trait_impl!(syn::TraitItemMethod);
impl From<&syn::TraitItemMethod> for TraitItemMethod {
fn from(other: &syn::TraitItemMethod) -> Self {
if other.default.is_some() {
// `fn foo() -> bool {};`
assert!(other.semi_token.is_none(), "unexpected token: `;`");
} else {
// `fn foo() -> bool`
assert!(other.semi_token.is_some(), "expected `;`");
}
Self {
attrs: other.attrs.map_into(),
sig: other.sig.ref_into(),
default: other.default.map_into(),
}
}
}
impl From<&TraitItemMethod> for syn::TraitItemMethod {
fn from(other: &TraitItemMethod) -> Self {
Self {
attrs: other.attrs.map_into(),
sig: other.sig.ref_into(),
default: other.default.map_into(),
semi_token: default_or_none(other.default.is_none()),
}
}
}
// UseTree
syn_trait_impl!(syn::UseTree);
impl From<&syn::UseTree> for UseTree {
fn from(other: &syn::UseTree) -> Self {
use super::UseTree::*;
use syn::UseTree;
match other {
UseTree::Path(x) => Path(x.into()),
UseTree::Name(x) => Name(x.into()),
UseTree::Rename(x) => Rename(x.into()),
UseTree::Glob(_) => Glob,
UseTree::Group(x) => Group(x.into()),
}
}
}
impl From<&UseTree> for syn::UseTree {
fn from(other: &UseTree) -> Self {
use syn::UseTree::*;
match other {
UseTree::Path(x) => Path(x.into()),
UseTree::Name(x) => Name(x.into()),
UseTree::Rename(x) => Rename(x.into()),
UseTree::Glob => Glob(syn::UseGlob {
star_token: default(),
}),
UseTree::Group(x) => Group(x.into()),
}
}
}
// Receiver
syn_trait_impl!(syn::Receiver);
impl From<&syn::Receiver> for Receiver {
fn from(node: &syn::Receiver) -> Self {
Receiver {
attrs: node.attrs.map_into(),
reference: node.reference.is_some(),
lifetime: node.reference.as_ref().and_then(|(_0, _1)| _1.map_into()),
mutability: node.mutability.is_some(),
}
}
}
impl From<&Receiver> for syn::Receiver {
fn from(node: &Receiver) -> Self {
syn::Receiver {
attrs: node.attrs.map_into(),
reference: if node.reference {
Some((default(), node.lifetime.map_into()))
} else {
None
},
mutability: default_or_none(node.mutability),
self_token: default(),
}
}
}
}

210
syn-serde/src/json.rs Normal file
View file

@ -0,0 +1,210 @@
//! A module to provide functions for JSON <-> Rust serialize and deserialize.
//!
//! *This module is available if syn-serde is built with the `"json"` feature.*
use super::*;
use serde_json::Result;
use std::io;
// Serialize [`Syn`] type into JSON data.
/// Serialize the given [`Syn`] type as JSON into the IO stream.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # use std::io;
/// # fn to_writer<W>(writer: W, syn_file: &syn::File) -> serde_json::Result<()>
/// # where
/// # W: io::Write,
/// # {
/// use syn_serde::Syn;
///
/// let serializable_file = syn_file.to_adapter();
/// serde_json::to_writer(writer, &serializable_file)
/// # }
/// ```
#[inline]
pub fn to_writer<S, W>(writer: W, syn: &S) -> Result<()>
where
S: Syn,
W: io::Write,
{
let adapter = syn.to_adapter();
serde_json::to_writer(writer, &adapter)
}
/// Serialize the given [`Syn`] type as pretty-printed JSON into the IO
/// stream.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # use std::io;
/// # fn to_writer_pretty<W: io::Write>(writer: W, syn_file: &syn::File) -> serde_json::Result<()> {
/// use syn_serde::Syn;
///
/// let serializable_file = syn_file.to_adapter();
/// serde_json::to_writer_pretty(writer, &serializable_file)
/// # }
/// ```
#[inline]
pub fn to_writer_pretty<S, W>(writer: W, syn: &S) -> Result<()>
where
S: Syn,
W: io::Write,
{
let adapter = syn.to_adapter();
serde_json::to_writer_pretty(writer, &adapter)
}
/// Serialize the given [`Syn`] type as a JSON byte vector.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # fn to_vec(syn_file: &syn::File) -> Vec<u8> {
/// use syn_serde::Syn;
///
/// let serializable_file = syn_file.to_adapter();
/// serde_json::to_vec(&serializable_file).unwrap()
/// # }
/// ```
#[inline]
pub fn to_vec<S>(syn: &S) -> Vec<u8>
where
S: Syn,
{
let adapter = syn.to_adapter();
serde_json::to_vec(&adapter).unwrap()
}
/// Serialize the given [`Syn`] type as a pretty-printed JSON byte vector.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # fn to_vec_pretty(syn_file: &syn::File) -> Vec<u8> {
/// use syn_serde::Syn;
///
/// let serializable_file = syn_file.to_adapter();
/// serde_json::to_vec_pretty(&serializable_file).unwrap()
/// # }
/// ```
#[inline]
pub fn to_vec_pretty<S>(syn: &S) -> Vec<u8>
where
S: Syn,
{
let adapter = syn.to_adapter();
serde_json::to_vec_pretty(&adapter).unwrap()
}
/// Serialize the given [`Syn`] type as a String of JSON.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # fn to_string(syn_file: &syn::File) -> String {
/// use syn_serde::Syn;
///
/// let serializable_file = syn_file.to_adapter();
/// serde_json::to_string(&serializable_file).unwrap()
/// # }
/// ```
#[inline]
pub fn to_string<S>(syn: &S) -> String
where
S: Syn,
{
let adapter = syn.to_adapter();
serde_json::to_string(&adapter).unwrap()
}
/// Serialize the given [`Syn`] type as a pretty-printed String of JSON.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # fn to_string_pretty(syn_file: &syn::File) -> String {
/// use syn_serde::Syn;
///
/// let serializable_file = syn_file.to_adapter();
/// serde_json::to_string_pretty(&serializable_file).unwrap()
/// # }
/// ```
#[inline]
pub fn to_string_pretty<S>(syn: &S) -> String
where
S: Syn,
{
let adapter = syn.to_adapter();
serde_json::to_string_pretty(&adapter).unwrap()
}
// Deserialize JSON data to [`Syn`] type.
/// Deserialize an instance of [`Syn`] type from an IO stream of JSON.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # use std::io;
/// # fn from_reader<R: io::Read>(rdr: R) -> serde_json::Result<syn::File> {
/// use syn_serde::Syn;
///
/// let serializable_file: <syn::File as Syn>::Adapter = serde_json::from_reader(rdr)?;
/// let syn_file = syn::File::from_adapter(&serializable_file);
/// Ok(syn_file)
/// # }
/// ```
pub fn from_reader<S, R>(rdr: R) -> Result<S>
where
S: Syn,
R: io::Read,
{
let adapter: S::Adapter = serde_json::from_reader(rdr)?;
Ok(S::from_adapter(&adapter))
}
/// Deserialize an instance of [`Syn`] type from bytes of JSON text.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # fn from_reader(v: &[u8]) -> serde_json::Result<syn::File> {
/// use syn_serde::Syn;
///
/// let serializable_file: <syn::File as Syn>::Adapter = serde_json::from_slice(v)?;
/// let syn_file = syn::File::from_adapter(&serializable_file);
/// Ok(syn_file)
/// # }
/// ```
pub fn from_slice<S>(v: &[u8]) -> Result<S>
where
S: Syn,
{
let adapter: S::Adapter = serde_json::from_slice(v)?;
Ok(S::from_adapter(&adapter))
}
/// Deserialize an instance of [`Syn`] type from a string of JSON text.
///
/// This function is equivalent to the following code:
///
/// ```rust
/// # fn from_str(s: &str) -> serde_json::Result<syn::File> {
/// use syn_serde::Syn;
///
/// let serializable_file: <syn::File as Syn>::Adapter = serde_json::from_str(s)?;
/// let syn_file = syn::File::from_adapter(&serializable_file);
/// Ok(syn_file)
/// # }
/// ```
pub fn from_str<S>(s: &str) -> Result<S>
where
S: Syn,
{
let adapter: S::Adapter = serde_json::from_str(s)?;
Ok(S::from_adapter(&adapter))
}

350
syn-serde/src/lib.rs Normal file
View file

@ -0,0 +1,350 @@
//! Library to serialize and deserialize [Syn] syntax trees.
//!
//! ## Examples
//!
//! ```toml
//! [dependencies]
//! syn-serde = { version = "0.2", features = ["json"] }
//! syn = { version = "1", features = ["full"] }
//! ```
//!
//! ```rust
//! # #[cfg(feature = "json")]
//! # fn dox() -> Result<(), Box<dyn std::error::Error>> {
//! use syn_serde::json;
//!
//! let syn_file: syn::File = syn::parse_quote! {
//! fn main() {
//! println!("Hello, world!");
//! }
//! };
//! println!("{}", json::to_string_pretty(&syn_file));
//! # Ok(())
//! # }
//! ```
//!
//! 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.
//!
//! ### JSON file -> Rust syntax tree
//!
//! The [`json2rust`] example parse a JSON file into a `syn_serde::File` and
//! print out a Rust syntax tree.
//!
//! ## Optional features
//!
//! - **`json`** — Provides functions for JSON <-> Rust serializing and
//! deserializing.
//!
//! [Syn]: https://github.com/dtolnay/syn
//! [`rust2json`]: https://github.com/taiki-e/syn-serde/tree/master/examples/rust2json
//! [`json2rust`]: https://github.com/taiki-e/syn-serde/tree/master/examples/json2rust
#![doc(html_root_url = "https://docs.rs/syn-serde/0.2.0")]
#![doc(test(
no_crate_inject,
attr(
deny(warnings, rust_2018_idioms, single_use_lifetimes),
allow(dead_code)
)
))]
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, unreachable_pub)]
// It cannot be included in the published code because these lints have false positives in the minimum required version.
#![cfg_attr(test, warn(single_use_lifetimes))]
#![warn(clippy::all)]
// mem::take requires Rust 1.40
#![allow(clippy::mem_replace_with_default)]
#![allow(clippy::large_enum_variant, clippy::needless_doctest_main)]
#[macro_use]
mod macros;
mod gen;
mod attr;
#[doc(hidden)]
pub use crate::attr::*;
mod data;
#[doc(hidden)]
pub use crate::data::*;
mod expr;
#[doc(hidden)]
pub use crate::expr::*;
mod generics;
#[doc(hidden)]
pub use crate::generics::*;
mod item;
#[doc(hidden)]
pub use crate::item::*;
mod file;
#[doc(hidden)]
pub use crate::file::File;
mod lifetime;
#[doc(hidden)]
pub use crate::lifetime::Lifetime;
mod lit;
#[doc(hidden)]
pub use crate::lit::*;
mod mac;
#[doc(hidden)]
pub use crate::mac::{Macro, MacroDelimiter};
mod op;
#[doc(hidden)]
pub use crate::op::{BinOp, UnOp};
mod ty;
#[doc(hidden)]
pub use crate::ty::*;
mod pat;
#[doc(hidden)]
pub use crate::pat::*;
mod path;
#[doc(hidden)]
pub use crate::path::*;
mod stmt;
#[doc(hidden)]
pub use crate::stmt::{Block, Local, Stmt};
mod token_stream;
#[doc(hidden)]
pub use crate::token_stream::{
Delimiter, Group, Ident, Literal, Punct, Spacing, TokenStream, TokenTree,
};
#[cfg(feature = "json")]
pub mod json;
// =============================================================================
// Syn trait
mod private {
pub trait Sealed {}
}
/// A trait for the data structures of [Syn] and [proc-macro2].
///
/// [Syn]: https://github.com/dtolnay/syn
/// [proc-macro2]: https://github.com/alexcrichton/proc-macro2
#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058
pub trait Syn: Sized + private::Sealed {
type Adapter: Serialize + for<'de> Deserialize<'de>;
/// Converts a `Syn` type into an adapter.
///
/// ## Examples
///
/// ```rust
/// # #[cfg(feature = "json")]
/// # fn dox() {
/// use syn_serde::Syn;
///
/// let syn_file: syn::File = syn::parse_quote! {
/// fn main() {
/// println!("Hello, world!");
/// }
/// };
///
/// let serializable_file = syn_file.to_adapter();
/// println!("{}", serde_json::to_string_pretty(&serializable_file).unwrap());
/// # }
/// ```
fn to_adapter(&self) -> Self::Adapter;
/// Converts an adapter into a `Syn` type.
///
/// ## Examples
///
/// ```rust
/// # #[cfg(feature = "json")]
/// # fn dox() -> Result<(), Box<dyn std::error::Error>> {
/// use syn_serde::Syn;
///
/// // `struct Unit;`
/// let json = r#"{
/// "struct": {
/// "ident": "Unit",
/// "fields": "unit"
/// }
/// }"#;
///
/// let serializable_file: <syn::File as Syn>::Adapter = serde_json::from_str(json)?;
/// let _syn_file = syn::File::from_adapter(&serializable_file);
/// # Ok(())
/// # }
/// ```
fn from_adapter(adapter: &Self::Adapter) -> Self;
}
// =============================================================================
use proc_macro2::Span;
use serde::{Deserialize, Serialize};
type Punctuated<T> = Vec<T>;
fn default<T>() -> T
where
T: Default,
{
T::default()
}
fn default_or_none<T>(x: bool) -> Option<T>
where
T: Default,
{
if x {
Some(T::default())
} else {
None
}
}
fn not<T>(x: T) -> T::Output
where
T: std::ops::Not,
{
!x
}
// https://github.com/rust-lang/rust/issues/51443
trait RefInto<U>: Sized {
fn ref_into<'a>(&'a self) -> U
where
&'a Self: Into<U>,
{
self.into()
}
}
impl<T, U> RefInto<U> for T {}
trait MapInto<U, M> {
type T;
fn ref_map<'a, F>(&'a self, f: F) -> M
where
Self::T: 'a,
F: FnMut(&'a Self::T) -> U;
fn map_into<'a>(&'a self) -> M
where
Self::T: 'a,
&'a Self::T: Into<U>,
{
self.ref_map(Into::into)
}
}
impl<T, U> MapInto<U, Vec<U>> for Vec<T> {
type T = T;
fn ref_map<'a, F>(&'a self, f: F) -> Vec<U>
where
F: FnMut(&'a Self::T) -> U,
{
self.iter().map(f).collect()
}
}
impl<T, U, P> MapInto<U, syn::punctuated::Punctuated<U, P>> for Vec<T>
where
P: Default,
{
type T = T;
fn ref_map<'a, F>(&'a self, f: F) -> syn::punctuated::Punctuated<U, P>
where
F: FnMut(&'a Self::T) -> U,
{
self.iter().map(f).collect()
}
}
impl<T, U, P> MapInto<U, Vec<U>> for syn::punctuated::Punctuated<T, P>
where
P: Default,
{
type T = T;
fn ref_map<'a, F>(&'a self, f: F) -> Vec<U>
where
F: FnMut(&'a Self::T) -> U,
{
self.iter().map(f).collect()
}
}
impl<T, U> MapInto<U, Option<U>> for Option<T> {
type T = T;
fn ref_map<'a, F>(&'a self, f: F) -> Option<U>
where
F: FnMut(&'a Self::T) -> U,
{
self.as_ref().map(f)
}
}
impl<T, U> MapInto<U, Box<U>> for Box<T> {
type T = T;
fn ref_map<'a, F>(&'a self, mut f: F) -> Box<U>
where
F: FnMut(&'a Self::T) -> U,
{
Box::new(f(&**self))
}
}

19
syn-serde/src/lifetime.rs Normal file
View file

@ -0,0 +1,19 @@
use super::*;
ast_struct! {
/// A Rust lifetime: `'a`.
///
/// Lifetime names must conform to the following rules:
///
/// - Must start with an apostrophe.
/// - Must not consist of just an apostrophe: `'`.
/// - Character after the apostrophe must be `_` or a Unicode code point with
/// the XID_Start property.
/// - All following characters must be Unicode code points with the XID_Continue
/// property.
#[derive(Clone)]
#[serde(transparent)]
pub struct Lifetime {
pub(crate) ident: Ident,
}
}

525
syn-serde/src/lit.rs Normal file
View file

@ -0,0 +1,525 @@
use super::*;
ast_enum! {
/// A Rust literal such as a string or integer or boolean.
pub enum Lit {
/// A UTF-8 string literal: `"foo"`.
Str(LitStr),
/// A byte string literal: `b"foo"`.
ByteStr(LitByteStr),
/// A byte literal: `b'f'`.
Byte(LitByte),
/// A character literal: `'a'`.
Char(LitChar),
/// An integer literal: `1` or `1u16`.
Int(LitInt),
/// A floating point literal: `1f64` or `1.0e10f64`.
///
/// Must be finite. May not be infinte or NaN.
Float(LitFloat),
/// A boolean literal: `true` or `false`.
Bool(LitBool),
/// A raw token literal not interpreted by Syn.
Verbatim(Literal),
}
}
ast_struct! {
/// A UTF-8 string literal: `"foo"`.
#[serde(transparent)]
pub struct LitStr {
token: Literal,
}
}
ast_struct! {
/// A byte string literal: `b"foo"`.
#[serde(transparent)]
pub struct LitByteStr {
token: Literal,
}
}
ast_struct! {
/// A byte literal: `b'f'`.
#[serde(transparent)]
pub struct LitByte {
token: Literal,
}
}
ast_struct! {
/// A character literal: `'a'`.
#[serde(transparent)]
pub struct LitChar {
token: Literal,
}
}
ast_struct! {
/// An integer literal: `1` or `1u16`.
#[serde(transparent)]
pub struct LitInt {
token: Literal,
}
}
ast_struct! {
/// A floating point literal: `1f64` or `1.0e10f64`.
///
/// Must be finite. May not be infinte or NaN.
#[serde(transparent)]
pub struct LitFloat {
token: Literal,
}
}
ast_struct! {
/// A boolean literal: `true` or `false`.
#[serde(transparent)]
pub struct LitBool {
pub(crate) value: bool,
}
}
ast_enum! {
/// The style of a string literal, either plain quoted or a raw string like
/// `r##"data"##`.
pub enum StrStyle {
/// An ordinary string like `"data"`.
Cooked,
/// A raw string like `r##"data"##`.
///
/// The unsigned integer is the number of `#` symbols used.
Raw(usize),
}
}
mod value {
use super::*;
use proc_macro2::{TokenStream, TokenTree};
use std::{
char,
ops::{Index, RangeFrom},
};
/// Get the byte at offset idx, or a default of `b'\0'` if we're looking
/// past the end of the input buffer.
pub(crate) fn byte<S: AsRef<[u8]> + ?Sized>(s: &S, idx: usize) -> u8 {
let s = s.as_ref();
if idx < s.len() {
s[idx]
} else {
0
}
}
fn next_chr(s: &str) -> char {
s.chars().next().unwrap_or('\0')
}
// Returns (content, suffix).
pub(crate) fn parse_lit_str(s: &str) -> (Box<str>, Box<str>) {
match byte(s, 0) {
b'"' => parse_lit_str_cooked(s),
b'r' => parse_lit_str_raw(s),
_ => unreachable!(),
}
}
fn parse_lit_str_cooked(mut s: &str) -> (Box<str>, Box<str>) {
assert_eq!(byte(s, 0), b'"');
s = &s[1..];
let mut content = String::new();
'outer: loop {
let ch = match byte(s, 0) {
b'"' => break,
b'\\' => {
let b = byte(s, 1);
s = &s[2..];
match b {
b'x' => {
let (byte, rest) = backslash_x(s);
s = rest;
assert!(byte <= 0x80, "Invalid \\x byte in string literal");
char::from_u32(u32::from(byte)).unwrap()
}
b'u' => {
let (chr, rest) = backslash_u(s);
s = rest;
chr
}
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
b'\\' => '\\',
b'0' => '\0',
b'\'' => '\'',
b'"' => '"',
b'\r' | b'\n' => loop {
let ch = next_chr(s);
if ch.is_whitespace() {
s = &s[ch.len_utf8()..];
} else {
continue 'outer;
}
},
b => panic!("unexpected byte {:?} after \\ character in byte literal", b),
}
}
b'\r' => {
assert_eq!(byte(s, 1), b'\n', "Bare CR not allowed in string");
s = &s[2..];
'\n'
}
_ => {
let ch = next_chr(s);
s = &s[ch.len_utf8()..];
ch
}
};
content.push(ch);
}
assert!(s.starts_with('"'));
let content = content.into_boxed_str();
let suffix = s[1..].to_owned().into_boxed_str();
(content, suffix)
}
fn parse_lit_str_raw(mut s: &str) -> (Box<str>, Box<str>) {
assert_eq!(byte(s, 0), b'r');
s = &s[1..];
let mut pounds = 0;
while byte(s, pounds) == b'#' {
pounds += 1;
}
assert_eq!(byte(s, pounds), b'"');
assert_eq!(byte(s, s.len() - pounds - 1), b'"');
for end in s[s.len() - pounds..].bytes() {
assert_eq!(end, b'#');
}
let content = s[pounds + 1..s.len() - pounds - 1]
.to_owned()
.into_boxed_str();
let suffix = Box::<str>::default(); // todo
(content, suffix)
}
pub(crate) fn parse_lit_byte_str(s: &str) -> Vec<u8> {
assert_eq!(byte(s, 0), b'b');
match byte(s, 1) {
b'"' => parse_lit_byte_str_cooked(s),
b'r' => parse_lit_byte_str_raw(s),
_ => unreachable!(),
}
}
fn parse_lit_byte_str_cooked(mut s: &str) -> Vec<u8> {
assert_eq!(byte(s, 0), b'b');
assert_eq!(byte(s, 1), b'"');
s = &s[2..];
// We're going to want to have slices which don't respect codepoint boundaries.
let mut s = s.as_bytes();
let mut out = Vec::new();
'outer: loop {
let byte = match byte(s, 0) {
b'"' => break,
b'\\' => {
let b = byte(s, 1);
s = &s[2..];
match b {
b'x' => {
let (b, rest) = backslash_x(s);
s = rest;
b
}
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'0' => b'\0',
b'\'' => b'\'',
b'"' => b'"',
b'\r' | b'\n' => loop {
let byte = byte(s, 0);
let ch = char::from_u32(u32::from(byte)).unwrap();
if ch.is_whitespace() {
s = &s[1..];
} else {
continue 'outer;
}
},
b => panic!("unexpected byte {:?} after \\ character in byte literal", b),
}
}
b'\r' => {
assert_eq!(byte(s, 1), b'\n', "Bare CR not allowed in string");
s = &s[2..];
b'\n'
}
b => {
s = &s[1..];
b
}
};
out.push(byte);
}
assert_eq!(s, b"\"");
out
}
fn parse_lit_byte_str_raw(s: &str) -> Vec<u8> {
assert_eq!(byte(s, 0), b'b');
String::from(parse_lit_str_raw(&s[1..]).0).into_bytes()
}
pub(crate) fn parse_lit_byte(s: &str) -> u8 {
assert_eq!(byte(s, 0), b'b');
assert_eq!(byte(s, 1), b'\'');
// We're going to want to have slices which don't respect codepoint boundaries.
let mut s = s[2..].as_bytes();
let b = match byte(s, 0) {
b'\\' => {
let b = byte(s, 1);
s = &s[2..];
match b {
b'x' => {
let (b, rest) = backslash_x(s);
s = rest;
b
}
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'0' => b'\0',
b'\'' => b'\'',
b'"' => b'"',
b => panic!("unexpected byte {:?} after \\ character in byte literal", b),
}
}
b => {
s = &s[1..];
b
}
};
assert_eq!(byte(s, 0), b'\'');
b
}
pub(crate) fn parse_lit_char(mut s: &str) -> char {
assert_eq!(byte(s, 0), b'\'');
s = &s[1..];
let ch = if byte(s, 0) == b'\\' {
let b = byte(s, 1);
s = &s[2..];
match b {
b'x' => {
let (byte, rest) = backslash_x(s);
s = rest;
assert!(byte <= 0x80, "Invalid \\x byte in string literal");
char::from_u32(u32::from(byte)).unwrap()
}
b'u' => {
let (chr, rest) = backslash_u(s);
s = rest;
chr
}
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
b'\\' => '\\',
b'0' => '\0',
b'\'' => '\'',
b'"' => '"',
b => panic!("unexpected byte {:?} after \\ character in byte literal", b),
}
} else {
let ch = next_chr(s);
s = &s[ch.len_utf8()..];
ch
};
assert_eq!(s, "\'", "Expected end of char literal");
ch
}
fn backslash_x<S>(s: &S) -> (u8, &S)
where
S: Index<RangeFrom<usize>, Output = S> + AsRef<[u8]> + ?Sized,
{
let mut ch = 0;
let b0 = byte(s, 0);
let b1 = byte(s, 1);
ch += 0x10
* match b0 {
b'0'..=b'9' => b0 - b'0',
b'a'..=b'f' => 10 + (b0 - b'a'),
b'A'..=b'F' => 10 + (b0 - b'A'),
_ => panic!("unexpected non-hex character after \\x"),
};
ch += match b1 {
b'0'..=b'9' => b1 - b'0',
b'a'..=b'f' => 10 + (b1 - b'a'),
b'A'..=b'F' => 10 + (b1 - b'A'),
_ => panic!("unexpected non-hex character after \\x"),
};
(ch, &s[2..])
}
fn backslash_u(mut s: &str) -> (char, &str) {
if byte(s, 0) != b'{' {
panic!("expected {{ after \\u");
}
s = &s[1..];
let mut ch = 0;
for _ in 0..6 {
let b = byte(s, 0);
match b {
b'0'..=b'9' => {
ch *= 0x10;
ch += u32::from(b - b'0');
s = &s[1..];
}
b'a'..=b'f' => {
ch *= 0x10;
ch += u32::from(10 + b - b'a');
s = &s[1..];
}
b'A'..=b'F' => {
ch *= 0x10;
ch += u32::from(10 + b - b'A');
s = &s[1..];
}
b'}' => break,
_ => panic!("unexpected non-hex character after \\u"),
}
}
assert!(byte(s, 0) == b'}');
s = &s[1..];
if let Some(ch) = char::from_u32(ch) {
(ch, s)
} else {
panic!("character code {:x} is not a valid unicode character", ch);
}
}
pub(crate) fn to_literal(s: &str) -> Literal {
let stream = s.parse::<TokenStream>().unwrap();
match stream.into_iter().next().unwrap() {
TokenTree::Literal(l) => l.ref_into(),
_ => unreachable!(),
}
}
}
mod convert {
use super::*;
// LitStr
impl From<&syn::LitStr> for LitStr {
fn from(other: &syn::LitStr) -> Self {
Self {
token: Literal::string(&other.value()),
}
}
}
impl From<&LitStr> for syn::LitStr {
fn from(other: &LitStr) -> Self {
let (value, _) = value::parse_lit_str(&other.token.text);
Self::new(&value, Span::call_site())
}
}
// LitByteStr
impl From<&syn::LitByteStr> for LitByteStr {
fn from(other: &syn::LitByteStr) -> Self {
Self {
token: Literal::byte_string(&other.value()),
}
}
}
impl From<&LitByteStr> for syn::LitByteStr {
fn from(other: &LitByteStr) -> Self {
let value = value::parse_lit_byte_str(&other.token.text);
Self::new(&value, Span::call_site())
}
}
// LitByte
impl From<&syn::LitByte> for LitByte {
fn from(other: &syn::LitByte) -> Self {
Self {
token: Literal::u8_suffixed(other.value()),
}
}
}
impl From<&LitByte> for syn::LitByte {
fn from(other: &LitByte) -> Self {
let value = value::parse_lit_byte(&other.token.text);
Self::new(value, Span::call_site())
}
}
// LitChar
impl From<&syn::LitChar> for LitChar {
fn from(other: &syn::LitChar) -> Self {
Self {
token: Literal::character(other.value()),
}
}
}
impl From<&LitChar> for syn::LitChar {
fn from(other: &LitChar) -> Self {
let value = value::parse_lit_char(&other.token.text);
Self::new(value, Span::call_site())
}
}
// LitInt
impl From<&syn::LitInt> for LitInt {
fn from(other: &syn::LitInt) -> Self {
Self {
token: value::to_literal(&other.to_string()),
}
}
}
impl From<&LitInt> for syn::LitInt {
fn from(other: &LitInt) -> Self {
Self::new(&other.token.text, Span::call_site())
}
}
// LitFloat
impl From<&syn::LitFloat> for LitFloat {
fn from(other: &syn::LitFloat) -> Self {
Self {
token: value::to_literal(&other.to_string()),
}
}
}
impl From<&LitFloat> for syn::LitFloat {
fn from(other: &LitFloat) -> Self {
Self::new(&other.token.text, Span::call_site())
}
}
}

19
syn-serde/src/mac.rs Normal file
View file

@ -0,0 +1,19 @@
use super::*;
ast_struct! {
/// A macro invocation: `println!("{}", mac)`.
pub struct Macro {
pub(crate) path: Path,
pub(crate) delimiter: MacroDelimiter,
pub(crate) tokens: TokenStream,
}
}
ast_enum! {
/// A grouping token that surrounds a macro body: `m!(...)` or `m!{...}` or `m![...]`.
pub enum MacroDelimiter {
Paren,
Brace,
Bracket,
}
}

57
syn-serde/src/macros.rs Normal file
View file

@ -0,0 +1,57 @@
macro_rules! ast_struct {
(
[$($attrs_pub:tt)*]
struct $name:ident $($rest:tt)*
) => {
#[derive(Debug, crate::Serialize, crate::Deserialize)]
$($attrs_pub)* struct $name $($rest)*
};
($($t:tt)*) => {
strip_attrs_pub!(ast_struct!($($t)*));
};
}
macro_rules! ast_enum {
(
[$($attrs_pub:tt)*]
enum $name:ident $($rest:tt)*
) => (
#[derive(Debug, crate::Serialize, crate::Deserialize)]
#[serde(rename_all = "snake_case")]
$($attrs_pub)* enum $name $($rest)*
);
($($t:tt)*) => {
strip_attrs_pub!(ast_enum!($($t)*));
};
}
macro_rules! strip_attrs_pub {
($mac:ident!($(#[$m:meta])* $pub:ident $($t:tt)*)) => {
check_keyword_matches!(pub $pub);
$mac!([$(#[$m])* $pub] $($t)*);
};
}
macro_rules! check_keyword_matches {
(struct struct) => {};
(enum enum) => {};
(pub pub) => {};
}
macro_rules! syn_trait_impl {
($path:ident :: $ty:ident) => {
impl crate::private::Sealed for $path::$ty {}
impl crate::Syn for $path::$ty {
type Adapter = $ty;
fn to_adapter(&self) -> Self::Adapter {
Self::Adapter::from(self)
}
fn from_adapter(adapter: &Self::Adapter) -> Self {
Self::from(adapter)
}
}
};
}

104
syn-serde/src/op.rs Normal file
View file

@ -0,0 +1,104 @@
ast_enum! {
/// A binary operator: `+`, `+=`, `&`.
pub enum BinOp {
/// The `+` operator (addition)
#[serde(rename = "+")]
Add,
/// The `-` operator (subtraction)
#[serde(rename = "-")]
Sub,
/// The `*` operator (multiplication)
#[serde(rename = "*")]
Mul,
/// The `/` operator (division)
#[serde(rename = "/")]
Div,
/// The `%` operator (modulus)
#[serde(rename = "%")]
Rem,
/// The `&&` operator (logical and)
#[serde(rename = "&&")]
And,
/// The `||` operator (logical or)
#[serde(rename = "||")]
Or,
/// The `^` operator (bitwise xor)
#[serde(rename = "^")]
BitXor,
/// The `&` operator (bitwise and)
#[serde(rename = "&")]
BitAnd,
/// The `|` operator (bitwise or)
#[serde(rename = "|")]
BitOr,
/// The `<<` operator (shift left)
#[serde(rename = "<<")]
Shl,
/// The `>>` operator (shift right)
#[serde(rename = ">>")]
Shr,
/// The `==` operator (equality)
#[serde(rename = "==")]
Eq,
/// The `<` operator (less than)
#[serde(rename = "<")]
Lt,
/// The `<=` operator (less than or equal to)
#[serde(rename = "<=")]
Le,
/// The `!=` operator (not equal to)
#[serde(rename = "!=")]
Ne,
/// The `>=` operator (greater than or equal to)
#[serde(rename = ">=")]
Ge,
/// The `>` operator (greater than)
#[serde(rename = ">")]
Gt,
/// The `+=` operator
#[serde(rename = "+=")]
AddEq,
/// The `-=` operator
#[serde(rename = "-=")]
SubEq,
/// The `*=` operator
#[serde(rename = "*=")]
MulEq,
/// The `/=` operator
#[serde(rename = "/=")]
DivEq,
/// The `%=` operator
#[serde(rename = "%=")]
RemEq,
/// The `^=` operator
#[serde(rename = "^=")]
BitXorEq,
/// The `&=` operator
#[serde(rename = "&=")]
BitAndEq,
/// The `|=` operator
#[serde(rename = "|=")]
BitOrEq,
/// The `<<=` operator
#[serde(rename = "<<=")]
ShlEq,
/// The `>>=` operator
#[serde(rename = ">>=")]
ShrEq,
}
}
ast_enum! {
/// A unary operator: `*`, `!`, `-`.
pub enum UnOp {
/// The `*` operator for dereferencing
#[serde(rename = "*")]
Deref,
/// The `!` operator for logical inversion
#[serde(rename = "!")]
Not,
/// The `-` operator for negation
#[serde(rename = "-")]
Neg,
}
}

250
syn-serde/src/pat.rs Normal file
View file

@ -0,0 +1,250 @@
use super::*;
ast_enum! {
/// A pattern in a local binding, function signature, match expression, or
/// various other places.
pub enum Pat {
/// A box pattern: `box v`.
Box(PatBox),
/// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`.
Ident(PatIdent),
/// A literal pattern: `0`.
///
/// This holds an `Expr` rather than a `Lit` because negative numbers
/// are represented as an `Expr::Unary`.
Lit(PatLit),
/// A macro in pattern position.
Macro(PatMacro),
/// A pattern that matches any one of a set of cases.
Or(PatOr),
/// A path pattern like `Color::Red`, optionally qualified with a
/// self-type.
///
/// Unqualified path patterns can legally refer to variants, structs,
/// constants or associated constants. Qualified path patterns like
/// `<A>::B::C` and `<A as Trait>::B::C` can only legally refer to
/// associated constants.
Path(PatPath),
/// A range pattern: `1..=2`.
Range(PatRange),
/// A reference pattern: `&mut var`.
Reference(PatReference),
/// The dots in a tuple or slice pattern: `[0, 1, ..]`
Rest(PatRest),
/// A dynamically sized slice pattern: `[a, b, ref i @ .., y, z]`.
Slice(PatSlice),
/// A struct or struct variant pattern: `Variant { x, y, .. }`.
Struct(PatStruct),
/// A tuple pattern: `(a, b)`.
Tuple(PatTuple),
/// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`.
TupleStruct(PatTupleStruct),
/// A type ascription pattern: `foo: f64`.
Type(PatType),
/// Tokens in pattern position not interpreted by Syn.
Verbatim(TokenStream),
/// A pattern that matches any value: `_`.
#[serde(rename = "_")]
Wild(PatWild),
#[doc(hidden)]
__Nonexhaustive,
}
}
ast_struct! {
/// A box pattern: `box v`.
pub struct PatBox {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) pat: Box<Pat>,
}
}
ast_struct! {
/// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`.
pub struct PatIdent {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "ref")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) by_ref: bool,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) subpat: Option<Box<Pat>>,
}
}
ast_struct! {
/// A literal pattern: `0`.
///
/// This holds an `Expr` rather than a `Lit` because negative numbers
/// are represented as an `Expr::Unary`.
pub struct PatLit {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) expr: Box<Expr>,
}
}
ast_struct! {
/// A macro in pattern position.
pub struct PatMacro {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) mac: Macro,
}
}
ast_struct! {
/// A pattern that matches any one of a set of cases.
pub struct PatOr {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "not")]
pub(crate) leading_vert: bool,
pub(crate) cases: Punctuated<Pat>,
}
}
ast_struct! {
/// A path pattern like `Color::Red`, optionally qualified with a
/// self-type.
///
/// Unqualified path patterns can legally refer to variants, structs,
/// constants or associated constants. Qualified path patterns like
/// `<A>::B::C` and `<A as Trait>::B::C` can only legally refer to
/// associated constants.
pub struct PatPath {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) qself: Option<QSelf>,
#[serde(flatten)]
pub(crate) path: Path,
}
}
ast_struct! {
/// A range pattern: `1..=2`.
pub struct PatRange {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) lo: Box<Expr>,
pub(crate) limits: RangeLimits,
pub(crate) hi: Box<Expr>,
}
}
ast_struct! {
/// A reference pattern: `&mut var`.
pub struct PatReference {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) pat: Box<Pat>,
}
}
ast_struct! {
/// The dots in a tuple or slice pattern: `[0, 1, ..]`
pub struct PatRest {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
}
}
ast_struct! {
/// A dynamically sized slice pattern: `[a, b, ref i @ .., y, z]`.
pub struct PatSlice {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) elems: Punctuated<Pat>,
}
}
ast_struct! {
/// A struct or struct variant pattern: `Variant { x, y, .. }`.
pub struct PatStruct {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) path: Path,
pub(crate) fields: Punctuated<FieldPat>,
#[serde(default, skip_serializing_if = "not")]
pub(crate) dot2_token: bool,
}
}
ast_struct! {
/// A tuple pattern: `(a, b)`.
pub struct PatTuple {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) elems: Punctuated<Pat>,
}
}
ast_struct! {
/// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`.
pub struct PatTupleStruct {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) path: Path,
pub(crate) pat: PatTuple,
}
}
ast_struct! {
/// A type ascription pattern: `foo: f64`.
pub struct PatType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) pat: Box<Pat>,
pub(crate) ty: Box<Type>,
}
}
ast_struct! {
/// A pattern that matches any value: `_`.
pub struct PatWild {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
}
}
ast_struct! {
/// A single field in a struct pattern.
///
/// Patterns like the fields of Foo `{ x, ref y, ref mut z }` are treated
/// the same as `x: x, y: ref y, z: ref mut z` but there is no colon token.
pub struct FieldPat {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(flatten)]
pub(crate) member: Member,
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon_token: bool,
pub(crate) pat: Box<Pat>,
}
}

136
syn-serde/src/path.rs Normal file
View file

@ -0,0 +1,136 @@
use super::*;
ast_struct! {
/// A path at which a named item is exported: `std::collections::HashMap`.
pub struct Path {
#[serde(default, skip_serializing_if = "not")]
pub(crate) leading_colon: bool,
pub(crate) segments: Punctuated<PathSegment>,
}
}
ast_struct! {
/// A segment of a path together with any path arguments on that segment.
pub struct PathSegment {
pub(crate) ident: Ident,
#[serde(default, skip_serializing_if = "PathArguments::is_none")]
pub(crate) arguments: PathArguments,
}
}
ast_enum! {
/// Angle bracketed or parenthesized arguments of a path segment.
///
/// ## Angle bracketed
///
/// The `<'a, T>` in `std::slice::iter<'a, T>`.
///
/// ## Parenthesized
///
/// The `(A, B) -> C` in `Fn(A, B) -> C`.
pub enum PathArguments {
None,
/// The `<'a, T>` in `std::slice::iter<'a, T>`.
AngleBracketed(AngleBracketedGenericArguments),
/// The `(A, B) -> C` in `Fn(A, B) -> C`.
Parenthesized(ParenthesizedGenericArguments),
}
}
impl Default for PathArguments {
fn default() -> Self {
PathArguments::None
}
}
impl PathArguments {
fn is_none(&self) -> bool {
match self {
PathArguments::None => true,
PathArguments::AngleBracketed(_) | PathArguments::Parenthesized(_) => false,
}
}
}
ast_enum! {
/// An individual generic argument, like `'a`, `T`, or `Item = T`.
pub enum GenericArgument {
/// A lifetime argument.
Lifetime(Lifetime),
/// A type argument.
Type(Type),
/// A binding (equality constraint) on an associated type: the `Item =
/// u8` in `Iterator<Item = u8>`.
Binding(Binding),
/// An associated type bound: `Iterator<Item: Display>`.
Constraint(Constraint),
/// A const expression. Must be inside of a block.
///
/// NOTE: Identity expressions are represented as Type arguments, as
/// they are indistinguishable syntactically.
Const(Expr),
}
}
ast_struct! {
/// Angle bracketed arguments of a path segment: the `<K, V>` in `HashMap<K,
/// V>`.
pub struct AngleBracketedGenericArguments {
#[serde(default, skip_serializing_if = "not")]
pub(crate) colon2_token: bool,
pub(crate) args: Punctuated<GenericArgument>,
}
}
ast_struct! {
/// A binding (equality constraint) on an associated type: `Item = u8`.
pub struct Binding {
pub(crate) ident: Ident,
pub(crate) ty: Type,
}
}
ast_struct! {
/// An associated type bound: `Iterator<Item: Display>`.
pub struct Constraint {
pub(crate) ident: Ident,
pub(crate) bounds: Punctuated<TypeParamBound>,
}
}
ast_struct! {
/// Arguments of a function path segment: the `(A, B) -> C` in `Fn(A,B) ->
/// C`.
pub struct ParenthesizedGenericArguments {
/// `(A, B)`
pub(crate) inputs: Punctuated<Type>,
/// `C`
#[serde(default)]
pub(crate) output: ReturnType,
}
}
ast_struct! {
/// The explicit Self type in a qualified path: the `T` in `<T as
/// Display>::fmt`.
///
/// The actual path, including the trait and the associated item, is stored
/// separately. The `position` field represents the index of the associated
/// item qualified with this Self type.
///
/// ```text
/// <Vec<T> as a::b::Trait>::AssociatedItem
/// ^~~~~~ ~~~~~~~~~~~~~~^
/// ty position = 3
///
/// <Vec<T>>::AssociatedItem
/// ^~~~~~ ^
/// ty position = 0
/// ```
pub struct QSelf {
pub(crate) ty: Box<Type>,
pub(crate) position: usize,
#[serde(default, skip_serializing_if = "not")]
pub(crate) as_token: bool,
}
}

39
syn-serde/src/stmt.rs Normal file
View file

@ -0,0 +1,39 @@
use super::*;
ast_struct! {
/// A braced block containing Rust statements.
#[serde(transparent)]
pub struct Block {
/// Statements in a block
pub(crate) stmts: Vec<Stmt>,
}
}
ast_enum! {
/// A statement, usually ending in a semicolon.
pub enum Stmt {
/// A local (let) binding.
#[serde(rename = "let")]
Local(Local),
/// An item definition.
Item(Item),
/// Expr without trailing semicolon.
Expr(Expr),
/// Expression with trailing semicolon.
Semi(Expr),
}
}
ast_struct! {
/// A local `let` binding: `let x: u64 = s.parse()?`.
pub struct Local {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
pub(crate) pat: Pat,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) init: Option<Box<Expr>>,
}
}

View file

@ -0,0 +1,345 @@
use super::*;
use std::fmt;
ast_struct! {
/// An abstract stream of tokens, or more concretely a sequence of token trees.
///
/// This type provides interfaces for iterating over token trees and for
/// collecting token trees into one stream.
#[derive(Clone, Default)]
#[serde(transparent)]
pub struct TokenStream {
inner: Vec<TokenTree>,
}
}
impl TokenStream {
fn _new(inner: Vec<TokenTree>) -> Self {
Self { inner }
}
pub(crate) fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
ast_enum! {
/// A single token or a delimited sequence of token trees (e.g. `[1, (), ..]`).
#[derive(Clone)]
pub enum TokenTree {
/// A token stream surrounded by bracket delimiters.
Group(Group),
/// An identifier.
Ident(Ident),
/// A single punctuation character (`+`, `,`, `$`, etc.).
Punct(Punct),
/// A literal character (`'a'`), string (`"hello"`), number (`2.3`), etc.
#[serde(rename = "lit")]
Literal(Literal),
}
}
ast_struct! {
/// A delimited token stream.
///
/// A `Group` internally contains a `TokenStream` which is surrounded by
/// `Delimiter`s.
#[derive(Clone)]
pub struct Group {
delimiter: Delimiter,
stream: TokenStream,
}
}
ast_enum! {
/// Describes how a sequence of token trees is delimited.
#[derive(Clone, Copy)]
pub enum Delimiter {
/// `( ... )`
Parenthesis,
/// `{ ... }`
Brace,
/// `[ ... ]`
Bracket,
/// `Ø ... Ø`
///
/// An implicit delimiter, that may, for example, appear around tokens
/// coming from a "macro variable" `$var`. It is important to preserve
/// operator priorities in cases like `$var * 3` where `$var` is `1 + 2`.
/// Implicit delimiters may not survive roundtrip of a token stream through
/// a string.
None,
}
}
ast_struct! {
/// An `Punct` is an single punctuation character like `+`, `-` or `#`.
///
/// Multicharacter operators like `+=` are represented as two instances of
/// `Punct` with different forms of `Spacing` returned.
#[derive(Clone, Copy)]
pub struct Punct {
op: char,
spacing: Spacing,
}
}
ast_enum! {
/// Whether an `Punct` is followed immediately by another `Punct` or followed by
/// another token or whitespace.
#[derive(Clone, Copy)]
pub enum Spacing {
/// E.g. `+` is `Alone` in `+ =`, `+ident` or `+()`.
Alone,
/// E.g. `+` is `Joint` in `+=` or `'#`.
///
/// Additionally, single quote `'` can join with identifiers to form
/// lifetimes `'ident`.
Joint,
}
}
ast_struct! {
/// A word of Rust code, which may be a keyword or legal variable name.
///
/// An identifier consists of at least one Unicode code point, the first of
/// which has the XID_Start property and the rest of which have the XID_Continue
/// property.
///
/// - The empty string is not an identifier. Use `Option<Ident>`.
/// - A lifetime is not an identifier. Use `syn::Lifetime` instead.
#[derive(Clone, Eq, PartialEq)]
#[serde(transparent)]
pub struct Ident {
inner: String,
}
}
ast_struct! {
/// A literal string (`"hello"`), byte string (`b"hello"`), character (`'a'`),
/// byte character (`b'a'`), an integer or floating point number with or without
/// a suffix (`1`, `1u8`, `2.3`, `2.3f32`).
///
/// Boolean literals like `true` and `false` do not belong here, they are
/// `Ident`s.
#[derive(Clone)]
#[serde(transparent)]
pub struct Literal {
pub(crate) text: String,
}
}
impl Literal {
fn _new(text: String) -> Self {
Self { text }
}
pub(crate) fn u8_suffixed(n: u8) -> Self {
Self::_new(format!(concat!("{}", stringify!(u8)), n))
}
pub(crate) fn string(t: &str) -> Self {
let mut s = t.chars().flat_map(char::escape_default).collect::<String>();
s.push('"');
s.insert(0, '"');
Self::_new(s)
}
pub(crate) fn character(t: char) -> Self {
Self::_new(format!("'{}'", t.escape_default().collect::<String>()))
}
#[allow(clippy::match_overlapping_arm)]
pub(crate) fn byte_string(bytes: &[u8]) -> Self {
let mut escaped = "b\"".to_string();
for b in bytes {
match *b {
b'\0' => escaped.push_str(r"\0"),
b'\t' => escaped.push_str(r"\t"),
b'\n' => escaped.push_str(r"\n"),
b'\r' => escaped.push_str(r"\r"),
b'"' => escaped.push_str("\\\""),
b'\\' => escaped.push_str("\\\\"),
b'\x20'..=b'\x7E' => escaped.push(*b as char),
_ => escaped.push_str(&format!("\\x{:02X}", b)),
}
}
escaped.push('"');
Self::_new(escaped)
}
}
// TODO: when release the next minor version, remove this.
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.text.fmt(f)
}
}
mod convert {
use super::*;
// TokenStream
syn_trait_impl!(proc_macro2::TokenStream);
impl From<&proc_macro2::TokenStream> for TokenStream {
fn from(other: &proc_macro2::TokenStream) -> Self {
Self::_new(
other
.clone()
.into_iter()
.map::<TokenTree, _>(|x| x.ref_into())
.collect(),
)
}
}
impl From<&TokenStream> for proc_macro2::TokenStream {
fn from(other: &TokenStream) -> Self {
other
.inner
.iter()
.map::<proc_macro2::TokenTree, _>(Into::into)
.collect()
}
}
// TokenTree
syn_trait_impl!(proc_macro2::TokenTree);
impl From<&proc_macro2::TokenTree> for TokenTree {
fn from(other: &proc_macro2::TokenTree) -> Self {
use super::TokenTree::*;
use proc_macro2::TokenTree;
match other {
TokenTree::Group(t) => Group(t.into()),
TokenTree::Ident(t) => Ident(t.into()),
TokenTree::Punct(t) => Punct(t.into()),
TokenTree::Literal(t) => Literal(t.into()),
}
}
}
impl From<&TokenTree> for proc_macro2::TokenTree {
fn from(other: &TokenTree) -> Self {
use proc_macro2::TokenTree::*;
match other {
TokenTree::Group(t) => Group(t.into()),
TokenTree::Ident(t) => Ident(t.into()),
TokenTree::Punct(t) => Punct(t.into()),
TokenTree::Literal(t) => Literal(t.into()),
}
}
}
// Group
syn_trait_impl!(proc_macro2::Group);
impl From<&proc_macro2::Group> for Group {
fn from(other: &proc_macro2::Group) -> Self {
Self {
delimiter: other.delimiter().ref_into(),
stream: other.stream().ref_into(),
}
}
}
impl From<&Group> for proc_macro2::Group {
fn from(other: &Group) -> Self {
Self::new(other.delimiter.ref_into(), other.stream.ref_into())
}
}
// Delimiter
syn_trait_impl!(proc_macro2::Delimiter);
impl From<&proc_macro2::Delimiter> for Delimiter {
fn from(other: &proc_macro2::Delimiter) -> Self {
use super::Delimiter::*;
use proc_macro2::Delimiter;
match other {
Delimiter::Parenthesis => Parenthesis,
Delimiter::Brace => Brace,
Delimiter::Bracket => Bracket,
Delimiter::None => None,
}
}
}
impl From<&Delimiter> for proc_macro2::Delimiter {
fn from(other: &Delimiter) -> Self {
use proc_macro2::Delimiter::*;
match other {
Delimiter::Parenthesis => Parenthesis,
Delimiter::Brace => Brace,
Delimiter::Bracket => Bracket,
Delimiter::None => None,
}
}
}
// Ident
syn_trait_impl!(proc_macro2::Ident);
impl From<&proc_macro2::Ident> for Ident {
fn from(other: &proc_macro2::Ident) -> Self {
Self {
inner: other.to_string(),
}
}
}
impl From<&Ident> for proc_macro2::Ident {
fn from(other: &Ident) -> Self {
Self::new(&other.inner, Span::call_site())
}
}
// Punct
syn_trait_impl!(proc_macro2::Punct);
impl From<&proc_macro2::Punct> for Punct {
fn from(other: &proc_macro2::Punct) -> Self {
Self {
op: other.as_char(),
spacing: other.spacing().ref_into(),
}
}
}
impl From<&Punct> for proc_macro2::Punct {
fn from(other: &Punct) -> Self {
Self::new(other.op, other.spacing.ref_into())
}
}
// Spacing
syn_trait_impl!(proc_macro2::Spacing);
impl From<&proc_macro2::Spacing> for Spacing {
fn from(other: &proc_macro2::Spacing) -> Self {
use super::Spacing::*;
use proc_macro2::Spacing;
match other {
Spacing::Alone => Alone,
Spacing::Joint => Joint,
}
}
}
impl From<&Spacing> for proc_macro2::Spacing {
fn from(other: &Spacing) -> Self {
use proc_macro2::Spacing::*;
match other {
Spacing::Alone => Alone,
Spacing::Joint => Joint,
}
}
}
// Literal
syn_trait_impl!(proc_macro2::Literal);
impl From<&proc_macro2::Literal> for Literal {
fn from(other: &proc_macro2::Literal) -> Self {
Self {
text: other.to_string(),
}
}
}
impl From<&Literal> for proc_macro2::Literal {
fn from(other: &Literal) -> Self {
use proc_macro2::*;
let stream = other.text.parse::<TokenStream>().unwrap();
match stream.into_iter().next().unwrap() {
TokenTree::Literal(l) => l,
_ => unreachable!(),
}
}
}
}

291
syn-serde/src/ty.rs Normal file
View file

@ -0,0 +1,291 @@
use super::*;
ast_enum! {
/// The possible types that a Rust value could have.
pub enum Type {
/// A fixed size array type: `[T; n]`.
Array(TypeArray),
/// A bare function type: `fn(usize) -> bool`.
BareFn(TypeBareFn),
/// A type contained within invisible delimiters.
Group(TypeGroup),
/// An `impl Bound1 + Bound2 + Bound3` type where `Bound` is a trait or
/// a lifetime.
ImplTrait(TypeImplTrait),
/// Indication that a type should be inferred by the compiler: `_`.
#[serde(rename = "_")]
Infer,
/// A macro in the type position.
Macro(TypeMacro),
/// The never type: `!`.
#[serde(rename = "!")]
Never,
/// A parenthesized type equivalent to the inner type.
Paren(TypeParen),
/// A path like `std::slice::Iter`, optionally qualified with a
/// self-type as in `<Vec<T> as SomeTrait>::Associated`.
Path(TypePath),
/// A raw pointer type: `*const T` or `*mut T`.
Ptr(TypePtr),
/// A reference type: `&'a T` or `&'a mut T`.
Reference(TypeReference),
/// A dynamically sized slice type: `[T]`.
Slice(TypeSlice),
/// A trait object type `Bound1 + Bound2 + Bound3` where `Bound` is a
/// trait or a lifetime.
TraitObject(TypeTraitObject),
/// A tuple type: `(A, B, C, String)`.
Tuple(TypeTuple),
/// Tokens in type position not interpreted by Syn.
Verbatim(TokenStream),
#[doc(hidden)]
__Nonexhaustive,
}
}
ast_struct! {
/// A fixed size array type: `[T; n]`.
pub struct TypeArray {
pub(crate) elem: Box<Type>,
pub(crate) len: Expr,
}
}
ast_struct! {
/// A bare function type: `fn(usize) -> bool`.
pub struct TypeBareFn {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) lifetimes: Option<BoundLifetimes>,
#[serde(rename = "unsafe")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) unsafety: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) abi: Option<Abi>,
pub(crate) inputs: Punctuated<BareFnArg>,
pub(crate) variadic: Option<Variadic>,
pub(crate) output: ReturnType,
}
}
ast_struct! {
/// A type contained within invisible delimiters.
pub struct TypeGroup {
pub(crate) elem: Box<Type>,
}
}
ast_struct! {
/// An `impl Bound1 + Bound2 + Bound3` type where `Bound` is a trait or
/// a lifetime.
pub struct TypeImplTrait {
pub(crate) bounds: Punctuated<TypeParamBound>,
}
}
ast_struct! {
/// A macro in the type position.
pub struct TypeMacro {
#[serde(flatten)]
pub(crate) mac: Macro,
}
}
ast_struct! {
/// A parenthesized type equivalent to the inner type.
pub struct TypeParen {
pub(crate) elem: Box<Type>,
}
}
ast_struct! {
/// A path like `std::slice::Iter`, optionally qualified with a
/// self-type as in `<Vec<T> as SomeTrait>::Associated`.
pub struct TypePath {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) qself: Option<QSelf>,
#[serde(flatten)]
pub(crate) path: Path,
}
}
ast_struct! {
/// A raw pointer type: `*const T` or `*mut T`.
pub struct TypePtr {
#[serde(rename = "const")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) const_token: bool,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) elem: Box<Type>,
}
}
ast_struct! {
/// A reference type: `&'a T` or `&'a mut T`.
pub struct TypeReference {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) lifetime: Option<Lifetime>,
#[serde(rename = "mut")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) mutability: bool,
pub(crate) elem: Box<Type>,
}
}
ast_struct! {
/// A dynamically sized slice type: `[T]`.
pub struct TypeSlice {
pub(crate) elem: Box<Type>,
}
}
ast_struct! {
/// A trait object type `Bound1 + Bound2 + Bound3` where `Bound` is a
/// trait or a lifetime.
pub struct TypeTraitObject {
#[serde(rename = "dyn")]
#[serde(default, skip_serializing_if = "not")]
pub(crate) dyn_token: bool,
pub(crate) bounds: Punctuated<TypeParamBound>,
}
}
ast_struct! {
/// A tuple type: `(A, B, C, String)`.
pub struct TypeTuple {
pub(crate) elems: Punctuated<Type>,
}
}
ast_struct! {
/// The binary interface of a function: `extern "C"`.
pub struct Abi {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) name: Option<LitStr>,
}
}
ast_struct! {
/// An argument in a function type: the `usize` in `fn(usize) -> bool`.
pub struct BareFnArg {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) name: Option<Ident>,
pub(crate) ty: Type,
}
}
ast_struct! {
/// The variadic argument of a foreign function.
pub struct Variadic {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) attrs: Vec<Attribute>,
}
}
ast_struct! {
/// Return type of a function signature.
#[derive(Default)]
#[serde(transparent)]
pub struct ReturnType {
ty: Option<Box<Type>>,
}
}
mod convert {
use super::*;
// Type
syn_trait_impl!(syn::Type);
impl From<&syn::Type> for Type {
fn from(other: &syn::Type) -> Self {
use super::Type::*;
use syn::Type;
match other {
Type::Slice(x) => Slice(x.into()),
Type::Array(x) => Array(x.into()),
Type::Ptr(x) => Ptr(x.into()),
Type::Reference(x) => Reference(x.into()),
Type::BareFn(x) => BareFn(x.into()),
Type::Never(_) => Never,
Type::Tuple(x) => Tuple(x.into()),
Type::Path(x) => Path(x.into()),
Type::TraitObject(x) => TraitObject(x.into()),
Type::ImplTrait(x) => ImplTrait(x.into()),
Type::Paren(x) => Paren(x.into()),
Type::Group(x) => Group(x.into()),
Type::Infer(_) => Infer,
Type::Macro(x) => Macro(x.into()),
Type::Verbatim(x) => Verbatim(x.into()),
_ => unreachable!(),
}
}
}
impl From<&Type> for syn::Type {
fn from(other: &Type) -> Self {
use syn::Type::*;
match other {
Type::Slice(x) => Slice(x.into()),
Type::Array(x) => Array(x.into()),
Type::Ptr(x) => Ptr(x.into()),
Type::Reference(x) => Reference(x.into()),
Type::BareFn(x) => BareFn(x.into()),
Type::Never => Never(syn::TypeNever {
bang_token: default(),
}),
Type::Tuple(x) => Tuple(x.into()),
Type::Path(x) => Path(x.into()),
Type::TraitObject(x) => TraitObject(x.into()),
Type::ImplTrait(x) => ImplTrait(x.into()),
Type::Paren(x) => Paren(x.into()),
Type::Group(x) => Group(x.into()),
Type::Infer => Infer(syn::TypeInfer {
underscore_token: default(),
}),
Type::Macro(x) => Macro(x.into()),
Type::Verbatim(x) => Verbatim(x.into()),
_ => unreachable!(),
}
}
}
// ReturnType
syn_trait_impl!(syn::ReturnType);
impl From<&syn::ReturnType> for ReturnType {
fn from(other: &syn::ReturnType) -> Self {
use syn::ReturnType;
match other {
ReturnType::Default => Self { ty: None },
ReturnType::Type(_, x) => Self {
ty: Some(x.map_into()),
},
}
}
}
impl From<&ReturnType> for syn::ReturnType {
fn from(other: &ReturnType) -> Self {
use syn::ReturnType;
match &other.ty {
None => ReturnType::Default,
Some(x) => ReturnType::Type(default(), x.map_into()),
}
}
}
}

5616
syn-serde/syn.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,740 @@
use syn::*;
#[test]
fn test_unit() {
let raw = "struct Unit;";
let json = r#"
{
"struct": {
"ident": "Unit",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let ser: syn_serde::Item = serde_json::from_str(json).unwrap();
let ser = Item::from(&ser);
assert_eq!(ser, actual);
}
#[test]
fn test_struct() {
let raw = "
#[derive(Debug, Clone)]
pub struct Item {
pub ident: Ident,
pub attrs: Vec<Attribute>
}
";
let json = r#"
{
"struct": {
"attrs": [
{
"style": "outer",
"path": {
"segments": [
{
"ident": "derive"
}
]
},
"tokens": [
{
"group": {
"delimiter": "parenthesis",
"stream": [
{
"ident": "Debug"
},
{
"punct": {
"op": ",",
"spacing": "alone"
}
},
{
"ident": "Clone"
}
]
}
}
]
}
],
"vis": "pub",
"ident": "Item",
"fields": {
"named": [
{
"vis": "pub",
"ident": "ident",
"colon_token": true,
"ty": {
"path": {
"segments": [
{
"ident": "Ident"
}
]
}
}
},
{
"vis": "pub",
"ident": "attrs",
"colon_token": true,
"ty": {
"path": {
"segments": [
{
"ident": "Vec",
"arguments": {
"angle_bracketed": {
"args": [
{
"type": {
"path": {
"segments": [
{
"ident": "Attribute"
}
]
}
}
}
]
}
}
}
]
}
}
}
]
}
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_union() {
let raw = "
union MaybeUninit<T> {
uninit: (),
value: T
}
";
let json = r#"
{
"union": {
"ident": "MaybeUninit",
"generics": {
"params": [
{
"type": {
"ident": "T"
}
}
]
},
"fields": [
{
"ident": "uninit",
"colon_token": true,
"ty": {
"tuple": {
"elems": []
}
}
},
{
"ident": "value",
"colon_token": true,
"ty": {
"path": {
"segments": [
{
"ident": "T"
}
]
}
}
}
]
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_enum() {
let raw = r#"
/// See the std::result module documentation for details.
#[must_use]
pub enum Result<T, E> {
Ok(T),
Err(E),
Surprise = 0isize,
// Smuggling data into a proc_macro_derive,
// in the style of https://github.com/dtolnay/proc-macro-hack
ProcMacroHack = (0, "data").0
}
"#;
let json = r#"
{
"enum": {
"attrs": [
{
"style": "outer",
"path": {
"segments": [
{
"ident": "doc"
}
]
},
"tokens": [
{
"punct": {
"op": "=",
"spacing": "alone"
}
},
{
"lit": "\" See the std::result module documentation for details.\""
}
]
},
{
"style": "outer",
"path": {
"segments": [
{
"ident": "must_use"
}
]
}
}
],
"vis": "pub",
"ident": "Result",
"generics": {
"params": [
{
"type": {
"ident": "T"
}
},
{
"type": {
"ident": "E"
}
}
]
},
"variants": [
{
"ident": "Ok",
"fields": {
"unnamed": [
{
"ty": {
"path": {
"segments": [
{
"ident": "T"
}
]
}
}
}
]
}
},
{
"ident": "Err",
"fields": {
"unnamed": [
{
"ty": {
"path": {
"segments": [
{
"ident": "E"
}
]
}
}
}
]
}
},
{
"ident": "Surprise",
"fields": "unit",
"discriminant": {
"lit": {
"int": "0isize"
}
}
},
{
"ident": "ProcMacroHack",
"fields": "unit",
"discriminant": {
"field": {
"base": {
"tuple": {
"elems": [
{
"lit": {
"int": "0"
}
},
{
"lit": {
"str": "\"data\""
}
}
]
}
},
"index": 0
}
}
}
]
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_attr_with_path() {
let raw = r#"
#[::attr_args::identity
fn main() { assert_eq!(foo(), "Hello, world!"); }]
struct Dummy;
"#;
let json = r#"
{
"struct": {
"attrs": [
{
"style": "outer",
"path": {
"leading_colon": true,
"segments": [
{
"ident": "attr_args"
},
{
"ident": "identity"
}
]
},
"tokens": [
{
"ident": "fn"
},
{
"ident": "main"
},
{
"group": {
"delimiter": "parenthesis",
"stream": []
}
},
{
"group": {
"delimiter": "brace",
"stream": [
{
"ident": "assert_eq"
},
{
"punct": {
"op": "!",
"spacing": "alone"
}
},
{
"group": {
"delimiter": "parenthesis",
"stream": [
{
"ident": "foo"
},
{
"group": {
"delimiter": "parenthesis",
"stream": []
}
},
{
"punct": {
"op": ",",
"spacing": "alone"
}
},
{
"lit": "\"Hello, world!\""
}
]
}
},
{
"punct": {
"op": ";",
"spacing": "alone"
}
}
]
}
}
]
}
],
"ident": "Dummy",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_attr_with_non_mod_style_path() {
let raw = r#"
#[inert <T>]
struct S;
"#;
let json = r#"
{
"struct": {
"attrs": [
{
"style": "outer",
"path": {
"segments": [
{
"ident": "inert"
}
]
},
"tokens": [
{
"punct": {
"op": "<",
"spacing": "alone"
}
},
{
"ident": "T"
},
{
"punct": {
"op": ">",
"spacing": "alone"
}
}
]
}
],
"ident": "S",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_attr_with_mod_style_path_with_self() {
let raw = r#"
#[foo::self]
struct S;
"#;
let json = r#"
{
"struct": {
"attrs": [
{
"style": "outer",
"path": {
"segments": [
{
"ident": "foo"
},
{
"ident": "self"
}
]
}
}
],
"ident": "S",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_pub_restricted() {
// Taken from tests/rust/src/test/ui/resolve/auxiliary/privacy-struct-ctor.rs
let raw = r#"
pub(in m) struct Z(pub(in m::n) u8);
"#;
let json = r#"
{
"struct": {
"vis": {
"restricted": {
"in_token": true,
"path": {
"segments": [
{
"ident": "m"
}
]
}
}
},
"ident": "Z",
"fields": {
"unnamed": [
{
"vis": {
"restricted": {
"in_token": true,
"path": {
"segments": [
{
"ident": "m"
},
{
"ident": "n"
}
]
}
}
},
"ty": {
"path": {
"segments": [
{
"ident": "u8"
}
]
}
}
}
]
}
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_vis_crate() {
let raw = r#"
crate struct S;
"#;
let json = r#"
{
"struct": {
"vis": "crate",
"ident": "S",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_pub_restricted_crate() {
let raw = r#"
pub(crate) struct S;
"#;
let json = r#"
{
"struct": {
"vis": {
"restricted": {
"path": {
"segments": [
{
"ident": "crate"
}
]
}
}
},
"ident": "S",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_pub_restricted_super() {
let raw = r#"
pub(super) struct S;
"#;
let json = r#"
{
"struct": {
"vis": {
"restricted": {
"path": {
"segments": [
{
"ident": "super"
}
]
}
}
},
"ident": "S",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_pub_restricted_in_super() {
let raw = r#"
pub(in super) struct S;
"#;
let json = r#"
{
"struct": {
"vis": {
"restricted": {
"in_token": true,
"path": {
"segments": [
{
"ident": "super"
}
]
}
}
},
"ident": "S",
"fields": "unit"
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}
#[test]
fn test_ambiguous_crate() {
// The field type is `(crate::X)` not `crate (::X)`.
let raw = "struct S(crate::X);";
let json = r#"
{
"struct": {
"ident": "S",
"fields": {
"unnamed": [
{
"ty": {
"path": {
"segments": [
{
"ident": "crate"
},
{
"ident": "X"
}
]
}
}
}
]
}
}
}
"#;
let actual = syn::parse_str(raw).unwrap();
let json: syn_serde::Item = serde_json::from_str(json).unwrap();
let json = Item::from(&json);
assert_eq!(json, actual);
}