Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meta-type-system #35

Merged
merged 10 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,10 @@ tracing = { version = "0.1.40", default-features = false }
clap = { version = "4.5.4", features = ["derive"] }
env_logger = { version = "0.11.3" }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }


# proc macros
syn = { version = "2", features = ["full", "extra-traits"] }
quote = "1"
proc-macro2 = "1"
proc-macro-crate = "3"
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ clippy:
SKIP_WASM_BUILD= cargo clippy -- -D warnings
cd poc/guests; cargo clippy

test:
SKIP_WASM_BUILD= cargo test

chainspec:
cargo build -p poc-runtime --release
mkdir -p output
Expand Down
6 changes: 3 additions & 3 deletions xcq-extension/procedural/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ version.workspace = true
proc-macro = true

[dependencies]
syn = { version = "2", features = ["full", "extra-traits"] }
quote = "1"
proc-macro2 = "1"
quote = { workspace = true }
syn = { workspace = true }
proc-macro2 = { workspace = true }
twox-hash = "1.6.3"
11 changes: 10 additions & 1 deletion xcq-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ repository.workspace = true
version.workspace = true

[dependencies]
cfg-if = "1.0"
codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [
"derive",
] }
serde = { version = "1", default-features = false, optional = true, features = [
"derive",
] }
fortuples = "0.9"
xcq-types-derive = { path = "derive" }

[features]
default = ["std"]
std = []
std = ["codec/std"]
13 changes: 13 additions & 0 deletions xcq-types/derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "xcq-types-derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
quote = { workspace = true }
syn = { workspace = true }
proc-macro2 = { workspace = true }
proc-macro-crate = { workspace = true }
144 changes: 144 additions & 0 deletions xcq-types/derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput, Field, Fields};

#[proc_macro_derive(XcqTypeInfo)]
pub fn derive_xcq_type_info(input: TokenStream) -> TokenStream {
match generate(input.into()) {
Ok(output) => output.into(),
Err(err) => err.to_compile_error().into(),
}
}

fn generate(input: TokenStream2) -> syn::Result<TokenStream2> {
let type_info_impl: XcqTypeInfoImpl = XcqTypeInfoImpl::parse(input)?;
let type_info_impl_toks = type_info_impl.expand()?;
Ok(quote! {
// A rust pattern to ensure that the type info is implemented.
#[allow(non_upper_case_globals, unused_attributes, unused_imports)]
const _: () = {
#type_info_impl_toks
};
})
}

struct XcqTypeInfoImpl {
ast: DeriveInput,
}

impl XcqTypeInfoImpl {
fn parse(input: TokenStream2) -> syn::Result<Self> {
let ast = syn::parse2::<DeriveInput>(input)?;
// Assume no attributes
if !ast.attrs.is_empty() {
return Err(syn::Error::new_spanned(ast, "unexpected attributes"));
}
Ok(Self { ast })
}

fn expand(&self) -> syn::Result<TokenStream2> {
let xcq_types = import_xcq_types();
let ident = &self.ast.ident;

// Assume no generics
if self.ast.generics.type_params().next().is_some() {
return Err(syn::Error::new_spanned(
&self.ast.generics,
"generics are not supported",
));
}

let type_info = match &self.ast.data {
Data::Struct(ref s) => self.generate_struct_type(s),
Data::Enum(ref e) => self.generate_enum_type(e),
Data::Union(_) => {
return Err(syn::Error::new_spanned(&self.ast, "unions are not supported"));
}
};

// TODO: No token replacement supported yet
Ok(quote! {
impl #xcq_types::XcqTypeInfo for #ident {
type Identity = Self;
fn type_info() -> #xcq_types::XcqType {
#type_info
}
}
})
}

fn generate_struct_type(&self, data_struct: &DataStruct) -> TokenStream2 {
let xcq_types = import_xcq_types();
let ident = &self.ast.ident;
let fields = match data_struct.fields {
Fields::Named(ref fields) => self.generate_fields(&fields.named),
Fields::Unnamed(ref fields) => self.generate_fields(&fields.unnamed),
Fields::Unit => return quote! {},
};

quote! {
#xcq_types::StructType {
ident: stringify!(#ident).as_bytes().to_vec(),
fields: vec![#(#fields),*],
}.into()
}
}

fn generate_enum_type(&self, data_enum: &DataEnum) -> TokenStream2 {
let xcq_types = import_xcq_types();
let ident = &self.ast.ident;
let variants = data_enum.variants.iter().map(|variant| {
let ident = &variant.ident;
let fields = match variant.fields {
Fields::Named(ref fields) => self.generate_fields(&fields.named),
Fields::Unnamed(ref fields) => self.generate_fields(&fields.unnamed),
Fields::Unit => return quote! {},
};
quote! {
#xcq_types::Variant {
ident: stringify!(#ident).as_bytes().to_vec(),
fields: vec![#(#fields),*],
}
}
});
quote! {
#xcq_types::EnumType {
ident: stringify!(#ident).as_bytes().to_vec(),
variants: vec![#(#variants),*],
}.into()
}
}

fn generate_fields(&self, fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {
let xcq_types = import_xcq_types();
fields
.iter()
.map(|f| {
let ty = &f.ty;
let ident_toks = match &f.ident {
Some(ident) => quote! { stringify!(#ident).as_bytes().to_vec()},
None => quote! { vec![] },
};
quote! {
#xcq_types::Field {
ident: #ident_toks,
ty: <#ty as #xcq_types::XcqTypeInfo>::type_info(),
}
}
})
.collect()
}
}

fn import_xcq_types() -> TokenStream2 {
let found_crate = crate_name("xcq-types").expect("xcq-types not found in Cargo.toml");
match found_crate {
FoundCrate::Itself => quote! { crate },
FoundCrate::Name(name) => {
let name = syn::Ident::new(&name, proc_macro2::Span::call_site());
quote! { ::#name }
}
}
}
Loading