Skip to content

Commit

Permalink
Implement delegate_components! as proc macro, and introduce `define…
Browse files Browse the repository at this point in the history
…_components!` macro (#17)

* Compare the derived token stream in test

* Rename helper module to derive_component module

* Draft AST and parser for delegate_components

* Draft initial delegate_components macro

* Test basic delegate_components macro call

* Make generic params in target explicit

* Parse target_generics as Generics

* Propagate generics in component name

* Move merge_generics to separate module

* Draft with_components macro generator

* Remove redelegate module

* Name macro from components name

* Fix generated with_components macro

* Add new define_components! separate from delegate_components

* Clean up

* Move main macro code to plain library cgp-component-macro-lib

* Refactoring

* WIP

* Add delegates to trait

* Add generics to delegates to trait

* Generalize substitution macro

* Use new proc macros in cgp-core

* Fix errors

* Improve tests

* Fix clippy
  • Loading branch information
soareschen authored Jul 2, 2024
1 parent 9c323af commit 89293a9
Show file tree
Hide file tree
Showing 45 changed files with 1,033 additions and 334 deletions.
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.

20 changes: 11 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/cgp-sync",
"crates/cgp-component",
"crates/cgp-component-macro",
"crates/cgp-component-macro-lib",
"crates/cgp-error",
"crates/cgp-error-eyre",
"crates/cgp-error-std",
Expand All @@ -24,12 +25,13 @@ repository = "https://github.com/informalsystems/cgp"
authors = ["Informal Systems <[email protected]>", "Soares Chen <[email protected]>"]

[patch.crates-io]
cgp-core = { path = "./crates/cgp-core" }
cgp-async = { path = "./crates/cgp-async" }
cgp-async-macro = { path = "./crates/cgp-async-macro" }
cgp-sync = { path = "./crates/cgp-sync" }
cgp-component = { path = "./crates/cgp-component" }
cgp-component-macro = { path = "./crates/cgp-component-macro" }
cgp-error = { path = "./crates/cgp-error" }
cgp-run = { path = "./crates/cgp-run" }
cgp-inner = { path = "./crates/cgp-inner" }
cgp-core = { path = "./crates/cgp-core" }
cgp-async = { path = "./crates/cgp-async" }
cgp-async-macro = { path = "./crates/cgp-async-macro" }
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-error = { path = "./crates/cgp-error" }
cgp-run = { path = "./crates/cgp-run" }
cgp-inner = { path = "./crates/cgp-inner" }
23 changes: 23 additions & 0 deletions crates/cgp-component-macro-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "cgp-component-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"
139 changes: 139 additions & 0 deletions crates/cgp-component-macro-lib/src/delegate_components/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use core::iter;

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::{Bracket, Colon, Comma, Lt};
use syn::{braced, bracketed, Generics, Ident, Token, Type};

pub struct DelegateComponentsAst {
pub target_type: Type,
pub target_generics: Generics,
pub delegate_entries: DelegateEntriesAst,
}

pub struct DefineComponentsAst {
pub components_ident: Ident,
pub components_generics: Generics,
pub delegate_entries: DelegateEntriesAst,
}

pub struct DelegateEntriesAst {
pub entries: Punctuated<DelegateEntryAst, Comma>,
}

pub struct DelegateEntryAst {
pub components: Punctuated<ComponentAst, Comma>,
pub source: Type,
}

#[derive(Clone)]
pub struct ComponentAst {
pub component_type: Type,
pub component_generics: Generics,
}

impl DelegateEntriesAst {
pub fn all_components(&self) -> Punctuated<ComponentAst, Comma> {
self.entries
.iter()
.flat_map(|entry| entry.components.clone().into_iter())
.collect()
}
}

impl Parse for DelegateComponentsAst {
fn parse(input: ParseStream) -> syn::Result<Self> {
let target_generics = if input.peek(Lt) {
input.parse()?
} else {
Default::default()
};

let target_type: Type = input.parse()?;

let delegate_entries: DelegateEntriesAst = input.parse()?;

Ok(Self {
target_type,
target_generics,
delegate_entries,
})
}
}

impl Parse for DefineComponentsAst {
fn parse(input: ParseStream) -> syn::Result<Self> {
let components_ident: Ident = input.parse()?;

let components_generics = if input.peek(Lt) {
input.parse()?
} else {
Default::default()
};

let delegate_entries: DelegateEntriesAst = input.parse()?;

Ok(Self {
components_ident,
components_generics,
delegate_entries,
})
}
}

impl Parse for DelegateEntriesAst {
fn parse(input: ParseStream) -> syn::Result<Self> {
let entries = {
let entries_body;
braced!(entries_body in input);
entries_body.parse_terminated(DelegateEntryAst::parse, Comma)?
};

Ok(Self { entries })
}
}

impl Parse for DelegateEntryAst {
fn parse(input: ParseStream) -> syn::Result<Self> {
let components = if input.peek(Bracket) {
let components_body;
bracketed!(components_body in input);
components_body.parse_terminated(ComponentAst::parse, Token![,])?
} else {
let component: ComponentAst = input.parse()?;
Punctuated::from_iter(iter::once(component))
};

let _: Colon = input.parse()?;

let source: Type = input.parse()?;

Ok(Self { components, source })
}
}

impl Parse for ComponentAst {
fn parse(input: ParseStream) -> syn::Result<Self> {
let component_generics = if input.peek(Lt) {
input.parse()?
} else {
Default::default()
};

let component_type: Type = input.parse()?;

Ok(Self {
component_type,
component_generics,
})
}
}

impl ToTokens for ComponentAst {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.component_generics.to_token_stream());
tokens.extend(self.component_type.to_token_stream());
}
}
67 changes: 67 additions & 0 deletions crates/cgp-component-macro-lib/src/delegate_components/define.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{parse_quote, Ident};

use crate::delegate_components::ast::DefineComponentsAst;
use crate::delegate_components::define_struct::define_struct;
use crate::delegate_components::delegates_to::define_delegates_to_trait;
use crate::delegate_components::impl_delegate::impl_delegate_components;
use crate::delegate_components::substitution_macro::define_substitution_macro;
use crate::derive_component::snake_case::to_snake_case_str;

pub fn define_components(body: TokenStream) -> TokenStream {
let ast: DefineComponentsAst = syn::parse2(body).unwrap();

let components_type = {
let components_ident = &ast.components_ident;
let type_generics = ast.components_generics.split_for_impl().1;
parse_quote!( #components_ident #type_generics )
};

let impl_items = impl_delegate_components(
&components_type,
&ast.components_generics,
&ast.delegate_entries,
);

let item_struct = define_struct(&ast.components_ident, &ast.components_generics);

let mut output = TokenStream::new();

output.extend(item_struct.to_token_stream());

for impl_item in impl_items {
output.extend(impl_item.to_token_stream());
}

{
let delegates_to_trait_name = format!("DelegatesTo{}", ast.components_ident);

let (delegates_to_trait, delegates_to_impl) = define_delegates_to_trait(
&Ident::new(&delegates_to_trait_name, Span::call_site()),
&components_type,
&ast.components_generics,
&ast.delegate_entries,
);

output.extend(delegates_to_trait.to_token_stream());
output.extend(delegates_to_impl.to_token_stream());
}

{
let with_components_macro_name = format!(
"with_{}",
to_snake_case_str(&ast.components_ident.to_string())
);

let with_components_macro = define_substitution_macro(
&Ident::new(&with_components_macro_name, Span::call_site()),
&ast.components_ident,
&ast.delegate_entries.all_components().to_token_stream(),
);

output.extend(with_components_macro);
}

output
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{parse_quote, GenericParam, Generics, Ident, ItemStruct, Type};

pub fn define_struct(ident: &Ident, generics: &Generics) -> ItemStruct {
if generics.params.is_empty() {
parse_quote! {
pub struct #ident;
}
} else {
let mut generic_params = generics.params.clone();
let mut phantom_params: Punctuated<Type, Comma> = Default::default();

for param in generic_params.iter_mut() {
match param {
GenericParam::Type(type_param) => {
type_param.colon_token = None;
type_param.bounds.clear();

let type_ident = &type_param.ident;
phantom_params.push(parse_quote!( #type_ident ));
}
GenericParam::Lifetime(life_param) => {
life_param.colon_token = None;
life_param.bounds.clear();

let lifetime = &life_param.lifetime;
phantom_params.push(parse_quote!( & #lifetime () ));
}
_ => {}
}
}

parse_quote! {
pub struct #ident < #generic_params > (
pub ::core::marker::PhantomData<( #phantom_params )>
);
}
}
}
23 changes: 23 additions & 0 deletions crates/cgp-component-macro-lib/src/delegate_components/delegate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use proc_macro2::TokenStream;
use quote::ToTokens;

use crate::delegate_components::ast::DelegateComponentsAst;
use crate::delegate_components::impl_delegate::impl_delegate_components;

pub fn delegate_components(body: TokenStream) -> TokenStream {
let ast: DelegateComponentsAst = syn::parse2(body).unwrap();

let impl_items = impl_delegate_components(
&ast.target_type,
&ast.target_generics,
&ast.delegate_entries,
);

let mut output = TokenStream::new();

for impl_item in impl_items {
output.extend(impl_item.to_token_stream());
}

output
}
Loading

0 comments on commit 89293a9

Please sign in to comment.