feat: implemented deriving for structs of all types

This commit is contained in:
vegowotenks 2025-05-23 17:26:17 +02:00
parent 5af0d4eb2d
commit f52b1b5590
9 changed files with 377 additions and 36 deletions

3
.gitignore vendored
View file

@ -1 +1,2 @@
/target
**/target

49
Cargo.lock generated
View file

@ -1,7 +1,54 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "derive_generic"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "generic"
version = "0.1.0"
dependencies = [
"derive_generic",
]
[[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"

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
derive_generic = { path = "derive_generic" }

47
derive_generic/Cargo.lock generated Normal file
View 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
View 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
View 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!") },
}
}

View file

@ -1,28 +1,18 @@
use crate::generic::{Field, Sum, Generic};
use crate::generic;
use derive_generic::Generic;
#[derive(Generic)]
struct IntPairTuple (i16, i32);
struct IntPair {
#[derive(Generic)]
struct IntPairStruct {
a: i32,
b: i32
b: i16,
}
impl Generic for IntPair {
type Representation = Sum<Field<i32>, Field<i32>>;
fn generalize(self) -> Self::Representation {
Sum {
left: Field { value: self.a },
right: Field { value: self.b }
}
}
fn specialize(rep: Self::Representation) -> Self {
let Sum {
left: Field { value: a },
right: Field { value: b },
} = rep;
IntPair { a, b }
}
}
#[derive(Generic)]
struct UnitStruct;
fn convert(x: IntPair) -> (i32, i32) {
Generic::specialize(x.generalize())
#[derive(Generic)]
struct EmptyStruct {
}

View file

@ -1,26 +1,63 @@
pub struct Sum<Con, Rhs> where Con: IsConType, Rhs: IsRepType {
pub left: Con,
pub right: Rhs,
use std::ops::Add;
pub enum Sum<Con, Rhs> where Con: IsConType, Rhs: IsRepType {
Left(Con),
Right(Rhs),
}
pub struct Product<T, Rhs> where Rhs: IsConType {
pub left: Field<T>,
pub left: T,
pub right: Rhs
}
impl<Field, Rest, RestRhs, FieldRhs> Add<Product<FieldRhs, RestRhs>> for Product<Field, Rest>
where
Rest: Add<RestRhs> + IsConType,
Field: Add<FieldRhs>,
RestRhs: IsConType,
Rest::Output: IsConType
{
type Output = Product<Field::Output, Rest::Output>;
fn add(self, other: Product<FieldRhs, RestRhs>) -> Self::Output {
Product {
left: self.left + other.left,
right: self.right + other.right
}
}
}
pub struct Field<T> {
pub value: T
}
impl<R, T: Add<R>> Add<Field<R>> for Field<T> {
type Output=Field<T::Output>;
fn add(self, rhs: Field<R>) -> Self::Output {
Field {
value: self.value + rhs.value,
}
}
}
// types used for generic representation
pub trait IsRepType {}
impl<Lhs: IsConType, Rhs: IsRepType> IsRepType for Sum<Lhs, Rhs> {}
impl<T: IsRepType, Rhs: IsConType> IsRepType for Product<T, Rhs> {}
impl<Lhs, Rhs> IsRepType for Sum<Lhs, Rhs>
where Lhs: IsConType, Rhs: IsRepType {}
impl<T, Rhs> IsRepType for Product<T, Rhs>
where Rhs: IsConType {}
impl<T> IsRepType for Field<T> {}
// types that represent constructors
pub trait IsConType {}
impl<T, Rhs: IsConType> IsConType for Product<T, Rhs> {}
impl<T, Rhs> IsConType for Product<T, Rhs>
where Rhs: IsConType {}
impl<T> IsConType for Field<T> {}
@ -29,3 +66,4 @@ pub trait Generic {
fn generalize(self) -> Self::Representation;
fn specialize(rep: Self::Representation) -> Self;
}

View file

@ -1,19 +1,19 @@
use crate::generic::{Field, Generic, Sum};
use crate::generic::{Field, Generic, Product};
impl<A, B> Generic for (A, B) {
type Representation = Sum<Field<A>, Field<B>>;
type Representation = Product<A, Field<B>>;
fn generalize(self) -> Self::Representation {
let (l, r) = self;
Sum {
left: Field { value: l },
Product {
left: l,
right: Field { value: r }
}
}
fn specialize(rep: Self::Representation) -> Self {
let Sum {
left: Field { value: a },
let Product {
left: a,
right: Field { value: b },
} = rep;
(a, b)