diff --git a/Cargo.lock b/Cargo.lock index 14945d3c..bd3a639d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,7 @@ name = "cgp-error" version = "0.7.0" dependencies = [ "cgp-component", + "cgp-field", "cgp-macro", "cgp-type", ] diff --git a/crates/cgp-component/src/lib.rs b/crates/cgp-component/src/lib.rs index 0785c757..a92a79a1 100644 --- a/crates/cgp-component/src/lib.rs +++ b/crates/cgp-component/src/lib.rs @@ -5,8 +5,13 @@ CGP component implementation. */ +mod namespaces; mod traits; mod types; +pub use namespaces::DefaultNamespace; pub use traits::{CanUseComponent, DelegateComponent, IsProviderFor}; -pub use types::{UseContext, UseDelegate, UseFields, WithContext, WithProvider}; +pub use types::{ + ConcatPath, PathCons, PathNil, RedirectLookup, UseContext, UseDefault, UseDelegate, UseFields, + WithContext, WithProvider, +}; diff --git a/crates/cgp-component/src/namespaces.rs b/crates/cgp-component/src/namespaces.rs new file mode 100644 index 00000000..3c8e4bf5 --- /dev/null +++ b/crates/cgp-component/src/namespaces.rs @@ -0,0 +1,3 @@ +pub trait DefaultNamespace { + type Provider; +} diff --git a/crates/cgp-component/src/types/mod.rs b/crates/cgp-component/src/types/mod.rs index e2d83e97..56890656 100644 --- a/crates/cgp-component/src/types/mod.rs +++ b/crates/cgp-component/src/types/mod.rs @@ -1,9 +1,15 @@ +mod path; +mod redirect_lookup; mod use_context; +mod use_default; mod use_delegate; mod use_fields; mod with_provider; +pub use path::{ConcatPath, PathCons, PathNil}; +pub use redirect_lookup::RedirectLookup; pub use use_context::{UseContext, WithContext}; +pub use use_default::UseDefault; pub use use_delegate::UseDelegate; pub use use_fields::UseFields; pub use with_provider::WithProvider; diff --git a/crates/cgp-component/src/types/path.rs b/crates/cgp-component/src/types/path.rs new file mode 100644 index 00000000..c09902af --- /dev/null +++ b/crates/cgp-component/src/types/path.rs @@ -0,0 +1,20 @@ +use core::marker::PhantomData; + +pub struct PathCons(pub PhantomData, pub PhantomData); + +pub struct PathNil; + +pub trait ConcatPath { + type Output: ?Sized; +} + +impl ConcatPath for PathCons +where + Tail: ConcatPath, +{ + type Output = PathCons>::Output>; +} + +impl ConcatPath for PathNil { + type Output = Other; +} diff --git a/crates/cgp-component/src/types/redirect_lookup.rs b/crates/cgp-component/src/types/redirect_lookup.rs new file mode 100644 index 00000000..dbc117e5 --- /dev/null +++ b/crates/cgp-component/src/types/redirect_lookup.rs @@ -0,0 +1,3 @@ +use core::marker::PhantomData; + +pub struct RedirectLookup(pub PhantomData<(Key, Components)>); diff --git a/crates/cgp-component/src/types/use_default.rs b/crates/cgp-component/src/types/use_default.rs new file mode 100644 index 00000000..f6a2e318 --- /dev/null +++ b/crates/cgp-component/src/types/use_default.rs @@ -0,0 +1 @@ +pub struct UseDefault; diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index 1476edb3..cc2321ab 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -2,8 +2,8 @@ pub use core::marker::PhantomData; pub use cgp_async_macro::async_trait; pub use cgp_component::{ - CanUseComponent, DelegateComponent, IsProviderFor, UseContext, UseDelegate, UseFields, - WithContext, WithProvider, + CanUseComponent, ConcatPath, DefaultNamespace, DelegateComponent, IsProviderFor, PathCons, + PathNil, RedirectLookup, UseContext, UseDelegate, UseFields, WithContext, WithProvider, }; pub use cgp_error::{CanRaiseError, CanWrapError, HasErrorType}; pub use cgp_field::impls::{IsMut, IsNothing, IsPresent, IsRef, IsVoid, UseField}; diff --git a/crates/cgp-error/Cargo.toml b/crates/cgp-error/Cargo.toml index c4fb9648..061de754 100644 --- a/crates/cgp-error/Cargo.toml +++ b/crates/cgp-error/Cargo.toml @@ -15,3 +15,4 @@ description = """ cgp-component = { workspace = true } cgp-macro = { workspace = true } cgp-type = { workspace = true } +cgp-field = { workspace = true } diff --git a/crates/cgp-error/src/traits/can_raise_error.rs b/crates/cgp-error/src/traits/can_raise_error.rs index be617d30..835592af 100644 --- a/crates/cgp-error/src/traits/can_raise_error.rs +++ b/crates/cgp-error/src/traits/can_raise_error.rs @@ -1,4 +1,4 @@ -use cgp_component::{DelegateComponent, IsProviderFor, UseContext, UseDelegate}; +use cgp_component::*; use cgp_macro::cgp_component; use crate::traits::has_error_type::HasErrorType; diff --git a/crates/cgp-error/src/traits/can_wrap_error.rs b/crates/cgp-error/src/traits/can_wrap_error.rs index 7dfdf0de..a28b03a0 100644 --- a/crates/cgp-error/src/traits/can_wrap_error.rs +++ b/crates/cgp-error/src/traits/can_wrap_error.rs @@ -1,4 +1,4 @@ -use cgp_component::{DelegateComponent, IsProviderFor, UseContext, UseDelegate}; +use cgp_component::*; use cgp_macro::cgp_component; use crate::traits::HasErrorType; diff --git a/crates/cgp-error/src/traits/has_error_type.rs b/crates/cgp-error/src/traits/has_error_type.rs index 32e8fe11..c02beb4a 100644 --- a/crates/cgp-error/src/traits/has_error_type.rs +++ b/crates/cgp-error/src/traits/has_error_type.rs @@ -1,6 +1,10 @@ use core::fmt::Debug; -use cgp_component::{DelegateComponent, IsProviderFor, UseContext, WithProvider}; +use cgp_component::{ + DefaultNamespace, DelegateComponent, IsProviderFor, PathCons, PathNil, RedirectLookup, + UseContext, WithProvider, +}; +use cgp_field::types::*; use cgp_macro::cgp_type; use cgp_type::{TypeProvider, UseType}; @@ -24,6 +28,7 @@ use cgp_type::{TypeProvider, UseType}; */ #[cgp_type] +#[use_namespace(cgp.core.error)] pub trait HasErrorType { type Error: Debug; } diff --git a/crates/cgp-field/src/traits/append_product.rs b/crates/cgp-field/src/traits/append_product.rs new file mode 100644 index 00000000..a2aae98c --- /dev/null +++ b/crates/cgp-field/src/traits/append_product.rs @@ -0,0 +1,16 @@ +use crate::types::{Cons, Nil}; + +pub trait AppendProduct { + type Output; +} + +impl AppendProduct for Nil { + type Output = Cons; +} + +impl AppendProduct for Cons +where + Tail: AppendProduct, +{ + type Output = Cons; +} diff --git a/crates/cgp-field/src/traits/concat_product.rs b/crates/cgp-field/src/traits/concat_product.rs new file mode 100644 index 00000000..39d01911 --- /dev/null +++ b/crates/cgp-field/src/traits/concat_product.rs @@ -0,0 +1,16 @@ +use crate::types::{Cons, Nil}; + +pub trait ConcatProduct { + type Output; +} + +impl ConcatProduct for Nil { + type Output = Items; +} + +impl ConcatProduct for Cons +where + Tail: ConcatProduct, +{ + type Output = Cons; +} diff --git a/crates/cgp-field/src/traits/mod.rs b/crates/cgp-field/src/traits/mod.rs index dfdec21b..63d13dc7 100644 --- a/crates/cgp-field/src/traits/mod.rs +++ b/crates/cgp-field/src/traits/mod.rs @@ -1,4 +1,6 @@ +mod append_product; mod build_field; +mod concat_product; mod extract_field; mod from_fields; mod from_variant; @@ -18,7 +20,9 @@ mod to_fields; mod transform_map; mod update_field; +pub use append_product::*; pub use build_field::*; +pub use concat_product::*; pub use extract_field::*; pub use from_fields::*; pub use from_variant::*; diff --git a/crates/cgp-macro-lib/src/attributes/mod.rs b/crates/cgp-macro-lib/src/attributes/mod.rs new file mode 100644 index 00000000..98a06299 --- /dev/null +++ b/crates/cgp-macro-lib/src/attributes/mod.rs @@ -0,0 +1,3 @@ +mod use_namespace; + +pub use use_namespace::*; diff --git a/crates/cgp-macro-lib/src/attributes/use_namespace.rs b/crates/cgp-macro-lib/src/attributes/use_namespace.rs new file mode 100644 index 00000000..35199567 --- /dev/null +++ b/crates/cgp-macro-lib/src/attributes/use_namespace.rs @@ -0,0 +1,26 @@ +use syn::Ident; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::{Colon, Dot}; + +use crate::parse::PathType; + +pub struct UseNamespaceAttribute { + pub namespace: Ident, + pub path: Punctuated, +} + +impl Parse for UseNamespaceAttribute { + fn parse(input: ParseStream) -> syn::Result { + let namespace = if input.peek2(Colon) { + let namespace = input.parse()?; + let _: Colon = input.parse()?; + namespace + } else { + Ident::new("DefaultNamespace", input.span()) + }; + + let path = Punctuated::parse_separated_nonempty(input)?; + Ok(UseNamespaceAttribute { namespace, path }) + } +} diff --git a/crates/cgp-macro-lib/src/cgp_fn/bounds.rs b/crates/cgp-macro-lib/src/cgp_fn/bounds.rs index 76ec19c0..17b90993 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/bounds.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/bounds.rs @@ -13,7 +13,7 @@ pub fn build_implicit_args_bounds( let mut constraints: Punctuated = Punctuated::new(); for arg in implicit_args { - let field_symbol = symbol_from_string(&arg.field_name.to_string()); + let field_symbol = symbol_from_string(&arg.field_name.to_string())?; let constraint = derive_getter_constraint( &arg.field_type, diff --git a/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs index f4919e74..20684e17 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs @@ -16,7 +16,7 @@ pub fn inject_implicit_arg(arg: &ImplicitArgField, body: &mut Block) -> syn::Res let field_name = &arg.field_name; let arg_type = &arg.arg_type; - let field_symbol = symbol_from_string(&field_name.to_string()); + let field_symbol = symbol_from_string(&field_name.to_string())?; let call_expr = if arg.field_mut.is_none() { quote! { diff --git a/crates/cgp-macro-lib/src/delegate_components/attributes.rs b/crates/cgp-macro-lib/src/delegate_components/attributes.rs new file mode 100644 index 00000000..f5174e89 --- /dev/null +++ b/crates/cgp-macro-lib/src/delegate_components/attributes.rs @@ -0,0 +1,48 @@ +use syn::{Attribute, Ident, Meta}; + +pub fn parse_delegate_attributes(attributes: Vec) -> syn::Result { + let mut parsed_attributes = DelegateAttributes::default(); + + for attribute in attributes.iter() { + if let Some(ident) = attribute.path().get_ident() { + if ident == "use_namespace" { + if parsed_attributes.use_namespace.is_some() { + return Err(syn::Error::new_spanned( + attribute, + "Multiple #[use_namespace] attributes are not allowed", + )); + } + + if let Meta::Path(_) = attribute.meta { + parsed_attributes.use_namespace = + Some(DelegateNamespaceAttribute { namespace: None }); + } else { + let namespace = attribute.parse_args::>()?; + parsed_attributes.use_namespace = + Some(DelegateNamespaceAttribute { namespace }); + } + } else { + return Err(syn::Error::new_spanned( + attribute, + format!("Unknown attribute {} for delegate_components", ident), + )); + } + } else { + return Err(syn::Error::new_spanned( + attribute, + "Unexpected attribute format for delegate_components", + )); + } + } + + Ok(parsed_attributes) +} + +#[derive(Default)] +pub struct DelegateAttributes { + pub use_namespace: Option, +} + +pub struct DelegateNamespaceAttribute { + pub namespace: Option, +} diff --git a/crates/cgp-macro-lib/src/delegate_components/derive_namespace.rs b/crates/cgp-macro-lib/src/delegate_components/derive_namespace.rs new file mode 100644 index 00000000..071dc720 --- /dev/null +++ b/crates/cgp-macro-lib/src/delegate_components/derive_namespace.rs @@ -0,0 +1,49 @@ +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Generics, Ident, Type, parse2}; + +pub fn derive_namespace_delegate( + namespace: Option, + target_type: &Type, + target_generics: &Generics, +) -> syn::Result { + let namespace = namespace.unwrap_or_else(|| Ident::new("DefaultNamespace", Span::call_site())); + + let mut generics = target_generics.clone(); + generics.params.push(parse2(quote! { __Component__ })?); + + let impl_generics = generics.split_for_impl().0; + + let namespace_impl = quote! { + impl #impl_generics + DelegateComponent<__Component__> + for #target_type + where + __Component__: #namespace< #target_type >, + { + type Delegate = < __Component__ as #namespace< #target_type >>::Provider; + } + }; + + let mut generics = generics.clone(); + generics.params.push(parse2(quote! { __Context__ })?); + generics.params.push(parse2(quote! { __Params__ })?); + + let impl_generics = generics.split_for_impl().0; + + let is_provider_for_impl = quote! { + impl #impl_generics + IsProviderFor<__Component__, __Context__, __Params__> + for #target_type + where + __Component__: #namespace< #target_type >, + < __Component__ as #namespace< #target_type >>::Provider: IsProviderFor<__Component__, __Context__, __Params__>, + { + } + }; + + Ok(quote! { + #namespace_impl + #is_provider_for_impl + }) +} diff --git a/crates/cgp-macro-lib/src/delegate_components/mod.rs b/crates/cgp-macro-lib/src/delegate_components/mod.rs index 6a725623..3d9674c5 100644 --- a/crates/cgp-macro-lib/src/delegate_components/mod.rs +++ b/crates/cgp-macro-lib/src/delegate_components/mod.rs @@ -1,6 +1,10 @@ +mod attributes; mod define_struct; +mod derive_namespace; mod impl_delegate; mod merge_generics; +pub use attributes::*; pub use define_struct::*; +pub use derive_namespace::*; pub use impl_delegate::*; diff --git a/crates/cgp-macro-lib/src/derive_builder/utils.rs b/crates/cgp-macro-lib/src/derive_builder/utils.rs index 20823af9..07f86f90 100644 --- a/crates/cgp-macro-lib/src/derive_builder/utils.rs +++ b/crates/cgp-macro-lib/src/derive_builder/utils.rs @@ -26,7 +26,7 @@ pub fn field_to_member(index: usize, field: &Field) -> Member { pub fn field_to_tag(index: usize, field: &Field) -> syn::Result { match &field.ident { - Some(ident) => Ok(symbol_from_string(&ident.to_string())), + Some(ident) => symbol_from_string(&ident.to_string()), None => { let index = LitInt::new(&format!("{index}"), field.span()); parse2(quote! { δ< #index > }) diff --git a/crates/cgp-macro-lib/src/derive_component/attributes.rs b/crates/cgp-macro-lib/src/derive_component/attributes.rs index 1b3c4ce2..1ddee85d 100644 --- a/crates/cgp-macro-lib/src/derive_component/attributes.rs +++ b/crates/cgp-macro-lib/src/derive_component/attributes.rs @@ -4,6 +4,7 @@ use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{Attribute, TypeParamBound}; +use crate::attributes::UseNamespaceAttribute; use crate::cgp_fn::UseTypeSpec; pub fn parse_component_attributes( @@ -35,6 +36,11 @@ pub fn parse_component_attributes( } parsed_attributes.use_type.extend(use_type_specs); + } else if ident == "use_namespace" { + let use_namespace_specs = attribute.parse_args_with( + Punctuated::::parse_terminated, + )?; + parsed_attributes.use_namespace.extend(use_namespace_specs); } else { attributes.push(attribute); } @@ -50,4 +56,5 @@ pub fn parse_component_attributes( pub struct ComponentAttributes { pub extend: Vec, pub use_type: Vec, + pub use_namespace: Vec, } diff --git a/crates/cgp-macro-lib/src/derive_component/derive.rs b/crates/cgp-macro-lib/src/derive_component/derive.rs index be02ae26..75c0324a 100644 --- a/crates/cgp-macro-lib/src/derive_component/derive.rs +++ b/crates/cgp-macro-lib/src/derive_component/derive.rs @@ -2,8 +2,11 @@ use proc_macro2::TokenStream; use quote::{ToTokens, TokenStreamExt, quote}; use syn::{ItemImpl, ItemStruct, ItemTrait, parse2}; +use crate::derive_component::attributes::parse_component_attributes; use crate::derive_component::component_name::derive_component_name_struct; use crate::derive_component::consumer_impl::derive_consumer_impl; +use crate::derive_component::derive_namespace::derive_namespace_impls; +use crate::derive_component::derive_redirect_lookup::derive_redirect_lookup_impl; use crate::derive_component::preprocess_consumer_trait; use crate::derive_component::provider_impl::derive_provider_impl; use crate::derive_component::provider_trait::derive_provider_trait; @@ -22,7 +25,9 @@ pub fn derive_component_with_ast( let component_name = &spec.component_name; let component_params = &spec.component_params; - preprocess_consumer_trait(&mut consumer_trait)?; + let attributes = parse_component_attributes(&mut consumer_trait.attrs)?; + + preprocess_consumer_trait(&mut consumer_trait, &attributes)?; let component_struct = derive_component_name_struct(component_name, component_params)?; @@ -53,11 +58,21 @@ pub fn derive_component_with_ast( &use_context_impl, )?; + let redirect_lookup_impl = derive_redirect_lookup_impl(&consumer_trait, &provider_trait)?; + let redirect_lookup_is_provider_impl = derive_is_provider_for( + &parse2(quote! { + #component_name < #component_params > + })?, + &redirect_lookup_impl, + )?; + let mut item_impls = vec![ provider_impl, consumer_impl, use_context_impl, use_context_is_provider_impl, + redirect_lookup_impl, + redirect_lookup_is_provider_impl, ]; if !spec.use_delegate_spec.is_empty() { @@ -76,6 +91,9 @@ pub fn derive_component_with_ast( } } + let namespace_impls = derive_namespace_impls(&attributes.use_namespace, component_name)?; + item_impls.extend(namespace_impls); + let derived = DerivedComponent { component_struct, consumer_trait, diff --git a/crates/cgp-macro-lib/src/derive_component/derive_namespace.rs b/crates/cgp-macro-lib/src/derive_component/derive_namespace.rs new file mode 100644 index 00000000..6229f5af --- /dev/null +++ b/crates/cgp-macro-lib/src/derive_component/derive_namespace.rs @@ -0,0 +1,49 @@ +use quote::{ToTokens, quote}; +use syn::{Ident, ItemImpl, Type, parse2}; + +use crate::attributes::UseNamespaceAttribute; + +pub fn derive_namespace_impls( + attributes: &[UseNamespaceAttribute], + component_name: &Ident, +) -> syn::Result> { + let mut out = Vec::new(); + + for attribute in attributes { + out.push(derive_namespace_impl(attribute, component_name)?); + } + + Ok(out) +} + +pub fn derive_namespace_impl( + attribute: &UseNamespaceAttribute, + component_name: &Ident, +) -> syn::Result { + let namespace = &attribute.namespace; + let mut paths = Vec::from_iter(attribute.path.iter().map(|path| path.path_type.clone())); + paths.push(parse2(component_name.to_token_stream())?); + + let path = path_to_product(&paths)?; + + let out = quote! { + impl<__Components__> #namespace < __Components__ > for #component_name + { + type Provider = RedirectLookup< __Components__, #path >; + } + }; + + parse2(out) +} + +pub fn path_to_product(paths: &[Type]) -> syn::Result { + let mut out = quote! { PathNil }; + + for path in paths.iter().rev() { + out = quote! { + PathCons< #path , #out > + }; + } + + parse2(out) +} diff --git a/crates/cgp-macro-lib/src/derive_component/derive_redirect_lookup.rs b/crates/cgp-macro-lib/src/derive_component/derive_redirect_lookup.rs new file mode 100644 index 00000000..ea294f3f --- /dev/null +++ b/crates/cgp-macro-lib/src/derive_component/derive_redirect_lookup.rs @@ -0,0 +1,99 @@ +use quote::quote; +use syn::token::{Brace, For, Impl}; +use syn::{GenericParam, Generics, ItemImpl, ItemTrait, Path, Type, parse2}; + +use crate::derive_component::provider_impl::derive_provider_item_impls; + +pub fn derive_redirect_lookup_impl( + consumer_trait: &ItemTrait, + provider_trait: &ItemTrait, +) -> syn::Result { + let provider_name = &provider_trait.ident; + let provider_type_generics = provider_trait.generics.split_for_impl().1; + + let generic_params = extract_type_generics(&consumer_trait.generics)?; + + let mut impl_generics = provider_trait.generics.clone(); + + impl_generics + .params + .push(parse2(quote! { __Components__ })?); + + impl_generics.params.push(parse2(quote! { __Path__ })?); + + let where_clause = impl_generics.make_where_clause(); + + let delegate_constraint = if let Some(generic_params) = &generic_params { + where_clause.predicates.push(parse2(quote! { + __Path__: ConcatPath< #generic_params > + })?); + + quote! { + DelegateComponent<<__Path__ as ConcatPath< #generic_params >>::Output> + } + } else { + quote! { + DelegateComponent<__Path__> + } + }; + + where_clause.predicates.push(parse2(quote! { + __Components__: #delegate_constraint + })?); + + let delegate_type = quote! { + < __Components__ as #delegate_constraint > :: Delegate + }; + + where_clause.predicates.push(parse2(quote! { + #delegate_type : #provider_name #provider_type_generics + })?); + + let impl_items = derive_provider_item_impls(provider_trait, &delegate_type)?; + + let self_type = parse2(quote!(RedirectLookup<__Components__, __Path__>))?; + + let trait_path: Path = parse2(quote!( #provider_name #provider_type_generics ))?; + + let item = ItemImpl { + attrs: provider_trait.attrs.clone(), + defaultness: None, + unsafety: provider_trait.unsafety, + impl_token: Impl::default(), + generics: impl_generics, + trait_: Some((None, trait_path, For::default())), + self_ty: Box::new(self_type), + brace_token: Brace::default(), + items: impl_items, + }; + + Ok(item) +} + +pub fn extract_type_generics(generics: &Generics) -> syn::Result> { + let type_params = generics + .params + .iter() + .filter_map(|param| { + if let GenericParam::Type(type_param) = param { + Some(type_param.ident.clone()) + } else { + None + } + }) + .collect::>(); + + if type_params.is_empty() { + Ok(None) + } else { + let mut out = quote! { PathNil }; + + for param in type_params.iter().rev() { + out = quote! { + PathCons< #param , #out > + }; + } + + Ok(Some(parse2(out)?)) + } +} diff --git a/crates/cgp-macro-lib/src/derive_component/mod.rs b/crates/cgp-macro-lib/src/derive_component/mod.rs index 314c34fc..24f59d93 100644 --- a/crates/cgp-macro-lib/src/derive_component/mod.rs +++ b/crates/cgp-macro-lib/src/derive_component/mod.rs @@ -4,6 +4,8 @@ mod consumer_impl; mod delegate_fn; mod delegate_type; mod derive; +mod derive_namespace; +mod derive_redirect_lookup; mod preprocess; mod provider_impl; mod provider_trait; @@ -11,5 +13,6 @@ mod signature_args; mod use_context_impl; mod use_delegate_impl; +pub use attributes::*; pub use derive::*; pub use preprocess::*; diff --git a/crates/cgp-macro-lib/src/derive_component/preprocess.rs b/crates/cgp-macro-lib/src/derive_component/preprocess.rs index 93078214..26d14ca6 100644 --- a/crates/cgp-macro-lib/src/derive_component/preprocess.rs +++ b/crates/cgp-macro-lib/src/derive_component/preprocess.rs @@ -1,11 +1,12 @@ use syn::ItemTrait; use crate::cgp_fn::expand_use_type_attributes_on_trait; -use crate::derive_component::attributes::parse_component_attributes; - -pub fn preprocess_consumer_trait(consumer_trait: &mut ItemTrait) -> syn::Result<()> { - let attributes = parse_component_attributes(&mut consumer_trait.attrs)?; +use crate::derive_component::attributes::ComponentAttributes; +pub fn preprocess_consumer_trait( + consumer_trait: &mut ItemTrait, + attributes: &ComponentAttributes, +) -> syn::Result<()> { consumer_trait.supertraits.extend(attributes.extend.clone()); if !attributes.use_type.is_empty() { diff --git a/crates/cgp-macro-lib/src/derive_component/provider_impl.rs b/crates/cgp-macro-lib/src/derive_component/provider_impl.rs index 09e9ce33..3dcbe882 100644 --- a/crates/cgp-macro-lib/src/derive_component/provider_impl.rs +++ b/crates/cgp-macro-lib/src/derive_component/provider_impl.rs @@ -1,7 +1,7 @@ use alloc::boxed::Box; use alloc::vec::Vec; -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::punctuated::Punctuated; use syn::spanned::Spanned; @@ -13,7 +13,7 @@ use syn::{ use crate::derive_component::delegate_fn::derive_delegated_fn_impl; use crate::derive_component::delegate_type::derive_delegate_type_impl; -use crate::parse::{TypeGenerics, parse_is_provider_params}; +use crate::parse::parse_is_provider_params; pub fn derive_provider_impl( context_type: &Ident, @@ -24,57 +24,86 @@ pub fn derive_provider_impl( ) -> syn::Result { let provider_name = &provider_trait.ident; - let component_type = Ident::new("__Component__", Span::call_site()); + let provider_type = Ident::new("__Provider__", Span::call_site()); - let provider_generic_args = TypeGenerics::try_from(&provider_trait.generics)? - .generics - .params; + let delegate_constraint = quote! { + DelegateComponent< #component_name < #component_params > > + }; + + let delegate_type = quote! { + < #provider_type as #delegate_constraint > :: Delegate + }; + + let provider_type_generics = provider_trait.generics.split_for_impl().1; let impl_generics = { let mut impl_generics = provider_trait.generics.clone(); impl_generics .params - .insert(0, parse2(quote!(#component_type))?); + .insert(0, parse2(quote!(#provider_type))?); { let is_provider_params = parse_is_provider_params(&consumer_trait.generics)?; - let mut delegate_constraint: Punctuated = Punctuated::default(); + let mut delegate_constraints: Punctuated = Punctuated::default(); - delegate_constraint.push(parse2(quote! { - DelegateComponent< #component_name < #component_params > > - })?); + delegate_constraints.push(parse2(delegate_constraint)?); - delegate_constraint.push(parse2(quote!( + delegate_constraints.push(parse2(quote!( IsProviderFor< #component_name < #component_params >, #context_type, ( #is_provider_params ) > ))?); let provider_constraint: TypeParamBound = parse2(quote! { - #provider_name < #provider_generic_args > + #provider_name #provider_type_generics })?; let where_clause = impl_generics.make_where_clause(); where_clause.predicates.push(parse2(quote! { - #component_type : #delegate_constraint + #provider_type : #delegate_constraints })?); where_clause.predicates.push(parse2(quote! { - #component_type :: Delegate : #provider_constraint + #delegate_type : #provider_constraint })?); } impl_generics }; + let impl_items = derive_provider_item_impls(provider_trait, &delegate_type)?; + + let trait_path: Path = parse2(quote!( #provider_name #provider_type_generics ))?; + + let item = ItemImpl { + attrs: provider_trait.attrs.clone(), + defaultness: None, + unsafety: provider_trait.unsafety, + impl_token: Impl::default(), + generics: impl_generics, + trait_: Some((None, trait_path, For::default())), + self_ty: Box::new(parse2(quote!(#provider_type))?), + brace_token: Brace::default(), + items: impl_items, + }; + + Ok(item) +} + +pub fn derive_provider_item_impls( + provider_trait: &ItemTrait, + delegate_type: &TokenStream, +) -> syn::Result> { + let provider_name = &provider_trait.ident; + let provider_type_generics = provider_trait.generics.split_for_impl().1; + let mut impl_items: Vec = Vec::new(); for trait_item in provider_trait.items.iter() { match &trait_item { TraitItem::Fn(trait_fn) => { - let impl_fn = - derive_delegated_fn_impl(&trait_fn.sig, "e!(#component_type :: Delegate))?; + let impl_fn = derive_delegated_fn_impl(&trait_fn.sig, delegate_type)?; impl_items.push(ImplItem::Fn(impl_fn)) } @@ -97,7 +126,7 @@ pub fn derive_provider_impl( let impl_type = derive_delegate_type_impl( trait_type, parse2(quote!( - < #component_type :: Delegate as #provider_name < #provider_generic_args > > :: #type_name #type_generics + < #delegate_type as #provider_name #provider_type_generics > :: #type_name #type_generics ))?, ); @@ -108,7 +137,7 @@ pub fn derive_provider_impl( let (_, type_generics, _) = trait_item_const.generics.split_for_impl(); let impl_expr = parse2(quote! { - < #component_type :: Delegate as #provider_name < #provider_generic_args > > :: #const_ident #type_generics + < #delegate_type as #provider_name #provider_type_generics > :: #const_ident #type_generics })?; let impl_item_const = ImplItemConst { @@ -136,19 +165,5 @@ pub fn derive_provider_impl( } } - let trait_path: Path = parse2(quote!( #provider_name < #provider_generic_args > ))?; - - let item = ItemImpl { - attrs: provider_trait.attrs.clone(), - defaultness: None, - unsafety: provider_trait.unsafety, - impl_token: Impl::default(), - generics: impl_generics, - trait_: Some((None, trait_path, For::default())), - self_ty: Box::new(parse2(quote!(#component_type))?), - brace_token: Brace::default(), - items: impl_items, - }; - - Ok(item) + Ok(impl_items) } diff --git a/crates/cgp-macro-lib/src/derive_extractor/extract_field_impls.rs b/crates/cgp-macro-lib/src/derive_extractor/extract_field_impls.rs index e9682841..ccb941aa 100644 --- a/crates/cgp-macro-lib/src/derive_extractor/extract_field_impls.rs +++ b/crates/cgp-macro-lib/src/derive_extractor/extract_field_impls.rs @@ -83,7 +83,7 @@ pub fn derive_extract_field_impls( } }; - let tag_type = symbol_from_string(¤t_variant.ident.to_string()); + let tag_type = symbol_from_string(¤t_variant.ident.to_string())?; let source_type: Type = parse2(quote! { #extractor_ident < #source_generic_args > diff --git a/crates/cgp-macro-lib/src/derive_getter/blanket.rs b/crates/cgp-macro-lib/src/derive_getter/blanket.rs index 8dc02a9a..730a643e 100644 --- a/crates/cgp-macro-lib/src/derive_getter/blanket.rs +++ b/crates/cgp-macro-lib/src/derive_getter/blanket.rs @@ -64,7 +64,7 @@ pub fn derive_blanket_impl( ), }; - let field_symbol = symbol_from_string(&field.field_name.to_string()); + let field_symbol = symbol_from_string(&field.field_name.to_string())?; let method = derive_getter_method( &context_arg, diff --git a/crates/cgp-macro-lib/src/derive_getter/use_fields.rs b/crates/cgp-macro-lib/src/derive_getter/use_fields.rs index 068122db..73f6849c 100644 --- a/crates/cgp-macro-lib/src/derive_getter/use_fields.rs +++ b/crates/cgp-macro-lib/src/derive_getter/use_fields.rs @@ -55,7 +55,7 @@ pub fn derive_use_fields_impl( ReceiverMode::Type(ty) => ty.to_token_stream(), }; - let field_symbol = symbol_from_string(&field.field_name.to_string()); + let field_symbol = symbol_from_string(&field.field_name.to_string())?; let method = derive_getter_method( &ContextArg::Ident(receiver_type.clone()), diff --git a/crates/cgp-macro-lib/src/derive_has_fields/product.rs b/crates/cgp-macro-lib/src/derive_has_fields/product.rs index 72cbbd83..7ca37440 100644 --- a/crates/cgp-macro-lib/src/derive_has_fields/product.rs +++ b/crates/cgp-macro-lib/src/derive_has_fields/product.rs @@ -15,7 +15,7 @@ pub fn item_fields_to_product_type(fields: &Fields, reference: &TokenStream) -> Error::new_spanned(field, "expect struct field to contain name identifier") })?; - let field_tag = symbol_from_string(&field_name.to_string()); + let field_tag = symbol_from_string(&field_name.to_string())?; let field_type = &field.ty; fields_type = parse2(quote! { diff --git a/crates/cgp-macro-lib/src/derive_has_fields/sum.rs b/crates/cgp-macro-lib/src/derive_has_fields/sum.rs index 9eb29425..8f9ce680 100644 --- a/crates/cgp-macro-lib/src/derive_has_fields/sum.rs +++ b/crates/cgp-macro-lib/src/derive_has_fields/sum.rs @@ -15,7 +15,7 @@ pub fn variants_to_sum_type( for variant in variants.iter().rev() { let variant_ident = &variant.ident; - let variant_symbol = symbol_from_string(&variant_ident.to_string()); + let variant_symbol = symbol_from_string(&variant_ident.to_string())?; let variant_fields = item_fields_to_product_type(&variant.fields, reference)?; diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs index ee627762..d0e0eef6 100644 --- a/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Error, Ident, ItemTrait}; -use crate::derive_component::preprocess_consumer_trait; +use crate::derive_component::{parse_component_attributes, preprocess_consumer_trait}; use crate::derive_getter::{derive_blanket_impl, parse_getter_fields}; pub fn cgp_auto_getter(attr: TokenStream, body: TokenStream) -> syn::Result { @@ -15,7 +15,9 @@ pub fn cgp_auto_getter(attr: TokenStream, body: TokenStream) -> syn::Result syn::Result syn::Result { } pub fn derive_cgp_record_from_struct(item_struct: &ItemStruct) -> syn::Result { - let has_field_impls = derive_has_field_impls_from_struct(item_struct); + let has_field_impls = derive_has_field_impls_from_struct(item_struct)?; let has_fields_impls = derive_has_fields_impls_from_struct(item_struct)?; let build_field_impls = derive_build_field_from_struct(item_struct)?; diff --git a/crates/cgp-macro-lib/src/entrypoints/delegate_components.rs b/crates/cgp-macro-lib/src/entrypoints/delegate_components.rs index 2fe3b8d3..f43a873f 100644 --- a/crates/cgp-macro-lib/src/entrypoints/delegate_components.rs +++ b/crates/cgp-macro-lib/src/entrypoints/delegate_components.rs @@ -1,35 +1,43 @@ use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; +use quote::ToTokens; use syn::parse2; -use crate::delegate_components::{define_struct, impl_delegate_components}; +use crate::delegate_components::{ + DelegateNamespaceAttribute, define_struct, derive_namespace_delegate, impl_delegate_components, + parse_delegate_attributes, +}; use crate::parse::{DelegateComponents, SimpleType, TypeGenerics}; pub fn delegate_components(body: TokenStream) -> syn::Result { let spec: DelegateComponents = parse2(body)?; - let component_struct = if spec.new_struct { - let target_type: SimpleType = parse2(spec.target_type.to_token_stream())?; + let target_type = &spec.target_type; + let target_generics = &spec.target_generics; + + let mut output = TokenStream::new(); + + if spec.new_struct { + let target_type: SimpleType = parse2(target_type.to_token_stream())?; let type_generics = target_type.generics.unwrap_or_default().generics; let component_struct = define_struct(&target_type.name, &type_generics)?; - Some(component_struct) - } else { - None - }; + output.extend(component_struct.to_token_stream()); + } - let impl_items = - impl_delegate_components(&spec.target_type, &spec.target_generics, &spec.entries)?; + let attributes = parse_delegate_attributes(spec.attributes)?; - let mut output = quote! { - #component_struct - }; + if let Some(DelegateNamespaceAttribute { namespace }) = attributes.use_namespace { + let namespace_impl = + derive_namespace_delegate(namespace, target_type, &target_generics.generics)?; - for impl_item in impl_items { - output.extend(impl_item.to_token_stream()); + output.extend(namespace_impl); } + let impl_items = impl_delegate_components(target_type, target_generics, &spec.entries)?; + + output.extend(impl_items); + Ok(output) } diff --git a/crates/cgp-macro-lib/src/entrypoints/derive_from_variant.rs b/crates/cgp-macro-lib/src/entrypoints/derive_from_variant.rs index 0e6c0819..ffcf6bce 100644 --- a/crates/cgp-macro-lib/src/entrypoints/derive_from_variant.rs +++ b/crates/cgp-macro-lib/src/entrypoints/derive_from_variant.rs @@ -20,7 +20,7 @@ pub fn derive_from_variant_from_enum(item_enum: &ItemEnum) -> syn::Result Vec { +pub fn derive_has_field_impls_from_struct(item_struct: &ItemStruct) -> syn::Result> { let struct_ident = &item_struct.ident; let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl(); @@ -20,7 +20,7 @@ pub fn derive_has_field_impls_from_struct(item_struct: &ItemStruct) -> Vec Vec {} } - item_impls + Ok(item_impls) } -pub fn derive_has_field(input: TokenStream) -> TokenStream { - let item_struct: ItemStruct = syn::parse2(input).unwrap(); +pub fn derive_has_field(input: TokenStream) -> syn::Result { + let item_struct: ItemStruct = syn::parse2(input)?; - let item_impls = derive_has_field_impls_from_struct(&item_struct); + let item_impls = derive_has_field_impls_from_struct(&item_struct)?; - quote! { + Ok(quote! { #( #item_impls )* - } + }) } diff --git a/crates/cgp-macro-lib/src/lib.rs b/crates/cgp-macro-lib/src/lib.rs index d43f57d7..2b322b95 100644 --- a/crates/cgp-macro-lib/src/lib.rs +++ b/crates/cgp-macro-lib/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; +pub(crate) mod attributes; pub(crate) mod blanket_trait; pub(crate) mod cgp_fn; pub(crate) mod cgp_impl; diff --git a/crates/cgp-macro-lib/src/parse/delegate_components.rs b/crates/cgp-macro-lib/src/parse/delegate_components.rs index acd18e30..d5a98895 100644 --- a/crates/cgp-macro-lib/src/parse/delegate_components.rs +++ b/crates/cgp-macro-lib/src/parse/delegate_components.rs @@ -5,12 +5,13 @@ use quote::{ToTokens, TokenStreamExt, quote}; use syn::parse::discouraged::Speculative; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::token::{Bracket, Colon, Comma, Gt, Lt, RArrow}; -use syn::{Error, Generics, Ident, Token, Type, braced, bracketed, parse_quote}; +use syn::token::{At, Bracket, Colon, Comma, Gt, Lt, Pound, RArrow}; +use syn::{Attribute, Error, Generics, Ident, Token, Type, braced, bracketed, parse_quote}; -use crate::parse::{ImplGenerics, TypeGenerics}; +use crate::parse::{ComponentPaths, ImplGenerics, SimpleType, TypeGenerics}; pub struct DelegateComponents { + pub attributes: Vec, pub new_struct: bool, pub target_type: Type, pub target_generics: ImplGenerics, @@ -72,6 +73,12 @@ impl DelegateValue { impl Parse for DelegateComponents { fn parse(input: ParseStream) -> syn::Result { + let attributes = if input.peek(Pound) { + input.call(Attribute::parse_outer)? + } else { + Vec::new() + }; + let target_generics = if input.peek(Lt) { input.parse()? } else { @@ -99,6 +106,7 @@ impl Parse for DelegateComponents { }; Ok(Self { + attributes, new_struct, target_type, target_generics, @@ -107,15 +115,29 @@ impl Parse for DelegateComponents { } } -impl Parse for DelegateEntry -where - Type: Parse, -{ +impl Parse for DelegateEntry { fn parse(input: ParseStream) -> syn::Result { let components = if input.peek(Bracket) { let components_body; bracketed!(components_body in input); components_body.parse_terminated(DelegateKey::parse, Token![,])? + } else if input.peek(At) { + let _: At = input.parse()?; + + let path: ComponentPaths = input.parse()?; + + let mut keys = Punctuated::new(); + + for path in path.paths { + let key = DelegateKey { + ty: path.path_type, + generics: path.generics, + }; + + keys.push(key); + } + + keys } else { let component: DelegateKey = input.parse()?; Punctuated::from_iter(iter::once(component)) @@ -133,12 +155,32 @@ where } } -impl Parse for DelegateKey -where - Type: Parse, -{ +impl Parse for DelegateEntry { + fn parse(input: ParseStream) -> syn::Result { + let components = if input.peek(Bracket) { + let components_body; + bracketed!(components_body in input); + components_body.parse_terminated(DelegateKey::parse, Token![,])? + } else { + let component: DelegateKey = input.parse()?; + Punctuated::from_iter(iter::once(component)) + }; + + let mode = input.parse()?; + + let source = input.parse()?; + + Ok(Self { + keys: components, + mode, + value: source, + }) + } +} + +impl Parse for DelegateKey { fn parse(input: ParseStream) -> syn::Result { - let component_generics = if input.peek(Lt) { + let component_generics: ImplGenerics = if input.peek(Lt) { input.parse()? } else { Default::default() @@ -167,13 +209,13 @@ impl Parse for DelegateValue { fn parse(input: ParseStream) -> syn::Result { let fork = input.fork(); - match fork.parse::() { - Ok(value) => { - input.advance_to(&fork); - Ok(Self::New(value)) - } - _ => Ok(Self::Type(input.parse()?)), + if let Ok(value) = fork.parse::() { + input.advance_to(&fork); + return Ok(Self::New(value)); } + + let ty: Type = input.parse()?; + Ok(Self::Type(ty)) } } diff --git a/crates/cgp-macro-lib/src/parse/mod.rs b/crates/cgp-macro-lib/src/parse/mod.rs index 162be924..0c63770c 100644 --- a/crates/cgp-macro-lib/src/parse/mod.rs +++ b/crates/cgp-macro-lib/src/parse/mod.rs @@ -6,6 +6,7 @@ mod delegate_components; mod entry; mod impl_generics; mod is_provider_params; +mod path; mod simple_type; mod type_generics; mod type_spec; @@ -18,6 +19,7 @@ pub use delegate_components::*; pub use entry::*; pub use impl_generics::*; pub use is_provider_params::*; +pub use path::*; pub use simple_type::*; pub use type_generics::*; pub use type_spec::*; diff --git a/crates/cgp-macro-lib/src/parse/path.rs b/crates/cgp-macro-lib/src/parse/path.rs new file mode 100644 index 00000000..ebc09cd4 --- /dev/null +++ b/crates/cgp-macro-lib/src/parse/path.rs @@ -0,0 +1,186 @@ +use proc_macro2::{TokenStream, TokenTree}; +use quote::{ToTokens, quote}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::{Brace, Comma, Dot, Lt, Star}; +use syn::{Ident, Type, braced, parse_quote, parse2}; + +use crate::parse::ImplGenerics; +use crate::symbol::symbol_from_string_spanned; + +pub struct ComponentPaths { + pub paths: Vec>, +} + +impl Parse for ComponentPaths { + fn parse(input: ParseStream) -> syn::Result { + let path_head = PathHead::parse(input)?; + + if let PathHead::Nil = path_head { + return Err(syn::Error::new( + input.span(), + "Expected at least one path element", + )); + } + + let mut paths = Vec::new(); + + for path in path_head.to_paths() { + let path_type: Type = syn::parse2(path.path_type)?; + paths.push(ComponentPath { + path_type, + generics: path.generics, + }); + } + + Ok(Self { paths }) + } +} + +pub struct ComponentPath { + pub path_type: Path, + pub generics: ImplGenerics, +} + +pub enum PathHead { + Type(Option, Type, Box), + Symbol(Option, Ident, Box), + Group(Punctuated), + Wildcard, + Nil, +} + +impl PathHead { + pub fn to_paths(&self) -> Vec> { + match self { + Self::Type(generics, path_type, rest) => { + let rest_types = rest.to_paths(); + + prepend_path(path_type.to_token_stream(), generics.clone(), rest_types) + } + Self::Symbol(generics, ident, rest) => { + let ident_str = ident.to_string(); + let path_type = symbol_from_string_spanned(ident.span(), &ident_str); + + let rest_types = rest.to_paths(); + prepend_path(path_type, generics.clone(), rest_types) + } + Self::Group(paths) => paths.iter().flat_map(|path| path.to_paths()).collect(), + Self::Wildcard => { + vec![ComponentPath { + path_type: quote! { __Wildcard__ }, + generics: parse_quote! { <__Wildcard__> }, + }] + } + Self::Nil => { + vec![ComponentPath { + path_type: quote! { PathNil }, + generics: Default::default(), + }] + } + } + } +} + +pub fn prepend_path( + path_type: TokenStream, + generics: Option, + rest_types: Vec>, +) -> Vec> { + rest_types + .into_iter() + .map(|mut path| { + let rest_tokens = path.path_type; + + if let Some(generics) = &generics { + path.generics + .generics + .params + .extend(generics.generics.params.clone()); + } + + let new_path = quote! { PathCons< #path_type , #rest_tokens > }; + ComponentPath { + path_type: new_path, + generics: path.generics, + } + }) + .collect() +} + +impl Parse for PathHead { + fn parse(input: ParseStream) -> syn::Result { + if input.is_empty() { + Ok(Self::Nil) + } else if input.peek(Star) { + let _: Star = input.parse()?; + Ok(Self::Wildcard) + } else if input.peek(Brace) { + let body; + braced!(body in input); + + let group = Punctuated::parse_terminated(&body)?; + + Ok(Self::Group(group)) + } else { + let generics = if input.peek(Lt) { + Some(input.parse()?) + } else { + None + }; + + let path_type: Type = input.parse()?; + + let rest_path = if input.peek(Dot) { + let _: Dot = input.parse()?; + Box::new(Self::parse(input)?) + } else { + Box::new(Self::Nil) + }; + + if let Some(path_ident) = path_type_as_ident(&path_type) { + Ok(Self::Symbol(generics, path_ident, rest_path)) + } else { + Ok(Self::Type(generics, path_type, rest_path)) + } + } + } +} + +pub fn path_type_as_ident(path_type: &Type) -> Option { + let path_tokens = path_type.to_token_stream().into_iter().collect::>(); + let [path_token]: [TokenTree; 1] = path_tokens.try_into().ok()?; + + if let TokenTree::Ident(path_ident) = path_token { + let path_str = path_ident.to_string(); + if let Some(path_char) = path_str.chars().next() + && path_char.is_ascii_lowercase() + { + return Some(path_ident); + } + } + + None +} + +pub struct PathType { + pub path_type: Type, +} + +impl Parse for PathType { + fn parse(input: ParseStream) -> syn::Result { + let path_type: Type = input.parse()?; + + if let Some(path_ident) = path_type_as_ident(&path_type) { + let path_symbol = parse2(symbol_from_string_spanned( + path_ident.span(), + &path_ident.to_string(), + ))?; + Ok(Self { + path_type: path_symbol, + }) + } else { + Ok(Self { path_type }) + } + } +} diff --git a/crates/cgp-macro-lib/src/symbol.rs b/crates/cgp-macro-lib/src/symbol.rs index 600a27ac..c7b9ff51 100644 --- a/crates/cgp-macro-lib/src/symbol.rs +++ b/crates/cgp-macro-lib/src/symbol.rs @@ -1,23 +1,27 @@ -use proc_macro2::{Literal, TokenStream}; -use quote::ToTokens; -use syn::{LitStr, Type, parse_quote}; +use proc_macro2::{Literal, Span, TokenStream}; +use quote::quote_spanned; +use syn::{LitStr, Type, parse2}; -pub fn symbol_from_string(value: &str) -> Type { - let chars = value - .chars() - .rfold(parse_quote! { ε }, |tail, c: char| -> Type { - parse_quote!( ζ< #c, #tail > ) - }); +pub fn symbol_from_string(value: &str) -> syn::Result { + parse2(symbol_from_string_spanned(Span::call_site(), value)) +} + +pub fn symbol_from_string_spanned(span: Span, value: &str) -> TokenStream { + let mut chars = quote_spanned! { span => ε }; + + for c in value.chars().rev() { + chars = quote_spanned! { span => ζ< #c, #chars > }; + } let len = Literal::usize_unsuffixed(value.len()); - parse_quote!( ψ< #len, #chars > ) + quote_spanned! { span => ψ< #len, #chars > } } -pub fn make_symbol(input: TokenStream) -> TokenStream { - let literal: LitStr = syn::parse2(input).unwrap(); +pub fn make_symbol(input: TokenStream) -> syn::Result { + let literal: LitStr = syn::parse2(input)?; - let symbol = symbol_from_string(&literal.value()); + let symbol = symbol_from_string_spanned(literal.span(), &literal.value()); - symbol.to_token_stream() + Ok(symbol) } diff --git a/crates/cgp-macro/src/lib.rs b/crates/cgp-macro/src/lib.rs index a4f08a71..e70c0cd1 100644 --- a/crates/cgp-macro/src/lib.rs +++ b/crates/cgp-macro/src/lib.rs @@ -893,7 +893,9 @@ pub fn replace_with(body: TokenStream) -> TokenStream { #[proc_macro] #[allow(non_snake_case)] pub fn Symbol(body: TokenStream) -> TokenStream { - cgp_macro_lib::make_symbol(body.into()).into() + cgp_macro_lib::make_symbol(body.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } /** @@ -972,7 +974,9 @@ pub fn product(body: TokenStream) -> TokenStream { #[proc_macro_derive(HasField)] pub fn derive_fields(item: TokenStream) -> TokenStream { - cgp_macro_lib::derive_has_field(item.into()).into() + cgp_macro_lib::derive_has_field(item.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[proc_macro_derive(HasFields)] diff --git a/crates/cgp-tests/src/lib.rs b/crates/cgp-tests/src/lib.rs index 192cb697..3c4e0340 100644 --- a/crates/cgp-tests/src/lib.rs +++ b/crates/cgp-tests/src/lib.rs @@ -1,2 +1,4 @@ #[cfg(test)] pub mod tests; + +pub mod namespaces; diff --git a/crates/cgp-tests/src/namespaces/extend.rs b/crates/cgp-tests/src/namespaces/extend.rs new file mode 100644 index 00000000..13490859 --- /dev/null +++ b/crates/cgp-tests/src/namespaces/extend.rs @@ -0,0 +1,39 @@ +use cgp::core::component::RedirectLookup; +use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent}; +use cgp::prelude::*; + +pub trait ExtendedNamespace { + type Provider; +} + +pub struct ExtendedNamespaceComponents; + +impl ExtendedNamespace for Component +where + Component: DefaultNamespace + + DefaultNamespace, +{ + type Provider = Provider; +} + +impl ExtendedNamespace for ErrorRaiserComponent { + type Provider = RedirectLookup< + Components, + PathCons>, + >; +} + +impl ExtendedNamespace + for PathCons< + Symbol!("cgp"), + PathCons< + Symbol!("core"), + PathCons>, + >, + > +{ + type Provider = RedirectLookup< + Components, + PathCons>, + >; +} diff --git a/crates/cgp-tests/src/namespaces/mod.rs b/crates/cgp-tests/src/namespaces/mod.rs new file mode 100644 index 00000000..d5e97d22 --- /dev/null +++ b/crates/cgp-tests/src/namespaces/mod.rs @@ -0,0 +1,3 @@ +mod extend; + +pub use extend::*; diff --git a/crates/cgp-tests/tests/namespace.rs b/crates/cgp-tests/tests/namespace.rs new file mode 100644 index 00000000..97b83ada --- /dev/null +++ b/crates/cgp-tests/tests/namespace.rs @@ -0,0 +1 @@ +pub mod namespace_tests; diff --git a/crates/cgp-tests/tests/namespace_tests/experiments.rs b/crates/cgp-tests/tests/namespace_tests/experiments.rs new file mode 100644 index 00000000..b0b90a97 --- /dev/null +++ b/crates/cgp-tests/tests/namespace_tests/experiments.rs @@ -0,0 +1,31 @@ +use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent, ErrorWrapperComponent}; +use cgp::extra::error::RaiseFrom; +use cgp::extra::handler::CanTryCompute; +use cgp::prelude::*; +use cgp_tests::namespaces::ExtendedNamespace; + +pub struct App; + +delegate_components! { + #[use_namespace(ExtendedNamespace)] + App { + @app.ErrorTypeProviderComponent: + UseType, + @app.{ + ErrorRaiserComponent.{&'static str, String}, + ErrorWrapperComponent.*, + }: + RaiseFrom, + TryComputerComponent: + Foo, + } +} + +#[cgp_computer] +fn foo(x: u64) -> Result { + Ok(x * 2) +} + +pub trait CheckApp: HasErrorType + CanRaiseError<&'static str> + CanTryCompute<(), u64> {} + +impl CheckApp for App {} diff --git a/crates/cgp-tests/tests/namespace_tests/mod.rs b/crates/cgp-tests/tests/namespace_tests/mod.rs new file mode 100644 index 00000000..2a24debe --- /dev/null +++ b/crates/cgp-tests/tests/namespace_tests/mod.rs @@ -0,0 +1,3 @@ +pub mod experiments; +pub mod redirect; +pub mod use_namespace; diff --git a/crates/cgp-tests/tests/namespace_tests/redirect.rs b/crates/cgp-tests/tests/namespace_tests/redirect.rs new file mode 100644 index 00000000..b42f959a --- /dev/null +++ b/crates/cgp-tests/tests/namespace_tests/redirect.rs @@ -0,0 +1,35 @@ +use cgp::prelude::*; + +#[cgp_component(FooProvider)] +#[use_namespace(bar.baz)] +pub trait CanDoFoo { + fn foo(); +} + +pub struct BarComponent; + +pub struct BazComponent; + +#[cgp_impl(new TestProvider)] +impl FooProvider { + fn foo() {} +} + +pub struct App; + +delegate_components! { + #[use_namespace] + App { + // @bar.*: TestProvider, + + @bar.baz.*: TestProvider, + + // @bar.baz.FooProviderComponent: TestProvider, + } +} + +check_components! { + App { + FooProviderComponent, + } +} diff --git a/crates/cgp-tests/tests/namespace_tests/use_namespace.rs b/crates/cgp-tests/tests/namespace_tests/use_namespace.rs new file mode 100644 index 00000000..ada8fe0e --- /dev/null +++ b/crates/cgp-tests/tests/namespace_tests/use_namespace.rs @@ -0,0 +1,20 @@ +use cgp::core::error::ErrorTypeProviderComponent; +use cgp::prelude::*; + +pub struct MyComponents; + +#[cgp_component(FooProvider)] +#[use_namespace(DefaultNamespace: app.MyComponents)] +pub trait CanDoFoo { + fn foo(&self); +} + +pub struct App; + +delegate_components! { + #[use_namespace] + App { + @cgp.core.error.ErrorTypeProviderComponent: + UseType, + } +} diff --git a/crates/cgp-type/src/traits/has_type.rs b/crates/cgp-type/src/traits/has_type.rs index da5a4aad..84a39a3b 100644 --- a/crates/cgp-type/src/traits/has_type.rs +++ b/crates/cgp-type/src/traits/has_type.rs @@ -1,4 +1,4 @@ -use cgp_component::{DelegateComponent, IsProviderFor, UseContext, UseDelegate}; +use cgp_component::*; use cgp_macro::cgp_component; #[cgp_component {