Skip to content

Commit cc3b682

Browse files
feat(xcq-types): basic support (#35)
- No proc macro attributes support yet - No generics support yet - No type registry to compress type info and represent self-referential types
1 parent b29b6ec commit cc3b682

File tree

16 files changed

+687
-14
lines changed

16 files changed

+687
-14
lines changed

Cargo.lock

Lines changed: 28 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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,10 @@ tracing = { version = "0.1.40", default-features = false }
6565
clap = { version = "4.5.4", features = ["derive"] }
6666
env_logger = { version = "0.11.3" }
6767
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
68+
69+
70+
# proc macros
71+
syn = { version = "2", features = ["full", "extra-traits"] }
72+
quote = "1"
73+
proc-macro2 = "1"
74+
proc-macro-crate = "3"

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ clippy:
4242
SKIP_WASM_BUILD= cargo clippy -- -D warnings
4343
cd poc/guests; cargo clippy
4444

45+
test:
46+
SKIP_WASM_BUILD= cargo test
47+
4548
chainspec:
4649
cargo build -p poc-runtime --release
4750
mkdir -p output

xcq-extension/procedural/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ version.workspace = true
1010
proc-macro = true
1111

1212
[dependencies]
13-
syn = { version = "2", features = ["full", "extra-traits"] }
14-
quote = "1"
15-
proc-macro2 = "1"
13+
quote = { workspace = true }
14+
syn = { workspace = true }
15+
proc-macro2 = { workspace = true }
1616
twox-hash = "1.6.3"

xcq-types/Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ repository.workspace = true
88
version.workspace = true
99

1010
[dependencies]
11+
cfg-if = "1.0"
12+
codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [
13+
"derive",
14+
] }
15+
serde = { version = "1", default-features = false, optional = true, features = [
16+
"derive",
17+
] }
18+
fortuples = "0.9"
19+
xcq-types-derive = { path = "derive" }
1120

1221
[features]
1322
default = ["std"]
14-
std = []
23+
std = ["codec/std"]

xcq-types/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 = "xcq-types-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
quote = { workspace = true }
11+
syn = { workspace = true }
12+
proc-macro2 = { workspace = true }
13+
proc-macro-crate = { workspace = true }

xcq-types/derive/src/lib.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::TokenStream as TokenStream2;
3+
use proc_macro_crate::{crate_name, FoundCrate};
4+
use quote::quote;
5+
use syn::{punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput, Field, Fields};
6+
7+
#[proc_macro_derive(XcqTypeInfo)]
8+
pub fn derive_xcq_type_info(input: TokenStream) -> TokenStream {
9+
match generate(input.into()) {
10+
Ok(output) => output.into(),
11+
Err(err) => err.to_compile_error().into(),
12+
}
13+
}
14+
15+
fn generate(input: TokenStream2) -> syn::Result<TokenStream2> {
16+
let type_info_impl: XcqTypeInfoImpl = XcqTypeInfoImpl::parse(input)?;
17+
let type_info_impl_toks = type_info_impl.expand()?;
18+
Ok(quote! {
19+
// A rust pattern to ensure that the type info is implemented.
20+
#[allow(non_upper_case_globals, unused_attributes, unused_imports)]
21+
const _: () = {
22+
#type_info_impl_toks
23+
};
24+
})
25+
}
26+
27+
struct XcqTypeInfoImpl {
28+
ast: DeriveInput,
29+
}
30+
31+
impl XcqTypeInfoImpl {
32+
fn parse(input: TokenStream2) -> syn::Result<Self> {
33+
let ast = syn::parse2::<DeriveInput>(input)?;
34+
// Assume no attributes
35+
if !ast.attrs.is_empty() {
36+
return Err(syn::Error::new_spanned(ast, "unexpected attributes"));
37+
}
38+
Ok(Self { ast })
39+
}
40+
41+
fn expand(&self) -> syn::Result<TokenStream2> {
42+
let xcq_types = import_xcq_types();
43+
let ident = &self.ast.ident;
44+
45+
// Assume no generics
46+
if self.ast.generics.type_params().next().is_some() {
47+
return Err(syn::Error::new_spanned(
48+
&self.ast.generics,
49+
"generics are not supported",
50+
));
51+
}
52+
53+
let type_info = match &self.ast.data {
54+
Data::Struct(ref s) => self.generate_struct_type(s),
55+
Data::Enum(ref e) => self.generate_enum_type(e),
56+
Data::Union(_) => {
57+
return Err(syn::Error::new_spanned(&self.ast, "unions are not supported"));
58+
}
59+
};
60+
61+
// TODO: No token replacement supported yet
62+
Ok(quote! {
63+
impl #xcq_types::XcqTypeInfo for #ident {
64+
type Identity = Self;
65+
fn type_info() -> #xcq_types::XcqType {
66+
#type_info
67+
}
68+
}
69+
})
70+
}
71+
72+
fn generate_struct_type(&self, data_struct: &DataStruct) -> TokenStream2 {
73+
let xcq_types = import_xcq_types();
74+
let ident = &self.ast.ident;
75+
let fields = match data_struct.fields {
76+
Fields::Named(ref fields) => self.generate_fields(&fields.named),
77+
Fields::Unnamed(ref fields) => self.generate_fields(&fields.unnamed),
78+
Fields::Unit => return quote! {},
79+
};
80+
81+
quote! {
82+
#xcq_types::StructType {
83+
ident: stringify!(#ident).as_bytes().to_vec(),
84+
fields: vec![#(#fields),*],
85+
}.into()
86+
}
87+
}
88+
89+
fn generate_enum_type(&self, data_enum: &DataEnum) -> TokenStream2 {
90+
let xcq_types = import_xcq_types();
91+
let ident = &self.ast.ident;
92+
let variants = data_enum.variants.iter().map(|variant| {
93+
let ident = &variant.ident;
94+
let fields = match variant.fields {
95+
Fields::Named(ref fields) => self.generate_fields(&fields.named),
96+
Fields::Unnamed(ref fields) => self.generate_fields(&fields.unnamed),
97+
Fields::Unit => return quote! {},
98+
};
99+
quote! {
100+
#xcq_types::Variant {
101+
ident: stringify!(#ident).as_bytes().to_vec(),
102+
fields: vec![#(#fields),*],
103+
}
104+
}
105+
});
106+
quote! {
107+
#xcq_types::EnumType {
108+
ident: stringify!(#ident).as_bytes().to_vec(),
109+
variants: vec![#(#variants),*],
110+
}.into()
111+
}
112+
}
113+
114+
fn generate_fields(&self, fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {
115+
let xcq_types = import_xcq_types();
116+
fields
117+
.iter()
118+
.map(|f| {
119+
let ty = &f.ty;
120+
let ident_toks = match &f.ident {
121+
Some(ident) => quote! { stringify!(#ident).as_bytes().to_vec()},
122+
None => quote! { vec![] },
123+
};
124+
quote! {
125+
#xcq_types::Field {
126+
ident: #ident_toks,
127+
ty: <#ty as #xcq_types::XcqTypeInfo>::type_info(),
128+
}
129+
}
130+
})
131+
.collect()
132+
}
133+
}
134+
135+
fn import_xcq_types() -> TokenStream2 {
136+
let found_crate = crate_name("xcq-types").expect("xcq-types not found in Cargo.toml");
137+
match found_crate {
138+
FoundCrate::Itself => quote! { crate },
139+
FoundCrate::Name(name) => {
140+
let name = syn::Ident::new(&name, proc_macro2::Span::call_site());
141+
quote! { ::#name }
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)