diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9e04187c2..cc8a9d1a1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,7 +20,7 @@ jobs: tests: strategy: matrix: - rust_version: [1.65.0, stable] + rust_version: [1.66.0, stable] runs-on: ubuntu-20.04 steps: @@ -36,6 +36,10 @@ jobs: run: rustup default ${{ matrix.rust_version }} - name: print rustc version run: rustc --version + # remove this step when MSRV >= 1.67.0 + - name: downgrade `time` crate to support older Rust toolchain + if: matrix.rust_version == '1.66.0' + run: cargo update -p time --precise 0.3.23 - name: Run tests run: ./.github/test.sh diff --git a/Cargo.toml b/Cargo.toml index 8b17ab747..8c2100c9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ members = [ [workspace.package] # shared version of all public crates in the workspace version = "0.11.0" -rust-version = "1.65.0" +rust-version = "1.66.0" diff --git a/README.md b/README.md index b55d6983e..b13501b1b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Borsh in Rust   [![Latest Version]][crates.io] [![borsh: rustc 1.65+]][Rust 1.65] [![License Apache-2.0 badge]][License Apache-2.0] [![License MIT badge]][License MIT] +# Borsh in Rust   [![Latest Version]][crates.io] [![borsh: rustc 1.66+]][Rust 1.66] [![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.65+]: https://img.shields.io/badge/rustc-1.65+-lightgray.svg -[Rust 1.65]: https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html +[borsh: rustc 1.66+]: https://img.shields.io/badge/rustc-1.66+-lightgray.svg +[Rust 1.66]: https://blog.rust-lang.org/2022/12/15/Rust-1.66.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 @@ -76,6 +76,29 @@ struct A { } ``` +### Enum with explicit discriminant + +`#[borsh(use_discriminant=false|true])` is required if you have an enum with explicit discriminant. This setting affects `BorshSerialize` and `BorshDeserialize` behaviour at the same time. + +In the future, borsh will drop the requirement to explicitly use `#[borsh(use_discriminant=false|true)]`, and will default to `true`, but to make sure that the transition from the older versions of borsh (before 0.11 release) does not cause silent breaking changes in de-/serialization, borsh 1.0 will require to specify if the explicit enum discriminant should be used as a de-/serialization tag value. + +If you don't specify `use_discriminant` option for enum with explicit discriminant, you will get an error: + +```bash +error: You have to specify `#[borsh(use_discriminant=true)]` or `#[borsh(use_discriminant=false)]` for all enums with explicit discriminant +``` + +In order to preserve the behaviour of borsh versions before 0.11, which did not respect explicit enum discriminants for de-/serialization, use `#[borsh(use_discriminant=false)]`, otherwise, use `true`: + +```rust +#[derive(BorshDeserialize, BorshSerialize)] +#[borsh(use_discriminant=false)] +enum A { + X, + Y = 10, +} +``` + ## 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). diff --git a/borsh-derive-internal/src/attribute_helpers.rs b/borsh-derive-internal/src/attribute_helpers.rs index 77967d7a3..bdd6de247 100644 --- a/borsh-derive-internal/src/attribute_helpers.rs +++ b/borsh-derive-internal/src/attribute_helpers.rs @@ -1,6 +1,8 @@ // TODO: remove this unused attribute, when the unsplit is done #![allow(unused)] -use syn::{Attribute, Field, Path, WherePredicate}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{spanned::Spanned, Attribute, DeriveInput, Expr, Field, ItemEnum, Path, WherePredicate}; pub mod parsing_helpers; use parsing_helpers::get_where_predicates; @@ -13,6 +15,8 @@ pub struct Symbol(pub &'static str); pub const BORSH: Symbol = Symbol("borsh"); /// bound - sub-borsh nested meta, field-level only, `BorshSerialize` and `BorshDeserialize` contexts pub const BOUND: Symbol = Symbol("bound"); +// use_discriminant - sub-borsh nested meta, item-level only, enums only, `BorshSerialize` and `BorshDeserialize` contexts +pub const USE_DISCRIMINANT: &str = "use_discriminant"; /// serialize - sub-bound nested meta attribute pub const SERIALIZE: Symbol = Symbol("serialize"); /// deserialize - sub-bound nested meta attribute @@ -42,6 +46,88 @@ pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool { attrs.iter().any(|attr| attr.path() == SKIP) } +pub fn check_item_attributes(derive_input: &DeriveInput) -> Result<(), TokenStream> { + for attr in &derive_input.attrs { + if attr.path().is_ident(SKIP.0) { + return Err(syn::Error::new( + derive_input.ident.span(), + "`borsh_skip` is not allowed as derive input attribute", + ) + .to_compile_error()); + } + if attr.path().is_ident(BORSH.0) { + attr.parse_nested_meta(|meta| { + if !meta.path.is_ident(USE_DISCRIMINANT) { + return Err(syn::Error::new( + meta.path.span(), + "`use_discriminant` is the only supported attribute for `borsh`", + )); + } + if meta.path.is_ident(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", + )); + } + } + + Ok(()) + }) + .map_err(|err| err.to_compile_error())?; + } + } + 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; + for attr in attrs { + if attr.path().is_ident(BORSH.0) { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident(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`", + )); + } + }; + } + + 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]) -> Option { for attr in attrs.iter() { if attr.path() == INIT { @@ -134,7 +220,7 @@ pub(crate) fn collect_override_bounds( mod tests { use quote::{quote, ToTokens}; use std::fmt::Write; - use syn::ItemStruct; + use syn::{Item, ItemStruct}; use crate::attribute_helpers::parse_schema_attrs; @@ -387,4 +473,96 @@ mod tests { let schema_params = parse_schema_attrs(&first_field.attrs).unwrap(); assert!(schema_params.is_none()); } + + use super::*; + #[test] + fn test_check_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_check_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_check_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, + }; + insta::assert_debug_snapshot!(err); + } + #[test] + fn test_check_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_item_attributes(&item_enum); + insta::assert_snapshot!(actual.unwrap_err().to_token_stream().to_string()); + } + #[test] + fn test_check_use_borsh_skip_on_whole_struct() { + let item_enum: DeriveInput = syn::parse2(quote! { + #[derive(BorshDeserialize, Debug)] + #[borsh_skip] + struct AWithUseDiscriminantFalse { + x: X, + y: Y, + } + }) + .unwrap(); + let actual = check_item_attributes(&item_enum); + insta::assert_snapshot!(actual.unwrap_err().to_token_stream().to_string()); + } + #[test] + fn test_check_use_borsh_invalid_on_whole_struct() { + let item_enum: DeriveInput = syn::parse2(quote! { + #[derive(BorshDeserialize, Debug)] + #[borsh(invalid)] + enum AWithUseDiscriminantFalse { + X, + Y, + } + }) + .unwrap(); + let actual = check_item_attributes(&item_enum); + insta::assert_snapshot!(actual.unwrap_err().to_token_stream().to_string()); + } } diff --git a/borsh-derive-internal/src/enum_de.rs b/borsh-derive-internal/src/enum_de.rs index 86cc05b10..6f702b4e3 100644 --- a/borsh-derive-internal/src/enum_de.rs +++ b/borsh-derive-internal/src/enum_de.rs @@ -4,11 +4,13 @@ use syn::{Fields, Ident, ItemEnum, Path, WhereClause}; use crate::{ attribute_helpers::{ - collect_override_bounds, contains_initialize_with, contains_skip, BoundType, + collect_override_bounds, contains_initialize_with, contains_skip, + contains_use_discriminant, BoundType, }, enum_discriminant_map::discriminant_map, generics::{compute_predicates, without_defaults, FindTyParams}, }; +use std::convert::TryFrom; pub fn enum_de(input: &ItemEnum, cratename: Ident) -> syn::Result { let name = &input.ident; @@ -27,9 +29,19 @@ pub fn enum_de(input: &ItemEnum, cratename: Ident) -> syn::Result let mut default_params_visitor = FindTyParams::new(&generics); let init_method = contains_initialize_with(&input.attrs); + + let use_discriminant = contains_use_discriminant(input)?; + let mut variant_arms = TokenStream2::new(); let discriminants = discriminant_map(&input.variants); - for variant in input.variants.iter() { + + for (variant_idx, variant) in input.variants.iter().enumerate() { + 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. error{}", err), + ) + })?; let variant_ident = &variant.ident; let discriminant = discriminants.get(variant_ident).unwrap(); let mut variant_header = TokenStream2::new(); @@ -85,6 +97,11 @@ pub fn enum_de(input: &ItemEnum, cratename: Ident) -> syn::Result } Fields::Unit => {} } + let discriminant = if use_discriminant { + quote! { #discriminant } + } else { + quote! { #variant_idx } + }; variant_arms.extend(quote! { if variant_tag == #discriminant { #name::#variant_ident #variant_header } else }); @@ -291,4 +308,41 @@ mod tests { 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 = enum_de(&item_enum, Ident::new("borsh", Span::call_site())).unwrap(); + + 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 = enum_de(&item_enum, Ident::new("borsh", Span::call_site())).unwrap(); + + insta::assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); + } } diff --git a/borsh-derive-internal/src/enum_discriminant_map.rs b/borsh-derive-internal/src/enum_discriminant_map.rs index 9d7545a17..5ecb1eefa 100644 --- a/borsh-derive-internal/src/enum_discriminant_map.rs +++ b/borsh-derive-internal/src/enum_discriminant_map.rs @@ -8,7 +8,6 @@ use syn::{punctuated::Punctuated, token::Comma, Variant}; /// See: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values pub fn discriminant_map(variants: &Punctuated) -> HashMap { let mut map = HashMap::new(); - let mut next_discriminant_if_not_specified = quote! {0}; for variant in variants { @@ -16,6 +15,7 @@ pub fn discriminant_map(variants: &Punctuated) -> HashMap syn::Result let mut serialize_params_visitor = FindTyParams::new(&generics); let mut override_predicates = vec![]; + let use_discriminant = contains_use_discriminant(input)?; let mut all_variants_idx_body = TokenStream2::new(); let mut fields_body = TokenStream2::new(); let discriminants = discriminant_map(&input.variants); - for variant in input.variants.iter() { + + for (variant_idx, variant) in input.variants.iter().enumerate() { + 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. error{}", err), + ) + })?; let variant_ident = &variant.ident; let discriminant_value = discriminants.get(variant_ident).unwrap(); + let discriminant_value = if use_discriminant { + quote! { #discriminant_value } + } else { + quote! { #variant_idx } + }; let VariantParts { variant_header, variant_body, variant_idx_body, } = match &variant.fields { - Fields::Named(fields) => named_fields( - &cratename, - enum_ident, - variant_ident, - discriminant_value, - fields, - &mut serialize_params_visitor, - &mut override_predicates, - )?, - Fields::Unnamed(fields) => unnamed_fields( - &cratename, - enum_ident, - variant_ident, - discriminant_value, - fields, - &mut serialize_params_visitor, - &mut override_predicates, - )?, + Fields::Named(fields) => { + let variant_idx_body = quote!( + #enum_ident::#variant_ident {..} => #discriminant_value, + ); + named_fields( + &cratename, + fields, + &mut serialize_params_visitor, + &mut override_predicates, + variant_idx_body, + )? + } + Fields::Unnamed(fields) => { + let variant_idx_body = quote!( + #enum_ident::#variant_ident(..) => #discriminant_value, + ); + unnamed_fields( + &cratename, + fields, + &mut serialize_params_visitor, + &mut override_predicates, + variant_idx_body, + )? + } Fields::Unit => { let variant_idx_body = quote!( #enum_ident::#variant_ident => #discriminant_value, ); + VariantParts { variant_header: TokenStream2::new(), variant_body: TokenStream2::new(), @@ -100,12 +122,10 @@ struct VariantParts { } fn named_fields( cratename: &Ident, - enum_ident: &Ident, - variant_ident: &Ident, - discriminant_value: &TokenStream2, fields: &FieldsNamed, params_visitor: &mut FindTyParams, override_output: &mut Vec, + variant_idx_body: TokenStream2, ) -> syn::Result { let mut variant_header = TokenStream2::new(); let mut variant_body = TokenStream2::new(); @@ -127,9 +147,6 @@ fn named_fields( } // `..` pattern matching works even if all fields were specified variant_header = quote! { { #variant_header .. }}; - let variant_idx_body = quote!( - #enum_ident::#variant_ident { .. } => #discriminant_value, - ); Ok(VariantParts { variant_header, variant_body, @@ -139,12 +156,10 @@ fn named_fields( fn unnamed_fields( cratename: &Ident, - enum_ident: &Ident, - variant_ident: &Ident, - discriminant_value: &TokenStream2, fields: &FieldsUnnamed, params_visitor: &mut FindTyParams, override_output: &mut Vec, + variant_idx_body: TokenStream2, ) -> syn::Result { let mut variant_header = TokenStream2::new(); let mut variant_body = TokenStream2::new(); @@ -169,9 +184,6 @@ fn unnamed_fields( } } variant_header = quote! { ( #variant_header )}; - let variant_idx_body = quote!( - #enum_ident::#variant_ident(..) => #discriminant_value, - ); Ok(VariantParts { variant_header, variant_body, @@ -380,6 +392,42 @@ mod tests { let actual = enum_ser(&item_struct, Ident::new("borsh", Span::call_site())).unwrap(); + 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 = enum_ser(&item_enum, Ident::new("borsh", Span::call_site())).unwrap(); + + 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 = enum_ser(&item_enum, Ident::new("borsh", Span::call_site())).unwrap(); + insta::assert_snapshot!(pretty_print_syn_str(&actual).unwrap()); } } diff --git a/borsh-derive-internal/src/lib.rs b/borsh-derive-internal/src/lib.rs index 5d6f85c05..1795c549f 100644 --- a/borsh-derive-internal/src/lib.rs +++ b/borsh-derive-internal/src/lib.rs @@ -22,6 +22,7 @@ pub use union_de::union_de; pub use union_ser::union_ser; // TODO: similarly reexport this struct for documentation in `borsh-derive` when unsplit is done +pub use attribute_helpers::check_item_attributes; pub use attribute_helpers::parsing_helpers::SchemaParamsOverride; #[cfg(test)] diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_borsh_invalid_on_whole_struct.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_borsh_invalid_on_whole_struct.snap new file mode 100644 index 000000000..cd79e0152 --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_borsh_invalid_on_whole_struct.snap @@ -0,0 +1,5 @@ +--- +source: borsh-derive-internal/src/attribute_helpers.rs +expression: actual.unwrap_err().to_token_stream().to_string() +--- +:: core :: compile_error ! { "`use_discriminant` is the only supported attribute for `borsh`" } diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_borsh_skip_on_whole_struct.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_borsh_skip_on_whole_struct.snap new file mode 100644 index 000000000..9ee1ebf93 --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_borsh_skip_on_whole_struct.snap @@ -0,0 +1,5 @@ +--- +source: borsh-derive-internal/src/attribute_helpers.rs +expression: actual.unwrap_err().to_token_stream().to_string() +--- +:: core :: compile_error ! { "`borsh_skip` is not allowed as derive input attribute" } diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_discriminant_on_struct.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_discriminant_on_struct.snap new file mode 100644 index 000000000..a46e19983 --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_discriminant_on_struct.snap @@ -0,0 +1,5 @@ +--- +source: borsh-derive-internal/src/attribute_helpers.rs +expression: actual.unwrap_err().to_token_stream().to_string() +--- +:: core :: compile_error ! { "borsh(use_discriminant=) does not support structs" } diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_discriminant_wrong_value.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_discriminant_wrong_value.snap new file mode 100644 index 000000000..238df385e --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__attribute_helpers__tests__check_use_discriminant_wrong_value.snap @@ -0,0 +1,7 @@ +--- +source: borsh-derive-internal/src/attribute_helpers.rs +expression: err +--- +Error( + "`use_discriminant` accepts only `true` or `false`", +) diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_discriminant_false.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_discriminant_false.snap new file mode 100644 index 000000000..c5e95d7cc --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_discriminant_false.snap @@ -0,0 +1,43 @@ +--- +source: borsh-derive-internal/src/enum_de.rs +expression: pretty_print_syn_str(&actual).unwrap() +--- +impl borsh::de::BorshDeserialize for X { + fn deserialize_reader( + reader: &mut R, + ) -> ::core::result::Result { + let tag = ::deserialize_reader(reader)?; + ::deserialize_variant(reader, tag) + } +} +impl borsh::de::EnumExt for X { + fn deserialize_variant( + reader: &mut R, + variant_tag: u8, + ) -> ::core::result::Result { + let mut return_value = if variant_tag == 0u8 { + X::A + } else if variant_tag == 1u8 { + X::B + } else if variant_tag == 2u8 { + X::C + } else if variant_tag == 3u8 { + X::D + } else if variant_tag == 4u8 { + X::E + } else if variant_tag == 5u8 { + X::F + } else { + return Err( + borsh::__private::maybestd::io::Error::new( + borsh::__private::maybestd::io::ErrorKind::InvalidData, + borsh::__private::maybestd::format!( + "Unexpected variant tag: {:?}", variant_tag + ), + ), + ) + }; + Ok(return_value) + } +} + diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_discriminant_true.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_discriminant_true.snap new file mode 100644 index 000000000..d460c3e1b --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_discriminant_true.snap @@ -0,0 +1,43 @@ +--- +source: borsh-derive-internal/src/enum_de.rs +expression: pretty_print_syn_str(&actual).unwrap() +--- +impl borsh::de::BorshDeserialize for X { + fn deserialize_reader( + reader: &mut R, + ) -> ::core::result::Result { + let tag = ::deserialize_reader(reader)?; + ::deserialize_variant(reader, tag) + } +} +impl borsh::de::EnumExt for X { + fn deserialize_variant( + reader: &mut R, + variant_tag: u8, + ) -> ::core::result::Result { + let mut return_value = if variant_tag == 0 { + X::A + } else if variant_tag == 20 { + X::B + } else if variant_tag == 20 + 1 { + X::C + } else if variant_tag == 20 + 1 + 1 { + X::D + } else if variant_tag == 10 { + X::E + } else if variant_tag == 10 + 1 { + X::F + } else { + return Err( + borsh::__private::maybestd::io::Error::new( + borsh::__private::maybestd::io::ErrorKind::InvalidData, + borsh::__private::maybestd::format!( + "Unexpected variant tag: {:?}", variant_tag + ), + ), + ) + }; + Ok(return_value) + } +} + diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_struct_variant_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_struct_variant_field.snap index 4f7f858c6..05e35ae19 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_struct_variant_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_struct_variant_field.snap @@ -15,12 +15,12 @@ impl borsh::de::EnumExt for AA { reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { AA::B { c: core::default::Default::default(), d: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { AA::NegatedVariant { beta: borsh::BorshDeserialize::deserialize_reader(reader)?, } diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_tuple_variant_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_tuple_variant_field.snap index 829c6da3d..77b3b0919 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_tuple_variant_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__borsh_skip_tuple_variant_field.snap @@ -15,12 +15,12 @@ impl borsh::de::EnumExt for AAT { reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { AAT::B( core::default::Default::default(), borsh::BorshDeserialize::deserialize_reader(reader)?, ) - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { AAT::NegatedVariant { beta: borsh::BorshDeserialize::deserialize_reader(reader)?, } diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__bound_generics.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__bound_generics.snap index b8e9e228b..60eb6ac23 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__bound_generics.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__bound_generics.snap @@ -27,12 +27,12 @@ where reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { A::B { x: borsh::BorshDeserialize::deserialize_reader(reader)?, y: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { A::C( borsh::BorshDeserialize::deserialize_reader(reader)?, borsh::BorshDeserialize::deserialize_reader(reader)?, diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_struct_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_struct_field.snap index 22bcee41a..2659939bd 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_struct_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_struct_field.snap @@ -29,12 +29,12 @@ where reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { A::B { x: core::default::Default::default(), y: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { A::C( borsh::BorshDeserialize::deserialize_reader(reader)?, borsh::BorshDeserialize::deserialize_reader(reader)?, diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_tuple_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_tuple_field.snap index ba596a2f0..7ab273dd5 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_tuple_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_borsh_skip_tuple_field.snap @@ -27,12 +27,12 @@ where reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { A::B { x: borsh::BorshDeserialize::deserialize_reader(reader)?, y: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { A::C( borsh::BorshDeserialize::deserialize_reader(reader)?, core::default::Default::default(), diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_deserialize_bound.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_deserialize_bound.snap index 27dbf2fa1..b9ec60a4d 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_deserialize_bound.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__generic_deserialize_bound.snap @@ -23,12 +23,12 @@ where reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { A::C { a: borsh::BorshDeserialize::deserialize_reader(reader)?, b: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { A::D( borsh::BorshDeserialize::deserialize_reader(reader)?, borsh::BorshDeserialize::deserialize_reader(reader)?, diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__recursive_enum.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__recursive_enum.snap index 11782cc46..54761fbdf 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__recursive_enum.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__recursive_enum.snap @@ -25,12 +25,12 @@ where reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { A::B { x: borsh::BorshDeserialize::deserialize_reader(reader)?, y: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { A::C( borsh::BorshDeserialize::deserialize_reader(reader)?, borsh::BorshDeserialize::deserialize_reader(reader)?, diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__simple_generics.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__simple_generics.snap index 4f73da1fe..d61d0bff4 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__simple_generics.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_de__tests__simple_generics.snap @@ -25,12 +25,12 @@ where reader: &mut R, variant_tag: u8, ) -> ::core::result::Result { - let mut return_value = if variant_tag == 0 { + let mut return_value = if variant_tag == 0u8 { A::B { x: borsh::BorshDeserialize::deserialize_reader(reader)?, y: borsh::BorshDeserialize::deserialize_reader(reader)?, } - } else if variant_tag == 0 + 1 { + } else if variant_tag == 1u8 { A::C( borsh::BorshDeserialize::deserialize_reader(reader)?, borsh::BorshDeserialize::deserialize_reader(reader)?, diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_discriminant_false.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_discriminant_false.snap new file mode 100644 index 000000000..97aeb1d77 --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_discriminant_false.snap @@ -0,0 +1,30 @@ +--- +source: borsh-derive-internal/src/enum_ser.rs +expression: pretty_print_syn_str(&actual).unwrap() +--- +impl borsh::ser::BorshSerialize for X { + fn serialize( + &self, + writer: &mut W, + ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { + let variant_idx: u8 = match self { + X::A => 0u8, + X::B => 1u8, + X::C => 2u8, + X::D => 3u8, + X::E => 4u8, + X::F => 5u8, + }; + writer.write_all(&variant_idx.to_le_bytes())?; + match self { + X::A => {} + X::B => {} + X::C => {} + X::D => {} + X::E => {} + X::F => {} + } + Ok(()) + } +} + diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_discriminant_true.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_discriminant_true.snap new file mode 100644 index 000000000..e0e43a55b --- /dev/null +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_discriminant_true.snap @@ -0,0 +1,30 @@ +--- +source: borsh-derive-internal/src/enum_ser.rs +expression: pretty_print_syn_str(&actual).unwrap() +--- +impl borsh::ser::BorshSerialize for X { + fn serialize( + &self, + writer: &mut W, + ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { + let variant_idx: u8 = match self { + X::A => 0, + X::B => 20, + X::C => 20 + 1, + X::D => 20 + 1 + 1, + X::E => 10, + X::F => 10 + 1, + }; + writer.write_all(&variant_idx.to_le_bytes())?; + match self { + X::A => {} + X::B => {} + X::C => {} + X::D => {} + X::E => {} + X::F => {} + } + Ok(()) + } +} + diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_all_fields.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_all_fields.snap index 4bffccb84..5f4b9d530 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_all_fields.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_all_fields.snap @@ -8,8 +8,8 @@ impl borsh::ser::BorshSerialize for AAB { writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - AAB::B { .. } => 0, - AAB::NegatedVariant { .. } => 0 + 1, + AAB::B { .. } => 0u8, + AAB::NegatedVariant { .. } => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_field.snap index 9619db97c..bda423c4e 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_struct_variant_field.snap @@ -8,8 +8,8 @@ impl borsh::ser::BorshSerialize for AB { writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - AB::B { .. } => 0, - AB::NegatedVariant { .. } => 0 + 1, + AB::B { .. } => 0u8, + AB::NegatedVariant { .. } => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_tuple_variant_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_tuple_variant_field.snap index c4aedccee..2b4fd55cc 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_tuple_variant_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__borsh_skip_tuple_variant_field.snap @@ -8,8 +8,8 @@ impl borsh::ser::BorshSerialize for AATTB { writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - AATTB::B(..) => 0, - AATTB::NegatedVariant { .. } => 0 + 1, + AATTB::B(..) => 0u8, + AATTB::NegatedVariant { .. } => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__bound_generics.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__bound_generics.snap index 0ddc7105c..6f6567f45 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__bound_generics.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__bound_generics.snap @@ -14,8 +14,8 @@ where writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - A::B { .. } => 0, - A::C(..) => 0 + 1, + A::B { .. } => 0u8, + A::C(..) => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_struct_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_struct_field.snap index 5ab863c73..5340ae530 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_struct_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_struct_field.snap @@ -13,8 +13,8 @@ where writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - A::B { .. } => 0, - A::C(..) => 0 + 1, + A::B { .. } => 0u8, + A::C(..) => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_tuple_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_tuple_field.snap index 18ef4e667..6536ec602 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_tuple_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_borsh_skip_tuple_field.snap @@ -13,8 +13,8 @@ where writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - A::B { .. } => 0, - A::C(..) => 0 + 1, + A::B { .. } => 0u8, + A::C(..) => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_serialize_bound.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_serialize_bound.snap index 7571af534..dc6c7bce3 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_serialize_bound.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__generic_serialize_bound.snap @@ -12,8 +12,8 @@ where writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - A::C { .. } => 0, - A::D(..) => 0 + 1, + A::C { .. } => 0u8, + A::D(..) => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__recursive_enum.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__recursive_enum.snap index 52e7d27df..83d79faaa 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__recursive_enum.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__recursive_enum.snap @@ -13,8 +13,8 @@ where writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - A::B { .. } => 0, - A::C(..) => 0 + 1, + A::B { .. } => 0u8, + A::C(..) => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__simple_generics.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__simple_generics.snap index 25b07405e..8c1cb4f5c 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__simple_generics.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__simple_generics.snap @@ -13,8 +13,8 @@ where writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - A::B { .. } => 0, - A::C(..) => 0 + 1, + A::B { .. } => 0u8, + A::C(..) => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__struct_variant_field.snap b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__struct_variant_field.snap index 528c271f2..db115d2c0 100644 --- a/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__struct_variant_field.snap +++ b/borsh-derive-internal/src/snapshots/borsh_derive_internal__enum_ser__tests__struct_variant_field.snap @@ -8,8 +8,8 @@ impl borsh::ser::BorshSerialize for AB { writer: &mut W, ) -> ::core::result::Result<(), borsh::__private::maybestd::io::Error> { let variant_idx: u8 = match self { - AB::B { .. } => 0, - AB::NegatedVariant { .. } => 0 + 1, + AB::B { .. } => 0u8, + AB::NegatedVariant { .. } => 1u8, }; writer.write_all(&variant_idx.to_le_bytes())?; match self { diff --git a/borsh-derive/src/lib.rs b/borsh-derive/src/lib.rs index c88482afd..8570544e9 100644 --- a/borsh-derive/src/lib.rs +++ b/borsh-derive/src/lib.rs @@ -3,7 +3,7 @@ use proc_macro::TokenStream; use proc_macro2::Span; use proc_macro_crate::crate_name; use proc_macro_crate::FoundCrate; -use syn::{Ident, ItemEnum, ItemStruct, ItemUnion}; +use syn::{parse_macro_input, DeriveInput, Ident, ItemEnum, ItemStruct, ItemUnion}; use borsh_derive_internal::*; #[cfg(feature = "schema")] @@ -106,6 +106,66 @@ irrelevant of whether `#[borsh_skip]` attribute is present. Both attributes may be used simultaneously, separated by a comma: `#[borsh(bound(serialize = ..., deserialize = ...))]` +### `borsh(use_discriminant=)` (item level attribute) +This attribute is only applicable to enums. +`use_discriminant` allows to override the default behavior of serialization of enums with explicit discriminant. +`use_discriminant` is `false` behaves like version of borsh of 0.10.3. +You must specify `use_discriminant` for all enums with explicit discriminants in your project. + +This is equivalent of borsh version 0.10.3 (explicit discriminant is ignored and this enum is equivalent to `A` without explicit discriminant): +```ignore +#[derive(BorshSerialize)] +#[borsh(use_discriminant = false)] +enum A { + A + B = 10, +} +``` + +To have explicit discriminant value serialized as is, you must specify `borsh(use_discriminant=true)` for enum. +```ignore +#[derive(BorsSerialize)] +#[borsh(use_discriminant = true)] +enum B { + A + B = 10, +} +``` + +### borsh, expressions, evaluating to `isize`, as discriminant +This case is not supported: +```ignore +const fn discrim() -> isize { + 0x14 +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = true)] +enum X { + A, + B = discrim(), // expressions, evaluating to `isize`, which are allowed outside of `borsh` context + C, + D, + E = 10, + F, +} +``` + +### borsh explicit discriminant does not support literal values outside of u8 range +This is not supported: +```ignore +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = true)] +enum X { + A, + B = 0x100, // literal values outside of `u8` range + C, + D, + E = 10, + F, +} +``` + */ #[proc_macro_derive(BorshSerialize, attributes(borsh_skip, borsh))] pub fn borsh_serialize(input: TokenStream) -> TokenStream { @@ -116,6 +176,13 @@ pub fn borsh_serialize(input: TokenStream) -> TokenStream { }; let cratename = Ident::new(name, Span::call_site()); + let for_derive_input = input.clone(); + let derive_input = parse_macro_input!(for_derive_input as DeriveInput); + + if let Err(err) = check_item_attributes(&derive_input) { + return err.into(); + } + let res = if let Ok(input) = syn::parse::(input.clone()) { struct_ser(&input, cratename) } else if let Ok(input) = syn::parse::(input.clone()) { @@ -275,6 +342,72 @@ struct A( #### interaction with `#[borsh(bound(serialize = ...))]` Both attributes may be used simultaneously, separated by a comma: `#[borsh(bound(serialize = ..., deserialize = ...))]` + +This one will use proper version of serialization of enum with explicit discriminant. + +### `borsh(use_discriminant=)` (item level attribute) +This attribute is only applicable to enums. +`use_discriminant` allows to override the default behavior of serialization of enums with explicit discriminant. +`use_discriminant` is `false` behaves like version of borsh of 0.10.3. +It's useful for backward compatibility and you can set this value to `false` to deserialise data serialised by older version of `borsh`. +You must specify `use_discriminant` for all enums with explicit discriminants in your project. + +This is equivalent of borsh version 0.10.3 (explicit discriminant is ignored and this enum is equivalent to `A` without explicit discriminant): +```ignore +#[derive(BorshDeserialize)] +#[borsh(use_discriminant = false)] +enum A { + A + B = 10, +} +``` + +To have explicit discriminant value serialized as is, you must specify `borsh(use_discriminant=true)` for enum. +```ignore +#[derive(BorshDeserialize)] +#[borsh(use_discriminant = true)] +enum B { + A + B = 10, +} +``` + + +### borsh, expressions, evaluating to `isize`, as discriminant +This case is not supported: +```ignore +const fn discrim() -> isize { + 0x14 +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = true)] +enum X { + A, + B = discrim(), // expressions, evaluating to `isize`, which are allowed outside of `borsh` context + C, + D, + E = 10, + F, +} +``` + + +### borsh explicit discriminant does not support literal values outside of u8 range. +This is not supported: +```ignore +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = true)] +enum X { + A, + B = 0x100, // literal values outside of `u8` range + C, + D, + E = 10, + F, +} +``` + */ #[proc_macro_derive(BorshDeserialize, attributes(borsh_skip, borsh_init, borsh))] pub fn borsh_deserialize(input: TokenStream) -> TokenStream { @@ -285,6 +418,13 @@ pub fn borsh_deserialize(input: TokenStream) -> TokenStream { }; let cratename = Ident::new(name, Span::call_site()); + let for_derive_input = input.clone(); + let derive_input = parse_macro_input!(for_derive_input as DeriveInput); + + if let Err(err) = check_item_attributes(&derive_input) { + return err.into(); + } + let res = if let Ok(input) = syn::parse::(input.clone()) { struct_de(&input, cratename) } else if let Ok(input) = syn::parse::(input.clone()) { diff --git a/borsh/tests/test_de_errors.rs b/borsh/tests/test_de_errors.rs index 00d797b75..88253823b 100644 --- a/borsh/tests/test_de_errors.rs +++ b/borsh/tests/test_de_errors.rs @@ -1,6 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] - use borsh::from_slice; + #[cfg(feature = "derive")] use borsh::BorshDeserialize; @@ -16,11 +16,20 @@ use alloc::{ #[cfg(feature = "derive")] #[derive(BorshDeserialize, Debug)] +#[borsh(use_discriminant = true)] enum A { X, Y, } +#[cfg(feature = "derive")] +#[derive(BorshDeserialize, Debug)] +#[borsh(use_discriminant = false)] +enum AWithUseDiscriminantFalse { + X, + Y, +} + #[cfg(feature = "derive")] #[derive(BorshDeserialize, Debug)] struct B { @@ -53,6 +62,18 @@ fn test_invalid_enum_variant() { ); } +#[cfg(feature = "derive")] +#[test] +fn test_invalid_enum_variant_old() { + let bytes = vec![123]; + assert_eq!( + from_slice::(&bytes) + .unwrap_err() + .to_string(), + "Unexpected variant tag: 123" + ); +} + #[test] fn test_extra_bytes() { let bytes = vec![1, 0, 0, 0, 32, 32]; diff --git a/borsh/tests/test_enum_discriminants.rs b/borsh/tests/test_enum_discriminants.rs new file mode 100644 index 000000000..2909d8bcd --- /dev/null +++ b/borsh/tests/test_enum_discriminants.rs @@ -0,0 +1,185 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg(feature = "derive")] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::vec; + +use borsh::{from_slice, BorshDeserialize, BorshSerialize}; +// sequence, no unit enums +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = true)] +#[repr(u16)] +enum XY { + A, + B = 20, + C, + D(u32, u32), + E = 10, + F(u64), +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = false)] +#[repr(u16)] +enum XYNoDiscriminant { + A, + B = 20, + C, + D(u32, u32), + E = 10, + F(u64), +} + +#[test] +fn test_discriminant_serde_no_unit_type() { + let values = vec![XY::A, XY::B, XY::C, XY::E, XY::D(12, 14), XY::F(35325423)]; + let expected_discriminants = [0u8, 20, 21, 10, 22, 11]; + + for (ind, value) in values.iter().enumerate() { + let data = value.try_to_vec().unwrap(); + assert_eq!(data[0], expected_discriminants[ind]); + assert_eq!(from_slice::(&data).unwrap(), values[ind]); + } +} + +#[test] +fn test_discriminant_serde_no_unit_type_no_use_discriminant() { + let values = vec![ + XYNoDiscriminant::A, + XYNoDiscriminant::B, + XYNoDiscriminant::C, + XYNoDiscriminant::D(12, 14), + XYNoDiscriminant::E, + XYNoDiscriminant::F(35325423), + ]; + let expected_discriminants = [0u8, 1, 2, 3, 4, 5]; + + for (ind, value) in values.iter().enumerate() { + let data = value.try_to_vec().unwrap(); + assert_eq!(data[0], expected_discriminants[ind]); + assert_eq!(from_slice::(&data).unwrap(), values[ind]); + } +} + +// minimal +#[derive(BorshSerialize)] +#[borsh(use_discriminant = true)] +enum MyDiscriminantEnum { + A = 20, +} + +#[derive(BorshSerialize)] +#[borsh(use_discriminant = false)] +enum MyDiscriminantEnumFalse { + A = 20, +} + +#[derive(BorshSerialize)] +enum MyEnumNoDiscriminant { + A, +} +#[test] +fn test_discriminant_minimal_true() { + assert_eq!(MyDiscriminantEnum::A as u8, 20); + assert_eq!(MyDiscriminantEnum::A.try_to_vec().unwrap(), vec![20]); +} + +#[test] +fn test_discriminant_minimal_false() { + assert_eq!(MyDiscriminantEnumFalse::A as u8, 20); + assert_eq!( + MyEnumNoDiscriminant::A.try_to_vec().unwrap(), + MyDiscriminantEnumFalse::A.try_to_vec().unwrap(), + ); + assert_eq!(MyDiscriminantEnumFalse::A.try_to_vec().unwrap(), vec![0]); +} + +// sequence +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = false)] +enum XNoDiscriminant { + A, + B = 20, + C, + D, + E = 10, + F, +} + +#[test] +fn test_discriminant_serde_no_use_discriminant() { + let values = vec![ + XNoDiscriminant::A, + XNoDiscriminant::B, + XNoDiscriminant::C, + XNoDiscriminant::D, + XNoDiscriminant::E, + XNoDiscriminant::F, + ]; + let expected_discriminants = [0u8, 1, 2, 3, 4, 5]; + for (index, value) in values.iter().enumerate() { + let data = value.try_to_vec().unwrap(); + assert_eq!(data[0], expected_discriminants[index]); + assert_eq!(from_slice::(&data).unwrap(), values[index]); + } +} +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] +struct D { + x: u64, +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] +enum C { + C1, + C2(u64), + C3(u64, u64), + C4 { x: u64, y: u64 }, + C5(D), +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] +#[borsh(use_discriminant = true)] +enum X { + A, + B = 20, + C, + D, + E = 10, + F, +} + +#[test] +fn test_discriminant_serialization() { + let values = vec![X::A, X::B, X::C, X::D, X::E, X::F]; + for value in values { + assert_eq!(value.try_to_vec().unwrap(), [value as u8]); + } +} + +#[test] +fn test_discriminant_deserialization() { + let values = vec![X::A, X::B, X::C, X::D, X::E, X::F]; + for value in values { + assert_eq!(from_slice::(&[value as u8]).unwrap(), value,); + } +} + +#[test] +#[should_panic = "Unexpected variant tag: 2"] +fn test_deserialize_invalid_discriminant() { + from_slice::(&[2]).unwrap(); +} + +#[test] +fn test_discriminant_serde() { + let values = vec![X::A, X::B, X::C, X::D, X::E, X::F]; + let expected_discriminants = [0u8, 20, 21, 22, 10, 11]; + for (index, value) in values.iter().enumerate() { + let data = value.try_to_vec().unwrap(); + assert_eq!(data[0], expected_discriminants[index]); + assert_eq!(from_slice::(&data).unwrap(), values[index]); + } +} diff --git a/borsh/tests/test_simple_structs.rs b/borsh/tests/test_simple_structs.rs index 71837fbdc..dd960beec 100644 --- a/borsh/tests/test_simple_structs.rs +++ b/borsh/tests/test_simple_structs.rs @@ -99,38 +99,6 @@ struct F2<'b> { aa: Vec>, } -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Copy, Debug)] -enum X { - A, - B = 20, - C, - D, - E = 10, - F, -} - -#[test] -fn test_discriminant_serialization() { - let values = vec![X::A, X::B, X::C, X::D, X::E, X::F]; - for value in values { - assert_eq!(value.try_to_vec().unwrap(), [value as u8]); - } -} - -#[test] -fn test_discriminant_deserialization() { - let values = vec![X::A, X::B, X::C, X::D, X::E, X::F]; - for value in values { - assert_eq!(from_slice::(&[value as u8]).unwrap(), value,); - } -} - -#[test] -#[should_panic = "Unexpected variant tag: 2"] -fn test_deserialize_invalid_discriminant() { - from_slice::(&[2]).unwrap(); -} - #[test] fn test_simple_struct() { let mut map: BTreeMap = BTreeMap::new();