Skip to content

Commit

Permalink
Introduce cgp_type! macro for defining simple abstract CGP types (#55)
Browse files Browse the repository at this point in the history
* Draft cgp_type! macro

* Also derive WithProvider and UseDelegate

* Also derive alias type

* Use cgp_type! to derive HasErrorType

* Use cgp_type! to derive HasRuntimeType

* Add changelog
  • Loading branch information
soareschen authored Jan 7, 2025
1 parent 9d8cf04 commit 8fe487c
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## v0.3.0 (pre-release)

- Introduce `cgp_type!` macro for defining simple abstract CGP types - [#55](https://github.com/contextgeneric/cgp/pull/55)
- Use `cgp_type!` to derive `HasErrorType` and `HasRuntimeType`.

- Implement `ErrorWrapper` on generic `ErrorRaiser` providers - [#54](https://github.com/contextgeneric/cgp/pull/54)
- Implement `ErrorWrapper` for the following providers: `DebugError`, `DisplayError`,
`DebugAnyhowError`, `DisplayAnyhowError`, `RaiseAnyhowError`,
Expand Down
2 changes: 2 additions & 0 deletions crates/cgp-component-macro-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod delegate_components;
pub mod derive_component;
pub mod for_each_replace;
pub mod preset;
pub mod type_component;

#[cfg(test)]
mod tests;
Expand All @@ -21,3 +22,4 @@ pub use crate::delegate_components::delegate_components;
pub use crate::derive_component::derive_component;
pub use crate::for_each_replace::{handle_for_each_replace, handle_replace};
pub use crate::preset::define_preset;
pub use crate::type_component::derive::derive_type_component;
151 changes: 151 additions & 0 deletions crates/cgp-component-macro-lib/src/type_component/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use alloc::format;
use alloc::vec::Vec;
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::{Colon, Plus, Pound};
use syn::{parse_quote, Attribute, Ident, ItemImpl, ItemTrait, ItemType, TypeParamBound};

pub fn derive_type_component(stream: TokenStream) -> syn::Result<TokenStream> {
let spec: TypeComponentSpecs = syn::parse2(stream)?;

Ok(do_derive_type_component(
spec.attributes,
spec.ident,
spec.bounds,
))
}

pub struct TypeComponentSpecs {
pub attributes: Vec<Attribute>,
pub ident: Ident,
pub bounds: Punctuated<TypeParamBound, Plus>,
}

impl Parse for TypeComponentSpecs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attributes = {
let lookahead = input.lookahead1();
if lookahead.peek(Pound) {
input.call(Attribute::parse_outer)?
} else {
Vec::new()
}
};

let ident = input.parse()?;

if input.is_empty() {
return Ok(Self {
attributes,
ident,
bounds: Punctuated::new(),
});
}

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

let bounds = input.parse_terminated(TypeParamBound::parse, Plus)?;

Ok(Self {
attributes,
ident,
bounds,
})
}
}

pub fn do_derive_type_component(
attributes: Vec<Attribute>,
ident: Ident,
bounds: Punctuated<TypeParamBound, Plus>,
) -> TokenStream {
let consumer_trait_name = Ident::new(&format!("Has{ident}Type"), ident.span());

let provider_trait_name = Ident::new(&format!("Provide{ident}Type"), ident.span());

let alias_name = Ident::new(&format!("{ident}Of"), ident.span());

let component_name = Ident::new(&format!("{ident}TypeComponent"), ident.span());

let alias_type: ItemType = parse_quote! {
pub type #alias_name <Context> = <Context as #consumer_trait_name>:: #ident;
};

let mut consumer_trait: ItemTrait = parse_quote! {
pub trait #consumer_trait_name {
type #ident : #bounds ;
}
};

consumer_trait.attrs = attributes;

let provider_trait: ItemTrait = parse_quote! {
pub trait #provider_trait_name <Context> {
type #ident : #bounds;
}
};

let consumer_impl: ItemImpl = parse_quote! {
impl<Context, Components> #consumer_trait_name for Context
where
Context: HasComponents< Components = Components >,
Components: #provider_trait_name <Context>,
Components:: #ident : #bounds,
{
type #ident = Components:: #ident;
}
};

let provider_impl: ItemImpl = parse_quote! {
impl<Context, Component, Delegate>
#provider_trait_name <Context> for Component
where
Component: DelegateComponent< #component_name, Delegate = Delegate >,
Delegate: #provider_trait_name <Context>,
Delegate:: #ident : #bounds,
{
type #ident = Delegate:: #ident;
}
};

let with_provider_impl: ItemImpl = parse_quote! {
impl<Context, Provider, #ident> #provider_trait_name <Context>
for WithProvider<Provider>
where
Provider: ProvideType<Context, #component_name, Type = #ident >,
#ident: #bounds,
{
type #ident = #ident;
}
};

let use_type_impl: ItemImpl = parse_quote! {
impl<Context, #ident> #provider_trait_name <Context>
for UseType<#ident>
where
#ident: #bounds,
{
type #ident = #ident;
}
};

quote! {
pub struct #component_name;

#consumer_trait

#alias_type

#provider_trait

#consumer_impl

#provider_impl

#with_provider_impl

#use_type_impl
}
}
1 change: 1 addition & 0 deletions crates/cgp-component-macro-lib/src/type_component/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod derive;
7 changes: 7 additions & 0 deletions crates/cgp-component-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ pub fn cgp_preset(body: TokenStream) -> TokenStream {
.into()
}

#[proc_macro]
pub fn cgp_type(body: TokenStream) -> TokenStream {
cgp_component_macro_lib::derive_type_component(body.into())
.unwrap()
.into()
}

#[proc_macro]
pub fn for_each_replace(body: TokenStream) -> TokenStream {
cgp_component_macro_lib::handle_for_each_replace(body.into())
Expand Down
5 changes: 3 additions & 2 deletions crates/cgp-core/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
pub use cgp_async::{async_trait, Async, MaybeSend, MaybeStatic, MaybeSync};
pub use cgp_component::{DelegateComponent, HasComponents};
pub use cgp_component::{DelegateComponent, HasComponents, WithContext, WithProvider};
pub use cgp_component_macro::{
cgp_component, cgp_preset, delegate_components, for_each_replace, replace_with,
cgp_component, cgp_preset, cgp_type, delegate_components, for_each_replace, replace_with,
};
pub use cgp_error::{CanRaiseError, CanWrapError, HasErrorType};
pub use cgp_field::{Char, Cons, Either, HasField, HasFieldMut, Nil, Void};
pub use cgp_field_macro::{product, symbol, HasField, Product, Sum};
pub use cgp_type::{HasType, ProvideType, UseType};
46 changes: 16 additions & 30 deletions crates/cgp-error/src/traits/has_error_type.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,27 @@
use core::fmt::Debug;

use cgp_async::Async;
use cgp_component::{DelegateComponent, HasComponents, WithProvider};
use cgp_component_macro::cgp_component;
use cgp_type::ProvideType;
use cgp_component_macro::cgp_type;
use cgp_type::{ProvideType, UseType};

/**
This is used for contexts to declare that they have a _unique_ `Self::Error` type.
cgp_type! {
/**
This is used for contexts to declare that they have a _unique_ `Self::Error` type.
Although it is possible for each context to declare their own associated
`Error` type, doing so may result in having multiple ambiguous `Self::Error` types,
if there are multiple associated types with the same name in different traits.
Although it is possible for each context to declare their own associated
`Error` type, doing so may result in having multiple ambiguous `Self::Error` types,
if there are multiple associated types with the same name in different traits.
As a result, it is better for context traits to include `HasError` as their
parent traits, so that multiple traits can all refer to the same abstract
`Self::Error` type.
*/
#[cgp_component {
name: ErrorTypeComponent,
provider: ProvideErrorType,
}]
pub trait HasErrorType {
/**
The `Error` associated type is also required to implement [`Debug`].
As a result, it is better for context traits to include `HasError` as their
parent traits, so that multiple traits can all refer to the same abstract
`Self::Error` type.
The `Error` associated type is also required to implement [`Debug`].
This is to allow `Self::Error` to be used in calls like `.unwrap()`,
as well as for simpler error logging.
*/
type Error: Debug;
}
pub type ErrorOf<Context> = <Context as HasErrorType>::Error;

impl<Context, Provider, Error> ProvideErrorType<Context> for WithProvider<Provider>
where
Provider: ProvideType<Context, ErrorTypeComponent, Type = Error>,
Error: Async + Debug,
{
type Error = Error;
More details about how to use `HasErrorType` is available at
<https://patterns.contextgeneric.dev/error-handling.html>
*/
Error: Debug
}
19 changes: 2 additions & 17 deletions crates/cgp-runtime/src/traits/has_runtime_type.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
use cgp_core::component::WithProvider;
use cgp_core::prelude::*;
use cgp_core::types::ProvideType;

#[cgp_component {
name: RuntimeTypeComponent,
provider: ProvideRuntimeType,
}]
pub trait HasRuntimeType {
type Runtime;
}

pub type RuntimeOf<Context> = <Context as HasRuntimeType>::Runtime;

impl<Context, Provider, Runtime> ProvideRuntimeType<Context> for WithProvider<Provider>
where
Provider: ProvideType<Context, RuntimeTypeComponent, Type = Runtime>,
{
type Runtime = Runtime;
cgp_type! {
Runtime
}

0 comments on commit 8fe487c

Please sign in to comment.