diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index d4a02ac6..e29326c2 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -18,7 +18,7 @@ pub use cgp_field::types::{ }; pub use cgp_macro::{ BuildField, CgpData, CgpRecord, CgpVariant, ExtractField, FromVariant, HasField, HasFields, - Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_context, cgp_getter, + Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_context, cgp_getter, cgp_impl, cgp_new_provider, cgp_preset, cgp_provider, cgp_type, check_components, delegate_and_check_components, delegate_components, product, re_export_imports, replace_with, }; diff --git a/crates/cgp-error-anyhow/src/impls/raise_anyhow_error.rs b/crates/cgp-error-anyhow/src/impls/raise_anyhow_error.rs index addba759..8c29d025 100644 --- a/crates/cgp-error-anyhow/src/impls/raise_anyhow_error.rs +++ b/crates/cgp-error-anyhow/src/impls/raise_anyhow_error.rs @@ -7,8 +7,8 @@ use cgp_core::prelude::*; pub struct RaiseAnyhowError; -#[cgp_provider] -impl ErrorRaiser for RaiseAnyhowError +#[cgp_impl(RaiseAnyhowError)] +impl ErrorRaiser for Context where Context: HasErrorType, E: StdError + Send + Sync + 'static, @@ -18,8 +18,8 @@ where } } -#[cgp_provider] -impl ErrorWrapper for RaiseAnyhowError +#[cgp_impl(RaiseAnyhowError)] +impl ErrorWrapper for Context where Context: HasErrorType, Detail: Display + Send + Sync + 'static, diff --git a/crates/cgp-macro-lib/src/derive_component/mod.rs b/crates/cgp-macro-lib/src/derive_component/mod.rs index 3398b32e..9a1b035c 100644 --- a/crates/cgp-macro-lib/src/derive_component/mod.rs +++ b/crates/cgp-macro-lib/src/derive_component/mod.rs @@ -13,5 +13,6 @@ mod use_context_impl; mod use_delegate_impl; pub use derive::*; +pub use replace_self_receiver::*; pub use replace_self_type::*; pub use snake_case::*; diff --git a/crates/cgp-macro-lib/src/derive_component/provider_trait.rs b/crates/cgp-macro-lib/src/derive_component/provider_trait.rs index cc432542..98ce49e9 100644 --- a/crates/cgp-macro-lib/src/derive_component/provider_trait.rs +++ b/crates/cgp-macro-lib/src/derive_component/provider_trait.rs @@ -1,14 +1,15 @@ use alloc::vec::Vec; -use quote::quote; +use quote::{ToTokens, quote}; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{Ident, ItemTrait, TraitItem, TypeParamBound, parse2}; -use crate::derive_component::replace_self_receiver::replace_self_receiver; +use crate::derive_component::replace_self_receiver::replace_self_receiver_in_signature; use crate::derive_component::replace_self_type::{ iter_parse_and_replace_self_type, parse_and_replace_self_type, }; +use crate::derive_component::to_snake_case_ident; use crate::parse::parse_is_provider_params; pub fn derive_provider_trait( @@ -89,7 +90,11 @@ pub fn derive_provider_trait( parse_and_replace_self_type(item, context_type, &local_assoc_types)?; if let TraitItem::Fn(func) = &mut replaced_item { - replace_self_receiver(func, context_type); + replace_self_receiver_in_signature( + &mut func.sig, + &to_snake_case_ident(context_type), + context_type.to_token_stream(), + ); } *item = replaced_item; diff --git a/crates/cgp-macro-lib/src/derive_component/replace_self_receiver.rs b/crates/cgp-macro-lib/src/derive_component/replace_self_receiver.rs index b9493ac9..425843c5 100644 --- a/crates/cgp-macro-lib/src/derive_component/replace_self_receiver.rs +++ b/crates/cgp-macro-lib/src/derive_component/replace_self_receiver.rs @@ -1,31 +1,41 @@ -use proc_macro2::Ident; -use syn::{FnArg, TraitItemFn, parse_quote}; +use proc_macro2::{Ident, TokenStream}; +use syn::{FnArg, Receiver, Signature, parse_quote}; -use crate::derive_component::snake_case::to_snake_case_ident; - -pub fn replace_self_receiver(func: &mut TraitItemFn, replaced_type: &Ident) { - if let Some(arg) = func.sig.inputs.first_mut() +pub fn replace_self_receiver_in_signature( + sig: &mut Signature, + replaced_var: &Ident, + replaced_type: TokenStream, +) { + if let Some(arg) = sig.inputs.first_mut() && let FnArg::Receiver(receiver) = arg { - let replaced_var = to_snake_case_ident(replaced_type); + *arg = replace_self_receiver(receiver, replaced_var, replaced_type); + } +} - match (&receiver.reference, &receiver.mutability) { - (None, None) => { - *arg = parse_quote!(#replaced_var : #replaced_type); - } - (Some((_and, None)), None) => { - *arg = parse_quote!(#replaced_var : & #replaced_type); - } - (Some((_and, Some(life))), None) => { - *arg = parse_quote!(#replaced_var : & #life #replaced_type); - } - (Some((_and, None)), Some(_mut)) => { - *arg = parse_quote!(#replaced_var : &mut #replaced_type); - } - (Some((_and, Some(life))), Some(_mut)) => { - *arg = parse_quote!(#replaced_var : & #life mut #replaced_type); - } - _ => {} +pub fn replace_self_receiver( + receiver: &mut Receiver, + replaced_var: &Ident, + replaced_type: TokenStream, +) -> FnArg { + match (&receiver.reference, &receiver.mutability) { + (None, None) => { + parse_quote!(#replaced_var : #replaced_type) + } + (Some((_and, None)), None) => { + parse_quote!(#replaced_var : & #replaced_type) + } + (Some((_and, Some(life))), None) => { + parse_quote!(#replaced_var : & #life #replaced_type) + } + (Some((_and, None)), Some(_mut)) => { + parse_quote!(#replaced_var : &mut #replaced_type) + } + (Some((_and, Some(life))), Some(_mut)) => { + parse_quote!(#replaced_var : & #life mut #replaced_type) + } + (None, Some(_mut)) => { + parse_quote!(#replaced_var : mut #replaced_type) } } } diff --git a/crates/cgp-macro-lib/src/derive_component/replace_self_type.rs b/crates/cgp-macro-lib/src/derive_component/replace_self_type.rs index 619fd6a6..bf8a6952 100644 --- a/crates/cgp-macro-lib/src/derive_component/replace_self_type.rs +++ b/crates/cgp-macro-lib/src/derive_component/replace_self_type.rs @@ -27,13 +27,17 @@ pub fn parse_and_replace_self_type( where T: ToTokens + Parse, { - let stream = replace_self_type(val.to_token_stream(), replaced_ident, local_assoc_types); + let stream = replace_self_type( + val.to_token_stream(), + replaced_ident.to_token_stream(), + local_assoc_types, + ); syn::parse2(stream) } pub fn replace_self_type( stream: TokenStream, - replaced_ident: &Ident, + replaced_ident: TokenStream, local_assoc_types: &Vec, ) -> TokenStream { let self_type = format_ident!("Self"); @@ -57,7 +61,7 @@ pub fn replace_self_type( Some(TokenTree::Ident(assoc_type)) if local_assoc_types.contains(assoc_type) => { - ident + ident.to_token_stream() } _ => replaced_ident, } @@ -68,14 +72,14 @@ pub fn replace_self_type( _ => replaced_ident, }; - result_stream.push(TokenTree::Ident(replaced)); + result_stream.extend(replaced); } else { result_stream.push(TokenTree::Ident(ident)); } } TokenTree::Group(group) => { let replaced_stream = - replace_self_type(group.stream(), replaced_ident, local_assoc_types); + replace_self_type(group.stream(), replaced_ident.clone(), local_assoc_types); let replaced_group = Group::new(group.delimiter(), replaced_stream); result_stream.push(TokenTree::Group(replaced_group)); diff --git a/crates/cgp-macro-lib/src/derive_component/snake_case.rs b/crates/cgp-macro-lib/src/derive_component/snake_case.rs index dcb69d96..c7cc504e 100644 --- a/crates/cgp-macro-lib/src/derive_component/snake_case.rs +++ b/crates/cgp-macro-lib/src/derive_component/snake_case.rs @@ -19,5 +19,8 @@ pub fn to_snake_case_str(val: &str) -> String { } pub fn to_snake_case_ident(val: &Ident) -> Ident { - Ident::new(&to_snake_case_str(&val.to_string()), Span::call_site()) + Ident::new( + &format!("__{}__", to_snake_case_str(&val.to_string())), + Span::call_site(), + ) } diff --git a/crates/cgp-macro-lib/src/derive_getter/parse.rs b/crates/cgp-macro-lib/src/derive_getter/parse.rs index 49662858..9caff497 100644 --- a/crates/cgp-macro-lib/src/derive_getter/parse.rs +++ b/crates/cgp-macro-lib/src/derive_getter/parse.rs @@ -174,7 +174,7 @@ fn parse_receiver(context_ident: &Ident, arg: &FnArg) -> syn::Result<(ReceiverMo Type::Reference(ty) => { let receiver = parse2(replace_self_type( ty.elem.to_token_stream(), - context_ident, + context_ident.to_token_stream(), &Vec::new(), ))?; Ok((ReceiverMode::Type(receiver), ty.mutability)) @@ -191,7 +191,7 @@ fn parse_return_type(context_type: &Ident, return_type: &ReturnType) -> syn::Res match return_type { ReturnType::Type(_, ty) => parse2(replace_self_type( ty.to_token_stream(), - context_type, + context_type.to_token_stream(), &Vec::new(), )), _ => Err(Error::new( diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_impl.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_impl.rs new file mode 100644 index 00000000..b93fb094 --- /dev/null +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_impl.rs @@ -0,0 +1,187 @@ +use proc_macro2::{Group, Span, TokenStream, TokenTree}; +use quote::{ToTokens, format_ident, quote}; +use syn::parse::discouraged::Speculative; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::token::{Colon, For}; +use syn::{Error, FnArg, Ident, ImplItem, ItemImpl, Type, parse2}; + +use crate::derive_component::{replace_self_receiver, replace_self_type, to_snake_case_ident}; +use crate::derive_provider::{ + derive_component_name_from_provider_impl, derive_is_provider_for, derive_provider_struct, +}; +use crate::parse::SimpleType; + +pub fn cgp_impl(attr: TokenStream, body: TokenStream) -> syn::Result { + let spec: ImplProviderSpec = parse2(attr)?; + let item_impl: ItemImpl = parse2(body)?; + + let consumer_trait_path = &item_impl + .trait_ + .as_ref() + .ok_or_else(|| Error::new(item_impl.span(), "expect impl trait to contain path"))? + .1; + + let consumer_trait_path: SimpleType = parse2(consumer_trait_path.to_token_stream())?; + + let provider_impl = + transform_impl_trait(&item_impl, &consumer_trait_path, &spec.provider_type)?; + + let component_type = match &spec.component_type { + Some(component_type) => component_type.clone(), + None => derive_component_name_from_provider_impl(&provider_impl)?, + }; + + let is_provider_for_impl: ItemImpl = derive_is_provider_for(&component_type, &provider_impl)?; + + let provider_struct = if spec.new_struct { + Some(derive_provider_struct(&provider_impl)?) + } else { + None + }; + + Ok(quote! { + #provider_struct + + #provider_impl + + #is_provider_for_impl + }) +} + +pub struct ImplProviderSpec { + pub new_struct: bool, + pub provider_type: Type, + pub component_type: Option, +} + +impl Parse for ImplProviderSpec { + fn parse(input: ParseStream) -> syn::Result { + let new_struct = { + let fork = input.fork(); + let new_ident: Option = fork.parse().ok(); + match new_ident { + Some(new_ident) if new_ident == "new" => { + input.advance_to(&fork); + true + } + _ => false, + } + }; + + let provider_type = input.parse()?; + + let component_type = if let Some(_colon) = input.parse::>()? { + let component_type: Type = input.parse()?; + Some(component_type) + } else { + None + }; + + Ok(ImplProviderSpec { + new_struct, + provider_type, + component_type, + }) + } +} + +pub fn transform_impl_trait( + item_impl: &ItemImpl, + consumer_trait_path: &SimpleType, + provider_type: &Type, +) -> syn::Result { + let context_type = item_impl.self_ty.as_ref(); + + let context_var = if let Ok(ident) = parse2::(context_type.to_token_stream()) { + to_snake_case_ident(&ident) + } else { + Ident::new("__context__", Span::call_site()) + }; + + let local_assoc_types: Vec = item_impl + .items + .iter() + .filter_map(|item| { + if let ImplItem::Type(assoc_type) = item { + Some(assoc_type.ident.clone()) + } else { + None + } + }) + .collect(); + + let raw_out_impl = replace_self_type( + item_impl.to_token_stream(), + context_type.to_token_stream(), + &local_assoc_types, + ); + + let mut out_impl: ItemImpl = parse2(raw_out_impl)?; + out_impl.self_ty = Box::new(provider_type.clone()); + + let mut provider_trait_path: SimpleType = consumer_trait_path.clone(); + + match &mut provider_trait_path.generics { + Some(generics) => { + generics + .args + .insert(0, parse2(context_type.to_token_stream())?); + } + None => { + provider_trait_path.generics = Some(parse2(quote! { < #context_type > })?); + } + } + + out_impl.trait_ = Some(( + None, + parse2(provider_trait_path.to_token_stream())?, + For(Span::call_site()), + )); + + for item in out_impl.items.iter_mut() { + if let ImplItem::Fn(item_fn) = item + && let Some(arg) = item_fn.sig.inputs.first_mut() + && let FnArg::Receiver(receiver) = arg + { + *arg = replace_self_receiver(receiver, &context_var, context_type.to_token_stream()); + + let replaced_block = replace_self_var(item_fn.block.to_token_stream(), &context_var); + item_fn.block = parse2(replaced_block)?; + } + } + + Ok(out_impl) +} + +fn replace_self_var(stream: TokenStream, replaced_ident: &Ident) -> TokenStream { + let self_ident = format_ident!("self"); + + let mut result_stream: Vec = Vec::new(); + + let token_iter = stream.into_iter(); + + for tree in token_iter { + match tree { + TokenTree::Ident(ident) => { + if ident == self_ident { + result_stream.push(TokenTree::Ident(replaced_ident.clone())); + } else { + result_stream.push(TokenTree::Ident(ident)); + } + } + TokenTree::Group(group) => { + let replaced_stream = replace_self_var(group.stream(), replaced_ident); + let replaced_group = Group::new(group.delimiter(), replaced_stream); + + result_stream.push(TokenTree::Group(replaced_group)); + } + TokenTree::Punct(punct) => { + result_stream.push(TokenTree::Punct(punct)); + } + TokenTree::Literal(lit) => result_stream.push(TokenTree::Literal(lit)), + } + } + + result_stream.into_iter().collect() +} diff --git a/crates/cgp-macro-lib/src/entrypoints/mod.rs b/crates/cgp-macro-lib/src/entrypoints/mod.rs index ba5bdc44..16aa4233 100644 --- a/crates/cgp-macro-lib/src/entrypoints/mod.rs +++ b/crates/cgp-macro-lib/src/entrypoints/mod.rs @@ -4,6 +4,7 @@ mod cgp_component; mod cgp_context; mod cgp_data; mod cgp_getter; +mod cgp_impl; mod cgp_new_provider; mod cgp_preset; mod cgp_provider; @@ -26,6 +27,7 @@ pub use cgp_component::*; pub use cgp_context::*; pub use cgp_data::*; pub use cgp_getter::*; +pub use cgp_impl::*; pub use cgp_new_provider::*; pub use cgp_preset::*; pub use cgp_provider::*; diff --git a/crates/cgp-macro/src/lib.rs b/crates/cgp-macro/src/lib.rs index 7140b7d0..ac08186e 100644 --- a/crates/cgp-macro/src/lib.rs +++ b/crates/cgp-macro/src/lib.rs @@ -143,6 +143,13 @@ pub fn cgp_provider(attr: TokenStream, item: TokenStream) -> TokenStream { .into() } +#[proc_macro_attribute] +pub fn cgp_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + cgp_macro_lib::cgp_impl(attr.into(), item.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + /** The `#[cgp_new_provider]` macro is an extension to [`#[cgp_provider]`](macro@cgp_provider) that in addition to the derivation of `IsProviderFor`, also generates a new provider diff --git a/crates/cgp-tests/src/tests/cgp_impl/basic.rs b/crates/cgp-tests/src/tests/cgp_impl/basic.rs new file mode 100644 index 00000000..b4cfc13f --- /dev/null +++ b/crates/cgp-tests/src/tests/cgp_impl/basic.rs @@ -0,0 +1,50 @@ +use cgp::prelude::*; + +#[cgp_component(FooProvider)] +pub trait CanDoFoo { + fn foo(&self, value: u32) -> String; +} + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_impl(new ValueToString)] +impl FooProvider for Context { + fn foo(&self, value: u32) -> String { + value.to_string() + } +} + +pub mod inner { + use core::fmt::Display; + + use cgp::prelude::*; + + use super::{FooProvider, FooProviderComponent, HasName}; + + #[cgp_impl(new WithNamePrefix)] + impl FooProvider for Context + where + Context: HasName, + { + fn foo(&self, value: u32) -> String { + format!("{}: {}", self.name(), value) + } + } + + pub struct Foo { + pub tag: Tag, + } + + #[cgp_impl(new WithFooTag: FooProviderComponent)] + impl FooProvider for Foo + where + Tag: Display, + { + fn foo(&self, value: u32) -> String { + format!("{}: {}", self.tag, value) + } + } +} diff --git a/crates/cgp-tests/src/tests/cgp_impl/mod.rs b/crates/cgp-tests/src/tests/cgp_impl/mod.rs new file mode 100644 index 00000000..38883ee0 --- /dev/null +++ b/crates/cgp-tests/src/tests/cgp_impl/mod.rs @@ -0,0 +1 @@ +pub mod basic; diff --git a/crates/cgp-tests/src/tests/mod.rs b/crates/cgp-tests/src/tests/mod.rs index 06236f51..7031c152 100644 --- a/crates/cgp-tests/src/tests/mod.rs +++ b/crates/cgp-tests/src/tests/mod.rs @@ -1,6 +1,7 @@ pub mod r#async; pub mod blanket_trait; pub mod cgp_component; +pub mod cgp_impl; pub mod check_components; pub mod compose; pub mod delegate_and_check_components;