borsh-derive-1.6.0/.cargo_vcs_info.json0000644000000001520000000000100134270ustar { "git": { "sha1": "819fa55288c85c2f17b4691e49bed7db8da30b69" }, "path_in_vcs": "borsh-derive" }borsh-derive-1.6.0/Cargo.lock0000644000000164650000000000100114200ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "borsh-derive" version = "1.6.0" dependencies = [ "insta", "once_cell", "prettyplease", "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys", ] [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "indexmap" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "insta" version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8732d3774162a0851e3f2b150eb98f31a9885dd75985099421d393385a01dfd" dependencies = [ "console", "once_cell", "similar", ] [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "toml_datetime" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", "toml_parser", "winnow", ] [[package]] name = "toml_parser" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] borsh-derive-1.6.0/Cargo.toml0000644000000031640000000000100114330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.77.0" name = "borsh-derive" version = "1.6.0" authors = ["Near Inc "] build = false exclude = ["*.snap"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ Binary Object Representation Serializer for Hashing """ homepage = "https://borsh.io" readme = "README.md" categories = [ "encoding", "network-programming", ] license = "Apache-2.0" repository = "https://github.com/near/borsh-rs" [package.metadata.docs.rs] features = ["schema"] targets = ["x86_64-unknown-linux-gnu"] [features] default = [] force_exhaustive_checks = [] schema = [] [lib] name = "borsh_derive" path = "src/lib.rs" proc-macro = true [dependencies.once_cell] version = "1.18.0" [dependencies.proc-macro-crate] version = "3" [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2.0.81" features = [ "full", "fold", ] [dev-dependencies.insta] version = "1.29.0" [dev-dependencies.prettyplease] version = "0.2.9" [dev-dependencies.syn] version = "2.0.81" features = [ "full", "fold", "parsing", ] borsh-derive-1.6.0/Cargo.toml.orig000064400000000000000000000015541046102023000151150ustar 00000000000000[package] name = "borsh-derive" version.workspace = true rust-version.workspace = true authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" readme = "README.md" categories = ["encoding", "network-programming"] repository = "https://github.com/near/borsh-rs" homepage = "https://borsh.io" description = """ Binary Object Representation Serializer for Hashing """ exclude = ["*.snap"] [lib] proc-macro = true [dependencies] syn = { version = "2.0.81", features = ["full", "fold"] } proc-macro-crate = "3" proc-macro2 = "1" quote = "1" once_cell = "1.18.0" [dev-dependencies] syn = { version = "2.0.81", features = ["full", "fold", "parsing"] } prettyplease = "0.2.9" insta = "1.29.0" [package.metadata.docs.rs] features = ["schema"] targets = ["x86_64-unknown-linux-gnu"] [features] default = [] schema = [] force_exhaustive_checks = [] borsh-derive-1.6.0/LICENSE-APACHE000064400000000000000000000261061046102023000141520ustar 00000000000000 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 2019 Near 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. borsh-derive-1.6.0/LICENSE-MIT000064400000000000000000000017771046102023000136710ustar 00000000000000Permission 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. borsh-derive-1.6.0/README.md000064400000000000000000000060171046102023000135040ustar 00000000000000# Borsh in Rust   [![Latest Version]][crates.io] [![borsh: rustc 1.77+]][Rust 1.77] [![License Apache-2.0 badge]][License Apache-2.0] [![License MIT badge]][License MIT] [Borsh]: https://borsh.io [Latest Version]: https://img.shields.io/crates/v/borsh.svg [crates.io]: https://crates.io/crates/borsh [borsh: rustc 1.77+]: https://img.shields.io/badge/rustc-1.77+-lightgray.svg [Rust 1.77]: https://blog.rust-lang.org/2024/03/21/Rust-1.77.0/ [License Apache-2.0 badge]: https://img.shields.io/badge/license-Apache2.0-blue.svg [License Apache-2.0]: https://opensource.org/licenses/Apache-2.0 [License MIT badge]: https://img.shields.io/badge/license-MIT-blue.svg [License MIT]: https://opensource.org/licenses/MIT **borsh-rs** is Rust implementation of the [Borsh] binary serialization format. Borsh stands for _Binary Object Representation Serializer for Hashing_. It is meant to be used in security-critical projects as it prioritizes [consistency, safety, speed][Borsh], and comes with a strict [specification](https://github.com/near/borsh#specification). ## Example ```rust use borsh::{BorshSerialize, BorshDeserialize, from_slice, to_vec}; #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] struct A { x: u64, y: String, } #[test] fn test_simple_struct() { let a = A { x: 3301, y: "liber primus".to_string(), }; let encoded_a = to_vec(&a).unwrap(); let decoded_a = from_slice::(&encoded_a).unwrap(); assert_eq!(a, decoded_a); } ``` ## Docs shortcuts Following pages are highlighted here just to give reader a chance at learning that they exist. - [Derive Macro `BorshSerialize`](./borsh/docs/rustdoc_include/borsh_serialize.md) - [Derive Macro `BorshDeserialize`](./borsh/docs/rustdoc_include/borsh_deserialize.md) - [Derive Macro `BorshSchema`](./borsh/docs/rustdoc_include/borsh_schema.md) ## Advanced examples Some of the less trivial examples are present in [examples](./borsh/examples) folder: - [implementing `BorshSerialize`/`BorshDeserialize` for third-party `serde_json::Value`](./borsh/examples/serde_json_value.rs) ## Testing Integration tests should generally be preferred to unit ones. Root module of integration tests of `borsh` crate is [linked](./borsh/tests/tests.rs) here. ## Releasing The versions of all public crates in this repository are collectively managed by a single version in the [workspace manifest](https://github.com/near/borsh-rs/blob/master/Cargo.toml). So, to publish a new version of all the crates, you can do so by simply bumping that to the next "patch" version and submit a PR. We have CI Infrastructure put in place to automate the process of publishing all crates once a version change has merged into master. However, before you release, make sure the [CHANGELOG](CHANGELOG.md) is up to date and that the `[Unreleased]` section is present but empty. ## License This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE) for details. borsh-derive-1.6.0/src/internals/attributes/field/bounds.rs000064400000000000000000000040111046102023000221140ustar 00000000000000use std::collections::BTreeMap; use syn::{meta::ParseNestedMeta, WherePredicate}; use crate::internals::attributes::{parsing::parse_lit_into_vec, Symbol, DESERIALIZE, SERIALIZE}; use once_cell::sync::Lazy; pub enum Variants { Serialize(Vec), Deserialize(Vec), } type ParseFn = dyn Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result + Send + Sync; pub static BOUNDS_FIELD_PARSE_MAP: Lazy>> = Lazy::new(|| { let mut m = BTreeMap::new(); // assigning closure `let f = |args| {...};` and boxing closure `let f: Box = Box::new(f);` // on 2 separate lines doesn't work let f_serialize: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into_vec::(attr_name, meta_item_name, meta) .map(Variants::Serialize) }); let f_deserialize: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into_vec::(attr_name, meta_item_name, meta) .map(Variants::Deserialize) }); m.insert(SERIALIZE, f_serialize); m.insert(DESERIALIZE, f_deserialize); m }); #[derive(Default, Clone)] pub struct Bounds { pub serialize: Option>, pub deserialize: Option>, } impl From> for Bounds { fn from(mut map: BTreeMap) -> Self { let serialize = map.remove(&SERIALIZE); let deserialize = map.remove(&DESERIALIZE); let serialize = serialize.map(|variant| match variant { Variants::Serialize(ser) => ser, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); let deserialize = deserialize.map(|variant| match variant { Variants::Deserialize(de) => de, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); Self { serialize, deserialize, } } } borsh-derive-1.6.0/src/internals/attributes/field/mod.rs000064400000000000000000000577051046102023000214230ustar 00000000000000use std::collections::BTreeMap; use once_cell::sync::Lazy; use syn::{meta::ParseNestedMeta, Attribute, WherePredicate}; use self::bounds::BOUNDS_FIELD_PARSE_MAP; use super::{ get_one_attribute, parsing::{attr_get_by_symbol_keys, meta_get_by_symbol_keys, parse_lit_into}, BoundType, Symbol, BORSH, BOUND, DESERIALIZE_WITH, SERIALIZE_WITH, SKIP, }; #[cfg(feature = "schema")] use { super::schema_keys::{PARAMS, SCHEMA, WITH_FUNCS}, schema::SCHEMA_FIELD_PARSE_MAP, }; pub mod bounds; #[cfg(feature = "schema")] pub mod schema; enum Variants { Bounds(bounds::Bounds), SerializeWith(syn::ExprPath), DeserializeWith(syn::ExprPath), Skip(()), #[cfg(feature = "schema")] Schema(schema::Attributes), } type ParseFn = dyn Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result + Send + Sync; static BORSH_FIELD_PARSE_MAP: Lazy>> = Lazy::new(|| { let mut m = BTreeMap::new(); // assigning closure `let f = |args| {...};` and boxing closure `let f: Box = Box::new(f);` // on 2 separate lines doesn't work let f_bounds: Box = Box::new(|_attr_name, _meta_item_name, meta| { let map_result = meta_get_by_symbol_keys(BOUND, meta, &BOUNDS_FIELD_PARSE_MAP)?; let bounds_attributes: bounds::Bounds = map_result.into(); Ok(Variants::Bounds(bounds_attributes)) }); let f_serialize_with: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into::(attr_name, meta_item_name, meta) .map(Variants::SerializeWith) }); let f_deserialize_with: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into::(attr_name, meta_item_name, meta) .map(Variants::DeserializeWith) }); #[cfg(feature = "schema")] let f_schema: Box = Box::new(|_attr_name, _meta_item_name, meta| { let map_result = meta_get_by_symbol_keys(SCHEMA, meta, &SCHEMA_FIELD_PARSE_MAP)?; let schema_attributes: schema::Attributes = map_result.into(); Ok(Variants::Schema(schema_attributes)) }); let f_skip: Box = Box::new(|_attr_name, _meta_item_name, _meta| Ok(Variants::Skip(()))); m.insert(BOUND, f_bounds); m.insert(SERIALIZE_WITH, f_serialize_with); m.insert(DESERIALIZE_WITH, f_deserialize_with); m.insert(SKIP, f_skip); #[cfg(feature = "schema")] m.insert(SCHEMA, f_schema); m }); #[derive(Default, Clone)] pub(crate) struct Attributes { pub bounds: Option, pub serialize_with: Option, pub deserialize_with: Option, pub skip: bool, #[cfg(feature = "schema")] pub schema: Option, } impl From> for Attributes { fn from(mut map: BTreeMap) -> Self { let bounds = map.remove(&BOUND); let serialize_with = map.remove(&SERIALIZE_WITH); let deserialize_with = map.remove(&DESERIALIZE_WITH); let skip = map.remove(&SKIP); let bounds = bounds.map(|variant| match variant { Variants::Bounds(bounds) => bounds, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); let serialize_with = serialize_with.map(|variant| match variant { Variants::SerializeWith(serialize_with) => serialize_with, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); let deserialize_with = deserialize_with.map(|variant| match variant { Variants::DeserializeWith(deserialize_with) => deserialize_with, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); let skip = skip.map(|variant| match variant { Variants::Skip(skip) => skip, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); #[cfg(feature = "schema")] let schema = { let schema = map.remove(&SCHEMA); schema.map(|variant| match variant { Variants::Schema(schema) => schema, _ => { unreachable!("only one enum variant is expected to correspond to given map key") } }) }; Self { bounds, serialize_with, deserialize_with, skip: skip.is_some(), #[cfg(feature = "schema")] schema, } } } #[cfg(feature = "schema")] pub(crate) fn filter_attrs( attrs: impl Iterator, ) -> impl Iterator { attrs.filter(|attr| attr.path() == BORSH) } impl Attributes { fn check(&self, attr: &Attribute) -> Result<(), syn::Error> { if self.skip && (self.serialize_with.is_some() || self.deserialize_with.is_some()) { return Err(syn::Error::new_spanned( attr, format!( "`{}` cannot be used at the same time as `{}` or `{}`", SKIP.0, SERIALIZE_WITH.0, DESERIALIZE_WITH.0 ), )); } #[cfg(feature = "schema")] self.check_schema(attr)?; Ok(()) } pub(crate) fn parse(attrs: &[Attribute]) -> Result { let borsh = get_one_attribute(attrs)?; let result: Self = if let Some(attr) = borsh { let result: Self = attr_get_by_symbol_keys(BORSH, attr, &BORSH_FIELD_PARSE_MAP)?.into(); result.check(attr)?; result } else { BTreeMap::new().into() }; Ok(result) } pub(crate) fn needs_bounds_derive(&self, ty: BoundType) -> bool { let predicates = self.get_bounds(ty); predicates.is_none() } fn get_bounds(&self, ty: BoundType) -> Option> { let bounds = self.bounds.as_ref(); bounds.and_then(|bounds| match ty { BoundType::Serialize => bounds.serialize.clone(), BoundType::Deserialize => bounds.deserialize.clone(), }) } pub(crate) fn collect_bounds(&self, ty: BoundType) -> Vec { let predicates = self.get_bounds(ty); predicates.unwrap_or_default() } } #[cfg(feature = "schema")] impl Attributes { fn check_schema(&self, attr: &Attribute) -> Result<(), syn::Error> { if let Some(ref schema) = self.schema { if self.skip && schema.params.is_some() { return Err(syn::Error::new_spanned( attr, format!( "`{}` cannot be used at the same time as `{}({})`", SKIP.0, SCHEMA.0, PARAMS.1 ), )); } if self.skip && schema.with_funcs.is_some() { return Err(syn::Error::new_spanned( attr, format!( "`{}` cannot be used at the same time as `{}({})`", SKIP.0, SCHEMA.0, WITH_FUNCS.1 ), )); } } Ok(()) } pub(crate) fn needs_schema_params_derive(&self) -> bool { if let Some(ref schema) = self.schema { if schema.params.is_some() { return false; } } true } pub(crate) fn schema_declaration(&self) -> Option { self.schema.as_ref().and_then(|schema| { schema .with_funcs .as_ref() .and_then(|with_funcs| with_funcs.declaration.clone()) }) } pub(crate) fn schema_definitions(&self) -> Option { self.schema.as_ref().and_then(|schema| { schema .with_funcs .as_ref() .and_then(|with_funcs| with_funcs.definitions.clone()) }) } } #[cfg(test)] mod tests { use quote::quote; use syn::{Attribute, ItemStruct}; fn parse_bounds(attrs: &[Attribute]) -> Result, syn::Error> { // #[borsh(bound(serialize = "...", deserialize = "..."))] let borsh_attrs = Attributes::parse(attrs)?; Ok(borsh_attrs.bounds) } use crate::internals::test_helpers::{ debug_print_tokenizable, debug_print_vec_of_tokenizable, local_insta_assert_debug_snapshot, local_insta_assert_snapshot, }; use super::{bounds, Attributes}; #[test] fn test_reject_multiple_borsh_attrs() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(skip)] #[borsh(bound(deserialize = "K: Hash + Ord, V: Eq + Ord", serialize = "K: Hash + Eq + Ord, V: Ord" ))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match Attributes::parse(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_bounds_parsing1() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = "K: Hash + Ord, V: Eq + Ord", serialize = "K: Hash + Eq + Ord, V: Ord" ))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap(); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.serialize.clone())); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize)); } #[test] fn test_bounds_parsing2() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = "K: Hash + Eq + borsh::de::BorshDeserialize, V: borsh::de::BorshDeserialize", serialize = "K: Hash + Eq + borsh::ser::BorshSerialize, V: borsh::ser::BorshSerialize" ))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap(); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.serialize.clone())); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize)); } #[test] fn test_bounds_parsing3() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = "K: Hash + Eq + borsh::de::BorshDeserialize, V: borsh::de::BorshDeserialize", serialize = "" ))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap(); assert_eq!(attrs.serialize.as_ref().unwrap().len(), 0); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize)); } #[test] fn test_bounds_parsing4() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = "K: Hash"))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap(); assert!(attrs.serialize.is_none()); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize)); } #[test] fn test_bounds_parsing_error() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deser = "K: Hash"))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match parse_bounds(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_bounds_parsing_error2() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = "K Hash"))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match parse_bounds(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_bounds_parsing_error3() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = 42))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match parse_bounds(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_ser_de_with_parsing1() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh( serialize_with = "third_party_impl::serialize_third_party", deserialize_with = "third_party_impl::deserialize_third_party", )] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = Attributes::parse(&first_field.attrs).unwrap(); local_insta_assert_snapshot!(debug_print_tokenizable(attrs.serialize_with.as_ref())); local_insta_assert_snapshot!(debug_print_tokenizable(attrs.deserialize_with)); } #[test] fn test_borsh_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(skip)] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let result = Attributes::parse(&first_field.attrs).unwrap(); assert!(result.skip); } #[test] fn test_borsh_no_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let result = Attributes::parse(&first_field.attrs).unwrap(); assert!(!result.skip); } } #[cfg(feature = "schema")] #[cfg(test)] mod tests_schema { use crate::internals::{ attributes::field::Attributes, test_helpers::{ debug_print_tokenizable, debug_print_vec_of_tokenizable, local_insta_assert_debug_snapshot, local_insta_assert_snapshot, }, }; use quote::quote; use syn::{Attribute, ItemStruct}; use super::schema; fn parse_schema_attrs(attrs: &[Attribute]) -> Result, syn::Error> { // #[borsh(schema(params = "..."))] let borsh_attrs = Attributes::parse(attrs)?; Ok(borsh_attrs.schema) } #[test] fn test_root_bounds_and_params_combined() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh( serialize_with = "third_party_impl::serialize_third_party", bound(deserialize = "K: Hash"), schema(params = "T => ::Associated, V => Vec") )] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = Attributes::parse(&first_field.attrs).unwrap(); let bounds = attrs.bounds.clone().unwrap(); assert!(bounds.serialize.is_none()); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(bounds.deserialize)); assert!(attrs.deserialize_with.is_none()); let schema = attrs.schema.clone().unwrap(); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(schema.params.clone())); local_insta_assert_snapshot!(debug_print_tokenizable(attrs.serialize_with)); } #[test] fn test_schema_params_parsing1() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(params = "T => ::Associated" ))] field: ::Associated, another: V, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap(); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(schema_attrs.unwrap().params)); } #[test] fn test_schema_params_parsing_error() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(params = "T => ::Associated" ))] field: ::Associated, another: V, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match parse_schema_attrs(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_schema_params_parsing_error2() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(paramsbum = "T => ::Associated" ))] field: ::Associated, another: V, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match parse_schema_attrs(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_schema_params_parsing2() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(params = "T => ::Associated, V => Vec" ))] field: ::Associated, another: V, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap(); local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(schema_attrs.unwrap().params)); } #[test] fn test_schema_params_parsing3() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(params = "" ))] field: ::Associated, another: V, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap(); assert_eq!(schema_attrs.unwrap().params.unwrap().len(), 0); } #[test] fn test_schema_params_parsing4() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { field: ::Associated, another: V, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap(); assert!(schema_attrs.is_none()); } #[test] fn test_schema_with_funcs_parsing() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(schema(with_funcs( declaration = "third_party_impl::declaration::", definitions = "third_party_impl::add_definitions_recursively::" )))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = Attributes::parse(&first_field.attrs).unwrap(); let schema = attrs.schema.unwrap(); let with_funcs = schema.with_funcs.unwrap(); local_insta_assert_snapshot!(debug_print_tokenizable(with_funcs.declaration.clone())); local_insta_assert_snapshot!(debug_print_tokenizable(with_funcs.definitions)); } // both `declaration` and `definitions` have to be specified #[test] fn test_schema_with_funcs_parsing_error() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(schema(with_funcs( declaration = "third_party_impl::declaration::" )))] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let attrs = Attributes::parse(&first_field.attrs); let err = match attrs { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_root_error() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(boons)] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match Attributes::parse(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_root_bounds_and_wrong_key_combined() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(bound(deserialize = "K: Hash"), schhema(params = "T => ::Associated, V => Vec") )] x: u64, y: String, } }) .unwrap(); let first_field = &item_struct.fields.into_iter().collect::>()[0]; let err = match Attributes::parse(&first_field.attrs) { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } } borsh-derive-1.6.0/src/internals/attributes/field/schema/with_funcs.rs000064400000000000000000000040241046102023000242370ustar 00000000000000use std::collections::BTreeMap; use once_cell::sync::Lazy; use syn::meta::ParseNestedMeta; use crate::internals::attributes::{ parsing::parse_lit_into, schema_keys::{DECLARATION, DEFINITIONS}, Symbol, }; pub enum Variants { Declaration(syn::ExprPath), Definitions(syn::ExprPath), } type ParseFn = dyn Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result + Send + Sync; pub static WITH_FUNCS_FIELD_PARSE_MAP: Lazy>> = Lazy::new(|| { let mut m = BTreeMap::new(); // assigning closure `let f = |args| {...};` and boxing closure `let f: Box = Box::new(f);` // on 2 separate lines doesn't work let f_declaration: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into::(attr_name, meta_item_name, meta).map(Variants::Declaration) }); let f_definitions: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into::(attr_name, meta_item_name, meta).map(Variants::Definitions) }); m.insert(DECLARATION, f_declaration); m.insert(DEFINITIONS, f_definitions); m }); #[derive(Clone)] pub struct WithFuncs { pub declaration: Option, pub definitions: Option, } impl From> for WithFuncs { fn from(mut map: BTreeMap) -> Self { let declaration = map.remove(&DECLARATION); let definitions = map.remove(&DEFINITIONS); let declaration = declaration.map(|variant| match variant { Variants::Declaration(declaration) => declaration, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); let definitions = definitions.map(|variant| match variant { Variants::Definitions(definitions) => definitions, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); Self { declaration, definitions, } } } borsh-derive-1.6.0/src/internals/attributes/field/schema.rs000064400000000000000000000067441046102023000221010ustar 00000000000000use std::collections::BTreeMap; use crate::internals::attributes::{ parsing::{meta_get_by_symbol_keys, parse_lit_into_vec}, schema_keys::{DECLARATION, DEFINITIONS, PARAMS, WITH_FUNCS}, Symbol, }; use once_cell::sync::Lazy; use quote::ToTokens; use syn::{ meta::ParseNestedMeta, parse::{Parse, ParseStream}, Ident, Token, Type, }; use self::with_funcs::{WithFuncs, WITH_FUNCS_FIELD_PARSE_MAP}; pub mod with_funcs; pub enum Variants { Params(Vec), WithFuncs(WithFuncs), } type ParseFn = dyn Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result + Send + Sync; pub static SCHEMA_FIELD_PARSE_MAP: Lazy>> = Lazy::new(|| { let mut m = BTreeMap::new(); // assigning closure `let f = |args| {...};` and boxing closure `let f: Box = Box::new(f);` // on 2 separate lines doesn't work let f_params: Box = Box::new(|attr_name, meta_item_name, meta| { parse_lit_into_vec::(attr_name, meta_item_name, meta) .map(Variants::Params) }); let f_with_funcs: Box = Box::new(|_attr_name, _meta_item_name, meta| { let map_result = meta_get_by_symbol_keys(WITH_FUNCS, meta, &WITH_FUNCS_FIELD_PARSE_MAP)?; let with_funcs: WithFuncs = map_result.into(); if (with_funcs.declaration.is_some() && with_funcs.definitions.is_none()) || (with_funcs.declaration.is_none() && with_funcs.definitions.is_some()) { return Err(syn::Error::new_spanned( &meta.path, format!( "both `{}` and `{}` have to be specified at the same time", DECLARATION.1, DEFINITIONS.1, ), )); } Ok(Variants::WithFuncs(with_funcs)) }); m.insert(PARAMS, f_params); m.insert(WITH_FUNCS, f_with_funcs); m }); /** Struct describes an entry like `order_param => override_type`, e.g. `K => ::Associated` */ #[derive(Clone)] pub struct ParameterOverride { pub order_param: Ident, arrow_token: Token![=>], pub override_type: Type, } impl Parse for ParameterOverride { fn parse(input: ParseStream) -> Result { Ok(ParameterOverride { order_param: input.parse()?, arrow_token: input.parse()?, override_type: input.parse()?, }) } } impl ToTokens for ParameterOverride { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { self.order_param.to_tokens(tokens); self.arrow_token.to_tokens(tokens); self.override_type.to_tokens(tokens); } } #[allow(unused)] #[derive(Default, Clone)] pub(crate) struct Attributes { pub params: Option>, pub with_funcs: Option, } impl From> for Attributes { fn from(mut map: BTreeMap) -> Self { let params = map.remove(&PARAMS); let params = params.map(|variant| match variant { Variants::Params(params) => params, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); let with_funcs = map.remove(&WITH_FUNCS); let with_funcs = with_funcs.map(|variant| match variant { Variants::WithFuncs(with_funcs) => with_funcs, _ => unreachable!("only one enum variant is expected to correspond to given map key"), }); Self { params, with_funcs } } } borsh-derive-1.6.0/src/internals/attributes/item/mod.rs000064400000000000000000000317001046102023000212610ustar 00000000000000use crate::internals::attributes::{BORSH, CRATE, INIT, USE_DISCRIMINANT}; use quote::ToTokens; use syn::{spanned::Spanned, Attribute, DeriveInput, Error, Expr, ItemEnum, Path}; use super::{get_one_attribute, parsing}; pub fn check_attributes(derive_input: &DeriveInput) -> Result<(), Error> { let borsh = get_one_attribute(&derive_input.attrs)?; if let Some(attr) = borsh { attr.parse_nested_meta(|meta| { if meta.path != USE_DISCRIMINANT && meta.path != INIT && meta.path != CRATE { return Err(syn::Error::new( meta.path.span(), "`crate`, `use_discriminant` or `init` are the only supported attributes for `borsh`", )); } if meta.path == USE_DISCRIMINANT { let _expr: Expr = meta.value()?.parse()?; if let syn::Data::Struct(ref _data) = derive_input.data { return Err(syn::Error::new( derive_input.ident.span(), "borsh(use_discriminant=) does not support structs", )); } } else if meta.path == INIT || meta.path == CRATE { let _expr: Expr = meta.value()?.parse()?; } Ok(()) })?; } Ok(()) } pub(crate) fn contains_use_discriminant(input: &ItemEnum) -> Result { if input.variants.len() > 256 { return Err(syn::Error::new( input.span(), "up to 256 enum variants are supported", )); } let attrs = &input.attrs; let mut use_discriminant = None; let attr = attrs.iter().find(|attr| attr.path() == BORSH); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { if meta.path == USE_DISCRIMINANT { let value_expr: Expr = meta.value()?.parse()?; let value = value_expr.to_token_stream().to_string(); match value.as_str() { "true" => { use_discriminant = Some(true); } "false" => use_discriminant = Some(false), _ => { return Err(syn::Error::new( value_expr.span(), "`use_discriminant` accepts only `true` or `false`", )); } }; } else if meta.path == INIT || meta.path == CRATE { let _value_expr: Expr = meta.value()?.parse()?; } Ok(()) })?; } let has_explicit_discriminants = input .variants .iter() .any(|variant| variant.discriminant.is_some()); if has_explicit_discriminants && use_discriminant.is_none() { return Err(syn::Error::new( input.ident.span(), "You have to specify `#[borsh(use_discriminant=true)]` or `#[borsh(use_discriminant=false)]` for all enums with explicit discriminant", )); } Ok(use_discriminant.unwrap_or(false)) } pub(crate) fn contains_initialize_with(attrs: &[Attribute]) -> Result, Error> { let mut res = None; let attr = attrs.iter().find(|attr| attr.path() == BORSH); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { if meta.path == INIT { let value_expr: Path = meta.value()?.parse()?; res = Some(value_expr); } else if meta.path == USE_DISCRIMINANT || meta.path == CRATE { let _value_expr: Expr = meta.value()?.parse()?; } Ok(()) })?; } Ok(res) } pub(crate) fn get_crate(attrs: &[Attribute]) -> Result, Error> { let mut res = None; let attr = attrs.iter().find(|attr| attr.path() == BORSH); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { if meta.path == CRATE { let value_expr: Path = parsing::parse_lit_into(BORSH, CRATE, &meta)?; res = Some(value_expr); } else if meta.path == USE_DISCRIMINANT || meta.path == INIT { let _value_expr: Expr = meta.value()?.parse()?; } Ok(()) })?; } Ok(res) } #[cfg(test)] mod tests { use crate::internals::test_helpers::local_insta_assert_debug_snapshot; use quote::{quote, ToTokens}; use syn::ItemEnum; use super::*; #[test] fn test_use_discriminant() { let item_enum: ItemEnum = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(use_discriminant = false)] enum AWithUseDiscriminantFalse { X, Y, } }) .unwrap(); let actual = contains_use_discriminant(&item_enum); assert!(!actual.unwrap()); } #[test] fn test_use_discriminant_true() { let item_enum: ItemEnum = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(use_discriminant = true)] enum AWithUseDiscriminantTrue { X, Y, } }) .unwrap(); let actual = contains_use_discriminant(&item_enum); assert!(actual.unwrap()); } #[test] fn test_use_discriminant_wrong_value() { let item_enum: ItemEnum = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(use_discriminant = 111)] enum AWithUseDiscriminantFalse { X, Y, } }) .unwrap(); let actual = contains_use_discriminant(&item_enum); let err = match actual { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_check_attrs_use_discriminant_on_struct() { let item_enum: DeriveInput = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(use_discriminant = false)] struct AWithUseDiscriminantFalse { x: X, y: Y, } }) .unwrap(); let actual = check_attributes(&item_enum); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn test_check_attrs_borsh_skip_on_whole_item() { let item_enum: DeriveInput = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(skip)] struct AWithUseDiscriminantFalse { x: X, y: Y, } }) .unwrap(); let actual = check_attributes(&item_enum); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn test_check_attrs_borsh_invalid_on_whole_item() { let item_enum: DeriveInput = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(invalid)] enum AWithUseDiscriminantFalse { X, Y, } }) .unwrap(); let actual = check_attributes(&item_enum); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn test_check_attrs_init_function() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(init = initialization_method)] struct A<'a> { x: u64, } }) .unwrap(); let actual = check_attributes(&item_struct); assert!(actual.is_ok()); } #[test] fn test_check_attrs_init_function_with_use_discriminant_reversed() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(use_discriminant=true, init = initialization_method)] enum A { B, C, D= 10, } }) .unwrap(); let actual = check_attributes(&item_struct); assert!(actual.is_ok()); } #[test] fn test_reject_multiple_borsh_attrs() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(use_discriminant=true)] #[borsh(init = initialization_method)] enum A { B, C, D= 10, } }) .unwrap(); let actual = check_attributes(&item_struct); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn test_check_attrs_init_function_with_use_discriminant() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(init = initialization_method, use_discriminant=true)] enum A { B, C, D= 10, } }) .unwrap(); let actual = check_attributes(&item_struct); assert!(actual.is_ok()); } #[test] fn test_check_attrs_init_function_wrong_format() { let item_struct: DeriveInput = syn::parse2(quote! { #[derive(BorshDeserialize, Debug)] #[borsh(init_func = initialization_method)] struct A<'a> { x: u64, b: B, y: f32, z: String, v: Vec, } }) .unwrap(); let actual = check_attributes(&item_struct); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn test_init_function() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(init = initialization_method)] struct A<'a> { x: u64, } }) .unwrap(); let actual = contains_initialize_with(&item_struct.attrs); assert_eq!( actual.unwrap().to_token_stream().to_string(), "initialization_method" ); } #[test] fn test_init_function_parsing_error() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(init={strange; blocky})] struct A { lazy: Option, } }) .unwrap(); let actual = contains_initialize_with(&item_struct.attrs); let err = match actual { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } #[test] fn test_init_function_with_use_discriminant() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(init = initialization_method, use_discriminant=true)] enum A { B, C, D, } }) .unwrap(); let actual = contains_initialize_with(&item_struct.attrs); assert_eq!( actual.unwrap().to_token_stream().to_string(), "initialization_method" ); let actual = contains_use_discriminant(&item_struct); assert!(actual.unwrap()); } #[test] fn test_init_function_with_use_discriminant_reversed() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(use_discriminant=true, init = initialization_method)] enum A { B, C, D, } }) .unwrap(); let actual = contains_initialize_with(&item_struct.attrs); assert_eq!( actual.unwrap().to_token_stream().to_string(), "initialization_method" ); let actual = contains_use_discriminant(&item_struct); assert!(actual.unwrap()); } #[test] fn test_init_function_with_use_discriminant_with_crate() { let item_struct = syn::parse2::(quote! { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] #[borsh(init = initialization_method, crate = "reexporter::borsh", use_discriminant=true)] enum A { B, C, D, } }) .unwrap(); let actual = contains_initialize_with(&item_struct.attrs); assert_eq!( actual.unwrap().to_token_stream().to_string(), "initialization_method" ); let actual = contains_use_discriminant(&item_struct); assert!(actual.unwrap()); let crate_ = get_crate(&item_struct.attrs); assert_eq!( crate_.unwrap().to_token_stream().to_string(), "reexporter :: borsh" ); } } borsh-derive-1.6.0/src/internals/attributes/mod.rs000064400000000000000000000066321046102023000203310ustar 00000000000000use syn::{Attribute, Path}; pub mod field; pub mod item; pub mod parsing; /// first field is attr name /// second field is its expected value format representation for error printing #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct Symbol(pub &'static str, pub &'static str); /// borsh - top level prefix in nested meta attribute pub const BORSH: Symbol = Symbol("borsh", "borsh(...)"); /// bound - sub-borsh nested meta, field-level only, `BorshSerialize` and `BorshDeserialize` contexts pub const BOUND: Symbol = Symbol("bound", "bound(...)"); // use_discriminant - sub-borsh nested meta, item-level only, enums only, `BorshSerialize` and `BorshDeserialize` contexts pub const USE_DISCRIMINANT: Symbol = Symbol("use_discriminant", "use_discriminant = ..."); /// serialize - sub-bound nested meta attribute pub const SERIALIZE: Symbol = Symbol("serialize", "serialize = ..."); /// deserialize - sub-bound nested meta attribute pub const DESERIALIZE: Symbol = Symbol("deserialize", "deserialize = ..."); /// skip - sub-borsh nested meta, field-level only attribute, `BorshSerialize`, `BorshDeserialize`, `BorshSchema` contexts pub const SKIP: Symbol = Symbol("skip", "skip"); /// init - sub-borsh nested meta, item-level only attribute `BorshDeserialize` context pub const INIT: Symbol = Symbol("init", "init = ..."); /// serialize_with - sub-borsh nested meta, field-level only, `BorshSerialize` context pub const SERIALIZE_WITH: Symbol = Symbol("serialize_with", "serialize_with = ..."); /// deserialize_with - sub-borsh nested meta, field-level only, `BorshDeserialize` context pub const DESERIALIZE_WITH: Symbol = Symbol("deserialize_with", "deserialize_with = ..."); /// crate - sub-borsh nested meta, item-level only, `BorshSerialize`, `BorshDeserialize`, `BorshSchema` contexts pub const CRATE: Symbol = Symbol("crate", "crate = ..."); #[cfg(feature = "schema")] pub mod schema_keys { use super::Symbol; /// schema - sub-borsh nested meta, `BorshSchema` context pub const SCHEMA: Symbol = Symbol("schema", "schema(...)"); /// params - sub-schema nested meta, field-level only attribute pub const PARAMS: Symbol = Symbol("params", "params = ..."); /// serialize_with - sub-borsh nested meta, field-level only, `BorshSerialize` context /// with_funcs - sub-schema nested meta, field-level only attribute pub const WITH_FUNCS: Symbol = Symbol("with_funcs", "with_funcs(...)"); /// declaration - sub-with_funcs nested meta, field-level only attribute pub const DECLARATION: Symbol = Symbol("declaration", "declaration = ..."); /// definitions - sub-with_funcs nested meta, field-level only attribute pub const DEFINITIONS: Symbol = Symbol("definitions", "definitions = ..."); } #[derive(Clone, Copy)] pub enum BoundType { Serialize, Deserialize, } impl PartialEq for Path { fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) } } impl<'a> PartialEq for &'a Path { fn eq(&self, word: &Symbol) -> bool { self.is_ident(word.0) } } fn get_one_attribute(attrs: &[Attribute]) -> syn::Result> { let count = attrs.iter().filter(|attr| attr.path() == BORSH).count(); let borsh = attrs.iter().find(|attr| attr.path() == BORSH); if count > 1 { return Err(syn::Error::new_spanned( borsh.unwrap(), format!("multiple `{}` attributes not allowed", BORSH.0), )); } Ok(borsh) } borsh-derive-1.6.0/src/internals/attributes/parsing.rs000064400000000000000000000063641046102023000212170ustar 00000000000000use std::{collections::BTreeMap, iter::FromIterator}; use syn::{ meta::ParseNestedMeta, punctuated::Punctuated, token::Paren, Attribute, Expr, Lit, LitStr, Token, }; use super::Symbol; fn get_lit_str2( attr_name: Symbol, meta_item_name: Symbol, meta: &ParseNestedMeta, ) -> syn::Result { let expr: Expr = meta.value()?.parse()?; let mut value = &expr; while let Expr::Group(e) = value { value = &e.expr; } if let Expr::Lit(syn::ExprLit { lit: Lit::Str(lit), .. }) = value { Ok(lit.clone()) } else { Err(syn::Error::new_spanned( expr, format!( "expected borsh {} attribute to be a string: `{} = \"...\"`", attr_name.0, meta_item_name.0 ), )) } } pub(super) fn parse_lit_into( attr_name: Symbol, meta_item_name: Symbol, meta: &ParseNestedMeta, ) -> syn::Result { let string = get_lit_str2(attr_name, meta_item_name, meta)?; match string.parse() { Ok(expr) => Ok(expr), Err(err) => Err(syn::Error::new_spanned(string, err)), } } pub(super) fn parse_lit_into_vec( attr_name: Symbol, meta_item_name: Symbol, meta: &ParseNestedMeta, ) -> syn::Result> { let string = get_lit_str2(attr_name, meta_item_name, meta)?; match string.parse_with(Punctuated::::parse_terminated) { Ok(elements) => Ok(Vec::from_iter(elements)), Err(err) => Err(syn::Error::new_spanned(string, err)), } } fn get_nested_meta_logic( attr_name: Symbol, meta: ParseNestedMeta, map: &BTreeMap, result: &mut BTreeMap, ) -> syn::Result<()> where F: Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result, { let mut match_ = false; for (symbol_key, func) in map.iter() { if meta.path == *symbol_key { let v = func(attr_name, *symbol_key, &meta)?; result.insert(*symbol_key, v); match_ = true; } } if !match_ { let keys_strs = map.keys().map(|symbol| symbol.1).collect::>(); let keys_strs = keys_strs.join(", "); return Err(meta.error(format_args!( "malformed {0} attribute, expected `{0}({1})`", attr_name.0, keys_strs ))); } Ok(()) } pub(super) fn meta_get_by_symbol_keys( attr_name: Symbol, meta: &ParseNestedMeta, map: &BTreeMap, ) -> syn::Result> where F: Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result, { let mut result = BTreeMap::new(); let lookahead = meta.input.lookahead1(); if lookahead.peek(Paren) { meta.parse_nested_meta(|meta| get_nested_meta_logic(attr_name, meta, map, &mut result))?; } else { return Err(lookahead.error()); } Ok(result) } pub(super) fn attr_get_by_symbol_keys( attr_name: Symbol, attr: &Attribute, map: &BTreeMap, ) -> syn::Result> where F: Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result, { let mut result = BTreeMap::new(); attr.parse_nested_meta(|meta| get_nested_meta_logic(attr_name, meta, map, &mut result))?; Ok(result) } borsh-derive-1.6.0/src/internals/cratename.rs000064400000000000000000000014151046102023000173150ustar 00000000000000use proc_macro2::Span; use proc_macro_crate::{crate_name, FoundCrate}; use syn::{Attribute, Error, Ident, Path}; use super::attributes::item; pub(crate) const BORSH: &str = "borsh"; pub(crate) fn get(attrs: &[Attribute]) -> Result { let path = item::get_crate(attrs)?; match path { Some(path) => Ok(path), None => { let ident = get_from_cargo(); Ok(ident.into()) } } } pub(crate) fn get_from_cargo() -> Ident { let name = &crate_name(BORSH) .unwrap_or_else(|err| panic!("`proc_macro_crate::crate_name` call error: {:#?}", err)); let name = match name { FoundCrate::Itself => BORSH, FoundCrate::Name(name) => name.as_str(), }; Ident::new(name, Span::call_site()) } borsh-derive-1.6.0/src/internals/deserialize/enums/mod.rs000064400000000000000000000243001046102023000215620ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{Fields, ItemEnum, Path, Variant}; use crate::internals::{attributes::item, deserialize, enum_discriminant::Discriminants, generics}; pub fn process(input: &ItemEnum, cratename: Path) -> syn::Result { let name = &input.ident; let generics = generics::without_defaults(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut where_clause = generics::default_where(where_clause); let mut variant_arms = TokenStream2::new(); let use_discriminant = item::contains_use_discriminant(input)?; let discriminants = Discriminants::new(&input.variants); let mut generics_output = deserialize::GenericsOutput::new(&generics); for (variant_idx, variant) in input.variants.iter().enumerate() { let variant_body = process_variant(variant, &cratename, &mut generics_output)?; let variant_ident = &variant.ident; let discriminant_value = discriminants.get(variant_ident, use_discriminant, variant_idx)?; variant_arms.extend(quote! { if variant_tag == #discriminant_value { #name::#variant_ident #variant_body } else }); } let init = if let Some(method_ident) = item::contains_initialize_with(&input.attrs)? { quote! { return_value.#method_ident(); } } else { quote! {} }; generics_output.extend(&mut where_clause, &cratename); Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::de::BorshDeserialize for #name #ty_generics #where_clause { fn deserialize_reader<__R: #cratename::io::Read>(reader: &mut __R) -> ::core::result::Result { let tag = ::deserialize_reader(reader)?; ::deserialize_variant(reader, tag) } } #[automatically_derived] impl #impl_generics #cratename::de::EnumExt for #name #ty_generics #where_clause { fn deserialize_variant<__R: #cratename::io::Read>( reader: &mut __R, variant_tag: u8, ) -> ::core::result::Result { let mut return_value = #variant_arms { return Err(#cratename::io::Error::new( #cratename::io::ErrorKind::InvalidData, #cratename::__private::maybestd::format!("Unexpected variant tag: {:?}", variant_tag), )) }; #init Ok(return_value) } } }) } fn process_variant( variant: &Variant, cratename: &Path, generics: &mut deserialize::GenericsOutput, ) -> syn::Result { let mut body = TokenStream2::new(); match &variant.fields { Fields::Named(fields) => { for field in &fields.named { deserialize::process_field(field, cratename, &mut body, generics)?; } body = quote! { { #body }}; } Fields::Unnamed(fields) => { for field in fields.unnamed.iter() { deserialize::process_field(field, cratename, &mut body, generics)?; } body = quote! { ( #body )}; } Fields::Unit => {} } Ok(body) } #[cfg(test)] mod tests { use crate::internals::test_helpers::{ default_cratename, local_insta_assert_snapshot, pretty_print_syn_str, }; use super::*; #[test] fn borsh_skip_struct_variant_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum AA { B { #[borsh(skip)] c: i32, d: u32, }, NegatedVariant { beta: u8, } } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_skip_tuple_variant_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum AAT { B(#[borsh(skip)] i32, u32), NegatedVariant { beta: u8, } } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_enum_with_custom_crate() { let item_struct: ItemEnum = syn::parse2(quote! { enum A { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap(); let actual = process(&item_struct, crate_).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generics() { let item_struct: ItemEnum = syn::parse2(quote! { enum A { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn bound_generics() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn recursive_enum() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_borsh_skip_struct_field() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { #[borsh(skip)] x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_borsh_skip_tuple_field() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, #[borsh(skip)] Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_deserialize_bound() { let item_struct: ItemEnum = syn::parse2(quote! { enum A { C { a: String, #[borsh(bound(deserialize = "T: PartialOrd + Hash + Eq + borsh::de::BorshDeserialize, U: borsh::de::BorshDeserialize" ))] b: HashMap, }, D(u32, u32), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn check_deserialize_with_attr() { let item_struct: ItemEnum = syn::parse2(quote! { enum C { C3(u64, u64), C4 { x: u64, #[borsh(deserialize_with = "third_party_impl::deserialize_third_party")] y: ThirdParty }, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_discriminant_false() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(use_discriminant = false)] enum X { A, B = 20, C, D, E = 10, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_discriminant_true() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(use_discriminant = true)] enum X { A, B = 20, C, D, E = 10, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_init_func() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(init = initialization_method)] enum A { A, B, C, D, E, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } borsh-derive-1.6.0/src/internals/deserialize/mod.rs000064400000000000000000000062211046102023000204350ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{ExprPath, Generics, Ident, Path}; use super::{ attributes::{field, BoundType}, generics, }; pub mod enums; pub mod structs; pub mod unions; struct GenericsOutput { overrides: Vec, default_visitor: generics::FindTyParams, deserialize_visitor: generics::FindTyParams, } impl GenericsOutput { fn new(generics: &Generics) -> Self { Self { overrides: vec![], deserialize_visitor: generics::FindTyParams::new(generics), default_visitor: generics::FindTyParams::new(generics), } } fn extend(self, where_clause: &mut syn::WhereClause, cratename: &Path) { let de_trait: Path = syn::parse2(quote! { #cratename::de::BorshDeserialize }).unwrap(); let default_trait: Path = syn::parse2(quote! { core::default::Default }).unwrap(); let de_predicates = generics::compute_predicates(self.deserialize_visitor.process_for_bounds(), &de_trait); let default_predicates = generics::compute_predicates(self.default_visitor.process_for_bounds(), &default_trait); where_clause.predicates.extend(de_predicates); where_clause.predicates.extend(default_predicates); where_clause.predicates.extend(self.overrides); } } fn process_field( field: &syn::Field, cratename: &Path, body: &mut TokenStream2, generics: &mut GenericsOutput, ) -> syn::Result<()> { let parsed = field::Attributes::parse(&field.attrs)?; generics .overrides .extend(parsed.collect_bounds(BoundType::Deserialize)); let needs_bounds_derive = parsed.needs_bounds_derive(BoundType::Deserialize); let field_name = field.ident.as_ref(); let delta = if parsed.skip { if needs_bounds_derive { generics.default_visitor.visit_field(field); } field_default_output(field_name) } else { if needs_bounds_derive { generics.deserialize_visitor.visit_field(field); } field_output(field_name, cratename, parsed.deserialize_with) }; body.extend(delta); Ok(()) } /// function which computes derive output [proc_macro2::TokenStream] /// of code, which deserializes single field fn field_output( field_name: Option<&Ident>, cratename: &Path, deserialize_with: Option, ) -> TokenStream2 { let default_path: ExprPath = syn::parse2(quote! { #cratename::BorshDeserialize::deserialize_reader }).unwrap(); let path: ExprPath = deserialize_with.unwrap_or(default_path); if let Some(field_name) = field_name { quote! { #field_name: #path(reader)?, } } else { quote! { #path(reader)?, } } } /// function which computes derive output [proc_macro2::TokenStream] /// of code, which deserializes single skipped field fn field_default_output(field_name: Option<&Ident>) -> TokenStream2 { if let Some(field_name) = field_name { quote! { #field_name: core::default::Default::default(), } } else { quote! { core::default::Default::default(), } } } borsh-derive-1.6.0/src/internals/deserialize/structs/mod.rs000064400000000000000000000177641046102023000221620ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{Fields, ItemStruct, Path}; use crate::internals::{attributes::item, deserialize, generics}; pub fn process(input: &ItemStruct, cratename: Path) -> syn::Result { let name = &input.ident; let generics = generics::without_defaults(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut where_clause = generics::default_where(where_clause); let mut body = TokenStream2::new(); let mut generics_output = deserialize::GenericsOutput::new(&generics); let return_value = match &input.fields { Fields::Named(fields) => { for field in &fields.named { deserialize::process_field(field, &cratename, &mut body, &mut generics_output)?; } quote! { Self { #body } } } Fields::Unnamed(fields) => { for field in fields.unnamed.iter() { deserialize::process_field(field, &cratename, &mut body, &mut generics_output)?; } quote! { Self( #body ) } } Fields::Unit => { quote! { Self {} } } }; generics_output.extend(&mut where_clause, &cratename); if let Some(method_ident) = item::contains_initialize_with(&input.attrs)? { Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::de::BorshDeserialize for #name #ty_generics #where_clause { fn deserialize_reader<__R: #cratename::io::Read>(reader: &mut __R) -> ::core::result::Result { let mut return_value = #return_value; return_value.#method_ident(); Ok(return_value) } } }) } else { Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::de::BorshDeserialize for #name #ty_generics #where_clause { fn deserialize_reader<__R: #cratename::io::Read>(reader: &mut __R) -> ::core::result::Result { Ok(#return_value) } } }) } } #[cfg(test)] mod tests { use crate::internals::test_helpers::{ default_cratename, local_insta_assert_snapshot, pretty_print_syn_str, }; use super::*; #[test] fn simple_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_struct_with_custom_crate() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap(); let actual = process(&item_struct, crate_).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generics() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: HashMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generic_tuple_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct TupleA(T, u32); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn bound_generics() { let item_struct: ItemStruct = syn::parse2(quote! { struct A where V: Value { x: HashMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn recursive_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct CRecC { a: String, b: HashMap, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip1() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( #[borsh(skip)] HashMap, U, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip2() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( HashMap, #[borsh(skip)] U, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_named_fields_struct_borsh_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct G { #[borsh(skip)] x: HashMap, y: U, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_deserialize_bound() { let item_struct: ItemStruct = syn::parse2(quote! { struct C { a: String, #[borsh(bound(deserialize = "T: PartialOrd + Hash + Eq + borsh::de::BorshDeserialize, U: borsh::de::BorshDeserialize" ))] b: HashMap, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn test_override_automatically_added_default_trait() { let item_struct: ItemStruct = syn::parse2(quote! { struct G1( #[borsh(skip,bound(deserialize = ""))] HashMap, U ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn check_deserialize_with_attr() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(deserialize_with = "third_party_impl::deserialize_third_party")] x: ThirdParty, y: u64, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_init_func() { let item_enum: ItemStruct = syn::parse2(quote! { #[borsh(init=initialization_method)] struct A { x: u64, y: String, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } borsh-derive-1.6.0/src/internals/deserialize/unions/mod.rs000064400000000000000000000002661046102023000217530ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use syn::{ItemUnion, Path}; pub fn process(_input: &ItemUnion, _cratename: Path) -> syn::Result { unimplemented!() } borsh-derive-1.6.0/src/internals/enum_discriminant.rs000064400000000000000000000031771046102023000210750ustar 00000000000000use std::collections::HashMap; use std::convert::TryFrom; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{punctuated::Punctuated, token::Comma, Variant}; pub struct Discriminants(HashMap); impl Discriminants { /// Calculates the discriminant that will be assigned by the compiler. /// See: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values pub fn new(variants: &Punctuated) -> Self { let mut map = HashMap::new(); let mut next_discriminant_if_not_specified = quote! {0}; for variant in variants { let this_discriminant = variant.discriminant.clone().map_or_else( || quote! { #next_discriminant_if_not_specified }, |(_, e)| quote! { #e }, ); next_discriminant_if_not_specified = quote! { #this_discriminant + 1 }; map.insert(variant.ident.clone(), this_discriminant); } Self(map) } pub fn get( &self, variant_ident: &Ident, use_discriminant: bool, variant_idx: usize, ) -> syn::Result { let variant_idx = u8::try_from(variant_idx).map_err(|err| { syn::Error::new( variant_ident.span(), format!("up to 256 enum variants are supported: {}", err), ) })?; let result = if use_discriminant { let discriminant_value = self.0.get(variant_ident).unwrap(); quote! { #discriminant_value } } else { quote! { #variant_idx } }; Ok(result) } } borsh-derive-1.6.0/src/internals/generics.rs000064400000000000000000000257021046102023000171620ustar 00000000000000use std::collections::{HashMap, HashSet}; use quote::{quote, ToTokens}; use syn::{ punctuated::Pair, Field, GenericArgument, Generics, Ident, Macro, Path, PathArguments, PathSegment, ReturnType, Type, TypeParamBound, TypePath, WhereClause, WherePredicate, }; pub fn default_where(where_clause: Option<&WhereClause>) -> WhereClause { where_clause.map_or_else( || WhereClause { where_token: Default::default(), predicates: Default::default(), }, Clone::clone, ) } pub fn compute_predicates(params: Vec, traitname: &Path) -> Vec { params .into_iter() .map(|param| { syn::parse2(quote! { #param: #traitname }) .unwrap() }) .collect() } // Remove the default from every type parameter because in the generated impls // they look like associated types: "error: associated type bindings are not // allowed here". pub fn without_defaults(generics: &Generics) -> Generics { syn::Generics { params: generics .params .iter() .map(|param| match param { syn::GenericParam::Type(param) => syn::GenericParam::Type(syn::TypeParam { eq_token: None, default: None, ..param.clone() }), _ => param.clone(), }) .collect(), ..generics.clone() } } #[cfg(feature = "schema")] pub fn type_contains_some_param(type_: &Type, params: &HashSet) -> bool { let mut find: FindTyParams = FindTyParams::from_params(params.iter()); find.visit_type_top_level(type_); find.at_least_one_hit() } /// a Visitor-like struct, which helps determine, if a type parameter is found in field #[derive(Clone)] pub struct FindTyParams { // Set of all generic type parameters on the current struct . Initialized up front. all_type_params: HashSet, all_type_params_ordered: Vec, // Set of generic type parameters used in fields for which filter // returns true . Filled in as the visitor sees them. relevant_type_params: HashSet, // [Param] => [Type, containing Param] mapping associated_type_params_usage: HashMap>, } fn ungroup(mut ty: &Type) -> &Type { while let Type::Group(group) = ty { ty = &group.elem; } ty } impl FindTyParams { pub fn new(generics: &Generics) -> Self { let all_type_params = generics .type_params() .map(|param| param.ident.clone()) .collect(); let all_type_params_ordered = generics .type_params() .map(|param| param.ident.clone()) .collect(); FindTyParams { all_type_params, all_type_params_ordered, relevant_type_params: HashSet::new(), associated_type_params_usage: HashMap::new(), } } pub fn process_for_bounds(self) -> Vec { let relevant_type_params = self.relevant_type_params; let associated_type_params_usage = self.associated_type_params_usage; let mut new_predicates: Vec = vec![]; let mut new_predicates_set: HashSet = HashSet::new(); self.all_type_params_ordered.iter().for_each(|param| { if relevant_type_params.contains(param) { let ty = Type::Path(TypePath { qself: None, path: param.clone().into(), }); let ty_str_repr = ty.to_token_stream().to_string(); if !new_predicates_set.contains(&ty_str_repr) { new_predicates.push(ty); new_predicates_set.insert(ty_str_repr); } } if let Some(vec_type) = associated_type_params_usage.get(param) { for type_ in vec_type { let ty_str_repr = type_.to_token_stream().to_string(); if !new_predicates_set.contains(&ty_str_repr) { new_predicates.push(type_.clone()); new_predicates_set.insert(ty_str_repr); } } } }); new_predicates } } #[cfg(feature = "schema")] impl FindTyParams { pub fn from_params<'a>(params: impl Iterator) -> Self { let all_type_params_ordered: Vec = params.cloned().collect(); let all_type_params = all_type_params_ordered.clone().into_iter().collect(); FindTyParams { all_type_params, all_type_params_ordered, relevant_type_params: HashSet::new(), associated_type_params_usage: HashMap::new(), } } pub fn process_for_params(self) -> Vec { let relevant_type_params = self.relevant_type_params; let associated_type_params_usage = self.associated_type_params_usage; let mut params: Vec = vec![]; let mut params_set: HashSet = HashSet::new(); self.all_type_params_ordered.iter().for_each(|param| { if relevant_type_params.contains(param) && !params_set.contains(param) { params.push(param.clone()); params_set.insert(param.clone()); } if associated_type_params_usage.contains_key(param) && !params_set.contains(param) { params.push(param.clone()); params_set.insert(param.clone()); } }); params } pub fn at_least_one_hit(&self) -> bool { !self.relevant_type_params.is_empty() || !self.associated_type_params_usage.is_empty() } } impl FindTyParams { pub fn visit_field(&mut self, field: &Field) { self.visit_type_top_level(&field.ty); } pub fn visit_type_top_level(&mut self, type_: &Type) { if let Type::Path(ty) = ungroup(type_) { if let Some(Pair::Punctuated(t, _)) = ty.path.segments.pairs().next() { if self.all_type_params.contains(&t.ident) { self.param_associated_type_insert(t.ident.clone(), type_.clone()); } } } self.visit_type(type_); } pub fn param_associated_type_insert(&mut self, param: Ident, type_: Type) { if let Some(type_vec) = self.associated_type_params_usage.get_mut(¶m) { type_vec.push(type_); } else { let type_vec = vec![type_]; self.associated_type_params_usage.insert(param, type_vec); } } fn visit_return_type(&mut self, return_type: &ReturnType) { match return_type { ReturnType::Default => {} ReturnType::Type(_, output) => self.visit_type(output), } } fn visit_path_segment(&mut self, segment: &PathSegment) { self.visit_path_arguments(&segment.arguments); } fn visit_path_arguments(&mut self, arguments: &PathArguments) { match arguments { PathArguments::None => {} PathArguments::AngleBracketed(arguments) => { for arg in &arguments.args { #[cfg_attr( feature = "force_exhaustive_checks", deny(non_exhaustive_omitted_patterns) )] match arg { GenericArgument::Type(arg) => self.visit_type(arg), GenericArgument::AssocType(arg) => self.visit_type(&arg.ty), GenericArgument::Lifetime(_) | GenericArgument::Const(_) | GenericArgument::AssocConst(_) | GenericArgument::Constraint(_) => {} _ => {} } } } PathArguments::Parenthesized(arguments) => { for argument in &arguments.inputs { self.visit_type(argument); } self.visit_return_type(&arguments.output); } } } fn visit_path(&mut self, path: &Path) { if let Some(seg) = path.segments.last() { if seg.ident == "PhantomData" { // Hardcoded exception, because PhantomData implements // Serialize and Deserialize and Schema whether or not T implements it. return; } } if path.leading_colon.is_none() && path.segments.len() == 1 { let id = &path.segments[0].ident; if self.all_type_params.contains(id) { self.relevant_type_params.insert(id.clone()); } } for segment in &path.segments { self.visit_path_segment(segment); } } fn visit_type_param_bound(&mut self, bound: &TypeParamBound) { #[cfg_attr( feature = "force_exhaustive_checks", deny(non_exhaustive_omitted_patterns) )] match bound { TypeParamBound::Trait(bound) => self.visit_path(&bound.path), TypeParamBound::Lifetime(_) | TypeParamBound::Verbatim(_) | TypeParamBound::PreciseCapture(_) => {} _ => {} } } // Type parameter should not be considered used by a macro path. // // struct TypeMacro { // mac: T!(), // marker: PhantomData, // } fn visit_macro(&mut self, _mac: &Macro) {} fn visit_type(&mut self, ty: &Type) { #[cfg_attr( feature = "force_exhaustive_checks", deny(non_exhaustive_omitted_patterns) )] match ty { Type::Array(ty) => self.visit_type(&ty.elem), Type::BareFn(ty) => { for arg in &ty.inputs { self.visit_type(&arg.ty); } self.visit_return_type(&ty.output); } Type::Group(ty) => self.visit_type(&ty.elem), Type::ImplTrait(ty) => { for bound in &ty.bounds { self.visit_type_param_bound(bound); } } Type::Macro(ty) => self.visit_macro(&ty.mac), Type::Paren(ty) => self.visit_type(&ty.elem), Type::Path(ty) => { if let Some(qself) = &ty.qself { self.visit_type(&qself.ty); } self.visit_path(&ty.path); } Type::Ptr(ty) => self.visit_type(&ty.elem), Type::Reference(ty) => self.visit_type(&ty.elem), Type::Slice(ty) => self.visit_type(&ty.elem), Type::TraitObject(ty) => { for bound in &ty.bounds { self.visit_type_param_bound(bound); } } Type::Tuple(ty) => { for elem in &ty.elems { self.visit_type(elem); } } Type::Infer(_) | Type::Never(_) | Type::Verbatim(_) => {} _ => {} } } } borsh-derive-1.6.0/src/internals/mod.rs000064400000000000000000000003001046102023000161250ustar 00000000000000pub mod attributes; pub mod deserialize; mod enum_discriminant; mod generics; #[cfg(feature = "schema")] pub mod schema; pub mod serialize; pub mod cratename; #[cfg(test)] mod test_helpers; borsh-derive-1.6.0/src/internals/schema/enums/mod.rs000064400000000000000000000400361046102023000205260ustar 00000000000000use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; use std::collections::HashSet; use syn::{Fields, Generics, Ident, ItemEnum, ItemStruct, Path, Variant, Visibility}; use crate::internals::{ attributes::{field, item}, enum_discriminant::Discriminants, generics, schema, }; fn transform_variant_fields(mut input: Fields) -> Fields { match input { Fields::Named(ref mut named) => { for field in &mut named.named { let field_attrs = field::filter_attrs(field.attrs.drain(..)).collect::>(); field.attrs = field_attrs; } } Fields::Unnamed(ref mut unnamed) => { for field in &mut unnamed.unnamed { let field_attrs = field::filter_attrs(field.attrs.drain(..)).collect::>(); field.attrs = field_attrs; } } _ => {} } input } pub fn process(input: &ItemEnum, cratename: Path) -> syn::Result { let name = &input.ident; let enum_name = name.to_token_stream().to_string(); let generics = generics::without_defaults(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut where_clause = generics::default_where(where_clause); let mut generics_output = schema::GenericsOutput::new(&generics); let use_discriminant = item::contains_use_discriminant(input)?; let discriminants = Discriminants::new(&input.variants); // Generate functions that return the schema for variants. let mut variants_defs = vec![]; let mut inner_defs = TokenStream2::new(); let mut add_recursive_defs = TokenStream2::new(); for (variant_idx, variant) in input.variants.iter().enumerate() { let discriminant_info = DiscriminantInfo { variant_idx, discriminants: &discriminants, use_discriminant, }; let variant_output = process_variant( variant, discriminant_info, &cratename, &enum_name, &generics, &mut generics_output, )?; inner_defs.extend(variant_output.inner_struct); add_recursive_defs.extend(variant_output.add_definitions_recursively_call); variants_defs.push(variant_output.variant_entry); } let type_definitions = quote! { fn add_definitions_recursively(definitions: &mut #cratename::__private::maybestd::collections::BTreeMap<#cratename::schema::Declaration, #cratename::schema::Definition>) { #inner_defs #add_recursive_defs let definition = #cratename::schema::Definition::Enum { tag_width: 1, variants: #cratename::__private::maybestd::vec![#(#variants_defs),*], }; #cratename::schema::add_definition(::declaration(), definition, definitions); } }; let (predicates, declaration) = generics_output.result(&enum_name, &cratename); where_clause.predicates.extend(predicates); Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::BorshSchema for #name #ty_generics #where_clause { fn declaration() -> #cratename::schema::Declaration { #declaration } #type_definitions } }) } struct VariantOutput { /// rust definition of the inner struct used in variant. inner_struct: TokenStream2, /// call to `add_definitions_recursively`. add_definitions_recursively_call: TokenStream2, /// entry with a variant's declaration, element in vector of whole enum's definition variant_entry: TokenStream2, } struct DiscriminantInfo<'a> { variant_idx: usize, discriminants: &'a Discriminants, use_discriminant: bool, } fn process_discriminant( variant_ident: &Ident, info: DiscriminantInfo<'_>, ) -> syn::Result { info.discriminants .get(variant_ident, info.use_discriminant, info.variant_idx) } fn process_variant( variant: &Variant, discriminant_info: DiscriminantInfo, cratename: &Path, enum_name: &str, enum_generics: &Generics, generics_output: &mut schema::GenericsOutput, ) -> syn::Result { let variant_name = variant.ident.to_token_stream().to_string(); let full_variant_name = format!("{}__{}", enum_name, variant_name); let full_variant_ident = Ident::new(&full_variant_name, Span::call_site()); schema::visit_struct_fields(&variant.fields, &mut generics_output.params_visitor)?; let (inner_struct, inner_struct_generics) = inner_struct_definition(variant, cratename, &full_variant_ident, enum_generics); let (_ig, inner_struct_ty_generics, _wc) = inner_struct_generics.split_for_impl(); let variant_type = quote! { <#full_variant_ident #inner_struct_ty_generics as #cratename::BorshSchema> }; let discriminant_value = process_discriminant(&variant.ident, discriminant_info)?; Ok(VariantOutput { inner_struct, add_definitions_recursively_call: quote! { #variant_type::add_definitions_recursively(definitions); }, variant_entry: quote! { (u8::from(#discriminant_value) as i64, #variant_name.into(), #variant_type::declaration()) }, }) } fn inner_struct_definition( variant: &Variant, cratename: &Path, inner_struct_ident: &Ident, enum_generics: &Generics, ) -> (TokenStream2, Generics) { let transformed_fields = transform_variant_fields(variant.fields.clone()); let mut variant_schema_params_visitor = generics::FindTyParams::new(enum_generics); schema::visit_struct_fields_unconditional(&variant.fields, &mut variant_schema_params_visitor); let variant_not_skipped_params = variant_schema_params_visitor .process_for_params() .into_iter() .collect::>(); let inner_struct_generics = schema::filter_used_params(enum_generics, variant_not_skipped_params); let inner_struct = ItemStruct { attrs: vec![], vis: Visibility::Inherited, struct_token: Default::default(), ident: inner_struct_ident.clone(), generics: inner_struct_generics.clone(), fields: transformed_fields, semi_token: Some(Default::default()), }; let crate_str = syn::LitStr::new(&cratename.to_token_stream().to_string(), Span::call_site()); let inner_struct = quote! { #[allow(dead_code)] #[allow(non_camel_case_types)] #[derive(#cratename::BorshSchema)] #[borsh(crate = #crate_str)] #inner_struct }; (inner_struct, inner_struct_generics) } #[cfg(test)] mod tests { use crate::internals::test_helpers::{ default_cratename, local_insta_assert_debug_snapshot, local_insta_assert_snapshot, pretty_print_syn_str, }; use super::*; #[test] fn simple_enum() { let item_enum: ItemEnum = syn::parse2(quote! { enum A { Bacon, Eggs } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_enum_with_custom_crate() { let item_enum: ItemEnum = syn::parse2(quote! { enum A { Bacon, Eggs } }) .unwrap(); let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap(); let actual = process(&item_enum, crate_).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_discriminant_false() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(use_discriminant = false)] enum X { A, B = 20, C, D, E = 10, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_discriminant_true() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(use_discriminant = true)] enum X { A, B = 20, C, D, E = 10, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn single_field_enum() { let item_enum: ItemEnum = syn::parse2(quote! { enum A { Bacon, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn complex_enum() { let item_enum: ItemEnum = syn::parse2(quote! { enum A { Bacon, Eggs, Salad(Tomatoes, Cucumber, Oil), Sausage{wrapper: Wrapper, filling: Filling}, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn complex_enum_generics() { let item_enum: ItemEnum = syn::parse2(quote! { enum A { Bacon, Eggs, Salad(Tomatoes, C, Oil), Sausage{wrapper: W, filling: Filling}, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn trailing_comma_generics() { let item_struct: ItemEnum = syn::parse2(quote! { enum Side where A: Display + Debug, B: Display + Debug, { Left(A), Right(B), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn test_filter_foreign_attrs() { let item_struct: ItemEnum = syn::parse2(quote! { enum A { #[serde(rename = "ab")] B { #[serde(rename = "abc")] c: i32, #[borsh(skip)] d: u32, l: u64, }, Negative { beta: String, } } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn complex_enum_generics_borsh_skip_tuple_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum A where W: Hash { Bacon, Eggs, Salad(Tomatoes, #[borsh(skip)] C, Oil), Sausage{wrapper: W, filling: Filling}, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn complex_enum_generics_borsh_skip_named_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum A { Bacon, Eggs, Salad(Tomatoes, C, Oil), Sausage{ #[borsh(skip)] wrapper: W, filling: Filling, unexpected: U, }, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn recursive_enum() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type() { let item_struct: ItemEnum = syn::parse2(quote! { enum EnumParametrized where K: TraitName, K: core::cmp::Ord, V: core::cmp::Ord, T: Eq + Hash, { B { x: BTreeMap, y: String, z: K::Associated, }, C(T, u16), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type_param_override() { let item_struct: ItemEnum = syn::parse2(quote! { enum EnumParametrized where K: TraitName, K: core::cmp::Ord, V: core::cmp::Ord, T: Eq + Hash, { B { x: BTreeMap, y: String, #[borsh(schema(params = "K => ::Associated"))] z: ::Associated, }, C(T, u16), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type_param_override_conflict() { let item_struct: ItemEnum = syn::parse2(quote! { enum EnumParametrized where K: TraitName, { B { x: Vec, #[borsh(skip,schema(params = "K => ::Associated"))] z: ::Associated, }, C(T, u16), } }) .unwrap(); let actual = process(&item_struct, default_cratename()); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn check_with_funcs_skip_conflict() { let item_struct: ItemEnum = syn::parse2(quote! { enum C { C3(u64, u64), C4( u64, #[borsh(skip,schema(with_funcs( declaration = "third_party_impl::declaration::", definitions = "third_party_impl::add_definitions_recursively::" )))] ThirdParty, ), } }) .unwrap(); let actual = process(&item_struct, default_cratename()); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn with_funcs_attr() { let item_struct: ItemEnum = syn::parse2(quote! { enum C { C3(u64, u64), C4( u64, #[borsh(schema(with_funcs( declaration = "third_party_impl::declaration::", definitions = "third_party_impl::add_definitions_recursively::" )))] ThirdParty, ), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } borsh-derive-1.6.0/src/internals/schema/mod.rs000064400000000000000000000124051046102023000173760ustar 00000000000000use std::collections::HashSet; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{ punctuated::Punctuated, token::Comma, Field, Fields, GenericParam, Generics, Ident, Path, Type, WherePredicate, }; use crate::internals::{attributes::field, generics}; pub mod enums; pub mod structs; struct GenericsOutput { params_visitor: generics::FindTyParams, } impl GenericsOutput { fn new(generics: &Generics) -> Self { Self { params_visitor: generics::FindTyParams::new(generics), } } fn result(self, item_name: &str, cratename: &Path) -> (Vec, TokenStream2) { let trait_path: Path = syn::parse2(quote! { #cratename::BorshSchema }).unwrap(); let predicates = generics::compute_predicates( self.params_visitor.clone().process_for_bounds(), &trait_path, ); // Generate function that returns the name of the type. let declaration = declaration( item_name, cratename.clone(), self.params_visitor.process_for_bounds(), ); (predicates, declaration) } } fn declaration(ident_str: &str, cratename: Path, params_for_bounds: Vec) -> TokenStream2 { // Generate function that returns the name of the type. let mut declaration_params = vec![]; for type_param in params_for_bounds { declaration_params.push(quote! { <#type_param as #cratename::BorshSchema>::declaration() }); } if declaration_params.is_empty() { quote! { #ident_str.to_string() } } else { quote! { let params = #cratename::__private::maybestd::vec![#(#declaration_params),*]; format!(r#"{}<{}>"#, #ident_str, params.join(", ")) } } } fn filter_used_params(generics: &Generics, not_skipped_type_params: HashSet) -> Generics { let new_params = generics .params .clone() .into_iter() .filter(|param| match param { GenericParam::Lifetime(..) | GenericParam::Const(..) => true, GenericParam::Type(ty_param) => not_skipped_type_params.contains(&ty_param.ident), }) .collect(); let mut where_clause = generics.where_clause.clone(); where_clause = where_clause.map(|mut clause| { let new_predicates: Punctuated = clause .predicates .iter() .filter(|predicate| { #[cfg_attr( feature = "force_exhaustive_checks", deny(non_exhaustive_omitted_patterns) )] match predicate { WherePredicate::Lifetime(..) => true, WherePredicate::Type(predicate_type) => generics::type_contains_some_param( &predicate_type.bounded_ty, ¬_skipped_type_params, ), _ => true, } }) .cloned() .collect(); clause.predicates = new_predicates; clause }); Generics { params: new_params, where_clause, ..generics.clone() } } fn visit_field(field: &Field, visitor: &mut generics::FindTyParams) -> syn::Result<()> { let parsed = field::Attributes::parse(&field.attrs)?; let needs_schema_params_derive = parsed.needs_schema_params_derive(); let schema_attrs = parsed.schema; if !parsed.skip { if needs_schema_params_derive { visitor.visit_field(field); } // there's no need to override params when field is skipped, because when field is skipped // derive for it doesn't attempt to add any bounds, unlike `BorshDeserialize`, which // adds `Default` bound on any type parameters in skipped field if let Some(schema_attrs) = schema_attrs { if let Some(schema_params) = schema_attrs.params { for field::schema::ParameterOverride { order_param, override_type, .. } in schema_params { visitor.param_associated_type_insert(order_param, override_type); } } } } Ok(()) } /// check param usage in fields with respect to `borsh(skip)` attribute usage fn visit_struct_fields(fields: &Fields, visitor: &mut generics::FindTyParams) -> syn::Result<()> { match &fields { Fields::Named(fields) => { for field in &fields.named { visit_field(field, visitor)?; } } Fields::Unnamed(fields) => { for field in &fields.unnamed { visit_field(field, visitor)?; } } Fields::Unit => {} } Ok(()) } /// check param usage in fields fn visit_struct_fields_unconditional(fields: &Fields, visitor: &mut generics::FindTyParams) { match &fields { Fields::Named(fields) => { for field in &fields.named { visitor.visit_field(field); } } Fields::Unnamed(fields) => { for field in &fields.unnamed { visitor.visit_field(field); } } Fields::Unit => {} } } borsh-derive-1.6.0/src/internals/schema/structs/mod.rs000064400000000000000000000376751046102023000211250ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::{ExprPath, Fields, Ident, ItemStruct, Path, Type}; use crate::internals::{attributes::field, generics, schema}; /// function which computes derive output [proc_macro2::TokenStream] /// of code, which computes declaration of a single field, which is later added to /// the struct's definition as a whole fn field_declaration_output( field_name: Option<&Ident>, field_type: &Type, cratename: &Path, declaration_override: Option, ) -> TokenStream2 { let default_path: ExprPath = syn::parse2(quote! { <#field_type as #cratename::BorshSchema>::declaration }).unwrap(); let path = declaration_override.unwrap_or(default_path); if let Some(field_name) = field_name { let field_name = field_name.to_token_stream().to_string(); quote! { (#field_name.to_string(), #path()) } } else { quote! { #path() } } } /// function which computes derive output [proc_macro2::TokenStream] /// of code, which adds definitions of a field to the output `definitions: &mut BTreeMap` fn field_definitions_output( field_type: &Type, cratename: &Path, definitions_override: Option, ) -> TokenStream2 { let default_path: ExprPath = syn::parse2( quote! { <#field_type as #cratename::BorshSchema>::add_definitions_recursively }, ) .unwrap(); let path = definitions_override.unwrap_or(default_path); quote! { #path(definitions); } } pub fn process(input: &ItemStruct, cratename: Path) -> syn::Result { let name = &input.ident; let struct_name = name.to_token_stream().to_string(); let generics = generics::without_defaults(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut where_clause = generics::default_where(where_clause); let mut generics_output = schema::GenericsOutput::new(&generics); let (struct_fields, add_definitions_recursively) = process_fields(&cratename, &input.fields, &mut generics_output)?; let add_definitions_recursively = quote! { fn add_definitions_recursively(definitions: &mut #cratename::__private::maybestd::collections::BTreeMap<#cratename::schema::Declaration, #cratename::schema::Definition>) { #struct_fields let definition = #cratename::schema::Definition::Struct { fields }; let no_recursion_flag = definitions.get(&::declaration()).is_none(); #cratename::schema::add_definition(::declaration(), definition, definitions); if no_recursion_flag { #add_definitions_recursively } } }; let (predicates, declaration) = generics_output.result(&struct_name, &cratename); where_clause.predicates.extend(predicates); Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::BorshSchema for #name #ty_generics #where_clause { fn declaration() -> #cratename::schema::Declaration { #declaration } #add_definitions_recursively } }) } fn process_fields( cratename: &Path, fields: &Fields, generics: &mut schema::GenericsOutput, ) -> syn::Result<(TokenStream2, TokenStream2)> { let mut struct_fields = TokenStream2::new(); let mut add_definitions_recursively = TokenStream2::new(); // Generate function that returns the schema of required types. let mut fields_vec = vec![]; schema::visit_struct_fields(fields, &mut generics.params_visitor)?; match fields { Fields::Named(fields) => { for field in &fields.named { process_field( field, cratename, &mut fields_vec, &mut add_definitions_recursively, )?; } if !fields_vec.is_empty() { struct_fields = quote! { let fields = #cratename::schema::Fields::NamedFields(#cratename::__private::maybestd::vec![#(#fields_vec),*]); }; } } Fields::Unnamed(fields) => { for field in &fields.unnamed { process_field( field, cratename, &mut fields_vec, &mut add_definitions_recursively, )?; } if !fields_vec.is_empty() { struct_fields = quote! { let fields = #cratename::schema::Fields::UnnamedFields(#cratename::__private::maybestd::vec![#(#fields_vec),*]); }; } } Fields::Unit => {} } if fields_vec.is_empty() { struct_fields = quote! { let fields = #cratename::schema::Fields::Empty; }; } Ok((struct_fields, add_definitions_recursively)) } fn process_field( field: &syn::Field, cratename: &Path, fields_vec: &mut Vec, add_definitions_recursively: &mut TokenStream2, ) -> syn::Result<()> { let parsed = field::Attributes::parse(&field.attrs)?; if !parsed.skip { let field_name = field.ident.as_ref(); let field_type = &field.ty; fields_vec.push(field_declaration_output( field_name, field_type, cratename, parsed.schema_declaration(), )); add_definitions_recursively.extend(field_definitions_output( field_type, cratename, parsed.schema_definitions(), )); } Ok(()) } #[cfg(test)] mod tests { use crate::internals::test_helpers::{ default_cratename, local_insta_assert_debug_snapshot, local_insta_assert_snapshot, pretty_print_syn_str, }; use super::*; #[test] fn unit_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct A; }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn wrapper_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct A(T); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn tuple_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct A(u64, String); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn tuple_struct_params() { let item_struct: ItemStruct = syn::parse2(quote! { struct A(K, V); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_struct_with_custom_crate() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap(); let actual = process(&item_struct, crate_).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generics() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: HashMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn trailing_comma_generics() { let item_struct: ItemStruct = syn::parse2(quote! { struct A where K: Display + Debug, { x: HashMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn tuple_struct_whole_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct A(#[borsh(skip)] String); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn tuple_struct_partial_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct A(#[borsh(skip)] u64, String); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip1() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( #[borsh(skip)] HashMap, U, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip2() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( HashMap, #[borsh(skip)] U, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip3() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( #[borsh(skip)] HashMap, U, K, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip4() { let item_struct: ItemStruct = syn::parse2(quote! { struct ASalad(Tomatoes, #[borsh(skip)] C, Oil); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_named_fields_struct_borsh_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct G { #[borsh(skip)] x: HashMap, y: U, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn recursive_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct CRecC { a: String, b: HashMap, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { field: T::Associated, another: V, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type_param_override() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(params = "T => ::Associated" ))] field: ::Associated, another: V, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type_param_override2() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(schema(params = "T => T, T => ::Associated" ))] field: (::Associated, T), another: V, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type_param_override_conflict() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { #[borsh(skip,schema(params = "T => ::Associated" ))] field: ::Associated, another: V, } }) .unwrap(); let actual = process(&item_struct, default_cratename()); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn check_with_funcs_skip_conflict() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(skip,schema(with_funcs( declaration = "third_party_impl::declaration::", definitions = "third_party_impl::add_definitions_recursively::" )))] x: ThirdParty, y: u64, } }) .unwrap(); let actual = process(&item_struct, default_cratename()); local_insta_assert_debug_snapshot!(actual.unwrap_err()); } #[test] fn with_funcs_attr() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(schema(with_funcs( declaration = "third_party_impl::declaration::", definitions = "third_party_impl::add_definitions_recursively::" )))] x: ThirdParty, y: u64, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn schema_param_override3() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh( schema( params = "V => V" ) )] x: PrimaryMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } borsh-derive-1.6.0/src/internals/serialize/enums/mod.rs000064400000000000000000000346571046102023000212710ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{Fields, Ident, ItemEnum, Path, Variant}; use crate::internals::{ attributes::{field, item, BoundType}, enum_discriminant::Discriminants, generics, serialize, }; pub fn process(input: &ItemEnum, cratename: Path) -> syn::Result { let enum_ident = &input.ident; let generics = generics::without_defaults(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut where_clause = generics::default_where(where_clause); let mut generics_output = serialize::GenericsOutput::new(&generics); let mut all_variants_idx_body = TokenStream2::new(); let mut fields_body = TokenStream2::new(); let use_discriminant = item::contains_use_discriminant(input)?; let discriminants = Discriminants::new(&input.variants); let mut has_unit_variant = false; for (variant_idx, variant) in input.variants.iter().enumerate() { let variant_ident = &variant.ident; let discriminant_value = discriminants.get(variant_ident, use_discriminant, variant_idx)?; let variant_output = process_variant( variant, enum_ident, &discriminant_value, &cratename, &mut generics_output, )?; all_variants_idx_body.extend(variant_output.variant_idx_body); match variant_output.body { VariantBody::Unit => has_unit_variant = true, VariantBody::Fields(VariantFields { header, body }) => fields_body.extend(quote!( #enum_ident::#variant_ident #header => { #body } )), } } let fields_body = optimize_fields_body(fields_body, has_unit_variant); generics_output.extend(&mut where_clause, &cratename); Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::ser::BorshSerialize for #enum_ident #ty_generics #where_clause { fn serialize<__W: #cratename::io::Write>(&self, writer: &mut __W) -> ::core::result::Result<(), #cratename::io::Error> { let variant_idx: u8 = match self { #all_variants_idx_body }; writer.write_all(&variant_idx.to_le_bytes())?; #fields_body Ok(()) } } }) } fn optimize_fields_body(fields_body: TokenStream2, has_unit_variant: bool) -> TokenStream2 { if fields_body.is_empty() { // If we no variants with fields, there's nothing to match against. Just // re-use the empty token stream. fields_body } else { let unit_fields_catchall = if has_unit_variant { // We had some variants with unit fields, create a catch-all for // these to be used at the bottom. quote!( _ => {} ) } else { TokenStream2::new() }; // Create a match that serialises all the fields for each non-unit // variant and add a catch-all at the bottom if we do have unit // variants. quote!( match self { #fields_body #unit_fields_catchall } ) } } #[derive(Default)] struct VariantFields { header: TokenStream2, body: TokenStream2, } impl VariantFields { fn named_header(self) -> Self { let header = self.header; VariantFields { // `..` pattern matching works even if all fields were specified header: quote! { { #header.. }}, body: self.body, } } fn unnamed_header(self) -> Self { let header = self.header; VariantFields { header: quote! { ( #header )}, body: self.body, } } } enum VariantBody { // No body variant, unit enum variant. Unit, // Variant with body (fields) Fields(VariantFields), } struct VariantOutput { body: VariantBody, variant_idx_body: TokenStream2, } fn process_variant( variant: &Variant, enum_ident: &Ident, discriminant_value: &TokenStream2, cratename: &Path, generics: &mut serialize::GenericsOutput, ) -> syn::Result { let variant_ident = &variant.ident; let variant_output = match &variant.fields { Fields::Named(fields) => { let mut variant_fields = VariantFields::default(); for field in &fields.named { let field_id = serialize::FieldId::Enum(field.ident.clone().unwrap()); process_field(field, field_id, cratename, generics, &mut variant_fields)?; } VariantOutput { body: VariantBody::Fields(variant_fields.named_header()), variant_idx_body: quote!( #enum_ident::#variant_ident {..} => #discriminant_value, ), } } Fields::Unnamed(fields) => { let mut variant_fields = VariantFields::default(); for (field_idx, field) in fields.unnamed.iter().enumerate() { let field_id = serialize::FieldId::new_enum_unnamed(field_idx)?; process_field(field, field_id, cratename, generics, &mut variant_fields)?; } VariantOutput { body: VariantBody::Fields(variant_fields.unnamed_header()), variant_idx_body: quote!( #enum_ident::#variant_ident(..) => #discriminant_value, ), } } Fields::Unit => VariantOutput { body: VariantBody::Unit, variant_idx_body: quote!( #enum_ident::#variant_ident => #discriminant_value, ), }, }; Ok(variant_output) } fn process_field( field: &syn::Field, field_id: serialize::FieldId, cratename: &Path, generics: &mut serialize::GenericsOutput, output: &mut VariantFields, ) -> syn::Result<()> { let parsed = field::Attributes::parse(&field.attrs)?; let needs_bounds_derive = parsed.needs_bounds_derive(BoundType::Serialize); generics .overrides .extend(parsed.collect_bounds(BoundType::Serialize)); let field_variant_header = field_id.enum_variant_header(parsed.skip); if let Some(field_variant_header) = field_variant_header { output.header.extend(field_variant_header); } if !parsed.skip { let delta = field_id.serialize_output(cratename, parsed.serialize_with); output.body.extend(delta); if needs_bounds_derive { generics.serialize_visitor.visit_field(field); } } Ok(()) } #[cfg(test)] mod tests { use crate::internals::test_helpers::{ default_cratename, local_insta_assert_snapshot, pretty_print_syn_str, }; use super::*; #[test] fn borsh_skip_tuple_variant_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum AATTB { B(#[borsh(skip)] i32, #[borsh(skip)] u32), NegatedVariant { beta: u8, } } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn struct_variant_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum AB { B { c: i32, d: u32, }, NegatedVariant { beta: String, } } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_enum_with_custom_crate() { let item_enum: ItemEnum = syn::parse2(quote! { enum AB { B { c: i32, d: u32, }, NegatedVariant { beta: String, } } }) .unwrap(); let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap(); let actual = process(&item_enum, crate_).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_skip_struct_variant_field() { let item_enum: ItemEnum = syn::parse2(quote! { enum AB { B { #[borsh(skip)] c: i32, d: u32, }, NegatedVariant { beta: String, } } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_skip_struct_variant_all_fields() { let item_enum: ItemEnum = syn::parse2(quote! { enum AAB { B { #[borsh(skip)] c: i32, #[borsh(skip)] d: u32, }, NegatedVariant { beta: String, } } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generics() { let item_struct: ItemEnum = syn::parse2(quote! { enum A { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn bound_generics() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn recursive_enum() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_borsh_skip_struct_field() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { #[borsh(skip)] x: HashMap, y: String, }, C(K, Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_borsh_skip_tuple_field() { let item_struct: ItemEnum = syn::parse2(quote! { enum A where V: Value { B { x: HashMap, y: String, }, C(K, #[borsh(skip)] Vec), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_serialize_bound() { let item_struct: ItemEnum = syn::parse2(quote! { enum A { C { a: String, #[borsh(bound(serialize = "T: borsh::ser::BorshSerialize + PartialOrd, U: borsh::ser::BorshSerialize" ))] b: HashMap, }, D(u32, u32), } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn check_serialize_with_attr() { let item_struct: ItemEnum = syn::parse2(quote! { enum C { C3(u64, u64), C4 { x: u64, #[borsh(serialize_with = "third_party_impl::serialize_third_party")] y: ThirdParty }, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_discriminant_false() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(use_discriminant = false)] enum X { A, B = 20, C, D, E = 10, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn borsh_discriminant_true() { let item_enum: ItemEnum = syn::parse2(quote! { #[borsh(use_discriminant = true)] enum X { A, B = 20, C, D, E = 10, F, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn mixed_with_unit_variants() { let item_enum: ItemEnum = syn::parse2(quote! { enum X { A(u16), B, C {x: i32, y: i32}, D, } }) .unwrap(); let actual = process(&item_enum, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } borsh-derive-1.6.0/src/internals/serialize/mod.rs000064400000000000000000000066531046102023000201350ustar 00000000000000use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use std::convert::TryFrom; use syn::{Expr, ExprPath, Generics, Ident, Index, Path}; use super::generics; pub mod enums; pub mod structs; pub mod unions; struct GenericsOutput { overrides: Vec, serialize_visitor: generics::FindTyParams, } impl GenericsOutput { fn new(generics: &Generics) -> Self { Self { overrides: vec![], serialize_visitor: generics::FindTyParams::new(generics), } } fn extend(self, where_clause: &mut syn::WhereClause, cratename: &Path) { let trait_path: Path = syn::parse2(quote! { #cratename::ser::BorshSerialize }).unwrap(); let predicates = generics::compute_predicates(self.serialize_visitor.process_for_bounds(), &trait_path); where_clause.predicates.extend(predicates); where_clause.predicates.extend(self.overrides); } } pub enum FieldId { Struct(Ident), StructUnnamed(Index), Enum(Ident), EnumUnnamed(Index), } impl FieldId { fn index(field_idx: usize) -> syn::Result { let index = u32::try_from(field_idx).map_err(|err| { syn::Error::new( Span::call_site(), format!("up to 2^32 fields are supported {}", err), ) })?; Ok(Index { index, span: Span::call_site(), }) } pub fn new_struct_unnamed(field_idx: usize) -> syn::Result { let index = Self::index(field_idx)?; let result = Self::StructUnnamed(index); Ok(result) } pub fn new_enum_unnamed(field_idx: usize) -> syn::Result { let index = Self::index(field_idx)?; let result = Self::EnumUnnamed(index); Ok(result) } } impl FieldId { fn serialize_arg(&self) -> Expr { match self { Self::Struct(name) => syn::parse2(quote! { &self.#name }).unwrap(), Self::StructUnnamed(index) => syn::parse2(quote! { &self.#index }).unwrap(), Self::Enum(name) => syn::parse2(quote! { #name }).unwrap(), Self::EnumUnnamed(ind) => { let field = Ident::new(&format!("id{}", ind.index), Span::mixed_site()); syn::parse2(quote! { #field }).unwrap() } } } /// function which computes derive output [proc_macro2::TokenStream] /// of code, which serializes single field pub fn serialize_output( &self, cratename: &Path, serialize_with: Option, ) -> TokenStream2 { let arg: Expr = self.serialize_arg(); if let Some(func) = serialize_with { quote! { #func(#arg, writer)?; } } else { quote! { #cratename::BorshSerialize::serialize(#arg, writer)?; } } } pub fn enum_variant_header(&self, skipped: bool) -> Option { match self { Self::Struct(..) | Self::StructUnnamed(..) => unreachable!("no variant header"), Self::Enum(name) => (!skipped).then_some(quote! { #name, }), Self::EnumUnnamed(index) => { let field_ident = if skipped { Ident::new(&format!("_id{}", index.index), Span::mixed_site()) } else { Ident::new(&format!("id{}", index.index), Span::mixed_site()) }; Some(quote! { #field_ident, }) } } } } borsh-derive-1.6.0/src/internals/serialize/structs/mod.rs000064400000000000000000000215471046102023000216430ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{Fields, ItemStruct, Path}; use crate::internals::{ attributes::{field, BoundType}, generics, serialize, }; pub fn process(input: &ItemStruct, cratename: Path) -> syn::Result { let name = &input.ident; let generics = generics::without_defaults(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let mut where_clause = generics::default_where(where_clause); let mut body = TokenStream2::new(); let mut generics_output = serialize::GenericsOutput::new(&generics); match &input.fields { Fields::Named(fields) => { for field in &fields.named { let field_id = serialize::FieldId::Struct(field.ident.clone().unwrap()); process_field(field, field_id, &cratename, &mut generics_output, &mut body)?; } } Fields::Unnamed(fields) => { for (field_idx, field) in fields.unnamed.iter().enumerate() { let field_id = serialize::FieldId::new_struct_unnamed(field_idx)?; process_field(field, field_id, &cratename, &mut generics_output, &mut body)?; } } Fields::Unit => {} } generics_output.extend(&mut where_clause, &cratename); Ok(quote! { #[automatically_derived] impl #impl_generics #cratename::ser::BorshSerialize for #name #ty_generics #where_clause { fn serialize<__W: #cratename::io::Write>(&self, writer: &mut __W) -> ::core::result::Result<(), #cratename::io::Error> { #body Ok(()) } } }) } fn process_field( field: &syn::Field, field_id: serialize::FieldId, cratename: &Path, generics: &mut serialize::GenericsOutput, body: &mut TokenStream2, ) -> syn::Result<()> { let parsed = field::Attributes::parse(&field.attrs)?; let needs_bounds_derive = parsed.needs_bounds_derive(BoundType::Serialize); generics .overrides .extend(parsed.collect_bounds(BoundType::Serialize)); if !parsed.skip { let delta = field_id.serialize_output(cratename, parsed.serialize_with); body.extend(delta); if needs_bounds_derive { generics.serialize_visitor.visit_field(field); } } Ok(()) } #[cfg(test)] mod tests { use crate::internals::test_helpers::{ default_cratename, local_insta_assert_debug_snapshot, local_insta_assert_snapshot, pretty_print_syn_str, }; use super::*; #[test] fn simple_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_struct_with_custom_crate() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: u64, y: String, } }) .unwrap(); let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap(); let actual = process(&item_struct, crate_).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generics() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { x: HashMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn simple_generic_tuple_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct TupleA(T, u32); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn bound_generics() { let item_struct: ItemStruct = syn::parse2(quote! { struct A where V: Value { x: HashMap, y: String, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn recursive_struct() { let item_struct: ItemStruct = syn::parse2(quote! { struct CRecC { a: String, b: HashMap, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip1() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( #[borsh(skip)] HashMap, U, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_tuple_struct_borsh_skip2() { let item_struct: ItemStruct = syn::parse2(quote! { struct G ( HashMap, #[borsh(skip)] U, ); }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_named_fields_struct_borsh_skip() { let item_struct: ItemStruct = syn::parse2(quote! { struct G { #[borsh(skip)] x: HashMap, y: U, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_associated_type() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName, { field: T::Associated, another: V, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn generic_serialize_bound() { let item_struct: ItemStruct = syn::parse2(quote! { struct C { a: String, #[borsh(bound(serialize = "T: borsh::ser::BorshSerialize + PartialOrd, U: borsh::ser::BorshSerialize" ))] b: HashMap, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn override_generic_associated_type_wrong_derive() { let item_struct: ItemStruct = syn::parse2(quote! { struct Parametrized where T: TraitName { #[borsh(bound(serialize = "::Associated: borsh::ser::BorshSerialize" ))] field: ::Associated, another: V, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn check_serialize_with_attr() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(serialize_with = "third_party_impl::serialize_third_party")] x: ThirdParty, y: u64, } }) .unwrap(); let actual = process(&item_struct, default_cratename()).unwrap(); local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } #[test] fn check_serialize_with_skip_conflict() { let item_struct: ItemStruct = syn::parse2(quote! { struct A { #[borsh(skip,serialize_with = "third_party_impl::serialize_third_party")] x: ThirdParty, y: u64, } }) .unwrap(); let actual = process(&item_struct, default_cratename()); let err = match actual { Ok(..) => unreachable!("expecting error here"), Err(err) => err, }; local_insta_assert_debug_snapshot!(err); } } borsh-derive-1.6.0/src/internals/serialize/unions/mod.rs000064400000000000000000000002661046102023000214420ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use syn::{ItemUnion, Path}; pub fn process(_input: &ItemUnion, _cratename: Path) -> syn::Result { unimplemented!() } borsh-derive-1.6.0/src/internals/test_helpers.rs000064400000000000000000000031331046102023000200560ustar 00000000000000use super::cratename::BORSH; use proc_macro2::Span; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use std::fmt::Write; use syn::{Ident, Path}; pub fn pretty_print_syn_str(input: &TokenStream) -> syn::Result { let input = format!("{}", quote!(#input)); let syn_file = syn::parse_str::(&input)?; Ok(prettyplease::unparse(&syn_file)) } pub fn debug_print_vec_of_tokenizable(optional: Option>) -> String { let mut s = String::new(); if let Some(vec) = optional { for element in vec { writeln!(&mut s, "{}", element.to_token_stream()).unwrap(); } } else { write!(&mut s, "None").unwrap(); } s } pub fn debug_print_tokenizable(optional: Option) -> String { let mut s = String::new(); if let Some(type_) = optional { writeln!(&mut s, "{}", type_.to_token_stream()).unwrap(); } else { write!(&mut s, "None").unwrap(); } s } macro_rules! local_insta_assert_debug_snapshot { ($value:expr) => {{ insta::with_settings!({prepend_module_to_snapshot => false}, { insta::assert_debug_snapshot!($value); }); }}; } macro_rules! local_insta_assert_snapshot { ($value:expr) => {{ insta::with_settings!({prepend_module_to_snapshot => false}, { insta::assert_snapshot!($value); }); }}; } pub(crate) fn default_cratename() -> Path { let cratename = Ident::new(BORSH, Span::call_site()); cratename.into() } pub(crate) use {local_insta_assert_debug_snapshot, local_insta_assert_snapshot}; borsh-derive-1.6.0/src/lib.rs000064400000000000000000000075731046102023000141400ustar 00000000000000#![recursion_limit = "128"] #![cfg_attr( feature = "force_exhaustive_checks", feature(non_exhaustive_omitted_patterns_lint) )] #![allow(clippy::needless_lifetimes)] extern crate proc_macro; use proc_macro::TokenStream; #[cfg(feature = "schema")] use proc_macro2::Span; use syn::{DeriveInput, Error, ItemEnum, ItemStruct, ItemUnion, Path}; /// by convention, local to borsh-derive crate, imports from proc_macro (1) are not allowed in `internals` module or in any of its submodules. mod internals; use crate::internals::attributes::item; #[cfg(feature = "schema")] use internals::schema; use internals::{cratename, deserialize, serialize}; fn check_attrs_get_cratename(input: &TokenStream) -> Result { let input = input.clone(); let derive_input = syn::parse::(input)?; item::check_attributes(&derive_input)?; cratename::get(&derive_input.attrs) } /// --- /// /// moved to docs of **Derive Macro** `BorshSerialize` in `borsh` crate #[proc_macro_derive(BorshSerialize, attributes(borsh))] pub fn borsh_serialize(input: TokenStream) -> TokenStream { let cratename = match check_attrs_get_cratename(&input) { Ok(cratename) => cratename, Err(err) => { return err.to_compile_error().into(); } }; let res = if let Ok(input) = syn::parse::(input.clone()) { serialize::structs::process(&input, cratename) } else if let Ok(input) = syn::parse::(input.clone()) { serialize::enums::process(&input, cratename) } else if let Ok(input) = syn::parse::(input) { serialize::unions::process(&input, cratename) } else { // Derive macros can only be defined on structs, enums, and unions. unreachable!() }; TokenStream::from(match res { Ok(res) => res, Err(err) => err.to_compile_error(), }) } /// --- /// /// moved to docs of **Derive Macro** `BorshDeserialize` in `borsh` crate #[proc_macro_derive(BorshDeserialize, attributes(borsh))] pub fn borsh_deserialize(input: TokenStream) -> TokenStream { let cratename = match check_attrs_get_cratename(&input) { Ok(cratename) => cratename, Err(err) => { return err.to_compile_error().into(); } }; let res = if let Ok(input) = syn::parse::(input.clone()) { deserialize::structs::process(&input, cratename) } else if let Ok(input) = syn::parse::(input.clone()) { deserialize::enums::process(&input, cratename) } else if let Ok(input) = syn::parse::(input) { deserialize::unions::process(&input, cratename) } else { // Derive macros can only be defined on structs, enums, and unions. unreachable!() }; TokenStream::from(match res { Ok(res) => res, Err(err) => err.to_compile_error(), }) } /// --- /// /// moved to docs of **Derive Macro** `BorshSchema` in `borsh` crate #[cfg(feature = "schema")] #[proc_macro_derive(BorshSchema, attributes(borsh))] pub fn borsh_schema(input: TokenStream) -> TokenStream { let cratename = match check_attrs_get_cratename(&input) { Ok(cratename) => cratename, Err(err) => { return err.to_compile_error().into(); } }; let res = if let Ok(input) = syn::parse::(input.clone()) { schema::structs::process(&input, cratename) } else if let Ok(input) = syn::parse::(input.clone()) { schema::enums::process(&input, cratename) } else if syn::parse::(input).is_ok() { Err(syn::Error::new( Span::call_site(), "Borsh schema does not support unions yet.", )) } else { // Derive macros can only be defined on structs, enums, and unions. unreachable!() }; TokenStream::from(match res { Ok(res) => res, Err(err) => err.to_compile_error(), }) }