feat: implemented deriving for structs of all types
This commit is contained in:
parent
5af0d4eb2d
commit
f52b1b5590
9 changed files with 377 additions and 36 deletions
47
derive_generic/Cargo.lock
generated
Normal file
47
derive_generic/Cargo.lock
generated
Normal file
|
@ -0,0 +1,47 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "derive_generic"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
12
derive_generic/Cargo.toml
Normal file
12
derive_generic/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "derive_generic"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
syn = "2.0"
|
||||
quote = "1"
|
205
derive_generic/src/lib.rs
Normal file
205
derive_generic/src/lib.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{Data, Fields, Index, Type};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn derive_generic_raw(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let data_name = ast.ident;
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let repr = repr_from_data(&ast.data);
|
||||
|
||||
let generalization = general_from_data(&ast.data);
|
||||
|
||||
let specialization = special_from_data(&ast.data);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics generic::Generic for #data_name #ty_generics #where_clause {
|
||||
type Representation = #repr;
|
||||
|
||||
fn generalize(self) -> Self::Representation {
|
||||
#generalization
|
||||
}
|
||||
fn specialize(rep: Self::Representation) -> Self {
|
||||
#specialization
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
|
||||
fn special_from_data(data: &Data) -> proc_macro2::TokenStream {
|
||||
match data {
|
||||
Data::Struct(ref r#struct) => {
|
||||
match &r#struct.fields {
|
||||
Fields::Named(ref fields) => {
|
||||
let bind_names: Vec<_> = fields.named.iter()
|
||||
.map(|field| field.ident.clone().expect("Named fields must have an identifier."))
|
||||
.collect();
|
||||
|
||||
let let_bind = generate_let_binds(bind_names.clone());
|
||||
|
||||
quote! {
|
||||
let #let_bind = rep;
|
||||
Self {
|
||||
#(#bind_names ,)*
|
||||
}
|
||||
}
|
||||
},
|
||||
Fields::Unnamed(ref fields) => {
|
||||
let bind_names: Vec<_> = fields.unnamed.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| Ident::new(&format!("field_{i}"), field.span()))
|
||||
.collect();
|
||||
|
||||
let let_bind = generate_let_binds(bind_names.clone());
|
||||
|
||||
quote! {
|
||||
let #let_bind = rep;
|
||||
Self( #(#bind_names ,)* )
|
||||
}
|
||||
},
|
||||
Fields::Unit => quote! {
|
||||
Self
|
||||
},
|
||||
}
|
||||
},
|
||||
Data::Enum(data_enum) => todo!(),
|
||||
Data::Union(data_union) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_let_binds(mut bind_names: Vec<Ident>) -> proc_macro2::TokenStream {
|
||||
match bind_names.len() {
|
||||
0 => quote! {
|
||||
_
|
||||
},
|
||||
_ => {
|
||||
let last = bind_names.pop().unwrap();
|
||||
let last = quote! {
|
||||
generic::Field { value: #last }
|
||||
};
|
||||
|
||||
bind_names.iter().fold(last, |acc, i|
|
||||
quote! {
|
||||
generic::Product {
|
||||
left: #i,
|
||||
right: #acc
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn general_from_data(data: &Data) -> proc_macro2::TokenStream {
|
||||
match data {
|
||||
Data::Struct(ref r#struct) => {
|
||||
match &r#struct.fields {
|
||||
Fields::Named(ref fields) => {
|
||||
let names: Vec<_> = fields.named.iter()
|
||||
.map(|field| field.ident.as_ref()
|
||||
.expect("Named Fields must have an identifier")
|
||||
.to_token_stream()
|
||||
)
|
||||
.collect();
|
||||
|
||||
match names.len() {
|
||||
0 => quote! {
|
||||
let _ = self;
|
||||
generic::Field { value: () }
|
||||
},
|
||||
_ => general_from_names(names)
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
Fields::Unnamed(ref fields) => {
|
||||
let indices: Vec<_> = fields.unnamed.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| Index::from(i).to_token_stream())
|
||||
.collect();
|
||||
|
||||
match fields.unnamed.len() {
|
||||
0 => quote! {
|
||||
let _ = self;
|
||||
generic::Field { value: () }
|
||||
},
|
||||
_ => general_from_names(indices)
|
||||
}
|
||||
},
|
||||
Fields::Unit => quote! { generic::Field { value: () } },
|
||||
}
|
||||
},
|
||||
Data::Enum(data_enum) => todo!(),
|
||||
Data::Union(data_union) => quote!{},
|
||||
}
|
||||
}
|
||||
|
||||
fn general_from_names(mut names: Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
|
||||
let last = names.pop().expect("Impossible");
|
||||
let expr = quote! { generic::Field { value: self.#last } };
|
||||
|
||||
names.iter().fold(expr, |e, i|
|
||||
quote! {
|
||||
generic::Product {
|
||||
left: self.#i,
|
||||
right: #e
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Generic)]
|
||||
pub fn derive_generic(input: TokenStream) -> TokenStream {
|
||||
derive_generic_raw(input)
|
||||
}
|
||||
|
||||
fn product_from_types(mut types: Vec<&Type>) -> proc_macro2::TokenStream {
|
||||
let last = types.pop().unwrap();
|
||||
let last = quote! { generic::Field<#last> };
|
||||
|
||||
types.iter().fold(last, |e, i|
|
||||
quote! {
|
||||
generic::Product<#i, #e>
|
||||
}.into_token_stream()
|
||||
)
|
||||
}
|
||||
|
||||
fn repr_from_data(data: &Data) -> proc_macro2::TokenStream {
|
||||
match data {
|
||||
Data::Struct(ref r#struct) => {
|
||||
match &r#struct.fields {
|
||||
Fields::Named(ref fields) => {
|
||||
let v: Vec<_> = fields.named
|
||||
.iter()
|
||||
.map(|f| &f.ty)
|
||||
.collect();
|
||||
|
||||
match v.len() {
|
||||
0 => quote! { generic::Field<()> },
|
||||
_ => product_from_types(v)
|
||||
}
|
||||
},
|
||||
Fields::Unnamed(ref fields) => {
|
||||
let v: Vec<_> = fields.unnamed
|
||||
.iter()
|
||||
.map(|f| &f.ty)
|
||||
.collect();
|
||||
|
||||
match v.len() {
|
||||
0 => quote! { generic::Field<()> },
|
||||
_ => product_from_types(v)
|
||||
}
|
||||
},
|
||||
Fields::Unit => quote! { generic::Field<()> },
|
||||
}
|
||||
},
|
||||
Data::Enum(data_enum) => todo!(),
|
||||
Data::Union(_) => quote! { compile_error!("Cannot derive Generics for union types!") },
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue