diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ec4b149..eb746be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,8 +24,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly override: true + components: rustfmt - uses: actions-rs/cargo@v1 with: command: fmt diff --git a/.rustfmt.toml b/.rustfmt.toml index 1900537..1161809 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,7 +1,4 @@ max_width = 100 reorder_imports = true - -# nightly only - -# group_imports = "StdExternalCrate" -# imports_granularity = "Module" \ No newline at end of file +group_imports = "StdExternalCrate" +imports_granularity = "Module" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef1efb..20a29e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## v0.3.0 (pre-release) +- Introduce Accessor Component Macros - [#56](https://github.com/contextgeneric/cgp/pull/55) + - Introduce `#[cgp_getter]` attribute macro that extends `#[cgp_component]` and implement + `UseFields` and `UseField` for accessor traits. + - Introduce `#[cgp_auto_getter]` attribute macro for deriving accessor traits with + blanket implementations that use `HasField` directly. + - 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`. diff --git a/Cargo.lock b/Cargo.lock index 0214ad3..2ac7741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,7 @@ name = "cgp-component-macro" version = "0.2.0" dependencies = [ "cgp-component-macro-lib", + "syn", ] [[package]] diff --git a/crates/cgp-component-macro-lib/src/derive_component/component_spec.rs b/crates/cgp-component-macro-lib/src/derive_component/component_spec.rs index d7c721a..0a52b56 100644 --- a/crates/cgp-component-macro-lib/src/derive_component/component_spec.rs +++ b/crates/cgp-component-macro-lib/src/derive_component/component_spec.rs @@ -21,12 +21,25 @@ pub struct ComponentNameSpec { pub component_params: Punctuated, } +static VALID_KEYS: [&str; 3] = ["context", "provider", "name"]; + impl Parse for ComponentSpec { fn parse(input: ParseStream) -> syn::Result { let Entries { entries } = input.parse()?; + for key in entries.keys() { + if !VALID_KEYS.iter().any(|valid| valid == key) { + return Err(syn::Error::new( + Span::call_site(), + format!( + r#"invalid key in component spec: {key}. the following keys are valid: "context", "provider", "name"."# + ), + )); + } + } + let context_type: Ident = { - let raw_context_type = entries.get(&Ident::new("context", Span::call_site())); + let raw_context_type = entries.get("context"); if let Some(context_type) = raw_context_type { syn::parse2(context_type.to_token_stream())? @@ -37,14 +50,14 @@ impl Parse for ComponentSpec { let provider_name: Ident = { let raw_provider_name = entries - .get(&Ident::new("provider", Span::call_site())) + .get("provider") .ok_or_else(|| Error::new(input.span(), "expect provider name to be given"))?; syn::parse2(raw_provider_name.to_token_stream())? }; let (component_name, component_params) = { - let raw_component_name = entries.get(&Ident::new("name", Span::call_site())); + let raw_component_name = entries.get("name"); if let Some(raw_component_name) = raw_component_name { let ComponentNameSpec { diff --git a/crates/cgp-component-macro-lib/src/derive_component/derive.rs b/crates/cgp-component-macro-lib/src/derive_component/derive.rs index f95307d..d3c805e 100644 --- a/crates/cgp-component-macro-lib/src/derive_component/derive.rs +++ b/crates/cgp-component-macro-lib/src/derive_component/derive.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::quote; use syn::ItemTrait; use crate::derive_component::component_name::derive_component_name_struct; @@ -8,21 +8,26 @@ use crate::derive_component::consumer_impl::derive_consumer_impl; use crate::derive_component::provider_impl::derive_provider_impl; use crate::derive_component::provider_trait::derive_provider_trait; -pub fn derive_component(attr: TokenStream, item: TokenStream) -> TokenStream { - let spec: ComponentSpec = syn::parse2(attr).unwrap(); +pub fn derive_component(attr: TokenStream, item: TokenStream) -> syn::Result { + let spec: ComponentSpec = syn::parse2(attr)?; + let consumer_trait: ItemTrait = syn::parse2(item)?; - let consumer_trait: ItemTrait = syn::parse2(item).unwrap(); + derive_component_with_ast(&spec, &consumer_trait) +} +pub fn derive_component_with_ast( + spec: &ComponentSpec, + consumer_trait: &ItemTrait, +) -> syn::Result { let provider_name = &spec.provider_name; let context_type = &spec.context_type; let component_struct = derive_component_name_struct(&spec.component_name, &spec.component_params); - let provider_trait = - derive_provider_trait(&consumer_trait, provider_name, context_type).unwrap(); + let provider_trait = derive_provider_trait(consumer_trait, provider_name, context_type)?; - let consumer_impl = derive_consumer_impl(&consumer_trait, provider_name, context_type); + let consumer_impl = derive_consumer_impl(consumer_trait, provider_name, context_type); let provider_impl = derive_provider_impl( &provider_trait, @@ -30,12 +35,13 @@ pub fn derive_component(attr: TokenStream, item: TokenStream) -> TokenStream { &spec.component_params, ); - let mut output = consumer_trait.to_token_stream(); - - output.extend(component_struct.to_token_stream()); - output.extend(provider_trait.to_token_stream()); - output.extend(consumer_impl.to_token_stream()); - output.extend(provider_impl.to_token_stream()); + let derived = quote! { + #consumer_trait + #component_struct + #provider_trait + #consumer_impl + #provider_impl + }; - output + Ok(derived) } diff --git a/crates/cgp-component-macro-lib/src/derive_component/entry.rs b/crates/cgp-component-macro-lib/src/derive_component/entry.rs index 9d76c09..dd0b83d 100644 --- a/crates/cgp-component-macro-lib/src/derive_component/entry.rs +++ b/crates/cgp-component-macro-lib/src/derive_component/entry.rs @@ -1,4 +1,5 @@ use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; @@ -21,15 +22,18 @@ impl Parse for Entry { } pub struct Entries { - pub entries: BTreeMap, + pub entries: BTreeMap, } impl Parse for Entries { fn parse(input: ParseStream) -> syn::Result { let entry_list: Punctuated = Punctuated::parse_terminated(input)?; - let entries = - BTreeMap::from_iter(entry_list.into_iter().map(|entry| (entry.key, entry.value))); + let entries = BTreeMap::from_iter( + entry_list + .into_iter() + .map(|entry| (entry.key.to_string(), entry.value)), + ); Ok(Entries { entries }) } diff --git a/crates/cgp-component-macro-lib/src/getter_component/blanket.rs b/crates/cgp-component-macro-lib/src/getter_component/blanket.rs new file mode 100644 index 0000000..ae36a80 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/blanket.rs @@ -0,0 +1,57 @@ +use alloc::string::ToString; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_quote, Ident, ItemImpl, ItemTrait}; + +use crate::getter_component::getter_field::GetterField; +use crate::getter_component::symbol::symbol_from_string; + +pub fn derive_blanket_impl( + context_type: &Ident, + consumer_trait: &ItemTrait, + fields: &[GetterField], +) -> ItemImpl { + let consumer_name = &consumer_trait.ident; + + let mut constraints = consumer_trait.supertraits.clone(); + + let mut methods: TokenStream = TokenStream::new(); + + for field in fields { + let field_name = &field.field_name; + let provider_type = &field.provider_type; + let field_symbol = symbol_from_string(&field.field_name.to_string()); + + if field.field_mut.is_none() { + constraints.push(parse_quote! { + HasField< #field_symbol, Value = #provider_type > + }); + + methods.extend(quote! { + fn #field_name( &self ) -> & #provider_type { + self.get_field( ::core::marker::PhantomData::< #field_symbol > ) + } + }); + } else { + constraints.push(parse_quote! { + HasFieldMut< #field_symbol, Value = #provider_type > + }); + + methods.extend(quote! { + fn #field_name( &mut self ) -> &mut #provider_type { + self.get_field_mut( ::core::marker::PhantomData::< #field_symbol > ) + } + }); + } + } + + parse_quote! { + impl< #context_type > #consumer_name for #context_type + where + #context_type: #constraints + { + #methods + } + } +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/derive.rs b/crates/cgp-component-macro-lib/src/getter_component/derive.rs new file mode 100644 index 0000000..bcb3583 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/derive.rs @@ -0,0 +1,66 @@ +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Error, Ident, ItemTrait}; + +use crate::derive_component::component_spec::ComponentSpec; +use crate::derive_component::derive::derive_component_with_ast; +use crate::getter_component::blanket::derive_blanket_impl; +use crate::getter_component::getter_field::GetterField; +use crate::getter_component::parse::parse_getter_fields; +use crate::getter_component::use_field::derive_use_field_impl; +use crate::getter_component::use_fields::derive_use_fields_impl; +use crate::getter_component::with_provider::derive_with_provider_impl; + +pub fn derive_getter_component(attr: TokenStream, body: TokenStream) -> syn::Result { + let spec: ComponentSpec = syn::parse2(attr)?; + let consumer_trait: ItemTrait = syn::parse2(body)?; + + let derived_component = derive_component_with_ast(&spec, &consumer_trait)?; + + let fields = parse_getter_fields(&spec.context_type, &consumer_trait)?; + + let use_fields_impl = derive_use_fields_impl(&spec, &consumer_trait, &fields); + + let m_field: Option<[GetterField; 1]> = fields.try_into().ok(); + + let mut derived = quote! { + #derived_component + + #use_fields_impl + }; + + if let Some([field]) = m_field { + let use_field_impl = derive_use_field_impl(&spec, &consumer_trait, &field); + let use_provider_impl = derive_with_provider_impl(&spec, &consumer_trait, &field); + + derived.extend(use_field_impl); + derived.extend(use_provider_impl); + } + + Ok(derived) +} + +pub fn derive_auto_getter_component( + attr: TokenStream, + body: TokenStream, +) -> syn::Result { + if !attr.is_empty() { + return Err(Error::new( + Span::call_site(), + "#[derive_auto_getter] does not accept any attribute argument", + )); + } + + let consumer_trait: ItemTrait = syn::parse2(body)?; + + let context_type = Ident::new("Context", Span::call_site()); + + let fields = parse_getter_fields(&context_type, &consumer_trait)?; + + let blanket_impl = derive_blanket_impl(&context_type, &consumer_trait, &fields); + + Ok(quote! { + #consumer_trait + #blanket_impl + }) +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/getter_field.rs b/crates/cgp-component-macro-lib/src/getter_component/getter_field.rs new file mode 100644 index 0000000..7e48535 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/getter_field.rs @@ -0,0 +1,9 @@ +use syn::token::Mut; +use syn::{Ident, Type}; + +pub struct GetterField { + pub field_name: Ident, + pub field_type: Type, + pub provider_type: Type, + pub field_mut: Option, +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/mod.rs b/crates/cgp-component-macro-lib/src/getter_component/mod.rs new file mode 100644 index 0000000..ec54db7 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/mod.rs @@ -0,0 +1,8 @@ +pub mod blanket; +pub mod derive; +pub mod getter_field; +pub mod parse; +pub mod symbol; +pub mod use_field; +pub mod use_fields; +pub mod with_provider; diff --git a/crates/cgp-component-macro-lib/src/getter_component/parse.rs b/crates/cgp-component-macro-lib/src/getter_component/parse.rs new file mode 100644 index 0000000..3b8fb72 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/parse.rs @@ -0,0 +1,144 @@ +use alloc::vec::Vec; + +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::{parse_quote, Error, FnArg, Ident, ItemTrait, ReturnType, TraitItem, Type}; + +use crate::derive_component::replace_self_type::replace_self_type; +use crate::getter_component::getter_field::GetterField; + +pub fn parse_getter_fields( + context_type: &Ident, + consumer_trait: &ItemTrait, +) -> syn::Result> { + if !consumer_trait.generics.params.is_empty() { + return Err(Error::new( + consumer_trait.generics.params.span(), + "getter trait cannot contain generic parameters", + )); + } + + let mut fields = Vec::new(); + + for item in consumer_trait.items.iter() { + match item { + TraitItem::Fn(method) => { + let signature = &method.sig; + + if signature.constness.is_some() { + return Err(Error::new( + signature.constness.span(), + "getter method must not be const fn", + )); + } + + if signature.asyncness.is_some() { + return Err(Error::new( + signature.asyncness.span(), + "getter method must not be async fn", + )); + } + + if signature.unsafety.is_some() { + return Err(Error::new( + signature.unsafety.span(), + "getter method must not be unsafe fn", + )); + } + + if !signature.generics.params.is_empty() { + return Err(Error::new( + signature.generics.params.span(), + "getter method must not contain generic param", + )); + } + + if signature.generics.where_clause.is_some() { + return Err(Error::new( + signature.generics.where_clause.span(), + "getter method must not contain where clause", + )); + } + + let field_name = signature.ident.clone(); + + let [arg]: [&FnArg; 1] = signature + .inputs + .iter() + .collect::>() + .try_into() + .map_err(|_| { + Error::new( + signature.inputs.span(), + "getter method must contain exactly one `&self` argument", + ) + })?; + + let field_mut = match arg { + FnArg::Receiver(receiver) => { + if receiver.reference.is_none() { + return Err(Error::new( + receiver.span(), + "first argument to getter method must be a reference to self, i.e. `&self`" + )); + } + + receiver.mutability + } + _ => { + return Err(Error::new( + arg.span(), + "first argument to getter method must be `&self`", + )) + } + }; + + let field_type: Type = match &signature.output { + ReturnType::Default => parse_quote!(()), + ReturnType::Type(_, ty) => { + let ty = ty.as_ref().clone(); + match &ty { + Type::Reference(type_ref) => { + if type_ref.mutability.is_some() != field_mut.is_some() { + return Err(Error::new( + type_ref.span(), + "return type have the same mutability as the self reference", + )); + } + + type_ref.elem.as_ref().clone() + } + _ => { + return Err(Error::new( + ty.span(), + "return type must be a reference", + )) + } + } + } + }; + + let provider_type: Type = syn::parse2(replace_self_type( + field_type.to_token_stream(), + context_type, + &Vec::new(), + ))?; + + fields.push(GetterField { + field_name, + field_type, + provider_type, + field_mut, + }) + } + _ => { + return Err(Error::new( + item.span(), + "getter trait can only contain getter methods", + )) + } + } + } + + Ok(fields) +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/symbol.rs b/crates/cgp-component-macro-lib/src/getter_component/symbol.rs new file mode 100644 index 0000000..87f3678 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/symbol.rs @@ -0,0 +1,9 @@ +use syn::{parse_quote, Type}; + +pub fn symbol_from_string(value: &str) -> Type { + value + .chars() + .rfold(parse_quote! { Nil }, |tail, c: char| -> Type { + parse_quote!( Cons< Char< #c >, #tail > ) + }) +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/use_field.rs b/crates/cgp-component-macro-lib/src/getter_component/use_field.rs new file mode 100644 index 0000000..48c8af5 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/use_field.rs @@ -0,0 +1,56 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_quote, ItemImpl, ItemTrait}; + +use crate::derive_component::component_spec::ComponentSpec; +use crate::getter_component::getter_field::GetterField; + +pub fn derive_use_field_impl( + spec: &ComponentSpec, + consumer_trait: &ItemTrait, + field: &GetterField, +) -> TokenStream { + let context_type = &spec.context_type; + let provider_name = &spec.provider_name; + + // FIXME: replace `Self` with `Context` inside super trait bound + let mut constraints = consumer_trait.supertraits.clone(); + + let field_name = &field.field_name; + let provider_type = &field.provider_type; + + let method = if field.field_mut.is_none() { + constraints.push(parse_quote! { + HasField< Tag, Value = #provider_type > + }); + + quote! { + fn #field_name( context: & #context_type ) -> & #provider_type { + context.get_field( ::core::marker::PhantomData ) + } + } + } else { + constraints.push(parse_quote! { + HasFieldMut< Tag, Value = #provider_type > + }); + + quote! { + fn #field_name( context: &mut #context_type ) -> &mut #provider_type { + context.get_field_mut( ::core::marker::PhantomData ) + } + } + }; + + let use_field_impl: ItemImpl = parse_quote! { + impl< #context_type, Tag > #provider_name < #context_type > for UseField + where + #context_type: #constraints + { + #method + } + }; + + quote! { + #use_field_impl + } +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/use_fields.rs b/crates/cgp-component-macro-lib/src/getter_component/use_fields.rs new file mode 100644 index 0000000..1dbc408 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/use_fields.rs @@ -0,0 +1,60 @@ +use alloc::string::ToString; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_quote, ItemImpl, ItemTrait}; + +use crate::derive_component::component_spec::ComponentSpec; +use crate::getter_component::getter_field::GetterField; +use crate::getter_component::symbol::symbol_from_string; + +pub fn derive_use_fields_impl( + spec: &ComponentSpec, + consumer_trait: &ItemTrait, + fields: &[GetterField], +) -> ItemImpl { + let context_type = &spec.context_type; + let provider_name = &spec.provider_name; + + // FIXME: replace `Self` with `Context` inside super trait bound + let mut constraints = consumer_trait.supertraits.clone(); + + let mut methods: TokenStream = TokenStream::new(); + + for field in fields { + let field_name = &field.field_name; + let provider_type = &field.provider_type; + let field_symbol = symbol_from_string(&field.field_name.to_string()); + + if field.field_mut.is_none() { + constraints.push(parse_quote! { + HasField< #field_symbol, Value = #provider_type > + }); + + methods.extend(quote! { + fn #field_name( context: & #context_type ) -> & #provider_type { + context.get_field( ::core::marker::PhantomData::< #field_symbol > ) + } + }); + } else { + constraints.push(parse_quote! { + HasFieldMut< #field_symbol, Value = #provider_type > + }); + + methods.extend(quote! { + fn #field_name( context: &mut #context_type ) -> &mut #provider_type { + context.get_field_mut( ::core::marker::PhantomData::< #field_symbol > ) + } + }); + } + } + + parse_quote! { + impl< #context_type > #provider_name < #context_type > for UseFields + where + #context_type: #constraints + { + #methods + } + } +} diff --git a/crates/cgp-component-macro-lib/src/getter_component/with_provider.rs b/crates/cgp-component-macro-lib/src/getter_component/with_provider.rs new file mode 100644 index 0000000..66e0633 --- /dev/null +++ b/crates/cgp-component-macro-lib/src/getter_component/with_provider.rs @@ -0,0 +1,56 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::ItemTrait; + +use crate::derive_component::component_spec::ComponentSpec; +use crate::getter_component::getter_field::GetterField; + +pub fn derive_with_provider_impl( + spec: &ComponentSpec, + consumer_trait: &ItemTrait, + field: &GetterField, +) -> TokenStream { + let component_name = &spec.component_name; + let context_type = &spec.context_type; + let provider_name = &spec.provider_name; + + // FIXME: replace `Self` with `Context` inside super trait bound + let context_constraints = consumer_trait.supertraits.clone(); + + let field_name = &field.field_name; + let provider_type = &field.provider_type; + + let provider_constraint = if field.field_mut.is_none() { + quote! { + FieldGetter< #context_type, #component_name, Value = #provider_type > + } + } else { + quote! { + MutFieldGetter< #context_type, #component_name, Value = #provider_type > + } + }; + + let method = if field.field_mut.is_none() { + quote! { + fn #field_name( context: & #context_type ) -> & #provider_type { + Provider::get_field(context, ::core::marker::PhantomData ) + } + } + } else { + quote! { + fn #field_name( context: &mut #context_type ) -> &mut #provider_type { + Provider::get_field_mut(context, ::core::marker::PhantomData ) + } + } + }; + + quote! { + impl< #context_type, Provider > #provider_name < #context_type > for WithProvider + where + #context_type: #context_constraints, + Provider: #provider_constraint, + { + #method + } + } +} diff --git a/crates/cgp-component-macro-lib/src/lib.rs b/crates/cgp-component-macro-lib/src/lib.rs index b2dc1a9..6534923 100644 --- a/crates/cgp-component-macro-lib/src/lib.rs +++ b/crates/cgp-component-macro-lib/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; pub mod delegate_components; pub mod derive_component; pub mod for_each_replace; +pub mod getter_component; pub mod preset; pub mod type_component; @@ -21,5 +22,6 @@ mod tests; 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::getter_component::derive::{derive_auto_getter_component, derive_getter_component}; pub use crate::preset::define_preset; pub use crate::type_component::derive::derive_type_component; diff --git a/crates/cgp-component-macro-lib/src/tests/derive_component.rs b/crates/cgp-component-macro-lib/src/tests/derive_component.rs index 8ce2171..9ed0872 100644 --- a/crates/cgp-component-macro-lib/src/tests/derive_component.rs +++ b/crates/cgp-component-macro-lib/src/tests/derive_component.rs @@ -17,7 +17,8 @@ fn test_basic_derive_component() { fn foo(&self) -> Self::Foo; } }, - ); + ) + .unwrap(); } #[test] @@ -34,7 +35,8 @@ fn test_derive_component_with_const_generic() { fn foo(&self) -> Self::Foo; } }, - ); + ) + .unwrap(); let expected = quote! { pub trait HasFoo { diff --git a/crates/cgp-component-macro-lib/src/type_component/derive.rs b/crates/cgp-component-macro-lib/src/type_component/derive.rs index 629d950..58fd246 100644 --- a/crates/cgp-component-macro-lib/src/type_component/derive.rs +++ b/crates/cgp-component-macro-lib/src/type_component/derive.rs @@ -1,5 +1,6 @@ use alloc::format; use alloc::vec::Vec; + use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; diff --git a/crates/cgp-component-macro/Cargo.toml b/crates/cgp-component-macro/Cargo.toml index 02e3542..7052328 100644 --- a/crates/cgp-component-macro/Cargo.toml +++ b/crates/cgp-component-macro/Cargo.toml @@ -15,4 +15,5 @@ description = """ proc-macro = true [dependencies] +syn = { version = "2.0.95", features = [ "full", "extra-traits" ] } cgp-component-macro-lib = { version = "0.2.0" } \ No newline at end of file diff --git a/crates/cgp-component-macro/src/lib.rs b/crates/cgp-component-macro/src/lib.rs index 4491644..7ea0904 100644 --- a/crates/cgp-component-macro/src/lib.rs +++ b/crates/cgp-component-macro/src/lib.rs @@ -10,40 +10,56 @@ use proc_macro::TokenStream; #[proc_macro_attribute] pub fn cgp_component(attr: TokenStream, item: TokenStream) -> TokenStream { - cgp_component_macro_lib::derive_component(attr.into(), item.into()).into() + cgp_component_macro_lib::derive_component(attr.into(), item.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_attribute] +pub fn cgp_getter(attr: TokenStream, item: TokenStream) -> TokenStream { + cgp_component_macro_lib::derive_getter_component(attr.into(), item.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_attribute] +pub fn cgp_auto_getter(attr: TokenStream, item: TokenStream) -> TokenStream { + cgp_component_macro_lib::derive_auto_getter_component(attr.into(), item.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[proc_macro] pub fn delegate_components(body: TokenStream) -> TokenStream { cgp_component_macro_lib::delegate_components(body.into()) - .unwrap() + .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro] pub fn cgp_preset(body: TokenStream) -> TokenStream { cgp_component_macro_lib::define_preset(body.into()) - .unwrap() + .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro] pub fn cgp_type(body: TokenStream) -> TokenStream { cgp_component_macro_lib::derive_type_component(body.into()) - .unwrap() + .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro] pub fn for_each_replace(body: TokenStream) -> TokenStream { cgp_component_macro_lib::handle_for_each_replace(body.into()) - .unwrap() + .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro] pub fn replace_with(body: TokenStream) -> TokenStream { cgp_component_macro_lib::handle_replace(body.into()) - .unwrap() + .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/crates/cgp-component/src/lib.rs b/crates/cgp-component/src/lib.rs index 76e55a0..3e46720 100644 --- a/crates/cgp-component/src/lib.rs +++ b/crates/cgp-component/src/lib.rs @@ -8,4 +8,4 @@ mod traits; mod types; pub use traits::{DelegateComponent, HasComponents}; -pub use types::{UseContext, UseDelegate, WithContext, WithProvider}; +pub use types::{UseContext, UseDelegate, UseFields, WithContext, WithProvider}; diff --git a/crates/cgp-component/src/types/mod.rs b/crates/cgp-component/src/types/mod.rs index b925781..e2d83e9 100644 --- a/crates/cgp-component/src/types/mod.rs +++ b/crates/cgp-component/src/types/mod.rs @@ -1,7 +1,9 @@ mod use_context; mod use_delegate; +mod use_fields; mod with_provider; pub use use_context::{UseContext, WithContext}; pub use use_delegate::UseDelegate; +pub use use_fields::UseFields; pub use with_provider::WithProvider; diff --git a/crates/cgp-component/src/types/use_field.rs b/crates/cgp-component/src/types/use_field.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/cgp-component/src/types/use_fields.rs b/crates/cgp-component/src/types/use_fields.rs new file mode 100644 index 0000000..9e18e09 --- /dev/null +++ b/crates/cgp-component/src/types/use_fields.rs @@ -0,0 +1 @@ +pub struct UseFields; diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index ffaef2d..42a3c1c 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -1,9 +1,12 @@ pub use cgp_async::{async_trait, Async, MaybeSend, MaybeStatic, MaybeSync}; -pub use cgp_component::{DelegateComponent, HasComponents, WithContext, WithProvider}; +pub use cgp_component::{DelegateComponent, HasComponents, UseFields, WithContext, WithProvider}; pub use cgp_component_macro::{ - cgp_component, cgp_preset, cgp_type, delegate_components, for_each_replace, replace_with, + cgp_auto_getter, cgp_component, cgp_getter, 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::{ + Char, Cons, Either, FieldGetter, HasField, HasFieldMut, MutFieldGetter, Nil, UseField, Void, +}; pub use cgp_field_macro::{product, symbol, HasField, Product, Sum}; pub use cgp_type::{HasType, ProvideType, UseType}; diff --git a/crates/cgp-error-anyhow/src/impls/debug_error.rs b/crates/cgp-error-anyhow/src/impls/debug_error.rs index e04dd63..75d6928 100644 --- a/crates/cgp-error-anyhow/src/impls/debug_error.rs +++ b/crates/cgp-error-anyhow/src/impls/debug_error.rs @@ -1,6 +1,6 @@ +use alloc::format; use core::fmt::Debug; -use alloc::format; use anyhow::{anyhow, Error}; use cgp_core::error::{ErrorRaiser, ErrorWrapper}; use cgp_core::prelude::*; diff --git a/crates/cgp-error-eyre/src/impls/debug_error.rs b/crates/cgp-error-eyre/src/impls/debug_error.rs index d44b3b0..abbe4ce 100644 --- a/crates/cgp-error-eyre/src/impls/debug_error.rs +++ b/crates/cgp-error-eyre/src/impls/debug_error.rs @@ -1,6 +1,6 @@ +use alloc::format; use core::fmt::Debug; -use alloc::format; use cgp_core::error::{ErrorRaiser, ErrorWrapper}; use cgp_core::prelude::*; use eyre::{eyre, Error}; diff --git a/crates/cgp-error-std/src/types/wrap.rs b/crates/cgp-error-std/src/types/wrap.rs index fa2e1d9..4e8675c 100644 --- a/crates/cgp-error-std/src/types/wrap.rs +++ b/crates/cgp-error-std/src/types/wrap.rs @@ -1,8 +1,7 @@ +use alloc::string::String; use core::error::Error as StdError; use core::fmt::{Debug, Display}; -use alloc::string::String; - use crate::Error; pub struct WrapError { diff --git a/crates/cgp-runtime/src/traits/has_runtime.rs b/crates/cgp-runtime/src/traits/has_runtime.rs index e1f70c7..387c1ba 100644 --- a/crates/cgp-runtime/src/traits/has_runtime.rs +++ b/crates/cgp-runtime/src/traits/has_runtime.rs @@ -1,24 +1,13 @@ -use core::marker::PhantomData; - use cgp_core::component::WithProvider; use cgp_core::field::FieldGetter; use cgp_core::prelude::*; use crate::HasRuntimeType; -#[cgp_component { +#[cgp_getter { + context: App, provider: RuntimeGetter, }] pub trait HasRuntime: HasRuntimeType { fn runtime(&self) -> &Self::Runtime; } - -impl RuntimeGetter for WithProvider -where - Context: HasRuntimeType, - Provider: FieldGetter, -{ - fn runtime(context: &Context) -> &Runtime { - Provider::get_field(context, PhantomData) - } -}