sbor/enum_variant.rs
1use crate::*;
2
3pub struct SborFixedEnumVariant<const DISCRIMINATOR: u8, T> {
4 pub fields: T,
5}
6
7impl<const DISCRIMINATOR: u8, T> SborFixedEnumVariant<DISCRIMINATOR, T> {
8 pub fn new(fields: T) -> Self {
9 Self { fields }
10 }
11
12 pub fn discriminator() -> u8 {
13 DISCRIMINATOR
14 }
15
16 pub fn for_encoding(fields: &T) -> SborFixedEnumVariant<DISCRIMINATOR, &T> {
17 SborFixedEnumVariant { fields }
18 }
19
20 pub fn into_fields(self) -> T {
21 self.fields
22 }
23}
24
25impl<X: CustomValueKind, const DISCRIMINATOR: u8, T: SborTuple<X>> Categorize<X>
26 for SborFixedEnumVariant<DISCRIMINATOR, T>
27{
28 fn value_kind() -> ValueKind<X> {
29 ValueKind::Enum
30 }
31}
32
33impl<X: CustomValueKind, const DISCRIMINATOR: u8, T: SborTuple<X>> SborEnum<X>
34 for SborFixedEnumVariant<DISCRIMINATOR, T>
35{
36 fn get_length(&self) -> usize {
37 self.fields.get_length()
38 }
39
40 fn get_discriminator(&self) -> u8 {
41 DISCRIMINATOR
42 }
43}
44
45impl<
46 X: CustomValueKind,
47 E: Encoder<X>,
48 const DISCRIMINATOR: u8,
49 T: Encode<X, E> + SborTuple<X>,
50 > Encode<X, E> for SborFixedEnumVariant<DISCRIMINATOR, T>
51{
52 fn encode_value_kind(&self, encoder: &mut E) -> Result<(), EncodeError> {
53 encoder.write_value_kind(Self::value_kind())
54 }
55
56 fn encode_body(&self, encoder: &mut E) -> Result<(), EncodeError> {
57 encoder.write_discriminator(DISCRIMINATOR)?;
58 self.fields.encode_body(encoder)
59 }
60}
61
62impl<
63 X: CustomValueKind,
64 D: Decoder<X>,
65 const DISCRIMINATOR: u8,
66 T: Decode<X, D> + SborTuple<X>,
67 > Decode<X, D> for SborFixedEnumVariant<DISCRIMINATOR, T>
68{
69 #[inline]
70 fn decode_body_with_value_kind(
71 decoder: &mut D,
72 value_kind: ValueKind<X>,
73 ) -> Result<Self, DecodeError> {
74 decoder.check_preloaded_value_kind(value_kind, Self::value_kind())?;
75 decoder.read_expected_discriminator(DISCRIMINATOR)?;
76 // The fields is actually a tuple type - so we pass in ValueKind::Tuple to trick the encoding
77 let fields = T::decode_body_with_value_kind(decoder, ValueKind::Tuple)?;
78 Ok(Self { fields })
79 }
80}
81
82//=======================================================================================
83// Now define a trait `IsSborFixedEnumVariant<T>` - this is intended to represent
84// `SborFixedEnumVariant<?, T>` of unknown discriminator.
85// This is only really needed because of https://github.com/rust-lang/rust/issues/76560
86// and the explanation below "Why is this required as an associated type?".
87// In particular, see eg `TransactionPayload` where we couldn't define `SborFixedEnumVariant<{ Self::DISCRIMINATOR }, X>`
88//=======================================================================================
89pub trait IsSborFixedEnumVariant<F> {
90 const DISCRIMINATOR: u8;
91 fn new(fields: F) -> Self;
92 fn into_fields(self) -> F;
93}
94
95impl<const DISCRIMINATOR: u8, F> IsSborFixedEnumVariant<F>
96 for SborFixedEnumVariant<DISCRIMINATOR, F>
97{
98 const DISCRIMINATOR: u8 = DISCRIMINATOR;
99
100 fn new(fields: F) -> Self {
101 Self::new(fields)
102 }
103
104 fn into_fields(self) -> F {
105 self.fields
106 }
107}
108
109/// This trait is output for unique unskipped single children of enum variants, when
110/// `#[sbor(impl_variant_traits)]` is specified on an Enum or
111/// `#[sbor(impl_variant_trait)]` is specified on a single Enum variant.
112///
113/// It allows considering this type as representing an enum variant type
114/// under its parent enum. There are two flavours of how this embedding works in SBOR:
115/// * In unflattened variants, it is a variant with fields (Self,)
116/// * In flattened variants (only possible for tuple types) it is a variant with fields Self
117///
118/// This trait pairs well with the `#[sbor(flatten)]` attribute, for implementing
119/// the "enum variant is singleton struct type" pattern, which allows a number of benefits:
120/// * A function can take or return a particular variant
121/// * If code size is important (e.g. when building Scrypto), we want to enable pruning of
122/// any serialization code we don't need. Using `as_encodable_variant` and `from_decoded_variant`
123/// avoids pulling in the parent `TEnum` serialization code; and the serialization code for any
124/// unused types in other discriminators
125///
126/// ### Note on generic parameter ordering
127/// On this trait, we do not put `X` first, as is normal with the SBOR traits.
128///
129/// Instead, `TEnum` comes before `X` so that the trait can be implemented on any foreign
130/// type assuming `TEnum` is local. This is so that the cryptic orphan rule
131/// discussed in this stack overflow comment is satisfied https://stackoverflow.com/a/63131661
132///
133/// With this ordering we have P0 = X, T0 = Child Type (possibly foreign), T1 = TEnum, T2 = X,
134/// which passes the check.
135pub trait SborEnumVariantFor<TEnum: SborEnum<X>, X: CustomValueKind> {
136 const DISCRIMINATOR: u8;
137 const IS_FLATTENED: bool;
138
139 /// VariantFields is either `Self` if `IS_FLATTENED` else is `(Self,)`
140 type VariantFields: SborTuple<X>;
141 fn from_variant_fields(variant_fields: Self::VariantFields) -> Self;
142
143 /// VariantFieldsRef is either `&Self` if `IS_FLATTENED` else is `(&Self,)`
144 type VariantFieldsRef<'a>: SborTuple<X>
145 where
146 Self: 'a,
147 Self::VariantFields: 'a;
148 fn as_variant_fields_ref(&self) -> Self::VariantFieldsRef<'_>;
149
150 /// This should always be `SborFixedEnumVariant<{ [DISCRIMINATOR] as u8 }, Self::VariantFields>`
151 ///
152 /// ### Why is this required as an associated type?
153 /// Ideally we'd not need this and just have `as_encodable_variant` return
154 /// `SborFixedEnumVariant<{ Self::DISCRIMINATOR as u8 }, Self::VariantFieldsRef<'_>>`
155 /// But this gets "error: generic parameters may not be used in const operations"
156 ///
157 /// ### Why doesn't this require `VecDecode<X>`?
158 /// We don't want a compiler error if a type only implements Categorize (and so gets this trait)
159 /// but not Decode. Really I'd like to say `OwnedVariant: VecDecode<X> if Self: VecDecode<X>`
160 /// but Rust doesn't support that. Instead, users will need to add the bound on the associated
161 /// type themselves.
162 type OwnedVariant: IsSborFixedEnumVariant<Self::VariantFields>;
163
164 /// Should always be `SborFixedEnumVariant<{ [DISCRIMINATOR] as u8 }, &'a Self::VariantFields>`
165 ///
166 /// ### Why is this required as an associated type?
167 /// Ideally we'd not need this and just have `as_encodable_variant` return
168 /// `SborFixedEnumVariant<{ Self::DISCRIMINATOR }, Self::VariantFields>`
169 /// But this gets "error: generic parameters may not be used in const operations" which needs
170 /// the `const-generics` feature which has been in progress for a number of years.
171 /// See https://github.com/rust-lang/project-const-generics/issues/31
172 ///
173 /// ### Why doesn't this require `VecEncode<X>`?
174 /// We don't want a compiler error if a type only implements Categorize (and so gets this trait)
175 /// but not Encode. Really I'd like to say `BorrowedVariant<'a>: VecEncode<X> if Self: VecEncode<X>`
176 /// but Rust doesn't support that. Instead, users will need to add the bound on the associated
177 /// type themselves.
178 ///
179 /// Instead, you can express a trait bound as such:
180 /// ```ignore
181 /// pub trait MyNewSuperTrait:
182 /// for<'a> SborEnumVariantFor<
183 /// TEnum,
184 /// X,
185 /// OwnedVariant: ManifestDecode,
186 /// BorrowedVariant<'a>: ManifestEncode,
187 /// >
188 /// {}
189 /// ```
190 type BorrowedVariant<'a>: IsSborFixedEnumVariant<Self::VariantFieldsRef<'a>>
191 where
192 Self: 'a,
193 Self::VariantFields: 'a;
194
195 /// Can be used to encode the type as a variant under `TEnum`, like this:
196 /// `encoder.encode(x.as_encodable_variant())`.
197 ///
198 /// To use this pattern in a generic context, you will likely need to add a bound like
199 /// `for<'a> T::BorrowedVariant<'a>: VecEncode<X>`.
200 fn as_encodable_variant<'a>(&'a self) -> Self::BorrowedVariant<'a> {
201 Self::BorrowedVariant::new(self.as_variant_fields_ref())
202 }
203
204 /// Can be used to decode the type from an encoded variant, like this:
205 /// `T::from_decoded_variant(decoder.decode()?)`.
206 ///
207 /// To use this pattern in a generic context, you will likely need to add a bound like
208 /// `T::OwnedVariant: VecDecode<X>`.
209 fn from_decoded_variant(variant: Self::OwnedVariant) -> Self
210 where
211 Self: core::marker::Sized,
212 {
213 Self::from_variant_fields(variant.into_fields())
214 }
215
216 fn into_enum(self) -> TEnum;
217}