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

Introduce cgp_type! macro for defining simple abstract CGP types #55

Merged
merged 6 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
Loading