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-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-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/attributes.rs b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs new file mode 100644 index 00000000..01103697 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs @@ -0,0 +1,40 @@ +use core::mem; + +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Attribute, TypeParamBound}; + +use crate::cgp_fn::{FunctionAttributes, UseTypeSpec}; +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 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); + } + } else { + attributes.push(attribute); + } + } + + Ok(parsed_attributes) +} 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 new file mode 100644 index 00000000..160b026b --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/derive.rs @@ -0,0 +1,50 @@ +use core::mem; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Ident, ItemFn, Visibility}; + +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() { + 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(&receiver, &mut item_fn.sig.inputs)?; + + let attributes = parse_function_attributes(&mut item_fn.attrs)?; + + item_fn.vis = Visibility::Inherited; + + inject_implicit_args(&implicit_args, &mut item_fn.block)?; + + let generics = mem::take(&mut item_fn.sig.generics); + + let item_trait = derive_item_trait(trait_ident, &item_fn, &generics, &attributes)?; + + let item_impl = derive_item_impl( + trait_ident, + &item_fn, + &implicit_args, + &generics, + &attributes, + )?; + + 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..bd6a4675 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/fn_body.rs @@ -0,0 +1,39 @@ +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(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()); + + 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); + + let statement = parse2(quote! { + let #field_name = #call_expr; + })?; + + body.stmts.insert(0, statement); + + Ok(()) +} 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..622a803f --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -0,0 +1,81 @@ +use quote::{ToTokens, quote}; +use syn::punctuated::Punctuated; +use syn::token::Plus; +use syn::{Generics, Ident, ItemFn, ItemImpl, TypeParamBound, parse2}; + +use crate::cgp_fn::{FunctionAttributes, ImplicitArgField, substitute_abstract_type}; +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], + generics: &Generics, + attributes: &FunctionAttributes, +) -> syn::Result { + let type_generics = generics.split_for_impl().1; + + let mut item_impl: ItemImpl = parse2(quote! { + impl #trait_ident #type_generics for __Context__ { + #item_fn + } + })?; + + item_impl.generics = generics.clone(); + item_impl + .generics + .params + .insert(0, parse2(quote! { __Context__ })?); + + let mut bounds: Punctuated = Punctuated::default(); + bounds.extend(attributes.extend.clone()); + + for import in attributes.uses.iter() { + 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() { + for use_type in attributes.use_type.iter() { + bounds.push(parse2(use_type.trait_path.to_token_stream())?); + } + + item_impl = parse2(substitute_abstract_type( + "e! { Self }, + &attributes.use_type, + item_impl.to_token_stream(), + ))?; + } + + if !bounds.is_empty() { + item_impl + .generics + .make_where_clause() + .predicates + .push(parse2(quote! { + Self: #bounds + })?); + } + + 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..ea36ea2b --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/item_trait.rs @@ -0,0 +1,50 @@ +use quote::{ToTokens, quote}; +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}; + +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(), + sig: item_fn.sig.clone(), + default: None, + semi_token: None, + }; + + let (_, type_generics, _) = generics.split_for_impl(); + + let mut item_trait: ItemTrait = parse2(quote! { + pub trait #trait_ident #type_generics { + #trait_item_fn + } + })?; + + let mut bounds: Punctuated = Punctuated::default(); + + for extend in &attributes.extend { + bounds.push(extend.clone()); + } + + if !attributes.use_type.is_empty() { + for use_type in attributes.use_type.iter() { + bounds.push(parse2(use_type.trait_path.to_token_stream())?); + } + + item_trait = parse2(substitute_abstract_type( + "e! { Self }, + &attributes.use_type, + item_trait.to_token_stream(), + ))?; + } + + item_trait.supertraits.extend(bounds); + + 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 new file mode 100644 index 00000000..83025a80 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -0,0 +1,17 @@ +mod attributes; +mod constraint; +mod derive; +mod fn_body; +mod item_impl; +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/parse_implicits.rs b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs new file mode 100644 index 00000000..b33c36fb --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/parse_implicits.rs @@ -0,0 +1,73 @@ +use std::mem; + +use syn::punctuated::Punctuated; +use syn::token::Comma; +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(); + + let process_args = mem::take(args); + + for mut arg in process_args.into_iter() { + if let Some(implicit_arg) = try_parse_implicit_arg(receiver, &mut arg)? { + implicit_args.push(implicit_arg); + } else { + args.push(arg); + } + } + + Ok(implicit_args) +} + +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(receiver, arg)?; + return Ok(Some(spec)); + } else { + arg.attrs.push(attr); + } + } + } + + Ok(None) +} + +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 arg_type = arg.ty.as_ref(); + let field_mut = receiver.mutability; + + let (field_type, field_mode) = parse_field_type(arg_type, &field_mut)?; + + let spec = ImplicitArgField { + field_name: pat_ident.ident.clone(), + field_type, + field_mut, + field_mode, + }; + + Ok(spec) +} + +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/cgp_fn/spec.rs b/crates/cgp-macro-lib/src/cgp_fn/spec.rs new file mode 100644 index 00000000..5bccb94f --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/spec.rs @@ -0,0 +1,20 @@ +use syn::token::Mut; +use syn::{Ident, Type, TypeParamBound}; + +use crate::cgp_fn::UseTypeSpec; +use crate::derive_getter::FieldMode; +use crate::parse::SimpleType; + +pub struct ImplicitArgField { + pub field_name: Ident, + pub field_type: Type, + pub field_mut: Option, + pub field_mode: FieldMode, +} + +#[derive(Default)] +pub struct FunctionAttributes { + pub extend: Vec, + pub uses: Vec, + pub use_type: Vec, +} 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..4ed75a72 --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -0,0 +1,60 @@ +use proc_macro2::{Group, TokenStream, TokenTree}; +use quote::quote; + +use crate::cgp_fn::UseTypeSpec; + +pub fn substitute_abstract_type( + context_type: &TokenStream, + type_specs: &[UseTypeSpec], + 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_specs, group.stream()); + out.extend([TokenTree::Group(Group::new(group.delimiter(), new_stream))]); + } + TokenTree::Ident(ident) => { + let mut replaced_ident = false; + + for type_spec in type_specs { + 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 > :: #replacement_ident + }); + replaced_ident = true; + break; + } + } + + if !replaced_ident { + 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 +} 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..1cf7aa0d --- /dev/null +++ b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs @@ -0,0 +1,91 @@ +use syn::parse::{Parse, ParseStream}; +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 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 { + fn parse(input: ParseStream) -> syn::Result { + let trait_path = if input.peek(Lt) { + let _: Lt = input.parse()?; + 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()?; + + let type_idents: Vec = if input.peek(Brace) { + let content; + braced!(content in input); + content + .parse_terminated(UseTypeIdent::parse, Comma)? + .into_iter() + .collect() + } else { + let ident: Ident = input.parse()?; + vec![UseTypeIdent { + type_ident: ident, + as_alias: None, + }] + }; + + Ok(Self { + trait_path, + type_idents, + }) + } +} + +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-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/parse.rs b/crates/cgp-macro-lib/src/derive_getter/parse.rs index 947efecb..a57a6d43 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,13 +261,19 @@ 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() { 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-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()), )?; 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..4103aec1 --- /dev/null +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_fn.rs @@ -0,0 +1,22 @@ +use proc_macro2::TokenStream; +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 { + let item_fn: ItemFn = parse2(body)?; + + 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)?; + + Ok(derived) +} 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..15fe3b25 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; @@ -17,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; @@ -27,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 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..2274c229 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/basic.rs @@ -0,0 +1,28 @@ +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 {} + +#[cgp_fn(CanCalculateRectangleArea)] +pub fn rectangle_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: 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/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/generics.rs b/crates/cgp-tests/tests/cgp_fn_tests/generics.rs new file mode 100644 index 00000000..367289d8 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/generics.rs @@ -0,0 +1,24 @@ +use std::ops::Mul; + +use cgp::prelude::*; + +#[cgp_fn] +pub fn rectangle_area( + &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 {} 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..8f756f0c --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -0,0 +1,8 @@ +pub mod basic; +pub mod call; +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/mutable.rs b/crates/cgp-tests/tests/cgp_fn_tests/mutable.rs new file mode 100644 index 00000000..507d127d --- /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() + && 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 {} 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 +} 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 +} 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 {}