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

Add new cgp-field crate for deriving HasField field accessors #19

Merged
merged 12 commits into from
Jul 4, 2024
35 changes: 29 additions & 6 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ members = [
"crates/cgp-component",
"crates/cgp-component-macro",
"crates/cgp-component-macro-lib",
"crates/cgp-field",
"crates/cgp-field-macro",
"crates/cgp-field-macro-lib",
"crates/cgp-error",
"crates/cgp-error-eyre",
"crates/cgp-error-std",
Expand All @@ -32,6 +35,9 @@ cgp-sync = { path = "./crates/cgp-sync" }
cgp-component = { path = "./crates/cgp-component" }
cgp-component-macro = { path = "./crates/cgp-component-macro" }
cgp-component-macro-lib = { path = "./crates/cgp-component-macro-lib" }
cgp-field = { path = "./crates/cgp-field" }
cgp-field-macro = { path = "./crates/cgp-field-macro" }
cgp-field-macro-lib = { path = "./crates/cgp-field-macro-lib" }
cgp-error = { path = "./crates/cgp-error" }
cgp-run = { path = "./crates/cgp-run" }
cgp-inner = { path = "./crates/cgp-inner" }
7 changes: 1 addition & 6 deletions crates/cgp-component-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,4 @@ proc-macro = true

[dependencies]
cgp-component-macro-lib = { version = "0.1.0" }

syn = { version = "2.0.37", features = [ "full" ] }
quote = "1.0.33"
proc-macro2 = "1.0.67"
itertools = "0.11.0"
prettyplease = "0.2.20"
proc-macro2 = "1.0.67"
1 change: 1 addition & 0 deletions crates/cgp-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ all-features = true
cgp-async = { version = "0.1.0" }
cgp-component = { version = "0.1.0" }
cgp-error = { version = "0.1.0" }
cgp-field = { version = "0.1.0" }
cgp-run = { version = "0.1.0" }
cgp-inner = { version = "0.1.0" }
5 changes: 4 additions & 1 deletion crates/cgp-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
pub mod prelude;

pub use cgp_async::{async_trait, Async};
pub use {cgp_component as component, cgp_error as error, cgp_inner as inner, cgp_run as run};
pub use {
cgp_component as component, cgp_error as error, cgp_field as field, cgp_inner as inner,
cgp_run as run,
};
1 change: 1 addition & 0 deletions crates/cgp-core/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub use cgp_component::{
define_components, delegate_components, derive_component, DelegateComponent, HasComponents,
};
pub use cgp_error::{CanRaiseError, HasErrorType};
pub use cgp_field::{symbol, Char, HasField};
23 changes: 23 additions & 0 deletions crates/cgp-field-macro-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "cgp-field-macro-lib"
version = "0.1.0"
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
rust-version = { workspace = true }
readme = "README.md"
keywords = ["context-generic programming"]
description = """
Context-generic programming core macros
"""

[package.metadata.docs.rs]
all-features = true

[dependencies]
syn = { version = "2.0.37", features = [ "full" ] }
quote = "1.0.33"
proc-macro2 = "1.0.67"
itertools = "0.11.0"
prettyplease = "0.2.20"
58 changes: 58 additions & 0 deletions crates/cgp-field-macro-lib/src/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{parse_quote, Fields, ItemImpl, ItemStruct};

use crate::symbol::symbol_from_string;

pub fn derive_has_field_impls(item_struct: &ItemStruct) -> Vec<ItemImpl> {
let struct_ident = &item_struct.ident;

let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();

let mut item_impls = Vec::new();

if let Fields::Named(fields) = &item_struct.fields {
for field in fields.named.iter() {
let field_ident = field.ident.as_ref().unwrap();

let field_symbol = symbol_from_string(&field_ident.to_string());

let field_type = &field.ty;

let item_impl: ItemImpl = parse_quote! {
impl #impl_generics HasField< #field_symbol >
for #struct_ident #ty_generics
#where_clause
{
type Field = #field_type;

fn get_field(
&self,
key: ::core::marker::PhantomData< #field_symbol >,
) -> &Self::Field
{
&self. #field_ident
}
}
};

item_impls.push(item_impl);
}
}

item_impls
}

pub fn derive_fields(input: TokenStream) -> TokenStream {
let item_struct: ItemStruct = syn::parse2(input).unwrap();

let item_impls = derive_has_field_impls(&item_struct);

let mut output = TokenStream::new();

for item_impl in item_impls {
output.extend(item_impl.to_token_stream());
}

output
}
8 changes: 8 additions & 0 deletions crates/cgp-field-macro-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod field;
pub mod symbol;

#[cfg(test)]
mod tests;

pub use field::derive_fields;
pub use symbol::make_symbol;
23 changes: 23 additions & 0 deletions crates/cgp-field-macro-lib/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{parse_quote, LitStr, Type};

pub fn symbol_from_string(value: &str) -> Type {
let char_types = <Punctuated<Type, Comma>>::from_iter(
value
.chars()
.map(|c: char| -> Type { parse_quote!( Char< #c > ) }),
);

parse_quote!( ( #char_types ) )
}

pub fn make_symbol(input: TokenStream) -> TokenStream {
let literal: LitStr = syn::parse2(input).unwrap();

let symbol = symbol_from_string(&literal.value());

symbol.to_token_stream()
}
87 changes: 87 additions & 0 deletions crates/cgp-field-macro-lib/src/tests/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use quote::quote;

use crate::field::derive_fields;
use crate::tests::helper::equal::equal_token_stream;

#[test]
fn test_basic_derive_fields() {
let derived = derive_fields(quote! {
pub struct Foo {
pub bar: Bar,
pub baz: Baz,
}
});

let expected = quote! {
impl HasField<(Char<'b'>, Char<'a'>, Char<'r'>)> for Foo {
type Field = Bar;

fn get_field(
&self,
key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'r'>)>,
) -> &Self::Field {
&self.bar
}
}

impl HasField<(Char<'b'>, Char<'a'>, Char<'z'>)> for Foo {
type Field = Baz;

fn get_field(
&self,
key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'z'>)>,
) -> &Self::Field {
&self.baz
}
}
};

assert!(equal_token_stream(&derived, &expected));
}

#[test]
fn test_generic_derive_fields() {
let derived = derive_fields(quote! {
pub struct Foo<FooParamA, FooParamB: Clone>
where
FooParamA: Eq,
{
pub bar: Bar<FooParamA>,
pub baz: Baz<String>,
}
});

let expected = quote! {
impl<FooParamA, FooParamB: Clone> HasField<(Char<'b'>, Char<'a'>, Char<'r'>)>
for Foo<FooParamA, FooParamB>
where
FooParamA: Eq,
{
type Field = Bar<FooParamA>;

fn get_field(
&self,
key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'r'>)>,
) -> &Self::Field {
&self.bar
}
}

impl<FooParamA, FooParamB: Clone> HasField<(Char<'b'>, Char<'a'>, Char<'z'>)>
for Foo<FooParamA, FooParamB>
where
FooParamA: Eq,
{
type Field = Baz<String>;

fn get_field(
&self,
key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'z'>)>,
) -> &Self::Field {
&self.baz
}
}
};

assert!(equal_token_stream(&derived, &expected));
}
7 changes: 7 additions & 0 deletions crates/cgp-field-macro-lib/src/tests/helper/equal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use proc_macro2::TokenStream;

use crate::tests::helper::format::format_token_stream;

pub fn equal_token_stream(left: &TokenStream, right: &TokenStream) -> bool {
format_token_stream(left) == format_token_stream(right)
}
7 changes: 7 additions & 0 deletions crates/cgp-field-macro-lib/src/tests/helper/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use prettyplease::unparse;
use proc_macro2::TokenStream;
use syn::parse_file;

pub fn format_token_stream(stream: &TokenStream) -> String {
unparse(&parse_file(&stream.to_string()).unwrap())
}
2 changes: 2 additions & 0 deletions crates/cgp-field-macro-lib/src/tests/helper/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod equal;
pub mod format;
3 changes: 3 additions & 0 deletions crates/cgp-field-macro-lib/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod field;
pub mod helper;
pub mod symbol;
31 changes: 31 additions & 0 deletions crates/cgp-field-macro-lib/src/tests/symbol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use quote::quote;

use crate::symbol::make_symbol;
use crate::tests::helper::equal::equal_token_stream;

#[test]
fn test_symbol_macro() {
let symbol = make_symbol(quote!("hello_world"));

let derived = quote! {
type Symbol = #symbol;
};

let expected = quote! {
type Symbol = (
Char<'h'>,
Char<'e'>,
Char<'l'>,
Char<'l'>,
Char<'o'>,
Char<'_'>,
Char<'w'>,
Char<'o'>,
Char<'r'>,
Char<'l'>,
Char<'d'>,
);
};

assert!(equal_token_stream(&derived, &expected));
}
Loading
Loading