diff --git a/crates/cgp-macro-lib/src/derive_getter/blanket.rs b/crates/cgp-macro-lib/src/derive_getter/blanket.rs index bbe9e7cb..db383649 100644 --- a/crates/cgp-macro-lib/src/derive_getter/blanket.rs +++ b/crates/cgp-macro-lib/src/derive_getter/blanket.rs @@ -2,7 +2,7 @@ use alloc::string::ToString; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; -use syn::{Ident, ItemImpl, ItemTrait, parse2}; +use syn::{Ident, ItemImpl, ItemTrait, TraitItemType, parse2}; use crate::derive_getter::getter_field::GetterField; use crate::derive_getter::{ @@ -14,12 +14,13 @@ pub fn derive_blanket_impl( context_type: &Ident, consumer_trait: &ItemTrait, fields: &[GetterField], + field_assoc_type: &Option, ) -> syn::Result { let consumer_name = &consumer_trait.ident; let supertrait_constraints = consumer_trait.supertraits.clone(); - let mut methods: TokenStream = TokenStream::new(); + let mut items: TokenStream = TokenStream::new(); let mut generics = consumer_trait.generics.clone(); @@ -27,6 +28,24 @@ pub fn derive_blanket_impl( .params .insert(0, parse2(context_type.to_token_stream())?); + if let Some(field_assoc_type) = field_assoc_type { + let field_assoc_type_ident = &field_assoc_type.ident; + + generics + .params + .push(parse2(field_assoc_type_ident.to_token_stream())?); + + items.extend(quote! { + type #field_assoc_type_ident = #field_assoc_type_ident; + }); + + let field_constraints = &field_assoc_type.bounds; + + generics.make_where_clause().predicates.push(parse2(quote! { + #field_assoc_type_ident: #field_constraints + })?); + } + let where_clause = generics.make_where_clause(); if !supertrait_constraints.is_empty() { @@ -53,9 +72,13 @@ pub fn derive_blanket_impl( None, ); - methods.extend(method); + items.extend(method); - let constraint = derive_getter_constraint(field, quote! { #field_symbol })?; + let constraint = derive_getter_constraint( + field, + quote! { #field_symbol }, + &field_assoc_type.as_ref().map(|item| item.ident.clone()), + )?; where_clause.predicates.push(parse2(quote! { #receiver_type: #constraint @@ -69,7 +92,7 @@ pub fn derive_blanket_impl( impl #impl_generics #consumer_name #type_generics for #context_type #where_clause { - #methods + #items } })?; diff --git a/crates/cgp-macro-lib/src/derive_getter/constraint.rs b/crates/cgp-macro-lib/src/derive_getter/constraint.rs index 8f977e6c..543c5d4d 100644 --- a/crates/cgp-macro-lib/src/derive_getter/constraint.rs +++ b/crates/cgp-macro-lib/src/derive_getter/constraint.rs @@ -1,28 +1,32 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{TypeParamBound, parse2}; +use syn::{Ident, TypeParamBound, parse_quote, parse2}; use crate::derive_getter::{FieldMode, GetterField}; pub fn derive_getter_constraint( spec: &GetterField, field_symbol: TokenStream, + field_assoc_type: &Option, ) -> syn::Result { - let provider_type = &spec.field_type; + let field_type = match field_assoc_type { + Some(field_assoc_type) => parse_quote! { #field_assoc_type }, + None => spec.field_type.clone(), + }; let constraint = if spec.field_mut.is_none() { if let FieldMode::Slice = spec.field_mode { quote! { - HasField< #field_symbol, Value: AsRef< [ #provider_type ] > + 'static > + HasField< #field_symbol, Value: AsRef< [ #field_type ] > + 'static > } } else { quote! { - HasField< #field_symbol, Value = #provider_type > + HasField< #field_symbol, Value = #field_type > } } } else { quote! { - HasFieldMut< #field_symbol, Value = #provider_type > + HasFieldMut< #field_symbol, Value = #field_type > } }; diff --git a/crates/cgp-macro-lib/src/derive_getter/parse.rs b/crates/cgp-macro-lib/src/derive_getter/parse.rs index d19a8e2b..947efecb 100644 --- a/crates/cgp-macro-lib/src/derive_getter/parse.rs +++ b/crates/cgp-macro-lib/src/derive_getter/parse.rs @@ -6,7 +6,7 @@ use syn::spanned::Spanned; use syn::token::{Comma, Mut}; use syn::{ Error, FnArg, GenericArgument, Ident, ItemTrait, PathArguments, PathSegment, ReturnType, - Signature, TraitItem, TraitItemFn, Type, TypePath, parse_quote, parse2, + Signature, TraitItem, TraitItemFn, TraitItemType, Type, TypePath, parse_quote, parse2, }; use crate::derive_getter::getter_field::GetterField; @@ -16,16 +16,45 @@ use crate::replace_self::replace_self_type; pub fn parse_getter_fields( context_type: &Ident, consumer_trait: &ItemTrait, -) -> syn::Result> { +) -> syn::Result<(Vec, Option)> { let mut fields = Vec::new(); + let mut field_assoc_type: Option = None; + + // Extract optional associated type first + for item in consumer_trait.items.iter() { + if let TraitItem::Type(item_type) = item { + if field_assoc_type.is_some() { + return Err(Error::new( + item_type.span(), + "at most one associated type is allowed in getter trait", + )); + } + + if !item_type.generics.params.is_empty() { + return Err(Error::new( + item_type.generics.params.span(), + "associated type in getter trait must not contain generic params", + )); + } + + field_assoc_type = Some(item_type.clone()); + } + } for item in consumer_trait.items.iter() { match item { TraitItem::Fn(method) => { - let getter_spec = parse_getter_method(context_type, method)?; + let getter_spec = parse_getter_method( + context_type, + method, + &field_assoc_type.as_ref().map(|item| item.ident.clone()), + )?; fields.push(getter_spec); } + TraitItem::Type(_) => { + // Already processed in the previous loop + } _ => { return Err(Error::new( item.span(), @@ -35,10 +64,37 @@ pub fn parse_getter_fields( } } - Ok(fields) + match (&field_assoc_type, fields.first(), fields.len()) { + (None, _, _) => {} + (Some(field_assoc_type), Some(field), 1) => { + let field_assoc_type_ident = &field_assoc_type.ident; + let field_type = &field.field_type; + + if field_type != &parse_quote! { Self :: #field_assoc_type_ident } + && field_type != &parse_quote! { #context_type :: #field_assoc_type_ident } + { + return Err(Error::new( + field.field_type.span(), + "getter method return type must match the associated type", + )); + } + } + _ => { + return Err(Error::new( + consumer_trait.span(), + "if associated type is defined, exactly one getter method must be defined", + )); + } + } + + Ok((fields, field_assoc_type)) } -fn parse_getter_method(context_type: &Ident, method: &TraitItemFn) -> syn::Result { +fn parse_getter_method( + context_type: &Ident, + method: &TraitItemFn, + field_assoc_type: &Option, +) -> syn::Result { let signature = &method.sig; validate_getter_method_signature(signature)?; @@ -49,7 +105,7 @@ fn parse_getter_method(context_type: &Ident, method: &TraitItemFn) -> syn::Resul let (receiver_mode, field_mut) = parse_receiver(context_type, arg)?; - let return_type = parse_return_type(context_type, &signature.output)?; + let return_type = parse_return_type(context_type, &signature.output, field_assoc_type)?; let (field_type, field_mode) = parse_field_type(&return_type, &field_mut)?; @@ -187,12 +243,16 @@ fn parse_receiver(context_ident: &Ident, arg: &FnArg) -> syn::Result<(ReceiverMo } } -fn parse_return_type(context_type: &Ident, return_type: &ReturnType) -> syn::Result { +fn parse_return_type( + context_type: &Ident, + return_type: &ReturnType, + field_assoc_type: &Option, +) -> syn::Result { match return_type { ReturnType::Type(_, ty) => parse2(replace_self_type( ty.to_token_stream(), context_type.to_token_stream(), - &Vec::new(), + &field_assoc_type.iter().cloned().collect::>(), )), _ => Err(Error::new( return_type.span(), 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 740a777c..ac118dbf 100644 --- a/crates/cgp-macro-lib/src/derive_getter/use_field.rs +++ b/crates/cgp-macro-lib/src/derive_getter/use_field.rs @@ -1,7 +1,8 @@ +use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::punctuated::Punctuated; use syn::token::Plus; -use syn::{Generics, ItemImpl, ItemTrait, TypeParamBound, parse2}; +use syn::{Generics, ItemImpl, ItemTrait, TraitItemType, TypeParamBound, parse2}; use crate::derive_getter::getter_field::GetterField; use crate::derive_getter::{ @@ -13,6 +14,7 @@ pub fn derive_use_field_impl( spec: &ComponentSpec, provider_trait: &ItemTrait, field: &GetterField, + field_assoc_type: &Option, ) -> syn::Result { let context_type = &spec.context_type; let provider_name = &provider_trait.ident; @@ -26,20 +28,53 @@ pub fn derive_use_field_impl( let tag_type = quote! { __Tag__ }; - let method = derive_getter_method(&ContextArg::Ident(receiver_type.clone()), field, None, None); + let mut items = TokenStream::new(); - let constraint = derive_getter_constraint(field, quote! { #tag_type })?; + let mut provider_generics = provider_trait.generics.clone(); - field_constraints.push(constraint); + if let Some(field_assoc_type) = field_assoc_type { + let field_assoc_type_ident = &field_assoc_type.ident; - let mut provider_generics = provider_trait.generics.clone(); + provider_generics + .params + .push(parse2(field_assoc_type_ident.to_token_stream())?); + + items.extend(quote! { + type #field_assoc_type_ident = #field_assoc_type_ident; + }); + + let field_constraints = &field_assoc_type.bounds; + + provider_generics + .make_where_clause() + .predicates + .push(parse2(quote! { + #field_assoc_type_ident: #field_constraints + })?); + } + + items.extend(derive_getter_method( + &ContextArg::Ident(receiver_type.clone()), + field, + None, + None, + )); + + let constraint = derive_getter_constraint( + field, + quote! { #tag_type }, + &field_assoc_type.as_ref().map(|item| item.ident.clone()), + )?; + + field_constraints.push(constraint); let mut where_clause = provider_generics.make_where_clause().clone(); where_clause .predicates .push(parse2(quote! { #receiver_type: #field_constraints })?); - let (impl_generics, type_generics, _) = provider_generics.split_for_impl(); + let (_, type_generics, _) = provider_trait.generics.split_for_impl(); + let (impl_generics, _, _) = provider_generics.split_for_impl(); let impl_generics = { let mut generics: Generics = parse2(impl_generics.to_token_stream())?; @@ -51,7 +86,7 @@ pub fn derive_use_field_impl( impl #impl_generics #provider_name #type_generics for UseField< #tag_type > #where_clause { - #method + #items } })?; 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 6a40cbd6..d83bdce6 100644 --- a/crates/cgp-macro-lib/src/derive_getter/use_fields.rs +++ b/crates/cgp-macro-lib/src/derive_getter/use_fields.rs @@ -2,7 +2,7 @@ use alloc::string::ToString; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; -use syn::{ItemImpl, ItemTrait, parse2}; +use syn::{ItemImpl, ItemTrait, TraitItemType, parse2}; use crate::derive_getter::getter_field::GetterField; use crate::derive_getter::{ @@ -15,15 +15,38 @@ pub fn derive_use_fields_impl( spec: &ComponentSpec, provider_trait: &ItemTrait, fields: &[GetterField], + field_assoc_type: &Option, ) -> syn::Result { let context_type = &spec.context_type; let provider_name = &spec.provider_name; - let mut methods: TokenStream = TokenStream::new(); + let mut items: TokenStream = TokenStream::new(); let mut provider_generics = provider_trait.generics.clone(); - let mut where_clause = provider_generics.make_where_clause().clone(); + + if let Some(field_assoc_type) = field_assoc_type { + let field_assoc_type_ident = &field_assoc_type.ident; + + provider_generics + .params + .push(parse2(field_assoc_type_ident.to_token_stream())?); + + items.extend(quote! { + type #field_assoc_type_ident = #field_assoc_type_ident; + }); + + let field_constraints = &field_assoc_type.bounds; + + provider_generics + .make_where_clause() + .predicates + .push(parse2(quote! { + #field_assoc_type_ident: #field_constraints + })?); + } + + let where_clause = provider_generics.make_where_clause(); for field in fields { let receiver_type = match &field.receiver_mode { @@ -40,22 +63,27 @@ pub fn derive_use_fields_impl( None, ); - methods.extend(method); + items.extend(method); - let constraint = derive_getter_constraint(field, quote! { #field_symbol })?; + let constraint = derive_getter_constraint( + field, + quote! { #field_symbol }, + &field_assoc_type.as_ref().map(|item| item.ident.clone()), + )?; where_clause .predicates .push(parse2(quote! { #receiver_type: #constraint })?); } - let (impl_generics, type_generics, _) = provider_generics.split_for_impl(); + let (_, type_generics, _) = provider_trait.generics.split_for_impl(); + let (impl_generics, _, where_clause) = provider_generics.split_for_impl(); let out = parse2(quote! { impl #impl_generics #provider_name #type_generics for UseFields #where_clause { - #methods + #items } })?; diff --git a/crates/cgp-macro-lib/src/derive_getter/with_provider.rs b/crates/cgp-macro-lib/src/derive_getter/with_provider.rs index 6805fb45..ee7c2fe5 100644 --- a/crates/cgp-macro-lib/src/derive_getter/with_provider.rs +++ b/crates/cgp-macro-lib/src/derive_getter/with_provider.rs @@ -1,6 +1,6 @@ -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, quote}; -use syn::{Generics, Ident, ItemImpl, ItemTrait, parse2}; +use syn::{Generics, Ident, ItemImpl, ItemTrait, TraitItemType, parse_quote, parse2}; use crate::derive_getter::getter_field::GetterField; use crate::derive_getter::{ContextArg, FieldMode, ReceiverMode, derive_getter_method}; @@ -10,6 +10,7 @@ pub fn derive_with_provider_impl( spec: &ComponentSpec, provider_trait: &ItemTrait, field: &GetterField, + field_assoc_type: &Option, ) -> syn::Result { let component_name = &spec.component_name; let component_params = &spec.component_params; @@ -22,43 +23,73 @@ pub fn derive_with_provider_impl( ReceiverMode::Type(ty) => ty.to_token_stream(), }; - let provider_type = &field.field_type; + let field_type = match field_assoc_type { + Some(field_assoc_type) => { + let field_assoc_type_ident = &field_assoc_type.ident; + parse_quote! { #field_assoc_type_ident } + } + None => field.field_type.clone(), + }; let provider_ident = Ident::new("__Provider__", Span::call_site()); let component_type = quote! { #component_name < #component_params > }; + let mut items = TokenStream::new(); + + let mut provider_generics = provider_trait.generics.clone(); + + if let Some(field_assoc_type) = field_assoc_type { + let field_assoc_type_ident = &field_assoc_type.ident; + + provider_generics + .params + .push(parse2(field_assoc_type_ident.to_token_stream())?); + + items.extend(quote! { + type #field_assoc_type_ident = #field_assoc_type_ident; + }); + + let field_constraints = &field_assoc_type.bounds; + + provider_generics + .make_where_clause() + .predicates + .push(parse2(quote! { + #field_assoc_type_ident: #field_constraints + })?); + } + let provider_constraint = if field.field_mut.is_none() { if let FieldMode::Slice = field.field_mode { quote! { - FieldGetter< #receiver_type, #component_type, Value: AsRef< [ #provider_type ] > + 'static > + FieldGetter< #receiver_type, #component_type, Value: AsRef< [ #field_type ] > + 'static > } } else { quote! { - FieldGetter< #receiver_type, #component_type , Value = #provider_type > + FieldGetter< #receiver_type, #component_type , Value = #field_type > } } } else { quote! { - MutFieldGetter< #receiver_type, #component_type, Value = #provider_type > + MutFieldGetter< #receiver_type, #component_type, Value = #field_type > } }; - let method = derive_getter_method( + items.extend(derive_getter_method( &ContextArg::Ident(receiver_type), field, None, Some(provider_ident.clone()), - ); - - let mut provider_generics = provider_trait.generics.clone(); + )); let mut where_clause = provider_generics.make_where_clause().clone(); where_clause .predicates .push(parse2(quote! { #provider_ident : #provider_constraint })?); - let (impl_generics, type_generics, _) = provider_generics.split_for_impl(); + let (_, type_generics, _) = provider_trait.generics.split_for_impl(); + let (impl_generics, _, _) = provider_generics.split_for_impl(); let impl_generics = { let mut generics: Generics = parse2(impl_generics.to_token_stream())?; @@ -70,7 +101,7 @@ pub fn derive_with_provider_impl( impl #impl_generics #provider_name #type_generics for WithProvider< #provider_ident > #where_clause { - #method + #items } })?; diff --git a/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs b/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs index c6100f0e..ba6a72c7 100644 --- a/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs @@ -16,9 +16,9 @@ pub fn cgp_auto_getter(attr: TokenStream, body: TokenStream) -> syn::Result syn::Result syn::Result &Self::Name; +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +pub trait CheckHasName: HasName {} +impl CheckHasName for Person {} diff --git a/crates/cgp-tests/tests/getter_tests/assoc_type/getter.rs b/crates/cgp-tests/tests/getter_tests/assoc_type/getter.rs new file mode 100644 index 00000000..441a4941 --- /dev/null +++ b/crates/cgp-tests/tests/getter_tests/assoc_type/getter.rs @@ -0,0 +1,25 @@ +use core::fmt::Display; + +use cgp::prelude::*; + +#[cgp_getter] +pub trait HasName { + type Name: Display; + + fn name(&self) -> &Self::Name; +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, +} + +delegate_components! { + Person { + NameGetterComponent: + UseField, + } +} + +pub trait CheckHasName: HasName {} +impl CheckHasName for Person {} diff --git a/crates/cgp-tests/tests/getter_tests/assoc_type/mod.rs b/crates/cgp-tests/tests/getter_tests/assoc_type/mod.rs new file mode 100644 index 00000000..98e2c237 --- /dev/null +++ b/crates/cgp-tests/tests/getter_tests/assoc_type/mod.rs @@ -0,0 +1,2 @@ +pub mod auto_getter; +pub mod getter; diff --git a/crates/cgp-tests/src/tests/getter/auto_generics.rs b/crates/cgp-tests/tests/getter_tests/auto_generics.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/auto_generics.rs rename to crates/cgp-tests/tests/getter_tests/auto_generics.rs diff --git a/crates/cgp-tests/src/tests/getter/clone.rs b/crates/cgp-tests/tests/getter_tests/clone.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/clone.rs rename to crates/cgp-tests/tests/getter_tests/clone.rs diff --git a/crates/cgp-tests/src/tests/getter/mod.rs b/crates/cgp-tests/tests/getter_tests/mod.rs similarity index 89% rename from crates/cgp-tests/src/tests/getter/mod.rs rename to crates/cgp-tests/tests/getter_tests/mod.rs index 2d113d64..30fc034d 100644 --- a/crates/cgp-tests/src/tests/getter/mod.rs +++ b/crates/cgp-tests/tests/getter_tests/mod.rs @@ -1,4 +1,5 @@ pub mod abstract_type; +pub mod assoc_type; pub mod auto_generics; pub mod clone; pub mod mref; diff --git a/crates/cgp-tests/src/tests/getter/mref.rs b/crates/cgp-tests/tests/getter_tests/mref.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/mref.rs rename to crates/cgp-tests/tests/getter_tests/mref.rs diff --git a/crates/cgp-tests/src/tests/getter/non_self.rs b/crates/cgp-tests/tests/getter_tests/non_self.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/non_self.rs rename to crates/cgp-tests/tests/getter_tests/non_self.rs diff --git a/crates/cgp-tests/src/tests/getter/non_self_auto.rs b/crates/cgp-tests/tests/getter_tests/non_self_auto.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/non_self_auto.rs rename to crates/cgp-tests/tests/getter_tests/non_self_auto.rs diff --git a/crates/cgp-tests/src/tests/getter/option.rs b/crates/cgp-tests/tests/getter_tests/option.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/option.rs rename to crates/cgp-tests/tests/getter_tests/option.rs diff --git a/crates/cgp-tests/src/tests/getter/slice.rs b/crates/cgp-tests/tests/getter_tests/slice.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/slice.rs rename to crates/cgp-tests/tests/getter_tests/slice.rs diff --git a/crates/cgp-tests/src/tests/getter/string.rs b/crates/cgp-tests/tests/getter_tests/string.rs similarity index 100% rename from crates/cgp-tests/src/tests/getter/string.rs rename to crates/cgp-tests/tests/getter_tests/string.rs