Skip to content

Commit 188d3d5

Browse files
neotheprogramistpiniomglihm
authored
Cairo Serde Derive (#57)
* basic impl * clippy * macros: changed some paths to absolute * macros: made the serialized size to be always dynamic * Implemented serde_derive for enums (#60) * macros: Implemented serde_derive for enums * clippy * added derive tests * fix: typo --------- Co-authored-by: Szymon Wojtulewicz <[email protected]> Co-authored-by: Szymon Wojtulewicz <[email protected]> Co-authored-by: glihm <[email protected]>
1 parent d63afd9 commit 188d3d5

File tree

9 files changed

+568
-0
lines changed

9 files changed

+568
-0
lines changed

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2021"
66
[workspace]
77
members = [
88
"crates/cairo-serde",
9+
"crates/cairo-serde-derive",
910
"crates/parser",
1011
"crates/rs",
1112
"crates/rs-macro",
@@ -14,6 +15,7 @@ members = [
1415
[workspace.dependencies]
1516
# workspace crates
1617
cainome-cairo-serde = { path = "crates/cairo-serde" }
18+
cainome-cairo-serde-derive = { path = "crates/cairo-serde-derive" }
1719
cainome-parser = { path = "crates/parser" }
1820
cainome-rs = { path = "crates/rs" }
1921

@@ -34,6 +36,7 @@ starknet-types-core = "0.1.6"
3436
camino.workspace = true
3537
cainome-parser.workspace = true
3638
cainome-cairo-serde.workspace = true
39+
cainome-cairo-serde-derive.workspace = true
3740
cainome-rs.workspace = true
3841
cainome-rs-macro = { path = "crates/rs-macro", optional = true }
3942

crates/cairo-serde-derive/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "cainome-cairo-serde-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
proc-macro2 = "1.0.86"
11+
quote = "1.0.37"
12+
syn = "2.0.77"
13+
unzip-n = "0.1.2"
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use proc_macro2::{Span, TokenStream};
2+
use quote::quote;
3+
use syn::{DataEnum, Ident, Type, Variant};
4+
use unzip_n::unzip_n;
5+
6+
pub fn derive_enum(ident: Ident, data: DataEnum) -> TokenStream {
7+
let matches = &data
8+
.variants
9+
.iter()
10+
.map(|v| derive_enum_matches(&ident, v))
11+
.collect::<Vec<_>>();
12+
13+
unzip_n!(3);
14+
let (serialized_size, serialize, deserialize) = data
15+
.variants
16+
.iter()
17+
.enumerate()
18+
.map(|(i, v)| derive_enum_variant(&ident, i, v))
19+
.collect::<Vec<_>>()
20+
.into_iter()
21+
.unzip_n_vec();
22+
23+
let cairo_serialized_size = quote! {
24+
fn cairo_serialized_size(rust: &Self::RustType) -> usize {
25+
match rust {
26+
#(
27+
#matches => #serialized_size,
28+
)*
29+
}
30+
}
31+
};
32+
33+
let cairo_serialize = quote! {
34+
fn cairo_serialize(rust: &Self::RustType) -> Vec<::starknet::core::types::Felt> {
35+
match rust {
36+
#(
37+
#matches => #serialize,
38+
)*
39+
}
40+
}
41+
};
42+
43+
let deserialize_matches = data
44+
.variants
45+
.iter()
46+
.enumerate()
47+
.map(|(i, _)| syn::LitInt::new(&i.to_string(), Span::call_site()))
48+
.collect::<Vec<_>>();
49+
let cairo_deserialize = quote! {
50+
fn cairo_deserialize(felt: &[Felt], offset: usize) -> Result<Self::RustType, ::cainome_cairo_serde::Error> {
51+
let offset = offset + 1;
52+
#(
53+
if felt[offset - 1] == ::starknet::core::types::Felt::from(#deserialize_matches) {
54+
return Ok(#deserialize);
55+
}
56+
)*
57+
Err(::cainome_cairo_serde::Error::Deserialize("Invalid variant Id".to_string()))
58+
}
59+
};
60+
61+
// There is no easy way to check for the members being staticaly sized at compile time.
62+
// Any of the members of the composite type can have a dynamic size.
63+
// This is why we return `None` for the `SERIALIZED_SIZE` constant.
64+
let output = quote! {
65+
impl ::cainome_cairo_serde::CairoSerde for #ident {
66+
type RustType = Self;
67+
68+
const SERIALIZED_SIZE: Option<usize> = None;
69+
70+
#cairo_serialized_size
71+
#cairo_serialize
72+
#cairo_deserialize
73+
}
74+
};
75+
output
76+
}
77+
78+
fn derive_enum_matches(ident: &Ident, variant: &Variant) -> TokenStream {
79+
let variant_ident = variant.ident.clone();
80+
let (fields, _) = fields_idents_and_types(&variant.fields);
81+
match &variant.fields {
82+
syn::Fields::Named(_) => quote! {
83+
#ident::#variant_ident { #(#fields,)* }
84+
},
85+
syn::Fields::Unnamed(_) => quote! {
86+
#ident::#variant_ident(#(#fields,)*)
87+
},
88+
syn::Fields::Unit => quote! {
89+
#ident::#variant_ident
90+
},
91+
}
92+
}
93+
94+
fn derive_enum_variant(
95+
ident: &Ident,
96+
index: usize,
97+
variant: &Variant,
98+
) -> (TokenStream, TokenStream, TokenStream) {
99+
let (fields, types) = fields_idents_and_types(&variant.fields);
100+
(
101+
derive_variant_cairo_serialized_size(&fields, &types),
102+
derive_variant_cairo_serialize(index, &fields, &types),
103+
derive_variant_cairo_deserialize(ident, variant, &fields, &types),
104+
)
105+
}
106+
107+
fn derive_variant_cairo_serialized_size(fields: &[TokenStream], types: &[Type]) -> TokenStream {
108+
quote! {
109+
{
110+
1
111+
#(
112+
+ <#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialized_size(&#fields)
113+
)*
114+
}
115+
}
116+
}
117+
118+
fn derive_variant_cairo_serialize(
119+
index: usize,
120+
fields: &[TokenStream],
121+
types: &[Type],
122+
) -> TokenStream {
123+
let index = syn::LitInt::new(&index.to_string(), Span::call_site());
124+
quote! {
125+
{
126+
let mut result = Vec::new();
127+
result.push(::starknet::core::types::Felt::from(#index));
128+
#(
129+
result.extend(<#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialize(&#fields));
130+
)*
131+
result
132+
}
133+
}
134+
}
135+
136+
fn derive_variant_cairo_deserialize(
137+
ident: &Ident,
138+
variant: &Variant,
139+
fields: &[TokenStream],
140+
types: &[Type],
141+
) -> TokenStream {
142+
let variant_ident = &variant.ident;
143+
144+
match &variant.fields {
145+
syn::Fields::Named(_) => quote! {
146+
{
147+
let mut current_offset = offset;
148+
#ident::#variant_ident {
149+
#(
150+
#fields: {
151+
let value = <#types as ::cainome_cairo_serde::CairoSerde>::cairo_deserialize(felt, current_offset)?;
152+
current_offset += <#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialized_size(&value);
153+
value
154+
},
155+
)*
156+
}
157+
}
158+
},
159+
syn::Fields::Unnamed(_) => quote! {
160+
{
161+
let mut current_offset = offset;
162+
#ident::#variant_ident (
163+
#(
164+
{
165+
let value = <#types as ::cainome_cairo_serde::CairoSerde>::cairo_deserialize(felt, current_offset)?;
166+
current_offset += <#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialized_size(&value);
167+
value
168+
},
169+
)*
170+
)
171+
}
172+
},
173+
syn::Fields::Unit => quote! { #ident::#variant_ident},
174+
}
175+
}
176+
177+
fn fields_idents_and_types(fields: &syn::Fields) -> (Vec<TokenStream>, Vec<Type>) {
178+
fields
179+
.iter()
180+
.cloned()
181+
.enumerate()
182+
.map(field_ident_and_type)
183+
.unzip()
184+
}
185+
186+
fn field_ident_and_type((i, field): (usize, syn::Field)) -> (TokenStream, Type) {
187+
(
188+
field
189+
.ident
190+
.clone()
191+
.map(|ident| quote! { #ident })
192+
.unwrap_or({
193+
let i = syn::Ident::new(&format!("__self_{}", i), Span::call_site());
194+
quote! { #i }
195+
}),
196+
field.ty,
197+
)
198+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use proc_macro2::TokenStream;
2+
use quote::quote;
3+
use syn::{DataStruct, Ident, Type};
4+
5+
pub fn derive_struct(ident: Ident, data: DataStruct) -> TokenStream {
6+
let (fields, types) = fields_accessors_and_types(&data.fields);
7+
8+
let cairo_serialized_size = quote! {
9+
fn cairo_serialized_size(rust: &Self::RustType) -> usize {
10+
0
11+
#(
12+
+ <#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialized_size(&rust.#fields)
13+
)*
14+
}
15+
};
16+
17+
let cairo_serialize = quote! {
18+
fn cairo_serialize(rust: &Self::RustType) -> Vec<::starknet::core::types::Felt> {
19+
let mut result = Vec::new();
20+
#(
21+
result.extend(<#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialize(&rust.#fields));
22+
)*
23+
result
24+
}
25+
};
26+
27+
let cairo_deserialize = quote! {
28+
fn cairo_deserialize(felt: &[Felt], offset: usize) -> Result<Self::RustType, ::cainome_cairo_serde::Error> {
29+
let mut current_offset = offset;
30+
Ok(Self {
31+
#(
32+
#fields: {
33+
let value = <#types as ::cainome_cairo_serde::CairoSerde>::cairo_deserialize(felt, current_offset)?;
34+
current_offset += <#types as ::cainome_cairo_serde::CairoSerde>::cairo_serialized_size(&value);
35+
value
36+
},
37+
)*
38+
})
39+
}
40+
};
41+
42+
// There is no easy way to check for the members being staticaly sized at compile time.
43+
// Any of the members of the composite type can have a dynamic size.
44+
// This is why we return `None` for the `SERIALIZED_SIZE` constant.
45+
let output = quote! {
46+
impl ::cainome_cairo_serde::CairoSerde for #ident {
47+
type RustType = Self;
48+
49+
const SERIALIZED_SIZE: Option<usize> = None;
50+
51+
#cairo_serialized_size
52+
#cairo_serialize
53+
#cairo_deserialize
54+
}
55+
};
56+
output
57+
}
58+
59+
fn fields_accessors_and_types(fields: &syn::Fields) -> (Vec<TokenStream>, Vec<Type>) {
60+
fields
61+
.iter()
62+
.cloned()
63+
.enumerate()
64+
.map(field_accessor_and_type)
65+
.unzip()
66+
}
67+
68+
fn field_accessor_and_type((i, field): (usize, syn::Field)) -> (TokenStream, Type) {
69+
(
70+
field
71+
.ident
72+
.clone()
73+
.map(|ident| quote! { #ident })
74+
.unwrap_or({
75+
let i = syn::Index::from(i);
76+
quote! { #i }
77+
}),
78+
field.ty,
79+
)
80+
}

crates/cairo-serde-derive/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use proc_macro::{self};
2+
use syn::{parse_macro_input, Data, DeriveInput};
3+
4+
mod derive_enum;
5+
mod derive_struct;
6+
7+
#[proc_macro_derive(CairoSerde)]
8+
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9+
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
10+
11+
let output = match data {
12+
Data::Struct(data) => derive_struct::derive_struct(ident, data),
13+
Data::Enum(data) => derive_enum::derive_enum(ident, data),
14+
Data::Union(_) => panic!("Unions are not supported for the cairo_serde_derive!"),
15+
};
16+
17+
output.into()
18+
}

0 commit comments

Comments
 (0)