borsh-derive-1.5.7/.cargo_vcs_info.json0000644000000001520000000000100134350ustar { "git": { "sha1": "abb9582c70b2afd54eef302c23b6e6d3a0b2c1c4" }, "path_in_vcs": "borsh-derive" }borsh-derive-1.5.7/Cargo.lock0000644000000164320000000000100114200ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "borsh-derive" version = "1.5.7" 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.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "indexmap" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "insta" version = "1.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084" dependencies = [ "console", "linked-hash-map", "once_cell", "pin-project", "similar", ] [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "pin-project" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "prettyplease" version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[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.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] borsh-derive-1.5.7/Cargo.toml0000644000000031640000000000100114410ustar # 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.67.0" name = "borsh-derive" version = "1.5.7" 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.5.7/Cargo.toml.orig000064400000000000000000000015541046102023000151230ustar 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.5.7/README.md000064400000000000000000000060231046102023000135070ustar 00000000000000# Borsh in Rust   [![Latest Version]][crates.io] [![borsh: rustc 1.67+]][Rust 1.67] [![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.67+]: https://img.shields.io/badge/rustc-1.67+-lightgray.svg [Rust 1.67]: https://blog.rust-lang.org/2023/01/26/Rust-1.67.0.html [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.5.7/src/internals/attributes/field/bounds.rs000064400000000000000000000040111046102023000221220ustar 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.5.7/src/internals/attributes/field/mod.rs000064400000000000000000000577051046102023000214310ustar 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.5.7/src/internals/attributes/field/schema/with_funcs.rs000064400000000000000000000040241046102023000242450ustar 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.5.7/src/internals/attributes/field/schema.rs000064400000000000000000000067441046102023000221070ustar 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.5.7/src/internals/attributes/item/mod.rs000064400000000000000000000317001046102023000212670ustar 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.5.7/src/internals/attributes/mod.rs000064400000000000000000000066321046102023000203370ustar 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.5.7/src/internals/attributes/parsing.rs000064400000000000000000000063641046102023000212250ustar 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.5.7/src/internals/cratename.rs000064400000000000000000000014151046102023000173230ustar 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.5.7/src/internals/deserialize/enums/mod.rs000064400000000000000000000241761046102023000216030ustar 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! { 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) } } 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.5.7/src/internals/deserialize/mod.rs000064400000000000000000000062211046102023000204430ustar 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.5.7/src/internals/deserialize/structs/mod.rs000064400000000000000000000176521046102023000221640ustar 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! { 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! { 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.5.7/src/internals/deserialize/unions/mod.rs000064400000000000000000000002661046102023000217610ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use syn::{ItemUnion, Path}; pub fn process(_input: &ItemUnion, _cratename: Path) -> syn::Result { unimplemented!() } borsh-derive-1.5.7/src/internals/enum_discriminant.rs000064400000000000000000000031771046102023000211030ustar 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.5.7/src/internals/generics.rs000064400000000000000000000257021046102023000171700ustar 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.5.7/src/internals/mod.rs000064400000000000000000000003001046102023000161330ustar 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.5.7/src/internals/schema/enums/mod.rs000064400000000000000000000377241046102023000205460ustar 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! { 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)] #[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.5.7/src/internals/schema/mod.rs000064400000000000000000000124051046102023000174040ustar 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.5.7/src/internals/schema/structs/mod.rs000064400000000000000000000376341046102023000211260ustar 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! { 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.5.7/src/internals/serialize/enums/mod.rs000064400000000000000000000346161046102023000212720ustar 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! { 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.5.7/src/internals/serialize/mod.rs000064400000000000000000000066531046102023000201430ustar 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.5.7/src/internals/serialize/structs/mod.rs000064400000000000000000000215061046102023000216440ustar 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! { 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.5.7/src/internals/serialize/unions/mod.rs000064400000000000000000000002661046102023000214500ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use syn::{ItemUnion, Path}; pub fn process(_input: &ItemUnion, _cratename: Path) -> syn::Result { unimplemented!() } borsh-derive-1.5.7/src/internals/test_helpers.rs000064400000000000000000000031331046102023000200640ustar 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.5.7/src/lib.rs000064400000000000000000000075731046102023000141460ustar 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(), }) }