From f52b1b559094bef58c030b02023654505a61a2b8 Mon Sep 17 00:00:00 2001 From: VegOwOtenks Date: Fri, 23 May 2025 17:26:17 +0200 Subject: [PATCH] feat: implemented deriving for structs of all types --- .gitignore | 3 +- Cargo.lock | 49 ++++++++- Cargo.toml | 1 + derive_generic/Cargo.lock | 47 +++++++++ derive_generic/Cargo.toml | 12 +++ derive_generic/src/lib.rs | 205 ++++++++++++++++++++++++++++++++++++++ src/example.rs | 32 ++---- src/generic.rs | 52 ++++++++-- src/instances.rs | 12 +-- 9 files changed, 377 insertions(+), 36 deletions(-) create mode 100644 derive_generic/Cargo.lock create mode 100644 derive_generic/Cargo.toml create mode 100644 derive_generic/src/lib.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..576f200 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target +**/target + diff --git a/Cargo.lock b/Cargo.lock index b69769b..146484d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index c1c4ae5..e4b6eee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] +derive_generic = { path = "derive_generic" } diff --git a/derive_generic/Cargo.lock b/derive_generic/Cargo.lock new file mode 100644 index 0000000..19a08a2 --- /dev/null +++ b/derive_generic/Cargo.lock @@ -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" diff --git a/derive_generic/Cargo.toml b/derive_generic/Cargo.toml new file mode 100644 index 0000000..3c3d21f --- /dev/null +++ b/derive_generic/Cargo.toml @@ -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" diff --git a/derive_generic/src/lib.rs b/derive_generic/src/lib.rs new file mode 100644 index 0000000..f6009ef --- /dev/null +++ b/derive_generic/src/lib.rs @@ -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) -> 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 { + 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!") }, + } +} + diff --git a/src/example.rs b/src/example.rs index 8b92e11..2cab1f0 100644 --- a/src/example.rs +++ b/src/example.rs @@ -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>; - 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 { } diff --git a/src/generic.rs b/src/generic.rs index c12c299..b5f2cbb 100644 --- a/src/generic.rs +++ b/src/generic.rs @@ -1,26 +1,63 @@ -pub struct Sum where Con: IsConType, Rhs: IsRepType { - pub left: Con, - pub right: Rhs, +use std::ops::Add; + +pub enum Sum where Con: IsConType, Rhs: IsRepType { + Left(Con), + Right(Rhs), } pub struct Product where Rhs: IsConType { - pub left: Field, + pub left: T, pub right: Rhs } +impl Add> for Product +where + Rest: Add + IsConType, + Field: Add, + RestRhs: IsConType, + Rest::Output: IsConType +{ + type Output = Product; + + fn add(self, other: Product) -> Self::Output { + Product { + left: self.left + other.left, + right: self.right + other.right + } + } +} + + pub struct Field { pub value: T } +impl> Add> for Field { + type Output=Field; + + fn add(self, rhs: Field) -> Self::Output { + Field { + value: self.value + rhs.value, + } + } +} + + // types used for generic representation pub trait IsRepType {} -impl IsRepType for Sum {} -impl IsRepType for Product {} + +impl IsRepType for Sum + where Lhs: IsConType, Rhs: IsRepType {} + +impl IsRepType for Product + where Rhs: IsConType {} + impl IsRepType for Field {} // types that represent constructors pub trait IsConType {} -impl IsConType for Product {} +impl IsConType for Product + where Rhs: IsConType {} impl IsConType for Field {} @@ -29,3 +66,4 @@ pub trait Generic { fn generalize(self) -> Self::Representation; fn specialize(rep: Self::Representation) -> Self; } + diff --git a/src/instances.rs b/src/instances.rs index f470fd1..3dd4519 100644 --- a/src/instances.rs +++ b/src/instances.rs @@ -1,19 +1,19 @@ -use crate::generic::{Field, Generic, Sum}; +use crate::generic::{Field, Generic, Product}; impl Generic for (A, B) { - type Representation = Sum, Field>; + type Representation = Product>; 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)