diff --git a/CHANGELOG.md b/CHANGELOG.md index a4dc39a..2ef1efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`, diff --git a/crates/cgp-component-macro-lib/src/lib.rs b/crates/cgp-component-macro-lib/src/lib.rs index bef098d..b2dc1a9 100644 --- a/crates/cgp-component-macro-lib/src/lib.rs +++ b/crates/cgp-component-macro-lib/src/lib.rs @@ -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; @@ -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; diff --git a/crates/cgp-component-macro-lib/src/type_component/derive.rs b/crates/cgp-component-macro-lib/src/type_component/derive.rs new file mode 100644 index 0000000..629d950 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/type_component/derive.rs @@ -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 { + let spec: TypeComponentSpecs = syn::parse2(stream)?; + + Ok(do_derive_type_component( + spec.attributes, + spec.ident, + spec.bounds, + )) +} + +pub struct TypeComponentSpecs { + pub attributes: Vec, + pub ident: Ident, + pub bounds: Punctuated, +} + +impl Parse for TypeComponentSpecs { + fn parse(input: ParseStream) -> syn::Result { + 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, + ident: Ident, + bounds: Punctuated, +) -> 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 = :: #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 { + type #ident : #bounds; + } + }; + + let consumer_impl: ItemImpl = parse_quote! { + impl #consumer_trait_name for Context + where + Context: HasComponents< Components = Components >, + Components: #provider_trait_name , + Components:: #ident : #bounds, + { + type #ident = Components:: #ident; + } + }; + + let provider_impl: ItemImpl = parse_quote! { + impl + #provider_trait_name for Component + where + Component: DelegateComponent< #component_name, Delegate = Delegate >, + Delegate: #provider_trait_name , + Delegate:: #ident : #bounds, + { + type #ident = Delegate:: #ident; + } + }; + + let with_provider_impl: ItemImpl = parse_quote! { + impl #provider_trait_name + for WithProvider + where + Provider: ProvideType, + #ident: #bounds, + { + type #ident = #ident; + } + }; + + let use_type_impl: ItemImpl = parse_quote! { + impl #provider_trait_name + 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 + } +} diff --git a/crates/cgp-component-macro-lib/src/type_component/mod.rs b/crates/cgp-component-macro-lib/src/type_component/mod.rs new file mode 100644 index 0000000..8fe0591 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/type_component/mod.rs @@ -0,0 +1 @@ +pub mod derive; diff --git a/crates/cgp-component-macro/src/lib.rs b/crates/cgp-component-macro/src/lib.rs index 6fc23d2..4491644 100644 --- a/crates/cgp-component-macro/src/lib.rs +++ b/crates/cgp-component-macro/src/lib.rs @@ -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()) diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index fa79baf..ffaef2d 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -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}; diff --git a/crates/cgp-error/src/traits/has_error_type.rs b/crates/cgp-error/src/traits/has_error_type.rs index 12d9d29..967a90f 100644 --- a/crates/cgp-error/src/traits/has_error_type.rs +++ b/crates/cgp-error/src/traits/has_error_type.rs @@ -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 = ::Error; - -impl ProvideErrorType for WithProvider -where - Provider: ProvideType, - Error: Async + Debug, -{ - type Error = Error; + More details about how to use `HasErrorType` is available at + + */ + Error: Debug } diff --git a/crates/cgp-runtime/src/traits/has_runtime_type.rs b/crates/cgp-runtime/src/traits/has_runtime_type.rs index 5b8497c..e1de937 100644 --- a/crates/cgp-runtime/src/traits/has_runtime_type.rs +++ b/crates/cgp-runtime/src/traits/has_runtime_type.rs @@ -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 = ::Runtime; - -impl ProvideRuntimeType for WithProvider -where - Provider: ProvideType, -{ - type Runtime = Runtime; +cgp_type! { + Runtime }