diff --git a/derive/src/decode.rs b/derive/src/decode.rs index 1e228776..262371c8 100644 --- a/derive/src/decode.rs +++ b/derive/src/decode.rs @@ -70,12 +70,17 @@ pub fn quote( }, } }); + let recurse_indices = + variants.iter().enumerate().map(|(i, v)| utils::variant_index(v, i)); + + let const_eval_check = utils::const_eval_check_variant_indexes(recurse_indices); let read_byte_err_msg = format!("Could not decode `{type_name}`, failed to read variant byte"); let invalid_variant_err_msg = format!("Could not decode `{type_name}`, variant doesn't exist"); quote! { + #const_eval_check match #input.read_byte() .map_err(|e| e.chain(#read_byte_err_msg))? { diff --git a/derive/src/encode.rs b/derive/src/encode.rs index df7af38a..403a4da9 100644 --- a/derive/src/encode.rs +++ b/derive/src/encode.rs @@ -17,7 +17,7 @@ use std::str::from_utf8; use proc_macro2::{Ident, Span, TokenStream}; use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Error, Field, Fields}; -use crate::utils; +use crate::utils::{self, const_eval_check_variant_indexes}; type FieldsList = Punctuated; @@ -338,7 +338,7 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS } }; - [hinting, encoding] + [hinting, encoding, index] }, Fields::Unnamed(ref fields) => { let fields = &fields.unnamed; @@ -371,7 +371,7 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS } }; - [hinting, encoding] + [hinting, encoding, index] }, Fields::Unit => { let hinting = quote_spanned! { f.span() => @@ -387,13 +387,14 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS } }; - [hinting, encoding] + [hinting, encoding, index] }, } }); - let recurse_hinting = recurse.clone().map(|[hinting, _]| hinting); - let recurse_encoding = recurse.clone().map(|[_, encoding]| encoding); + let recurse_hinting = recurse.clone().map(|[hinting, _, _]| hinting); + let recurse_encoding = recurse.clone().map(|[_, encoding, _]| encoding); + let recurse_indices = recurse.clone().map(|[_, _, index]| index); let hinting = quote! { // The variant index uses 1 byte. @@ -403,7 +404,10 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS } }; + let const_eval_check = const_eval_check_variant_indexes(recurse_indices); + let encoding = quote! { + #const_eval_check match *#self_ { #( #recurse_encoding )*, _ => (), diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 735e2348..a96bb298 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -38,6 +38,30 @@ where }) } +pub fn const_eval_check_variant_indexes( + recurse_indices: impl Iterator, +) -> TokenStream { + quote! { + const _: () = { + let indices = [#( #recurse_indices ,)*]; + let len = indices.len(); + + // Check each pair for uniqueness + let mut i = 0; + while i < len { + let mut j = i + 1; + while j < len { + if indices[i] == indices[j] { + ::core::panic!("Found Variants that have duplicate indexes. Use different indexes for each variant"); + } + j += 1; + } + i += 1; + } + }; + } +} + /// Look for a `#[scale(index = $int)]` attribute on a variant. If no attribute /// is found, fall back to the discriminant or just the variant index. pub fn variant_index(v: &Variant, i: usize) -> TokenStream { @@ -47,7 +71,7 @@ pub fn variant_index(v: &Variant, i: usize) -> TokenStream { if nv.path.is_ident("index") { if let Expr::Lit(ExprLit { lit: Lit::Int(ref v), .. }) = nv.value { let byte = v - .base10_parse::() + .base10_parse::() .expect("Internal error, index attribute must have been checked"); return Some(byte); } diff --git a/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs b/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs new file mode 100644 index 00000000..03836635 --- /dev/null +++ b/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs @@ -0,0 +1,16 @@ +#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] +#[codec(crate = ::parity_scale_codec)] +enum T { + A = 1, + B, +} + +#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] +#[codec(crate = ::parity_scale_codec)] +enum T2 { + #[codec(index = 1)] + A, + B, +} + +fn main() {} diff --git a/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.stderr b/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.stderr new file mode 100644 index 00000000..cd6a11e2 --- /dev/null +++ b/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.stderr @@ -0,0 +1,31 @@ +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:10 + | +1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:10 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:40 + | +1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:40 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:10 + | +8 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:10 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:40 + | +8 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:40 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/scale_codec_ui/codec_duplicate_index.rs b/tests/scale_codec_ui/codec_duplicate_index.rs new file mode 100644 index 00000000..dc3e666e --- /dev/null +++ b/tests/scale_codec_ui/codec_duplicate_index.rs @@ -0,0 +1,17 @@ +#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] +#[codec(crate = ::parity_scale_codec)] +enum T { + A = 3, + #[codec(index = 3)] + B, +} + +#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] +#[codec(crate = ::parity_scale_codec)] +enum T1 { + A, + #[codec(index = 0)] + B, +} + +fn main() {} diff --git a/tests/scale_codec_ui/codec_duplicate_index.stderr b/tests/scale_codec_ui/codec_duplicate_index.stderr new file mode 100644 index 00000000..2848d4f8 --- /dev/null +++ b/tests/scale_codec_ui/codec_duplicate_index.stderr @@ -0,0 +1,31 @@ +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_duplicate_index.rs:1:10 + | +1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:1:10 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_duplicate_index.rs:1:40 + | +1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:1:40 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_duplicate_index.rs:9:10 + | +9 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:9:10 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/scale_codec_ui/codec_duplicate_index.rs:9:40 + | +9 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found Variants that have duplicate indexes. Use different indexes for each variant', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:9:40 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/variant_number.rs b/tests/variant_number.rs index 9bdaba0a..42e3db5c 100644 --- a/tests/variant_number.rs +++ b/tests/variant_number.rs @@ -1,18 +1,6 @@ use parity_scale_codec::Encode; use parity_scale_codec_derive::Encode as DeriveEncode; -#[test] -fn discriminant_variant_counted_in_default_index() { - #[derive(DeriveEncode)] - enum T { - A = 1, - B, - } - - assert_eq!(T::A.encode(), vec![1]); - assert_eq!(T::B.encode(), vec![1]); -} - #[test] fn skipped_variant_not_counted_in_default_index() { #[derive(DeriveEncode)] @@ -25,16 +13,3 @@ fn skipped_variant_not_counted_in_default_index() { assert_eq!(T::A.encode(), vec![]); assert_eq!(T::B.encode(), vec![0]); } - -#[test] -fn index_attr_variant_counted_and_reused_in_default_index() { - #[derive(DeriveEncode)] - enum T { - #[codec(index = 1)] - A, - B, - } - - assert_eq!(T::A.encode(), vec![1]); - assert_eq!(T::B.encode(), vec![1]); -}