From 08c60c1525a5330769585aab7deaa9ccc50ed937 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 23 Feb 2026 20:00:46 +0100 Subject: [PATCH 1/2] Derive trait bounds in `#[use_provider]` --- .../cgp-macro-lib/src/cgp_impl/attributes.rs | 6 +++ crates/cgp-macro-lib/src/cgp_impl/derive.rs | 12 +++++- crates/cgp-macro-lib/src/cgp_impl/mod.rs | 2 + .../src/cgp_impl/provider_bounds.rs | 41 +++++++++++++++++++ .../src/cgp_impl/provider_impl.rs | 14 ++++--- .../src/cgp_impl/use_provider.rs | 28 +++++++++++++ .../tests/component_tests/cgp_impl/mod.rs | 1 + .../component_tests/cgp_impl/use_provider.rs | 21 ++++++++++ 8 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_impl/provider_bounds.rs create mode 100644 crates/cgp-macro-lib/src/cgp_impl/use_provider.rs create mode 100644 crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs diff --git a/crates/cgp-macro-lib/src/cgp_impl/attributes.rs b/crates/cgp-macro-lib/src/cgp_impl/attributes.rs index a492d491..7b81e3a3 100644 --- a/crates/cgp-macro-lib/src/cgp_impl/attributes.rs +++ b/crates/cgp-macro-lib/src/cgp_impl/attributes.rs @@ -5,6 +5,7 @@ use syn::punctuated::Punctuated; use syn::token::Comma; use crate::cgp_fn::UseTypeSpec; +use crate::cgp_impl::use_provider::UseProviderSpec; use crate::parse::SimpleType; pub fn parse_impl_attributes(attributes: &mut Vec) -> syn::Result { @@ -22,6 +23,10 @@ pub fn parse_impl_attributes(attributes: &mut Vec) -> syn::Result::parse_terminated)?; parsed_attributes.use_type.extend(use_type); + } else if ident == "use_provider" { + let use_provider = attribute + .parse_args_with(Punctuated::::parse_terminated)?; + parsed_attributes.use_provider.extend(use_provider); } else { attributes.push(attribute); } @@ -37,4 +42,5 @@ pub fn parse_impl_attributes(attributes: &mut Vec) -> syn::Result, pub use_type: Vec, + pub use_provider: Vec, } diff --git a/crates/cgp-macro-lib/src/cgp_impl/derive.rs b/crates/cgp-macro-lib/src/cgp_impl/derive.rs index d8df9edb..961ee7e9 100644 --- a/crates/cgp-macro-lib/src/cgp_impl/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_impl/derive.rs @@ -6,6 +6,7 @@ use syn::{ItemImpl, TypeParamBound, parse2}; use crate::cgp_fn::{apply_use_type_attributes_to_item_impl, build_implicit_args_bounds}; use crate::cgp_impl::attributes::parse_impl_attributes; +use crate::cgp_impl::provider_bounds::derive_provider_bounds; use crate::cgp_impl::{ImplProviderSpec, derive_provider_impl, implicit_args}; use crate::derive_provider::{ derive_component_name_from_provider_impl, derive_is_provider_for, derive_provider_struct, @@ -48,7 +49,7 @@ pub fn derive_cgp_impl( })?); } - let provider_impl = derive_provider_impl(&spec.provider_type, item_impl)?; + let (context_type, mut provider_impl) = derive_provider_impl(&spec.provider_type, item_impl)?; let component_type = match &spec.component_type { Some(component_type) => component_type.clone(), @@ -57,6 +58,15 @@ pub fn derive_cgp_impl( let is_provider_for_impl: ItemImpl = derive_is_provider_for(&component_type, &provider_impl)?; + if !attributes.use_provider.is_empty() { + let where_clause = provider_impl.generics.make_where_clause(); + + for spec in attributes.use_provider.iter() { + let provider_bounds = derive_provider_bounds(&context_type, spec)?; + where_clause.predicates.push(provider_bounds); + } + } + let provider_struct = if spec.new_struct { Some(derive_provider_struct(&provider_impl)?) } else { diff --git a/crates/cgp-macro-lib/src/cgp_impl/mod.rs b/crates/cgp-macro-lib/src/cgp_impl/mod.rs index 8a286d1c..d5126a08 100644 --- a/crates/cgp-macro-lib/src/cgp_impl/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_impl/mod.rs @@ -1,9 +1,11 @@ mod attributes; mod derive; mod implicit_args; +mod provider_bounds; mod provider_impl; mod spec; mod transform; +mod use_provider; pub use derive::*; pub use provider_impl::*; diff --git a/crates/cgp-macro-lib/src/cgp_impl/provider_bounds.rs b/crates/cgp-macro-lib/src/cgp_impl/provider_bounds.rs new file mode 100644 index 00000000..b518a4ef --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_impl/provider_bounds.rs @@ -0,0 +1,41 @@ +use quote::{ToTokens, quote}; +use syn::punctuated::Punctuated; +use syn::token::Plus; +use syn::{Type, TypeParamBound, WherePredicate, parse_quote, parse2}; + +use crate::cgp_impl::use_provider::UseProviderSpec; + +pub fn derive_provider_bounds( + context_type: &Type, + spec: &UseProviderSpec, +) -> syn::Result { + let context_type = if spec.context_type == parse_quote! { Self } { + context_type + } else { + &spec.context_type + }; + + let provider_type = &spec.provider_type; + let mut bounds = Punctuated::::new(); + + for bound in &spec.provider_trait_bounds { + let trait_ident = &bound.name; + let mut m_generics = bound.generics.clone(); + + let generics = m_generics.get_or_insert_with(|| parse_quote!(<>)); + generics + .args + .insert(0, parse2(context_type.to_token_stream())?); + + let trait_bound = parse2(quote! { + #trait_ident #generics + })?; + bounds.push(trait_bound); + } + + let predicate = parse2(quote! { + #provider_type: #bounds + })?; + + Ok(predicate) +} diff --git a/crates/cgp-macro-lib/src/cgp_impl/provider_impl.rs b/crates/cgp-macro-lib/src/cgp_impl/provider_impl.rs index c357ec8d..1d5374df 100644 --- a/crates/cgp-macro-lib/src/cgp_impl/provider_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_impl/provider_impl.rs @@ -6,17 +6,19 @@ use crate::cgp_impl::transform_impl_trait; pub fn derive_provider_impl( provider_type: &Type, mut item_impl: ItemImpl, -) -> syn::Result { +) -> syn::Result<(Type, ItemImpl)> { match &item_impl.trait_ { Some((_, path, _)) => { let consumer_trait_path = parse2(path.to_token_stream())?; let context_type = item_impl.self_ty.as_ref(); - transform_impl_trait( + let item_trait = transform_impl_trait( &item_impl, &consumer_trait_path, provider_type, context_type, - ) + )?; + + Ok((context_type.clone(), item_trait)) } None => { let consumer_trait_path = parse2(item_impl.self_ty.to_token_stream())?; @@ -27,12 +29,14 @@ pub fn derive_provider_impl( .params .insert(0, parse_quote! { __Context__ }); - transform_impl_trait( + let item_trait = transform_impl_trait( &item_impl, &consumer_trait_path, provider_type, &context_type, - ) + )?; + + Ok((context_type, item_trait)) } } } diff --git a/crates/cgp-macro-lib/src/cgp_impl/use_provider.rs b/crates/cgp-macro-lib/src/cgp_impl/use_provider.rs new file mode 100644 index 00000000..d6d380e4 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_impl/use_provider.rs @@ -0,0 +1,28 @@ +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::{Colon, Comma}; +use syn::{Type, parse_quote}; + +use crate::parse::SimpleType; + +pub struct UseProviderSpec { + pub context_type: Type, + pub provider_type: Type, + pub provider_trait_bounds: Punctuated, +} + +impl Parse for UseProviderSpec { + fn parse(input: ParseStream) -> syn::Result { + let context_type = parse_quote!(Self); + let provider_type = input.parse()?; + + let _: Colon = input.parse()?; + let provider_trait_bounds = Punctuated::parse_terminated(input)?; + + Ok(Self { + context_type, + provider_type, + provider_trait_bounds, + }) + } +} diff --git a/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs b/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs index 46750cc2..4e51e24d 100644 --- a/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs +++ b/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs @@ -1,3 +1,4 @@ pub mod basic; pub mod implicit_args; pub mod implicit_context; +pub mod use_provider; diff --git a/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs b/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs new file mode 100644 index 00000000..9cab44bc --- /dev/null +++ b/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs @@ -0,0 +1,21 @@ +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} + +#[cgp_impl(new ScaledArea)] +#[use_provider(Inner: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + Inner::area(self) * scale_factor + } +} From 5199ec3b754622ad10b74b751b371ed361080024 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 23 Feb 2026 20:59:02 +0100 Subject: [PATCH 2/2] Support #[use_provider] inside method call expression --- crates/cgp-macro-lib/Cargo.toml | 2 +- crates/cgp-macro-lib/src/cgp_impl/derive.rs | 8 +++ crates/cgp-macro-lib/src/cgp_impl/mod.rs | 1 + .../src/cgp_impl/provider_call.rs | 69 +++++++++++++++++++ .../component_tests/cgp_impl/use_provider.rs | 5 +- 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_impl/provider_call.rs diff --git a/crates/cgp-macro-lib/Cargo.toml b/crates/cgp-macro-lib/Cargo.toml index a50c39ad..241e0f1c 100644 --- a/crates/cgp-macro-lib/Cargo.toml +++ b/crates/cgp-macro-lib/Cargo.toml @@ -15,7 +15,7 @@ description = """ default = [] [dependencies] -syn = { version = "2.0.95", features = [ "full", "extra-traits", "visit" ] } +syn = { version = "2.0.95", features = [ "full", "extra-traits", "visit", "visit-mut" ] } quote = "1.0.38" proc-macro2 = "1.0.92" prettyplease = "0.2.27" diff --git a/crates/cgp-macro-lib/src/cgp_impl/derive.rs b/crates/cgp-macro-lib/src/cgp_impl/derive.rs index 961ee7e9..c50079c9 100644 --- a/crates/cgp-macro-lib/src/cgp_impl/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_impl/derive.rs @@ -2,11 +2,13 @@ use proc_macro2::TokenStream; use quote::quote; use syn::punctuated::Punctuated; use syn::token::Plus; +use syn::visit_mut::visit_item_impl_mut; use syn::{ItemImpl, TypeParamBound, parse2}; use crate::cgp_fn::{apply_use_type_attributes_to_item_impl, build_implicit_args_bounds}; use crate::cgp_impl::attributes::parse_impl_attributes; use crate::cgp_impl::provider_bounds::derive_provider_bounds; +use crate::cgp_impl::provider_call::TransformProviderCallVisitor; use crate::cgp_impl::{ImplProviderSpec, derive_provider_impl, implicit_args}; use crate::derive_provider::{ derive_component_name_from_provider_impl, derive_is_provider_for, derive_provider_struct, @@ -49,6 +51,12 @@ pub fn derive_cgp_impl( })?); } + let mut visitor = TransformProviderCallVisitor::default(); + visit_item_impl_mut(&mut visitor, &mut item_impl); + if let Some(err) = visitor.error { + return Err(err); + } + let (context_type, mut provider_impl) = derive_provider_impl(&spec.provider_type, item_impl)?; let component_type = match &spec.component_type { diff --git a/crates/cgp-macro-lib/src/cgp_impl/mod.rs b/crates/cgp-macro-lib/src/cgp_impl/mod.rs index d5126a08..9ee4d384 100644 --- a/crates/cgp-macro-lib/src/cgp_impl/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_impl/mod.rs @@ -2,6 +2,7 @@ mod attributes; mod derive; mod implicit_args; mod provider_bounds; +mod provider_call; mod provider_impl; mod spec; mod transform; diff --git a/crates/cgp-macro-lib/src/cgp_impl/provider_call.rs b/crates/cgp-macro-lib/src/cgp_impl/provider_call.rs new file mode 100644 index 00000000..f88cfc00 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_impl/provider_call.rs @@ -0,0 +1,69 @@ +use quote::quote; +use syn::parse::Parse; +use syn::visit_mut::VisitMut; +use syn::{Expr, ExprMethodCall, Type, parse2}; + +pub fn transform_provider_call(expr: &ExprMethodCall) -> syn::Result> { + let attributes = expr.attrs.clone(); + let mut out_attributes = Vec::new(); + + let mut m_use_provider = None; + + for attribute in attributes { + if attribute.path().is_ident("use_provider") { + if m_use_provider.is_some() { + return Err(syn::Error::new_spanned( + attribute, + "Multiple #[use_provider] attributes found", + )); + } + + m_use_provider = Some(attribute.parse_args_with(Type::parse)?); + } else { + out_attributes.push(attribute); + } + } + + if let Some(provider_type) = m_use_provider { + let mut args = expr.args.clone(); + args.insert(0, expr.receiver.as_ref().clone()); + + let method = &expr.method; + let turbofish = &expr.turbofish; + + let new_expr: Expr = parse2(quote! { + #provider_type::#method #turbofish ( #args ) + })?; + + Ok(Some(new_expr)) + } else { + Ok(None) + } +} + +#[derive(Default)] +pub struct TransformProviderCallVisitor { + pub error: Option, +} + +impl VisitMut for TransformProviderCallVisitor { + fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { + if self.error.is_some() { + return; + } + + if let syn::Expr::MethodCall(method_call) = expr { + match transform_provider_call(method_call) { + Ok(Some(new_expr)) => { + *expr = new_expr; + } + Ok(None) => {} + Err(e) => { + self.error = Some(e); + } + } + } + + syn::visit_mut::visit_expr_mut(self, expr); + } +} diff --git a/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs b/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs index 9cab44bc..a91c8a33 100644 --- a/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs +++ b/crates/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs @@ -16,6 +16,9 @@ impl AreaCalculator { #[use_provider(Inner: AreaCalculator)] impl AreaCalculator { fn area(&self, #[implicit] scale_factor: f64) -> f64 { - Inner::area(self) * scale_factor + let base_area = #[use_provider(Inner)] + self.area(); + + base_area * scale_factor * scale_factor } }