From 5a8257e023339d5c3e12ee588cd9c32e771a247d Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 13 Feb 2026 22:19:11 +0100 Subject: [PATCH 01/21] Implement extract_implicits_args --- crates/cgp-macro-lib/src/cgp_fn/derive.rs | 10 +++ crates/cgp-macro-lib/src/cgp_fn/mod.rs | 5 ++ .../src/cgp_fn/parse_implicits.rs | 63 +++++++++++++++++++ .../cgp-macro-lib/src/derive_getter/parse.rs | 7 ++- .../cgp-macro-lib/src/entrypoints/cgp_fn.rs | 8 +++ crates/cgp-macro-lib/src/entrypoints/mod.rs | 2 + crates/cgp-macro-lib/src/lib.rs | 1 + 7 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/derive.rs create mode 100644 crates/cgp-macro-lib/src/cgp_fn/mod.rs create mode 100644 crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs create mode 100644 crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs new file mode 100644 index 00000000..2377f75c --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -0,0 +1,10 @@ +use proc_macro2::TokenStream; +use syn::ItemFn; + +use crate::cgp_fn::extract_implicits_args; + +pub fn derive_cgp_fn(item_fn: &mut ItemFn) -> syn::Result { + let implicit_args = extract_implicits_args(&mut item_fn.sig.inputs)?; + + todo!() +} diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs new file mode 100644 index 00000000..ed0bf4f4 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -0,0 +1,5 @@ +mod derive; +mod parse_implicits; + +pub use derive::*; +pub use parse_implicits::*; diff --git a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs new file mode 100644 index 00000000..708029a8 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs @@ -0,0 +1,63 @@ +use std::mem; + +use syn::punctuated::Punctuated; +use syn::token::{Comma, Mut}; +use syn::{Attribute, FnArg, Ident, Meta, Pat, PatType, Type}; + +use crate::derive_getter::{FieldMode, parse_field_type}; + +pub fn extract_implicits_args( + args: &mut Punctuated, +) -> syn::Result> { + let mut implicit_args = Vec::new(); + + for arg in args.iter_mut() { + if let FnArg::Typed(arg) = arg { + let attrs = mem::take(&mut arg.attrs); + for attr in attrs { + if is_implicit_attr(&attr) { + let spec = parse_implicit_arg(arg)?; + implicit_args.push(spec); + break; + } else { + arg.attrs.push(attr); + } + } + } + } + + Ok(implicit_args) +} + +pub fn parse_implicit_arg(arg: &PatType) -> syn::Result { + let Pat::Ident(pat_ident) = &*arg.pat else { + return Err(syn::Error::new_spanned(&arg.pat, "Expected an identifier")); + }; + + let field_mut = pat_ident.mutability; + + let (field_type, field_mode) = parse_field_type(&arg.ty, &field_mut)?; + + let spec = ImplicitArgField { + field_name: pat_ident.ident.clone(), + field_type, + field_mut, + field_mode, + }; + + Ok(spec) +} + +pub struct ImplicitArgField { + pub field_name: Ident, + pub field_type: Type, + pub field_mut: Option, + pub field_mode: FieldMode, +} + +pub fn is_implicit_attr(attr: &Attribute) -> bool { + match &attr.meta { + Meta::Path(path) => path.is_ident("implicit"), + _ => false, + } +} diff --git a/crates/cgp-macro-lib/src/derive_getter/parse.rs b/crates/cgp-macro-lib/src/derive_getter/parse.rs index 947efecb..4bce7579 100644 --- a/crates/cgp-macro-lib/src/derive_getter/parse.rs +++ b/crates/cgp-macro-lib/src/derive_getter/parse.rs @@ -90,7 +90,7 @@ pub fn parse_getter_fields( Ok((fields, field_assoc_type)) } -fn parse_getter_method( +pub fn parse_getter_method( context_type: &Ident, method: &TraitItemFn, field_assoc_type: &Option, @@ -261,7 +261,10 @@ fn parse_return_type( } } -fn parse_field_type(return_type: &Type, field_mut: &Option) -> syn::Result<(Type, FieldMode)> { +pub fn parse_field_type( + return_type: &Type, + field_mut: &Option, +) -> syn::Result<(Type, FieldMode)> { match &return_type { Type::Reference(type_ref) => { if type_ref.mutability.is_some() != field_mut.is_some() { diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs new file mode 100644 index 00000000..6f0606fb --- /dev/null +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs @@ -0,0 +1,8 @@ +use proc_macro2::TokenStream; +use syn::{ItemFn, parse2}; + +pub fn cgp_fn(attr: TokenStream, body: TokenStream) -> syn::Result { + let item_fn: ItemFn = parse2(body)?; + + todo!() +} diff --git a/crates/cgp-macro-lib/src/entrypoints/mod.rs b/crates/cgp-macro-lib/src/entrypoints/mod.rs index 6b030b89..3bfd22d7 100644 --- a/crates/cgp-macro-lib/src/entrypoints/mod.rs +++ b/crates/cgp-macro-lib/src/entrypoints/mod.rs @@ -3,6 +3,7 @@ mod cgp_auto_getter; mod cgp_component; mod cgp_context; mod cgp_data; +mod cgp_fn; mod cgp_getter; mod cgp_impl; mod cgp_inherit; @@ -27,6 +28,7 @@ pub use cgp_auto_getter::*; pub use cgp_component::*; pub use cgp_context::*; pub use cgp_data::*; +pub use cgp_fn::*; pub use cgp_getter::*; pub use cgp_impl::*; pub use cgp_inherit::*; diff --git a/crates/cgp-macro-lib/src/lib.rs b/crates/cgp-macro-lib/src/lib.rs index 4a382da4..bb2f798e 100644 --- a/crates/cgp-macro-lib/src/lib.rs +++ b/crates/cgp-macro-lib/src/lib.rs @@ -8,6 +8,7 @@ extern crate alloc; pub(crate) mod blanket_trait; +pub(crate) mod cgp_fn; pub(crate) mod check_components; pub(crate) mod delegate_components; pub(crate) mod derive_builder; From 493c85f7e14ec15e436287eb5d80ffb58e6c91d1 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 13 Feb 2026 22:21:27 +0100 Subject: [PATCH 02/21] Move ImplicitArgField to separate module --- crates/cgp-macro-lib/src/cgp_fn/mod.rs | 2 ++ crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs | 14 ++++---------- crates/cgp-macro-lib/src/cgp_fn/spec.rs | 11 +++++++++++ 3 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/spec.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index ed0bf4f4..5985ef5e 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -1,5 +1,7 @@ mod derive; mod parse_implicits; +mod spec; pub use derive::*; pub use parse_implicits::*; +pub use spec::*; diff --git a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs index 708029a8..39b2ec3f 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs @@ -1,10 +1,11 @@ use std::mem; use syn::punctuated::Punctuated; -use syn::token::{Comma, Mut}; -use syn::{Attribute, FnArg, Ident, Meta, Pat, PatType, Type}; +use syn::token::Comma; +use syn::{Attribute, FnArg, Meta, Pat, PatType}; -use crate::derive_getter::{FieldMode, parse_field_type}; +use crate::cgp_fn::ImplicitArgField; +use crate::derive_getter::parse_field_type; pub fn extract_implicits_args( args: &mut Punctuated, @@ -48,13 +49,6 @@ pub fn parse_implicit_arg(arg: &PatType) -> syn::Result { Ok(spec) } -pub struct ImplicitArgField { - pub field_name: Ident, - pub field_type: Type, - pub field_mut: Option, - pub field_mode: FieldMode, -} - pub fn is_implicit_attr(attr: &Attribute) -> bool { match &attr.meta { Meta::Path(path) => path.is_ident("implicit"), diff --git a/crates/cgp-macro-lib/src/cgp_fn/spec.rs b/crates/cgp-macro-lib/src/cgp_fn/spec.rs new file mode 100644 index 00000000..8ed3816e --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/spec.rs @@ -0,0 +1,11 @@ +use syn::token::Mut; +use syn::{Ident, Type}; + +use crate::derive_getter::FieldMode; + +pub struct ImplicitArgField { + pub field_name: Ident, + pub field_type: Type, + pub field_mut: Option, + pub field_mode: FieldMode, +} From c0bdca6d98885823debfc98ad479a55ec403c933 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 18:52:56 +0100 Subject: [PATCH 03/21] Draft implement derive_cgp_fn --- crates/cgp-macro-lib/src/cgp_fn/constraint.rs | 24 ++++++++ crates/cgp-macro-lib/src/cgp_fn/derive.rs | 58 ++++++++++++++++++- crates/cgp-macro-lib/src/cgp_fn/fn_body.rs | 26 +++++++++ crates/cgp-macro-lib/src/cgp_fn/mod.rs | 2 + .../src/cgp_fn/parse_implicits.rs | 35 +++++++---- .../src/derive_getter/blanket.rs | 4 +- .../src/derive_getter/constraint.rs | 15 +++-- .../cgp-macro-lib/src/derive_getter/method.rs | 31 ++++++---- .../src/derive_getter/use_field.rs | 4 +- .../src/derive_getter/use_fields.rs | 4 +- 10 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/constraint.rs create mode 100644 crates/cgp-macro-lib/src/cgp_fn/fn_body.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/constraint.rs b/crates/cgp-macro-lib/src/cgp_fn/constraint.rs new file mode 100644 index 00000000..77193b55 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/constraint.rs @@ -0,0 +1,24 @@ +// pub fn derive_implicit_arg_constraint( +// field: &ImplicitArgField, +// ) -> syn::Result { +// let field_symbol = &field.field_name; +// let field_type = &field.field_type; + +// let constraint = if field.field_mut.is_none() { +// if let FieldMode::Slice = field.field_mode { +// quote! { +// HasField< #field_symbol, Value: AsRef< [ #field_type ] > + 'static > +// } +// } else { +// quote! { +// HasField< #field_symbol, Value = #field_type > +// } +// } +// } else { +// quote! { +// HasFieldMut< #field_symbol, Value = #field_type > +// } +// }; + +// parse2(constraint) +// } diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index 2377f75c..67c9c62c 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -1,10 +1,62 @@ use proc_macro2::TokenStream; -use syn::ItemFn; +use quote::{ToTokens, quote}; +use syn::{Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; use crate::cgp_fn::extract_implicits_args; +use crate::cgp_fn::fn_body::inject_implicit_args; +use crate::derive_getter::derive_getter_constraint; +use crate::symbol::symbol_from_string; -pub fn derive_cgp_fn(item_fn: &mut ItemFn) -> syn::Result { +pub fn derive_cgp_fn(trait_ident: &Ident, item_fn: &mut ItemFn) -> syn::Result { let implicit_args = extract_implicits_args(&mut item_fn.sig.inputs)?; - todo!() + let trait_item_fn = TraitItemFn { + attrs: item_fn.attrs.clone(), + sig: item_fn.sig.clone(), + default: None, + semi_token: None, + }; + + for arg in implicit_args.iter() { + inject_implicit_args(arg, &mut item_fn.block)?; + } + + let item_trait: ItemTrait = parse2(quote! { + pub trait #trait_ident { + #trait_item_fn + } + })?; + + let mut item_impl: ItemTrait = parse2(quote! { + impl<__Context__> #trait_ident for __Context__ { + #item_fn + } + })?; + + let where_clause = item_impl.generics.make_where_clause(); + + for arg in implicit_args { + let field_symbol = symbol_from_string(&arg.field_name.to_string()); + + let constraint = derive_getter_constraint( + &arg.field_type, + &arg.field_mut, + &arg.field_mode, + field_symbol.to_token_stream(), + &None, + )?; + + where_clause.predicates.push(parse2(quote! { + Self: #constraint + })?); + } + + let output = quote! { + #item_trait + + #item_impl + + }; + + Ok(output) } diff --git a/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs new file mode 100644 index 00000000..1fd8abe3 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs @@ -0,0 +1,26 @@ +use quote::quote; +use syn::{Block, parse2}; + +use crate::cgp_fn::ImplicitArgField; +use crate::derive_getter::extend_call_expr; +use crate::symbol::symbol_from_string; + +pub fn inject_implicit_args(arg: &ImplicitArgField, body: &mut Block) -> syn::Result<()> { + let field_name = &arg.field_name; + + let field_symbol = symbol_from_string(&field_name.to_string()); + + let call_expr = quote! { + self.get_field(::core::marker::PhantomData::< #field_symbol >) + }; + + let call_expr = extend_call_expr(call_expr, &arg.field_mode, &arg.field_mut); + + let statement = parse2(quote! { + let #field_name = #call_expr; + })?; + + body.stmts.push(statement); + + Ok(()) +} diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index 5985ef5e..9f121dca 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -1,4 +1,6 @@ +mod constraint; mod derive; +mod fn_body; mod parse_implicits; mod spec; diff --git a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs index 39b2ec3f..2c72c306 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs @@ -12,24 +12,35 @@ pub fn extract_implicits_args( ) -> syn::Result> { let mut implicit_args = Vec::new(); - for arg in args.iter_mut() { - if let FnArg::Typed(arg) = arg { - let attrs = mem::take(&mut arg.attrs); - for attr in attrs { - if is_implicit_attr(&attr) { - let spec = parse_implicit_arg(arg)?; - implicit_args.push(spec); - break; - } else { - arg.attrs.push(attr); - } - } + let process_args = mem::take(args); + + for mut arg in process_args.into_iter() { + if let Some(implicit_arg) = try_parse_implicit_arg(&mut arg)? { + implicit_args.push(implicit_arg); + } else { + args.push(arg); } } Ok(implicit_args) } +pub fn try_parse_implicit_arg(arg: &mut FnArg) -> syn::Result> { + if let FnArg::Typed(arg) = arg { + let attrs = mem::take(&mut arg.attrs); + for attr in attrs { + if is_implicit_attr(&attr) { + let spec = parse_implicit_arg(arg)?; + return Ok(Some(spec)); + } else { + arg.attrs.push(attr); + } + } + } + + Ok(None) +} + pub fn parse_implicit_arg(arg: &PatType) -> syn::Result { let Pat::Ident(pat_ident) = &*arg.pat else { return Err(syn::Error::new_spanned(&arg.pat, "Expected an identifier")); diff --git a/crates/cgp-macro-lib/src/derive_getter/blanket.rs b/crates/cgp-macro-lib/src/derive_getter/blanket.rs index db383649..85406929 100644 --- a/crates/cgp-macro-lib/src/derive_getter/blanket.rs +++ b/crates/cgp-macro-lib/src/derive_getter/blanket.rs @@ -75,7 +75,9 @@ pub fn derive_blanket_impl( items.extend(method); let constraint = derive_getter_constraint( - field, + &field.field_type, + &field.field_mut, + &field.field_mode, quote! { #field_symbol }, &field_assoc_type.as_ref().map(|item| item.ident.clone()), )?; diff --git a/crates/cgp-macro-lib/src/derive_getter/constraint.rs b/crates/cgp-macro-lib/src/derive_getter/constraint.rs index 543c5d4d..6c28596c 100644 --- a/crates/cgp-macro-lib/src/derive_getter/constraint.rs +++ b/crates/cgp-macro-lib/src/derive_getter/constraint.rs @@ -1,21 +1,24 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{Ident, TypeParamBound, parse_quote, parse2}; +use syn::token::Mut; +use syn::{Ident, Type, TypeParamBound, parse_quote, parse2}; -use crate::derive_getter::{FieldMode, GetterField}; +use crate::derive_getter::FieldMode; pub fn derive_getter_constraint( - spec: &GetterField, + field_type: &Type, + field_mut: &Option, + field_mode: &FieldMode, field_symbol: TokenStream, field_assoc_type: &Option, ) -> syn::Result { let field_type = match field_assoc_type { Some(field_assoc_type) => parse_quote! { #field_assoc_type }, - None => spec.field_type.clone(), + None => field_type.clone(), }; - let constraint = if spec.field_mut.is_none() { - if let FieldMode::Slice = spec.field_mode { + let constraint = if field_mut.is_none() { + if let FieldMode::Slice = field_mode { quote! { HasField< #field_symbol, Value: AsRef< [ #field_type ] > + 'static > } diff --git a/crates/cgp-macro-lib/src/derive_getter/method.rs b/crates/cgp-macro-lib/src/derive_getter/method.rs index a4f4e485..d2120034 100644 --- a/crates/cgp-macro-lib/src/derive_getter/method.rs +++ b/crates/cgp-macro-lib/src/derive_getter/method.rs @@ -1,6 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::Ident; +use syn::token::Mut; use crate::derive_getter::{FieldMode, GetterField}; @@ -71,10 +72,26 @@ pub fn derive_getter_method( } }; - let call_expr = match spec.field_mode { + let call_expr = extend_call_expr(call_expr, &spec.field_mode, &spec.field_mut); + + let return_type = &spec.return_type; + + quote! { + fn #field_name( #context_fn_arg #phantom_arg ) -> #return_type { + #call_expr + } + } +} + +pub fn extend_call_expr( + call_expr: TokenStream, + field_mode: &FieldMode, + field_mut: &Option, +) -> TokenStream { + match field_mode { FieldMode::Reference => call_expr, FieldMode::OptionRef => { - if spec.field_mut.is_none() { + if field_mut.is_none() { quote! { #call_expr .as_ref() } @@ -90,7 +107,7 @@ pub fn derive_getter_method( } } FieldMode::Str => { - if spec.field_mut.is_none() { + if field_mut.is_none() { quote! { #call_expr .as_str() } @@ -110,13 +127,5 @@ pub fn derive_getter_method( #call_expr .as_ref() } } - }; - - let return_type = &spec.return_type; - - quote! { - fn #field_name( #context_fn_arg #phantom_arg ) -> #return_type { - #call_expr - } } } diff --git a/crates/cgp-macro-lib/src/derive_getter/use_field.rs b/crates/cgp-macro-lib/src/derive_getter/use_field.rs index ac118dbf..333f145a 100644 --- a/crates/cgp-macro-lib/src/derive_getter/use_field.rs +++ b/crates/cgp-macro-lib/src/derive_getter/use_field.rs @@ -61,7 +61,9 @@ pub fn derive_use_field_impl( )); let constraint = derive_getter_constraint( - field, + &field.field_type, + &field.field_mut, + &field.field_mode, quote! { #tag_type }, &field_assoc_type.as_ref().map(|item| item.ident.clone()), )?; 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 d83bdce6..3e14031f 100644 --- a/crates/cgp-macro-lib/src/derive_getter/use_fields.rs +++ b/crates/cgp-macro-lib/src/derive_getter/use_fields.rs @@ -66,7 +66,9 @@ pub fn derive_use_fields_impl( items.extend(method); let constraint = derive_getter_constraint( - field, + &field.field_type, + &field.field_mut, + &field.field_mode, quote! { #field_symbol }, &field_assoc_type.as_ref().map(|item| item.ident.clone()), )?; From 5d6ec14e44648152b0c34601af0e9c41592b0d79 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 19:05:26 +0100 Subject: [PATCH 04/21] Export #[cgp_fn] --- Cargo.lock | 1 + crates/cgp-extra-macro-lib/Cargo.toml | 1 + .../src/entrypoints/cgp_auto_dispatch.rs | 3 +-- .../src/entrypoints/cgp_computer.rs | 2 +- .../src/entrypoints/cgp_producer.rs | 3 +-- crates/cgp-extra-macro-lib/src/lib.rs | 1 - crates/cgp-macro-lib/src/cgp_fn/derive.rs | 2 +- crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs | 16 +++++++++++++--- crates/cgp-macro-lib/src/lib.rs | 4 +++- crates/cgp-macro-lib/src/utils/mod.rs | 3 +++ .../src/utils/string.rs} | 0 crates/cgp-macro/src/lib.rs | 7 +++++++ 12 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 crates/cgp-macro-lib/src/utils/mod.rs rename crates/{cgp-extra-macro-lib/src/utils.rs => cgp-macro-lib/src/utils/string.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 468cbcbd..2b1b569a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,7 @@ dependencies = [ name = "cgp-extra-macro-lib" version = "0.6.1" dependencies = [ + "cgp-macro-lib", "proc-macro2", "quote", "syn", diff --git a/crates/cgp-extra-macro-lib/Cargo.toml b/crates/cgp-extra-macro-lib/Cargo.toml index adb0869d..e0975a82 100644 --- a/crates/cgp-extra-macro-lib/Cargo.toml +++ b/crates/cgp-extra-macro-lib/Cargo.toml @@ -18,3 +18,4 @@ default = [] syn = { version = "2.0.95", features = [ "full", "extra-traits" ] } quote = "1.0.38" proc-macro2 = "1.0.92" +cgp-macro-lib = { workspace = true } diff --git a/crates/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs b/crates/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs index 37e8257d..aded977a 100644 --- a/crates/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs +++ b/crates/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs @@ -1,5 +1,6 @@ use std::collections::BTreeSet; +use cgp_macro_lib::utils::to_camel_case_str; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::punctuated::Punctuated; @@ -10,8 +11,6 @@ use syn::{ ReturnType, TraitItemFn, Type, Visibility, parse2, }; -use crate::utils::to_camel_case_str; - pub fn cgp_auto_dispatch(_attr: TokenStream, mut out: TokenStream) -> syn::Result { let item_trait: ItemTrait = parse2(out.clone())?; diff --git a/crates/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs b/crates/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs index 0a30c415..2cc70090 100644 --- a/crates/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs +++ b/crates/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs @@ -1,3 +1,4 @@ +use cgp_macro_lib::utils::to_camel_case_str; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::punctuated::Punctuated; @@ -6,7 +7,6 @@ use syn::token::Comma; use syn::{FnArg, Ident, ItemFn, ItemImpl, ReturnType, Type, parse2}; use crate::parse::MaybeResultType; -use crate::utils::to_camel_case_str; pub fn cgp_computer(attr: TokenStream, body: TokenStream) -> syn::Result { let item_fn: ItemFn = parse2(body)?; diff --git a/crates/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs b/crates/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs index 7107271f..a409b822 100644 --- a/crates/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs +++ b/crates/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs @@ -1,10 +1,9 @@ +use cgp_macro_lib::utils::to_camel_case_str; use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; use syn::{Ident, ItemFn, ItemImpl, ReturnType, parse2}; -use crate::utils::to_camel_case_str; - pub fn cgp_producer(attr: TokenStream, body: TokenStream) -> syn::Result { let item_fn: ItemFn = parse2(body)?; diff --git a/crates/cgp-extra-macro-lib/src/lib.rs b/crates/cgp-extra-macro-lib/src/lib.rs index b2edcf26..9d4782ca 100644 --- a/crates/cgp-extra-macro-lib/src/lib.rs +++ b/crates/cgp-extra-macro-lib/src/lib.rs @@ -1,5 +1,4 @@ mod entrypoints; pub(crate) mod parse; -pub(crate) mod utils; pub use entrypoints::*; diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index 67c9c62c..ea56ba59 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -7,7 +7,7 @@ use crate::cgp_fn::fn_body::inject_implicit_args; use crate::derive_getter::derive_getter_constraint; use crate::symbol::symbol_from_string; -pub fn derive_cgp_fn(trait_ident: &Ident, item_fn: &mut ItemFn) -> syn::Result { +pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result { let implicit_args = extract_implicits_args(&mut item_fn.sig.inputs)?; let trait_item_fn = TraitItemFn { diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs index 6f0606fb..78aebd50 100644 --- a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs @@ -1,8 +1,18 @@ use proc_macro2::TokenStream; -use syn::{ItemFn, parse2}; +use syn::{Ident, ItemFn, parse2}; -pub fn cgp_fn(attr: TokenStream, body: TokenStream) -> syn::Result { +use crate::cgp_fn::derive_cgp_fn; +use crate::utils::to_camel_case_str; + +pub fn cgp_fn(_attr: TokenStream, body: TokenStream) -> syn::Result { let item_fn: ItemFn = parse2(body)?; - todo!() + let trait_ident = Ident::new( + &to_camel_case_str(&item_fn.sig.ident.to_string()), + item_fn.sig.ident.span(), + ); + + let derived = derive_cgp_fn(&trait_ident, item_fn)?; + + Ok(derived) } diff --git a/crates/cgp-macro-lib/src/lib.rs b/crates/cgp-macro-lib/src/lib.rs index bb2f798e..15fe3b25 100644 --- a/crates/cgp-macro-lib/src/lib.rs +++ b/crates/cgp-macro-lib/src/lib.rs @@ -18,7 +18,6 @@ pub(crate) mod derive_extractor; pub(crate) mod derive_getter; pub(crate) mod derive_has_fields; pub(crate) mod derive_provider; -mod entrypoints; pub(crate) mod field; pub(crate) mod for_each_replace; pub(crate) mod parse; @@ -28,6 +27,9 @@ pub(crate) mod replace_self; pub(crate) mod symbol; pub(crate) mod type_component; +mod entrypoints; +pub mod utils; + pub use field::derive_has_field; pub use product::{make_product_expr, make_product_type, make_sum_type}; pub use symbol::make_symbol; diff --git a/crates/cgp-macro-lib/src/utils/mod.rs b/crates/cgp-macro-lib/src/utils/mod.rs new file mode 100644 index 00000000..80160764 --- /dev/null +++ b/crates/cgp-macro-lib/src/utils/mod.rs @@ -0,0 +1,3 @@ +mod string; + +pub use string::*; diff --git a/crates/cgp-extra-macro-lib/src/utils.rs b/crates/cgp-macro-lib/src/utils/string.rs similarity index 100% rename from crates/cgp-extra-macro-lib/src/utils.rs rename to crates/cgp-macro-lib/src/utils/string.rs diff --git a/crates/cgp-macro/src/lib.rs b/crates/cgp-macro/src/lib.rs index 5d46694d..483bd5d0 100644 --- a/crates/cgp-macro/src/lib.rs +++ b/crates/cgp-macro/src/lib.rs @@ -210,6 +210,13 @@ pub fn cgp_impl(attr: TokenStream, item: TokenStream) -> TokenStream { .into() } +#[proc_macro_attribute] +pub fn cgp_fn(attr: TokenStream, item: TokenStream) -> TokenStream { + cgp_macro_lib::cgp_fn(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 From dd067c931fb817ad143d42789d2e3036a7ac1ad2 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 19:16:28 +0100 Subject: [PATCH 05/21] Basic #[cgp_fn] is working --- crates/cgp-core/src/prelude.rs | 4 ++-- crates/cgp-macro-lib/src/cgp_fn/derive.rs | 10 +++++----- crates/cgp-macro-lib/src/cgp_fn/fn_body.rs | 11 +++++++++-- crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs | 1 + crates/cgp-tests/tests/cgp_fn.rs | 1 + crates/cgp-tests/tests/cgp_fn_tests/basic.rs | 14 ++++++++++++++ crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 1 + 7 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 crates/cgp-tests/tests/cgp_fn.rs create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/basic.rs create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/mod.rs diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index 657590f7..8166820c 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -18,8 +18,8 @@ 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, cgp_impl, - cgp_inherit, cgp_new_provider, cgp_preset, cgp_provider, cgp_type, check_components, + Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_context, cgp_fn, cgp_getter, + cgp_impl, cgp_inherit, cgp_new_provider, cgp_preset, cgp_provider, cgp_type, check_components, delegate_and_check_components, delegate_components, product, re_export_imports, replace_with, }; pub use cgp_type::{HasType, ProvideType, UseType}; diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index ea56ba59..16fd0272 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; -use syn::{Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; +use syn::{Ident, ItemFn, ItemImpl, ItemTrait, TraitItemFn, Visibility, parse2}; use crate::cgp_fn::extract_implicits_args; use crate::cgp_fn::fn_body::inject_implicit_args; @@ -10,6 +10,8 @@ use crate::symbol::symbol_from_string; pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result { let implicit_args = extract_implicits_args(&mut item_fn.sig.inputs)?; + item_fn.vis = Visibility::Inherited; + let trait_item_fn = TraitItemFn { attrs: item_fn.attrs.clone(), sig: item_fn.sig.clone(), @@ -17,9 +19,7 @@ pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result syn::Result #trait_ident for __Context__ { #item_fn } 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 1fd8abe3..ed6e033c 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs @@ -5,7 +5,14 @@ use crate::cgp_fn::ImplicitArgField; use crate::derive_getter::extend_call_expr; use crate::symbol::symbol_from_string; -pub fn inject_implicit_args(arg: &ImplicitArgField, body: &mut Block) -> syn::Result<()> { +pub fn inject_implicit_args(args: &[ImplicitArgField], body: &mut Block) -> syn::Result<()> { + for arg in args.iter().rev() { + inject_implicit_arg(arg, body)?; + } + Ok(()) +} + +pub fn inject_implicit_arg(arg: &ImplicitArgField, body: &mut Block) -> syn::Result<()> { let field_name = &arg.field_name; let field_symbol = symbol_from_string(&field_name.to_string()); @@ -20,7 +27,7 @@ pub fn inject_implicit_args(arg: &ImplicitArgField, body: &mut Block) -> syn::Re let #field_name = #call_expr; })?; - body.stmts.push(statement); + body.stmts.insert(0, statement); Ok(()) } diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs index 78aebd50..f8661fdc 100644 --- a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs @@ -1,4 +1,5 @@ use proc_macro2::TokenStream; +use quote::ToTokens; use syn::{Ident, ItemFn, parse2}; use crate::cgp_fn::derive_cgp_fn; diff --git a/crates/cgp-tests/tests/cgp_fn.rs b/crates/cgp-tests/tests/cgp_fn.rs new file mode 100644 index 00000000..f623a10a --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn.rs @@ -0,0 +1 @@ +pub mod cgp_fn_tests; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/basic.rs b/crates/cgp-tests/tests/cgp_fn_tests/basic.rs new file mode 100644 index 00000000..b1da0246 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/basic.rs @@ -0,0 +1,14 @@ +use cgp::prelude::*; + +#[cgp_fn] +pub fn greet(&self, #[implicit] name: &str) { + println!("Hello, {}!", name); +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +pub trait CheckPerson: Greet {} +impl CheckPerson for Person {} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs new file mode 100644 index 00000000..38883ee0 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -0,0 +1 @@ +pub mod basic; From 81c68b5a2d30213fb147c378c0194f9918bade5a Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 19:25:48 +0100 Subject: [PATCH 06/21] Accept custom trait name --- crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs | 15 +++++++++------ crates/cgp-tests/tests/cgp_fn_tests/basic.rs | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs index f8661fdc..4103aec1 100644 --- a/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs @@ -1,17 +1,20 @@ use proc_macro2::TokenStream; -use quote::ToTokens; use syn::{Ident, ItemFn, parse2}; use crate::cgp_fn::derive_cgp_fn; use crate::utils::to_camel_case_str; -pub fn cgp_fn(_attr: TokenStream, body: TokenStream) -> syn::Result { +pub fn cgp_fn(attr: TokenStream, body: TokenStream) -> syn::Result { let item_fn: ItemFn = parse2(body)?; - let trait_ident = Ident::new( - &to_camel_case_str(&item_fn.sig.ident.to_string()), - item_fn.sig.ident.span(), - ); + let trait_ident = if attr.is_empty() { + Ident::new( + &to_camel_case_str(&item_fn.sig.ident.to_string()), + item_fn.sig.ident.span(), + ) + } else { + parse2(attr)? + }; let derived = derive_cgp_fn(&trait_ident, item_fn)?; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/basic.rs b/crates/cgp-tests/tests/cgp_fn_tests/basic.rs index b1da0246..1b09beb8 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/basic.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/basic.rs @@ -12,3 +12,17 @@ pub struct Person { pub trait CheckPerson: Greet {} impl CheckPerson for Person {} + +#[cgp_fn(CanCalculateArea)] +pub fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +pub trait CheckRectangle: CanCalculateArea {} +impl CheckRectangle for Rectangle {} From c199df7ad9e50cbe7660f3f3b166991d273176be Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 19:29:11 +0100 Subject: [PATCH 07/21] Require first argument to be self --- crates/cgp-macro-lib/src/cgp_fn/derive.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index 16fd0272..68c4944f 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -8,6 +8,16 @@ use crate::derive_getter::derive_getter_constraint; use crate::symbol::symbol_from_string; pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result { + match item_fn.sig.inputs.first() { + Some(syn::FnArg::Receiver(_)) => {} + _ => { + return Err(syn::Error::new_spanned( + &item_fn.sig.inputs, + "First argument must be self", + )); + } + } + let implicit_args = extract_implicits_args(&mut item_fn.sig.inputs)?; item_fn.vis = Visibility::Inherited; From c64768ee425b17ddb7c8510abde450eee9d83ec0 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 20:03:47 +0100 Subject: [PATCH 08/21] Test and fix implicit &mut fields --- crates/cgp-macro-lib/src/cgp_fn/derive.rs | 8 ++++---- crates/cgp-macro-lib/src/cgp_fn/fn_body.rs | 10 ++++++++-- .../src/cgp_fn/parse_implicits.rs | 19 +++++++++++------- .../cgp-macro-lib/src/derive_getter/parse.rs | 5 ++++- crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 1 + .../cgp-tests/tests/cgp_fn_tests/mutable.rs | 20 +++++++++++++++++++ 6 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/mutable.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index 68c4944f..db0009dc 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -8,17 +8,17 @@ use crate::derive_getter::derive_getter_constraint; use crate::symbol::symbol_from_string; pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result { - match item_fn.sig.inputs.first() { - Some(syn::FnArg::Receiver(_)) => {} + let receiver = match item_fn.sig.inputs.first() { + Some(syn::FnArg::Receiver(receiver)) => receiver.clone(), _ => { return Err(syn::Error::new_spanned( &item_fn.sig.inputs, "First argument must be self", )); } - } + }; - let implicit_args = extract_implicits_args(&mut item_fn.sig.inputs)?; + let implicit_args = extract_implicits_args(&receiver, &mut item_fn.sig.inputs)?; item_fn.vis = Visibility::Inherited; 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 ed6e033c..bd6a4675 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs @@ -17,8 +17,14 @@ pub fn inject_implicit_arg(arg: &ImplicitArgField, body: &mut Block) -> syn::Res let field_symbol = symbol_from_string(&field_name.to_string()); - let call_expr = quote! { - self.get_field(::core::marker::PhantomData::< #field_symbol >) + let call_expr = if arg.field_mut.is_none() { + quote! { + self.get_field(::core::marker::PhantomData::< #field_symbol >) + } + } else { + quote! { + self.get_field_mut(::core::marker::PhantomData::< #field_symbol >) + } }; let call_expr = extend_call_expr(call_expr, &arg.field_mode, &arg.field_mut); diff --git a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs index 2c72c306..529ec15a 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs @@ -2,12 +2,13 @@ use std::mem; use syn::punctuated::Punctuated; use syn::token::Comma; -use syn::{Attribute, FnArg, Meta, Pat, PatType}; +use syn::{Attribute, FnArg, Meta, Pat, PatType, Receiver}; use crate::cgp_fn::ImplicitArgField; use crate::derive_getter::parse_field_type; pub fn extract_implicits_args( + receiver: &Receiver, args: &mut Punctuated, ) -> syn::Result> { let mut implicit_args = Vec::new(); @@ -15,7 +16,7 @@ pub fn extract_implicits_args( let process_args = mem::take(args); for mut arg in process_args.into_iter() { - if let Some(implicit_arg) = try_parse_implicit_arg(&mut arg)? { + if let Some(implicit_arg) = try_parse_implicit_arg(receiver, &mut arg)? { implicit_args.push(implicit_arg); } else { args.push(arg); @@ -25,12 +26,15 @@ pub fn extract_implicits_args( Ok(implicit_args) } -pub fn try_parse_implicit_arg(arg: &mut FnArg) -> syn::Result> { +pub fn try_parse_implicit_arg( + receiver: &Receiver, + arg: &mut FnArg, +) -> syn::Result> { if let FnArg::Typed(arg) = arg { let attrs = mem::take(&mut arg.attrs); for attr in attrs { if is_implicit_attr(&attr) { - let spec = parse_implicit_arg(arg)?; + let spec = parse_implicit_arg(receiver, arg)?; return Ok(Some(spec)); } else { arg.attrs.push(attr); @@ -41,14 +45,15 @@ pub fn try_parse_implicit_arg(arg: &mut FnArg) -> syn::Result syn::Result { +pub fn parse_implicit_arg(receiver: &Receiver, arg: &PatType) -> syn::Result { let Pat::Ident(pat_ident) = &*arg.pat else { return Err(syn::Error::new_spanned(&arg.pat, "Expected an identifier")); }; - let field_mut = pat_ident.mutability; + let arg_type = arg.ty.as_ref(); + let field_mut = receiver.mutability; - let (field_type, field_mode) = parse_field_type(&arg.ty, &field_mut)?; + let (field_type, field_mode) = parse_field_type(&arg_type, &field_mut)?; let spec = ImplicitArgField { field_name: pat_ident.ident.clone(), diff --git a/crates/cgp-macro-lib/src/derive_getter/parse.rs b/crates/cgp-macro-lib/src/derive_getter/parse.rs index 4bce7579..a57a6d43 100644 --- a/crates/cgp-macro-lib/src/derive_getter/parse.rs +++ b/crates/cgp-macro-lib/src/derive_getter/parse.rs @@ -270,7 +270,10 @@ pub fn parse_field_type( 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", + format!( + "field type `{}` must have the same mutability as the self reference", + type_ref.to_token_stream() + ), )); } diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index 38883ee0..afaf684f 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -1 +1,2 @@ pub mod basic; +pub mod mutable; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs b/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs new file mode 100644 index 00000000..89d59bb9 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs @@ -0,0 +1,20 @@ +use cgp::prelude::*; + +#[cgp_fn] +pub fn capitalize_name(&mut self, #[implicit] name: &mut String) { + if let Some(first_char) = name.chars().next() { + if first_char.is_lowercase() { + let char_len = first_char.len_utf8(); + let capitalized = first_char.to_uppercase().to_string(); + name.replace_range(..char_len, &capitalized); + } + } +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +pub trait CheckPerson: CapitalizeName {} +impl CheckPerson for Person {} From 83e27a87836af738aa87d77f5f9aa84a9744fa1e Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 20:14:30 +0100 Subject: [PATCH 09/21] Refactor item trait and impl derive --- crates/cgp-macro-lib/src/cgp_fn/derive.rs | 46 +++---------------- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 38 +++++++++++++++ crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 19 ++++++++ crates/cgp-macro-lib/src/cgp_fn/mod.rs | 2 + 4 files changed, 65 insertions(+), 40 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/item_impl.rs create mode 100644 crates/cgp-macro-lib/src/cgp_fn/item_trait.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index db0009dc..af4325b8 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -1,11 +1,11 @@ use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; -use syn::{Ident, ItemFn, ItemImpl, ItemTrait, TraitItemFn, Visibility, parse2}; +use quote::quote; +use syn::{Ident, ItemFn, Visibility}; use crate::cgp_fn::extract_implicits_args; use crate::cgp_fn::fn_body::inject_implicit_args; -use crate::derive_getter::derive_getter_constraint; -use crate::symbol::symbol_from_string; +use crate::cgp_fn::item_impl::derive_item_impl; +use crate::cgp_fn::item_trait::derive_item_trait; pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result { let receiver = match item_fn.sig.inputs.first() { @@ -22,50 +22,16 @@ pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result #trait_ident for __Context__ { - #item_fn - } - })?; + let item_trait = derive_item_trait(trait_ident, &item_fn)?; - let where_clause = item_impl.generics.make_where_clause(); - - for arg in implicit_args { - let field_symbol = symbol_from_string(&arg.field_name.to_string()); - - let constraint = derive_getter_constraint( - &arg.field_type, - &arg.field_mut, - &arg.field_mode, - field_symbol.to_token_stream(), - &None, - )?; - - where_clause.predicates.push(parse2(quote! { - Self: #constraint - })?); - } + let item_impl = derive_item_impl(trait_ident, &item_fn, &implicit_args)?; let output = quote! { #item_trait #item_impl - }; Ok(output) diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs new file mode 100644 index 00000000..cd01c2c7 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -0,0 +1,38 @@ +use quote::{ToTokens, quote}; +use syn::{Ident, ItemFn, ItemImpl, parse2}; + +use crate::cgp_fn::ImplicitArgField; +use crate::derive_getter::derive_getter_constraint; +use crate::symbol::symbol_from_string; + +pub fn derive_item_impl( + trait_ident: &Ident, + item_fn: &ItemFn, + implicit_args: &[ImplicitArgField], +) -> syn::Result { + let mut item_impl: ItemImpl = parse2(quote! { + impl<__Context__> #trait_ident for __Context__ { + #item_fn + } + })?; + + let where_clause = item_impl.generics.make_where_clause(); + + for arg in implicit_args { + let field_symbol = symbol_from_string(&arg.field_name.to_string()); + + let constraint = derive_getter_constraint( + &arg.field_type, + &arg.field_mut, + &arg.field_mode, + field_symbol.to_token_stream(), + &None, + )?; + + where_clause.predicates.push(parse2(quote! { + Self: #constraint + })?); + } + + Ok(item_impl) +} diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs new file mode 100644 index 00000000..004200ec --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -0,0 +1,19 @@ +use quote::quote; +use syn::{Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; + +pub fn derive_item_trait(trait_ident: &Ident, item_fn: &ItemFn) -> syn::Result { + let trait_item_fn = TraitItemFn { + attrs: item_fn.attrs.clone(), + sig: item_fn.sig.clone(), + default: None, + semi_token: None, + }; + + let item_trait: ItemTrait = parse2(quote! { + pub trait #trait_ident { + #trait_item_fn + } + })?; + + Ok(item_trait) +} diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index 9f121dca..09b13daa 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -1,6 +1,8 @@ mod constraint; mod derive; mod fn_body; +mod item_impl; +mod item_trait; mod parse_implicits; mod spec; From 23f13dbf9ae9f2cb35a36d46ced2c6f8b080c6a3 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 20:31:02 +0100 Subject: [PATCH 10/21] Support and test generic params --- crates/cgp-macro-lib/src/cgp_fn/derive.rs | 8 ++++++-- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 10 ++++++++-- crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 8 +++++--- .../cgp-tests/tests/cgp_fn_tests/generics.rs | 20 +++++++++++++++++++ crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 1 + 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/generics.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index af4325b8..b2365060 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -1,3 +1,5 @@ +use core::mem; + use proc_macro2::TokenStream; use quote::quote; use syn::{Ident, ItemFn, Visibility}; @@ -24,9 +26,11 @@ pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result syn::Result { + let type_generics = generics.split_for_impl().1; + let mut item_impl: ItemImpl = parse2(quote! { - impl<__Context__> #trait_ident for __Context__ { + impl #trait_ident #type_generics for __Context__ { #item_fn } })?; + item_impl.generics = generics.clone(); + item_impl.generics.params.insert(0, parse2(quote! { __Context__ })?); + let where_clause = item_impl.generics.make_where_clause(); for arg in implicit_args { diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs index 004200ec..40fd5262 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -1,7 +1,7 @@ use quote::quote; -use syn::{Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; +use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; -pub fn derive_item_trait(trait_ident: &Ident, item_fn: &ItemFn) -> syn::Result { +pub fn derive_item_trait(trait_ident: &Ident, item_fn: &ItemFn, generics: &Generics) -> syn::Result { let trait_item_fn = TraitItemFn { attrs: item_fn.attrs.clone(), sig: item_fn.sig.clone(), @@ -9,8 +9,10 @@ pub fn derive_item_trait(trait_ident: &Ident, item_fn: &ItemFn) -> syn::Result(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar +where + Scalar: Mul + Clone, +{ + width * height +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f32, + pub height: f32, +} + +pub trait CheckRectangle: RectangleArea {} +impl CheckRectangle for Rectangle {} \ No newline at end of file diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index afaf684f..b0ead790 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -1,2 +1,3 @@ pub mod basic; pub mod mutable; +pub mod generics; \ No newline at end of file From aa5a8cc1abbc950b29f75607771f0a493af90814 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 20:37:40 +0100 Subject: [PATCH 11/21] Test calling an implicit function from another --- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 5 +++- crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 6 ++++- .../src/cgp_fn/parse_implicits.rs | 2 +- crates/cgp-tests/tests/cgp_fn_tests/basic.rs | 6 ++--- crates/cgp-tests/tests/cgp_fn_tests/call.rs | 24 +++++++++++++++++++ .../cgp-tests/tests/cgp_fn_tests/generics.rs | 8 +++++-- crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 3 ++- .../cgp-tests/tests/cgp_fn_tests/mutable.rs | 12 +++++----- 8 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/call.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs index 8f600525..3e4e1cb1 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -20,7 +20,10 @@ pub fn derive_item_impl( })?; item_impl.generics = generics.clone(); - item_impl.generics.params.insert(0, parse2(quote! { __Context__ })?); + item_impl + .generics + .params + .insert(0, parse2(quote! { __Context__ })?); let where_clause = item_impl.generics.make_where_clause(); diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs index 40fd5262..49c5c2d8 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -1,7 +1,11 @@ use quote::quote; use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; -pub fn derive_item_trait(trait_ident: &Ident, item_fn: &ItemFn, generics: &Generics) -> syn::Result { +pub fn derive_item_trait( + trait_ident: &Ident, + item_fn: &ItemFn, + generics: &Generics, +) -> syn::Result { let trait_item_fn = TraitItemFn { attrs: item_fn.attrs.clone(), sig: item_fn.sig.clone(), diff --git a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs index 529ec15a..b33c36fb 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs @@ -53,7 +53,7 @@ pub fn parse_implicit_arg(receiver: &Receiver, arg: &PatType) -> syn::Result f64 { +#[cgp_fn(CanCalculateRectangleArea)] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { width * height } @@ -24,5 +24,5 @@ pub struct Rectangle { pub height: f64, } -pub trait CheckRectangle: CanCalculateArea {} +pub trait CheckRectangle: CanCalculateRectangleArea {} impl CheckRectangle for Rectangle {} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/call.rs b/crates/cgp-tests/tests/cgp_fn_tests/call.rs new file mode 100644 index 00000000..c792c749 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/call.rs @@ -0,0 +1,24 @@ +use cgp::prelude::*; + +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} + +#[cgp_fn] +pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 +where + Self: RectangleArea, +{ + self.rectangle_area() * scale_factor * scale_factor +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, + pub scale_factor: f64, +} + +pub trait CheckRectangle: ScaledRectangleArea {} +impl CheckRectangle for Rectangle {} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/generics.rs b/crates/cgp-tests/tests/cgp_fn_tests/generics.rs index 519bae2c..367289d8 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/generics.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/generics.rs @@ -3,7 +3,11 @@ use std::ops::Mul; use cgp::prelude::*; #[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar +pub fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar where Scalar: Mul + Clone, { @@ -17,4 +21,4 @@ pub struct Rectangle { } pub trait CheckRectangle: RectangleArea {} -impl CheckRectangle for Rectangle {} \ No newline at end of file +impl CheckRectangle for Rectangle {} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index b0ead790..f6b02806 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -1,3 +1,4 @@ pub mod basic; +pub mod call; +pub mod generics; pub mod mutable; -pub mod generics; \ No newline at end of file diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs b/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs index 89d59bb9..507d127d 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs @@ -2,12 +2,12 @@ use cgp::prelude::*; #[cgp_fn] pub fn capitalize_name(&mut self, #[implicit] name: &mut String) { - if let Some(first_char) = name.chars().next() { - if first_char.is_lowercase() { - let char_len = first_char.len_utf8(); - let capitalized = first_char.to_uppercase().to_string(); - name.replace_range(..char_len, &capitalized); - } + if let Some(first_char) = name.chars().next() + && first_char.is_lowercase() + { + let char_len = first_char.len_utf8(); + let capitalized = first_char.to_uppercase().to_string(); + name.replace_range(..char_len, &capitalized); } } From 257d9dcdab9a4efbefe966a6f39ea08fc13ff5bf Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 22:29:16 +0100 Subject: [PATCH 12/21] Parse and test #[uses] and #[extend] attributes --- crates/cgp-macro-lib/src/cgp_fn/attributes.rs | 36 +++++++++++++++++++ crates/cgp-macro-lib/src/cgp_fn/derive.rs | 14 ++++++-- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 20 +++++++++-- crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 9 ++++- crates/cgp-macro-lib/src/cgp_fn/mod.rs | 2 ++ crates/cgp-macro-lib/src/cgp_fn/spec.rs | 9 ++++- crates/cgp-tests/tests/cgp_fn_tests/extend.rs | 34 ++++++++++++++++++ crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 2 ++ crates/cgp-tests/tests/cgp_fn_tests/uses.rs | 22 ++++++++++++ 9 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/attributes.rs create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/extend.rs create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/uses.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/attributes.rs b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs new file mode 100644 index 00000000..4d2b1e5d --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs @@ -0,0 +1,36 @@ +use core::mem; + +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Attribute, TypeParamBound}; + +use crate::cgp_fn::FunctionAttributes; +use crate::parse::SimpleType; + +pub fn parse_function_attributes( + attributes: &mut Vec, +) -> syn::Result { + let mut parsed_attributes = FunctionAttributes::default(); + + let in_attributes = mem::take(attributes); + + for attribute in in_attributes.into_iter() { + if let Some(ident) = attribute.path().get_ident() { + if ident == "extend" { + let extend_bound = attribute + .parse_args_with(Punctuated::::parse_terminated)?; + parsed_attributes.extend.extend(extend_bound); + } else if ident == "uses" { + let uses = + attribute.parse_args_with(Punctuated::::parse_terminated)?; + parsed_attributes.uses.extend(uses); + } else { + attributes.push(attribute); + } + } else { + attributes.push(attribute); + } + } + + Ok(parsed_attributes) +} diff --git a/crates/cgp-macro-lib/src/cgp_fn/derive.rs b/crates/cgp-macro-lib/src/cgp_fn/derive.rs index b2365060..160b026b 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/derive.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -4,10 +4,10 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Ident, ItemFn, Visibility}; -use crate::cgp_fn::extract_implicits_args; use crate::cgp_fn::fn_body::inject_implicit_args; use crate::cgp_fn::item_impl::derive_item_impl; use crate::cgp_fn::item_trait::derive_item_trait; +use crate::cgp_fn::{extract_implicits_args, parse_function_attributes}; pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result { let receiver = match item_fn.sig.inputs.first() { @@ -22,15 +22,23 @@ pub fn derive_cgp_fn(trait_ident: &Ident, mut item_fn: ItemFn) -> syn::Result syn::Result { let type_generics = generics.split_for_impl().1; @@ -27,6 +30,19 @@ pub fn derive_item_impl( let where_clause = item_impl.generics.make_where_clause(); + let mut bounds: Punctuated = Punctuated::default(); + bounds.extend(attributes.extend.clone()); + + for import in attributes.uses.iter() { + bounds.push(parse2(quote! { #import })?); + } + + if !bounds.is_empty() { + where_clause.predicates.push(parse2(quote! { + Self: #bounds + })?); + } + for arg in implicit_args { let field_symbol = symbol_from_string(&arg.field_name.to_string()); diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs index 49c5c2d8..42c1d51d 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -1,10 +1,13 @@ use quote::quote; use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; +use crate::cgp_fn::FunctionAttributes; + pub fn derive_item_trait( trait_ident: &Ident, item_fn: &ItemFn, generics: &Generics, + attributes: &FunctionAttributes, ) -> syn::Result { let trait_item_fn = TraitItemFn { attrs: item_fn.attrs.clone(), @@ -15,11 +18,15 @@ pub fn derive_item_trait( let (_, type_generics, _) = generics.split_for_impl(); - let item_trait: ItemTrait = parse2(quote! { + let mut item_trait: ItemTrait = parse2(quote! { pub trait #trait_ident #type_generics { #trait_item_fn } })?; + for extend in &attributes.extend { + item_trait.supertraits.push(extend.clone()); + } + Ok(item_trait) } diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index 09b13daa..807243de 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -1,3 +1,4 @@ +mod attributes; mod constraint; mod derive; mod fn_body; @@ -6,6 +7,7 @@ mod item_trait; mod parse_implicits; mod spec; +pub use attributes::*; pub use derive::*; pub use parse_implicits::*; pub use spec::*; diff --git a/crates/cgp-macro-lib/src/cgp_fn/spec.rs b/crates/cgp-macro-lib/src/cgp_fn/spec.rs index 8ed3816e..ada7070e 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/spec.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/spec.rs @@ -1,7 +1,8 @@ use syn::token::Mut; -use syn::{Ident, Type}; +use syn::{Ident, Type, TypeParamBound}; use crate::derive_getter::FieldMode; +use crate::parse::SimpleType; pub struct ImplicitArgField { pub field_name: Ident, @@ -9,3 +10,9 @@ pub struct ImplicitArgField { pub field_mut: Option, pub field_mode: FieldMode, } + +#[derive(Default)] +pub struct FunctionAttributes { + pub extend: Vec, + pub uses: Vec, +} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/extend.rs b/crates/cgp-tests/tests/cgp_fn_tests/extend.rs new file mode 100644 index 00000000..d8d52697 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/extend.rs @@ -0,0 +1,34 @@ +use std::ops::Mul; + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_fn] +#[extend(HasScalarType)] +pub fn rectangle_area( + &self, + #[implicit] width: Self::Scalar, + #[implicit] height: Self::Scalar, +) -> Self::Scalar +where + Self::Scalar: Mul + Clone, +{ + width * height +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +impl HasScalarType for Rectangle { + type Scalar = f64; +} + +pub trait CheckRectangle: RectangleArea {} +impl CheckRectangle for Rectangle {} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index f6b02806..7dbd741b 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -1,4 +1,6 @@ pub mod basic; pub mod call; +pub mod extend; pub mod generics; pub mod mutable; +pub mod uses; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/uses.rs b/crates/cgp-tests/tests/cgp_fn_tests/uses.rs new file mode 100644 index 00000000..a267732b --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/uses.rs @@ -0,0 +1,22 @@ +use cgp::prelude::*; + +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} + +#[cgp_fn] +#[uses(RectangleArea)] +pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { + self.rectangle_area() * scale_factor * scale_factor +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, + pub scale_factor: f64, +} + +pub trait CheckRectangle: ScaledRectangleArea {} +impl CheckRectangle for Rectangle {} From 8082e50a59a08091a57170f8af1d1da83e576eca Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:05:13 +0100 Subject: [PATCH 13/21] Draft implement substitute_abstract_type --- crates/cgp-macro-lib/src/cgp_fn/mod.rs | 2 + .../src/cgp_fn/substitute_type.rs | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index 807243de..d52a2d2e 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -6,8 +6,10 @@ mod item_impl; mod item_trait; mod parse_implicits; mod spec; +mod substitute_type; pub use attributes::*; pub use derive::*; pub use parse_implicits::*; pub use spec::*; +pub use substitute_type::*; diff --git a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs new file mode 100644 index 00000000..e3d9dbd2 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -0,0 +1,49 @@ +use proc_macro2::{Group, TokenStream, TokenTree}; +use quote::quote; +use syn::Ident; + +pub fn substitute_abstract_type( + context_type: &TokenStream, + type_idents: &[Ident], + body: TokenStream, +) -> TokenStream { + let mut out = TokenStream::new(); + let mut last_token_was_colon = false; + + for token_tree in body.into_iter() { + let token_is_colon = if let TokenTree::Punct(punct) = &token_tree + && punct.as_char() == ':' + { + true + } else { + false + }; + + match token_tree { + TokenTree::Group(group) => { + let new_stream = + substitute_abstract_type(context_type, type_idents, group.stream()); + out.extend([TokenTree::Group(Group::new(group.delimiter(), new_stream))]); + } + TokenTree::Ident(ident) => { + if type_idents.contains(&ident) && !last_token_was_colon { + out.extend(quote! { + #context_type :: #ident + }); + } else { + out.extend([TokenTree::Ident(ident)]); + } + } + TokenTree::Punct(punct) => { + out.extend([TokenTree::Punct(punct)]); + } + TokenTree::Literal(literal) => { + out.extend([TokenTree::Literal(literal)]); + } + } + + last_token_was_colon = token_is_colon; + } + + out +} From ea8090c305ba00dd3c69b85d402b75a37f67383c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:25:00 +0100 Subject: [PATCH 14/21] Draft implement #[use_type] attribute --- crates/cgp-macro-lib/src/cgp_fn/attributes.rs | 6 ++- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 22 +++++++-- crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 22 ++++++++- crates/cgp-macro-lib/src/cgp_fn/mod.rs | 2 + crates/cgp-macro-lib/src/cgp_fn/spec.rs | 2 + crates/cgp-macro-lib/src/cgp_fn/use_type.rs | 46 +++++++++++++++++++ 6 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 crates/cgp-macro-lib/src/cgp_fn/use_type.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/attributes.rs b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs index 4d2b1e5d..01103697 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/attributes.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs @@ -4,7 +4,7 @@ use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{Attribute, TypeParamBound}; -use crate::cgp_fn::FunctionAttributes; +use crate::cgp_fn::{FunctionAttributes, UseTypeSpec}; use crate::parse::SimpleType; pub fn parse_function_attributes( @@ -24,6 +24,10 @@ pub fn parse_function_attributes( let uses = attribute.parse_args_with(Punctuated::::parse_terminated)?; parsed_attributes.uses.extend(uses); + } else if ident == "use_type" { + let use_type = attribute + .parse_args_with(Punctuated::::parse_terminated)?; + parsed_attributes.use_type.extend(use_type); } else { attributes.push(attribute); } diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs index 0cf82ca0..ff49673d 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -3,7 +3,7 @@ use syn::punctuated::Punctuated; use syn::token::Plus; use syn::{Generics, Ident, ItemFn, ItemImpl, TypeParamBound, parse2}; -use crate::cgp_fn::{FunctionAttributes, ImplicitArgField}; +use crate::cgp_fn::{FunctionAttributes, ImplicitArgField, substitute_abstract_type}; use crate::derive_getter::derive_getter_constraint; use crate::symbol::symbol_from_string; @@ -28,8 +28,6 @@ pub fn derive_item_impl( .params .insert(0, parse2(quote! { __Context__ })?); - let where_clause = item_impl.generics.make_where_clause(); - let mut bounds: Punctuated = Punctuated::default(); bounds.extend(attributes.extend.clone()); @@ -37,6 +35,24 @@ pub fn derive_item_impl( bounds.push(parse2(quote! { #import })?); } + if !attributes.use_type.is_empty() { + let mut type_idents = Vec::new(); + + for use_type in attributes.use_type.iter() { + bounds.push(parse2(use_type.trait_path.to_token_stream())?); + + type_idents.extend(use_type.type_idents.clone()); + } + + item_impl = parse2(substitute_abstract_type( + "e! { Self }, + &type_idents, + item_impl.to_token_stream(), + ))?; + } + + let where_clause = item_impl.generics.make_where_clause(); + if !bounds.is_empty() { where_clause.predicates.push(parse2(quote! { Self: #bounds diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs index 42c1d51d..3e804549 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -1,7 +1,7 @@ -use quote::quote; +use quote::{ToTokens, quote}; use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; -use crate::cgp_fn::FunctionAttributes; +use crate::cgp_fn::{FunctionAttributes, substitute_abstract_type}; pub fn derive_item_trait( trait_ident: &Ident, @@ -28,5 +28,23 @@ pub fn derive_item_trait( item_trait.supertraits.push(extend.clone()); } + if !attributes.use_type.is_empty() { + let mut type_idents = Vec::new(); + + for use_type in attributes.use_type.iter() { + item_trait + .supertraits + .push(parse2(use_type.trait_path.to_token_stream())?); + + type_idents.extend(use_type.type_idents.clone()); + } + + item_trait = parse2(substitute_abstract_type( + "e! { Self }, + &type_idents, + item_trait.to_token_stream(), + ))?; + } + Ok(item_trait) } diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index d52a2d2e..83025a80 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -7,9 +7,11 @@ mod item_trait; mod parse_implicits; mod spec; mod substitute_type; +mod use_type; pub use attributes::*; pub use derive::*; pub use parse_implicits::*; pub use spec::*; pub use substitute_type::*; +pub use use_type::*; diff --git a/crates/cgp-macro-lib/src/cgp_fn/spec.rs b/crates/cgp-macro-lib/src/cgp_fn/spec.rs index ada7070e..5bccb94f 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/spec.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/spec.rs @@ -1,6 +1,7 @@ use syn::token::Mut; use syn::{Ident, Type, TypeParamBound}; +use crate::cgp_fn::UseTypeSpec; use crate::derive_getter::FieldMode; use crate::parse::SimpleType; @@ -15,4 +16,5 @@ pub struct ImplicitArgField { pub struct FunctionAttributes { pub extend: Vec, pub uses: Vec, + pub use_type: Vec, } diff --git a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs new file mode 100644 index 00000000..c2ba921a --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs @@ -0,0 +1,46 @@ +use syn::parse::{Parse, ParseStream}; +use syn::token::{Brace, Colon, Comma, Gt, Lt}; +use syn::{Ident, braced}; + +use crate::parse::SimpleType; + +pub struct UseTypeSpec { + pub trait_path: SimpleType, + pub type_idents: Vec, +} + +impl Parse for UseTypeSpec { + fn parse(input: ParseStream) -> syn::Result { + let turbofish = input.peek(Lt); + + if turbofish { + let _: Lt = input.parse()?; + } + + let trait_path: SimpleType = input.parse()?; + + if turbofish { + let _: Gt = input.parse()?; + } + + let _: Colon = input.parse()?; + let _: Colon = input.parse()?; + + let type_idents: Vec = if input.peek(Brace) { + let content; + braced!(content in input); + content + .parse_terminated(Ident::parse, Comma)? + .into_iter() + .collect() + } else { + let ident: Ident = input.parse()?; + vec![ident] + }; + + Ok(Self { + trait_path, + type_idents, + }) + } +} From 33fceccac027855de58a8f7f2ba3c845b4b4f61f Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:30:45 +0100 Subject: [PATCH 15/21] Test basic #[use_type] --- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 48 +++++++++++-------- crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 1 + .../cgp-tests/tests/cgp_fn_tests/use_type.rs | 17 +++++++ 3 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/use_type.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs index ff49673d..4ac0695d 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -35,6 +35,26 @@ pub fn derive_item_impl( bounds.push(parse2(quote! { #import })?); } + { + let where_clause = item_impl.generics.make_where_clause(); + + for arg in implicit_args { + let field_symbol = symbol_from_string(&arg.field_name.to_string()); + + let constraint = derive_getter_constraint( + &arg.field_type, + &arg.field_mut, + &arg.field_mode, + field_symbol.to_token_stream(), + &None, + )?; + + where_clause.predicates.push(parse2(quote! { + Self: #constraint + })?); + } + } + if !attributes.use_type.is_empty() { let mut type_idents = Vec::new(); @@ -51,28 +71,14 @@ pub fn derive_item_impl( ))?; } - let where_clause = item_impl.generics.make_where_clause(); - if !bounds.is_empty() { - where_clause.predicates.push(parse2(quote! { - Self: #bounds - })?); - } - - for arg in implicit_args { - let field_symbol = symbol_from_string(&arg.field_name.to_string()); - - let constraint = derive_getter_constraint( - &arg.field_type, - &arg.field_mut, - &arg.field_mode, - field_symbol.to_token_stream(), - &None, - )?; - - where_clause.predicates.push(parse2(quote! { - Self: #constraint - })?); + item_impl + .generics + .make_where_clause() + .predicates + .push(parse2(quote! { + Self: #bounds + })?); } Ok(item_impl) diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index 7dbd741b..7d5431e2 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -3,4 +3,5 @@ pub mod call; pub mod extend; pub mod generics; pub mod mutable; +pub mod use_type; pub mod uses; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/use_type.rs b/crates/cgp-tests/tests/cgp_fn_tests/use_type.rs new file mode 100644 index 00000000..3340bdae --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/use_type.rs @@ -0,0 +1,17 @@ +use std::ops::Mul; + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_fn] +#[use_type(HasScalarType::Scalar)] +pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar +where + Scalar: Mul + Clone, +{ + width * height +} From e62a3706e52886bdf768acb4f285b9351b8836e6 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:35:03 +0100 Subject: [PATCH 16/21] Use fully qualified type syntax --- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs index 4ac0695d..b4f7c12a 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -56,19 +56,20 @@ pub fn derive_item_impl( } if !attributes.use_type.is_empty() { - let mut type_idents = Vec::new(); + let mut item_impl_stream = item_impl.to_token_stream(); for use_type in attributes.use_type.iter() { - bounds.push(parse2(use_type.trait_path.to_token_stream())?); - - type_idents.extend(use_type.type_idents.clone()); + let trait_path = &use_type.trait_path; + bounds.push(parse2(trait_path.to_token_stream())?); + + item_impl_stream = substitute_abstract_type( + "e! { < Self as #trait_path > }, + &use_type.type_idents, + item_impl_stream, + ); } - item_impl = parse2(substitute_abstract_type( - "e! { Self }, - &type_idents, - item_impl.to_token_stream(), - ))?; + item_impl = parse2(item_impl_stream)?; } if !bounds.is_empty() { From 613e3a0ed19260e039ee3d9cf655fcc1cf97985f Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:41:22 +0100 Subject: [PATCH 17/21] Pass UseTypeSpec to substitute_abstract_type --- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 12 +++------ crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 25 ++++++++++--------- .../src/cgp_fn/substitute_type.rs | 13 +++++----- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs index b4f7c12a..d44248d1 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -59,14 +59,10 @@ pub fn derive_item_impl( let mut item_impl_stream = item_impl.to_token_stream(); for use_type in attributes.use_type.iter() { - let trait_path = &use_type.trait_path; - bounds.push(parse2(trait_path.to_token_stream())?); - - item_impl_stream = substitute_abstract_type( - "e! { < Self as #trait_path > }, - &use_type.type_idents, - item_impl_stream, - ); + bounds.push(parse2(use_type.trait_path.to_token_stream())?); + + item_impl_stream = + substitute_abstract_type("e! { Self }, &use_type, item_impl_stream); } item_impl = parse2(item_impl_stream)?; diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs index 3e804549..ada5051b 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -1,5 +1,7 @@ use quote::{ToTokens, quote}; -use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; +use syn::punctuated::Punctuated; +use syn::token::Plus; +use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, TypeParamBound, parse2}; use crate::cgp_fn::{FunctionAttributes, substitute_abstract_type}; @@ -24,27 +26,26 @@ pub fn derive_item_trait( } })?; + let mut bounds: Punctuated = Punctuated::default(); + for extend in &attributes.extend { - item_trait.supertraits.push(extend.clone()); + bounds.push(extend.clone()); } if !attributes.use_type.is_empty() { - let mut type_idents = Vec::new(); + let mut item_trait_stream = item_trait.to_token_stream(); for use_type in attributes.use_type.iter() { - item_trait - .supertraits - .push(parse2(use_type.trait_path.to_token_stream())?); + bounds.push(parse2(use_type.trait_path.to_token_stream())?); - type_idents.extend(use_type.type_idents.clone()); + item_trait_stream = + substitute_abstract_type("e! { Self }, &use_type, item_trait_stream); } - item_trait = parse2(substitute_abstract_type( - "e! { Self }, - &type_idents, - item_trait.to_token_stream(), - ))?; + item_trait = parse2(item_trait_stream)?; } + item_trait.supertraits.extend(bounds); + Ok(item_trait) } diff --git a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs index e3d9dbd2..19a33098 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -1,10 +1,11 @@ use proc_macro2::{Group, TokenStream, TokenTree}; use quote::quote; -use syn::Ident; + +use crate::cgp_fn::UseTypeSpec; pub fn substitute_abstract_type( context_type: &TokenStream, - type_idents: &[Ident], + type_spec: &UseTypeSpec, body: TokenStream, ) -> TokenStream { let mut out = TokenStream::new(); @@ -21,14 +22,14 @@ pub fn substitute_abstract_type( match token_tree { TokenTree::Group(group) => { - let new_stream = - substitute_abstract_type(context_type, type_idents, group.stream()); + let new_stream = substitute_abstract_type(context_type, type_spec, group.stream()); out.extend([TokenTree::Group(Group::new(group.delimiter(), new_stream))]); } TokenTree::Ident(ident) => { - if type_idents.contains(&ident) && !last_token_was_colon { + if type_spec.type_idents.contains(&ident) && !last_token_was_colon { + let trait_path = &type_spec.trait_path; out.extend(quote! { - #context_type :: #ident + < #context_type as #trait_path > :: #ident }); } else { out.extend([TokenTree::Ident(ident)]); From c002a24fd6c123fc40053400b3d4b232ca6c9d8e Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:45:47 +0100 Subject: [PATCH 18/21] Do abstract type substitution only once --- crates/cgp-macro-lib/src/cgp_fn/item_impl.rs | 11 ++++----- crates/cgp-macro-lib/src/cgp_fn/item_trait.rs | 11 ++++----- .../src/cgp_fn/substitute_type.rs | 24 ++++++++++++------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs index d44248d1..622a803f 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -56,16 +56,15 @@ pub fn derive_item_impl( } if !attributes.use_type.is_empty() { - let mut item_impl_stream = item_impl.to_token_stream(); - for use_type in attributes.use_type.iter() { bounds.push(parse2(use_type.trait_path.to_token_stream())?); - - item_impl_stream = - substitute_abstract_type("e! { Self }, &use_type, item_impl_stream); } - item_impl = parse2(item_impl_stream)?; + item_impl = parse2(substitute_abstract_type( + "e! { Self }, + &attributes.use_type, + item_impl.to_token_stream(), + ))?; } if !bounds.is_empty() { diff --git a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs index ada5051b..ea36ea2b 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -33,16 +33,15 @@ pub fn derive_item_trait( } if !attributes.use_type.is_empty() { - let mut item_trait_stream = item_trait.to_token_stream(); - for use_type in attributes.use_type.iter() { bounds.push(parse2(use_type.trait_path.to_token_stream())?); - - item_trait_stream = - substitute_abstract_type("e! { Self }, &use_type, item_trait_stream); } - item_trait = parse2(item_trait_stream)?; + item_trait = parse2(substitute_abstract_type( + "e! { Self }, + &attributes.use_type, + item_trait.to_token_stream(), + ))?; } item_trait.supertraits.extend(bounds); diff --git a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs index 19a33098..60660d75 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -5,7 +5,7 @@ use crate::cgp_fn::UseTypeSpec; pub fn substitute_abstract_type( context_type: &TokenStream, - type_spec: &UseTypeSpec, + type_specs: &[UseTypeSpec], body: TokenStream, ) -> TokenStream { let mut out = TokenStream::new(); @@ -22,16 +22,24 @@ pub fn substitute_abstract_type( match token_tree { TokenTree::Group(group) => { - let new_stream = substitute_abstract_type(context_type, type_spec, group.stream()); + let new_stream = substitute_abstract_type(context_type, type_specs, group.stream()); out.extend([TokenTree::Group(Group::new(group.delimiter(), new_stream))]); } TokenTree::Ident(ident) => { - if type_spec.type_idents.contains(&ident) && !last_token_was_colon { - let trait_path = &type_spec.trait_path; - out.extend(quote! { - < #context_type as #trait_path > :: #ident - }); - } else { + let mut replaced_ident = false; + + for type_spec in type_specs { + if type_spec.type_idents.contains(&ident) && !last_token_was_colon { + let trait_path = &type_spec.trait_path; + out.extend(quote! { + < #context_type as #trait_path > :: #ident + }); + replaced_ident = true; + break; + } + } + + if !replaced_ident { out.extend([TokenTree::Ident(ident)]); } } From f61275a33a848afb0482522086a2978e7aac1a9c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:48:02 +0100 Subject: [PATCH 19/21] Only allow trait generics inside turbofish --- crates/cgp-macro-lib/src/cgp_fn/use_type.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs index c2ba921a..a82ba9bf 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs @@ -11,17 +11,18 @@ pub struct UseTypeSpec { impl Parse for UseTypeSpec { fn parse(input: ParseStream) -> syn::Result { - let turbofish = input.peek(Lt); - - if turbofish { + let trait_path = if input.peek(Lt) { let _: Lt = input.parse()?; - } - - let trait_path: SimpleType = input.parse()?; - - if turbofish { + let trait_path: SimpleType = input.parse()?; let _: Gt = input.parse()?; - } + trait_path + } else { + let name: Ident = input.parse()?; + SimpleType { + name, + generics: None, + } + }; let _: Colon = input.parse()?; let _: Colon = input.parse()?; From a2f42ae3c804527678ddc45e63e0ae446de8a343 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:56:57 +0100 Subject: [PATCH 20/21] Support abstract type aliases --- .../src/cgp_fn/substitute_type.rs | 6 ++- crates/cgp-macro-lib/src/cgp_fn/use_type.rs | 51 +++++++++++++++++-- crates/cgp-tests/tests/cgp_fn_tests/mod.rs | 1 + .../tests/cgp_fn_tests/use_type_alias.rs | 17 +++++++ 4 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 crates/cgp-tests/tests/cgp_fn_tests/use_type_alias.rs diff --git a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs index 60660d75..4ed75a72 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -29,10 +29,12 @@ pub fn substitute_abstract_type( let mut replaced_ident = false; for type_spec in type_specs { - if type_spec.type_idents.contains(&ident) && !last_token_was_colon { + if !last_token_was_colon + && let Some(replacement_ident) = type_spec.replace_ident(&ident) + { let trait_path = &type_spec.trait_path; out.extend(quote! { - < #context_type as #trait_path > :: #ident + < #context_type as #trait_path > :: #replacement_ident }); replaced_ident = true; break; diff --git a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs index a82ba9bf..628a2bb4 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs @@ -1,12 +1,35 @@ use syn::parse::{Parse, ParseStream}; -use syn::token::{Brace, Colon, Comma, Gt, Lt}; +use syn::token::{As, Brace, Colon, Comma, Gt, Lt}; use syn::{Ident, braced}; use crate::parse::SimpleType; pub struct UseTypeSpec { pub trait_path: SimpleType, - pub type_idents: Vec, + pub type_idents: Vec, +} + +pub struct UseTypeIdent { + pub type_ident: Ident, + pub as_alias: Option, +} + +impl UseTypeSpec { + pub fn replace_ident(&self, ident: &Ident) -> Option { + for type_ident in &self.type_idents { + if type_ident.replacement_ident() == ident { + return Some(type_ident.type_ident.clone()); + } + } + + None + } +} + +impl UseTypeIdent { + pub fn replacement_ident(&self) -> &Ident { + self.as_alias.as_ref().unwrap_or(&self.type_ident) + } } impl Parse for UseTypeSpec { @@ -27,15 +50,15 @@ impl Parse for UseTypeSpec { let _: Colon = input.parse()?; let _: Colon = input.parse()?; - let type_idents: Vec = if input.peek(Brace) { + let type_idents: Vec = if input.peek(Brace) { let content; braced!(content in input); content - .parse_terminated(Ident::parse, Comma)? + .parse_terminated(UseTypeIdent::parse, Comma)? .into_iter() .collect() } else { - let ident: Ident = input.parse()?; + let ident: UseTypeIdent = input.parse()?; vec![ident] }; @@ -45,3 +68,21 @@ impl Parse for UseTypeSpec { }) } } + +impl Parse for UseTypeIdent { + fn parse(input: ParseStream) -> syn::Result { + let type_ident: Ident = input.parse()?; + + let as_alias = if input.peek(As) { + let _: As = input.parse()?; + Some(input.parse()?) + } else { + None + }; + + Ok(Self { + type_ident, + as_alias, + }) + } +} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index 7d5431e2..8f756f0c 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -4,4 +4,5 @@ pub mod extend; pub mod generics; pub mod mutable; pub mod use_type; +pub mod use_type_alias; pub mod uses; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/use_type_alias.rs b/crates/cgp-tests/tests/cgp_fn_tests/use_type_alias.rs new file mode 100644 index 00000000..b6748a16 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/use_type_alias.rs @@ -0,0 +1,17 @@ +use std::ops::Mul; + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_fn] +#[use_type(HasScalarType::{Scalar as S})] +pub fn rectangle_area(&self, #[implicit] width: S, #[implicit] height: S) -> S +where + S: Mul + Clone, +{ + width * height +} From a1a024c1b73660cd1742ab03a83ebdc8ae2a7f7a Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 17 Feb 2026 23:58:21 +0100 Subject: [PATCH 21/21] Only allow abstract type alias inside brace --- crates/cgp-macro-lib/src/cgp_fn/use_type.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs index 628a2bb4..1cf7aa0d 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs @@ -58,8 +58,11 @@ impl Parse for UseTypeSpec { .into_iter() .collect() } else { - let ident: UseTypeIdent = input.parse()?; - vec![ident] + let ident: Ident = input.parse()?; + vec![UseTypeIdent { + type_ident: ident, + as_alias: None, + }] }; Ok(Self {