diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index 04d1a1aa..0be0ae4a 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -9,7 +9,7 @@ pub use cgp_error::{ }; pub use cgp_field::{ Char, Cons, Either, Field, FieldGetter, FromFields, HasField, HasFieldMut, HasFields, - HasFieldsRef, Index, MutFieldGetter, Nil, ToFields, ToFieldsRef, UseField, Void, + HasFieldsRef, Index, MRef, MutFieldGetter, Nil, ToFields, ToFieldsRef, UseField, Void, }; pub use cgp_macro::{ cgp_auto_getter, cgp_component, cgp_context, cgp_getter, cgp_new_provider, cgp_preset, diff --git a/crates/cgp-field/src/types/mod.rs b/crates/cgp-field/src/types/mod.rs index e28f1c0c..5ae15a9f 100644 --- a/crates/cgp-field/src/types/mod.rs +++ b/crates/cgp-field/src/types/mod.rs @@ -1,11 +1,13 @@ mod char; mod field; mod index; +mod mref; mod product; mod sum; pub use char::*; pub use field::*; pub use index::*; +pub use mref::*; pub use product::*; pub use sum::*; diff --git a/crates/cgp-field/src/types/mref.rs b/crates/cgp-field/src/types/mref.rs new file mode 100644 index 00000000..385e706c --- /dev/null +++ b/crates/cgp-field/src/types/mref.rs @@ -0,0 +1,47 @@ +use core::ops::Deref; + +pub enum MRef<'a, T> { + Ref(&'a T), + Owned(T), +} + +impl Deref for MRef<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + match self { + Self::Ref(value) => value, + Self::Owned(value) => value, + } + } +} + +impl AsRef for MRef<'_, T> { + fn as_ref(&self) -> &T { + self.deref() + } +} + +impl From for MRef<'_, T> { + fn from(value: T) -> Self { + Self::Owned(value) + } +} + +impl<'a, T> From<&'a T> for MRef<'a, T> { + fn from(value: &'a T) -> Self { + Self::Ref(value) + } +} + +impl MRef<'_, T> +where + T: Clone, +{ + pub fn get_or_clone(self) -> T { + match self { + Self::Ref(value) => value.clone(), + Self::Owned(value) => value, + } + } +} diff --git a/crates/cgp-macro-lib/src/derive_getter/constraint.rs b/crates/cgp-macro-lib/src/derive_getter/constraint.rs index 20c9f676..d4d085fb 100644 --- a/crates/cgp-macro-lib/src/derive_getter/constraint.rs +++ b/crates/cgp-macro-lib/src/derive_getter/constraint.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{parse2, TypeParamBound}; -use crate::derive_getter::GetterField; +use crate::derive_getter::{FieldMode, GetterField}; pub fn derive_getter_constraint( spec: &GetterField, @@ -11,8 +11,14 @@ pub fn derive_getter_constraint( let provider_type = &spec.field_type; let constraint = if spec.field_mut.is_none() { - quote! { - HasField< #field_symbol, Value = #provider_type > + if let FieldMode::Slice = spec.field_mode { + quote! { + HasField< #field_symbol, Value: AsRef< [ #provider_type ] > + 'static > + } + } else { + quote! { + HasField< #field_symbol, Value = #provider_type > + } } } else { quote! { diff --git a/crates/cgp-macro-lib/src/derive_getter/getter_field.rs b/crates/cgp-macro-lib/src/derive_getter/getter_field.rs index f4418f29..babdb951 100644 --- a/crates/cgp-macro-lib/src/derive_getter/getter_field.rs +++ b/crates/cgp-macro-lib/src/derive_getter/getter_field.rs @@ -13,6 +13,8 @@ pub struct GetterField { pub enum FieldMode { Reference, OptionRef, + MRef, Str, Clone, + Slice, } diff --git a/crates/cgp-macro-lib/src/derive_getter/method.rs b/crates/cgp-macro-lib/src/derive_getter/method.rs index e88dad76..a4e24067 100644 --- a/crates/cgp-macro-lib/src/derive_getter/method.rs +++ b/crates/cgp-macro-lib/src/derive_getter/method.rs @@ -84,6 +84,11 @@ pub fn derive_getter_method( } } } + FieldMode::MRef => { + quote! { + MRef::Ref( #call_expr ) + } + } FieldMode::Str => { if spec.field_mut.is_none() { quote! { @@ -95,9 +100,16 @@ pub fn derive_getter_method( } } } - FieldMode::Clone => quote! { - #call_expr .clone() - }, + FieldMode::Clone => { + quote! { + #call_expr .clone() + } + } + FieldMode::Slice => { + quote! { + #call_expr .as_ref() + } + } }; let return_type = &spec.return_type; diff --git a/crates/cgp-macro-lib/src/derive_getter/parse.rs b/crates/cgp-macro-lib/src/derive_getter/parse.rs index ea8d52e5..f2a01c1c 100644 --- a/crates/cgp-macro-lib/src/derive_getter/parse.rs +++ b/crates/cgp-macro-lib/src/derive_getter/parse.rs @@ -206,8 +206,12 @@ fn parse_field_type(return_type: &Type, field_mut: &Option) -> syn::Result< let field_type: Type = parse_quote! { String }; Ok((field_type, FieldMode::Str)) + } else if let (Type::Slice(slice), None) = (type_ref.elem.as_ref(), field_mut) { + let field_type = slice.elem.as_ref().clone(); + + Ok((field_type, FieldMode::Slice)) } else { - let field_type: Type = type_ref.elem.as_ref().clone(); + let field_type = type_ref.elem.as_ref().clone(); Ok((field_type, FieldMode::Reference)) } @@ -218,6 +222,8 @@ fn parse_field_type(return_type: &Type, field_mut: &Option) -> syn::Result< parse2(quote! { Option< #field_type > })?, FieldMode::OptionRef, )) + } else if let (Some(field_type), None) = (try_parse_mref(type_path), field_mut) { + Ok((field_type.clone(), FieldMode::MRef)) } else { Ok((return_type.clone(), FieldMode::Clone)) } @@ -263,7 +269,9 @@ fn try_parse_option_ref(type_path: &TypePath) -> Option<&Type> { if segment.ident == "Option" { if let PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(GenericArgument::Type(Type::Reference(type_ref))) = args.args.first() { + let [arg] = Vec::from_iter(args.args.iter()).try_into().ok()?; + + if let GenericArgument::Type(Type::Reference(type_ref)) = arg { return Some(type_ref.elem.as_ref()); } } @@ -271,3 +279,19 @@ fn try_parse_option_ref(type_path: &TypePath) -> Option<&Type> { None } + +fn try_parse_mref(type_path: &TypePath) -> Option<&Type> { + let segment = parse_single_segment_type_path(type_path).ok()?; + + if segment.ident == "MRef" { + if let PathArguments::AngleBracketed(args) = &segment.arguments { + let [arg1, arg2] = Vec::from_iter(args.args.iter()).try_into().ok()?; + + if let (GenericArgument::Lifetime(_), GenericArgument::Type(ty)) = (arg1, arg2) { + return Some(ty); + } + } + } + + None +} 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 6c5a918c..819901b2 100644 --- a/crates/cgp-macro-lib/src/derive_getter/with_provider.rs +++ b/crates/cgp-macro-lib/src/derive_getter/with_provider.rs @@ -3,7 +3,7 @@ use quote::{quote, ToTokens}; use syn::{parse2, Generics, Ident, ItemImpl, ItemTrait}; use crate::derive_getter::getter_field::GetterField; -use crate::derive_getter::{derive_getter_method, ContextArg}; +use crate::derive_getter::{derive_getter_method, ContextArg, FieldMode}; use crate::parse::ComponentSpec; pub fn derive_with_provider_impl( @@ -24,8 +24,14 @@ pub fn derive_with_provider_impl( let component_type = quote! { #component_name < #component_params > }; let provider_constraint = if field.field_mut.is_none() { - quote! { - FieldGetter< #context_type, #component_type , Value = #provider_type > + if let FieldMode::Slice = field.field_mode { + quote! { + FieldGetter< #context_type, #component_type, Value: AsRef< [ #provider_type ] > + 'static > + } + } else { + quote! { + FieldGetter< #context_type, #component_type , Value = #provider_type > + } } } else { quote! { diff --git a/crates/cgp-tests/src/tests/getter/abstract_type.rs b/crates/cgp-tests/src/tests/getter/abstract_type.rs new file mode 100644 index 00000000..7756c168 --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/abstract_type.rs @@ -0,0 +1,66 @@ +use cgp::prelude::*; + +#[test] +pub fn test_abstract_type_getter() { + #[cgp_type] + pub trait HasNameType { + type Name; + } + + #[cgp_getter { + provider: NameGetter, + }] + pub trait HasName: HasNameType { + fn name(&self) -> &Self::Name; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub name: String, + } + + delegate_components! { + AppComponents { + NameTypeProviderComponent: UseType, + NameGetterComponent: UseField, + } + } + + let context = App { + name: "Alice".to_owned(), + }; + + assert_eq!(context.name(), "Alice"); +} + +#[test] +pub fn test_abstract_type_auto_getter() { + #[cgp_type] + pub trait HasNameType { + type Name; + } + + #[cgp_auto_getter] + pub trait HasName: HasNameType { + fn name(&self) -> &Self::Name; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub name: String, + } + + delegate_components! { + AppComponents { + NameTypeProviderComponent: UseType, + } + } + + let context = App { + name: "Alice".to_owned(), + }; + + assert_eq!(context.name(), "Alice"); +} diff --git a/crates/cgp-tests/src/tests/getter/clone.rs b/crates/cgp-tests/src/tests/getter/clone.rs new file mode 100644 index 00000000..b12e1d84 --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/clone.rs @@ -0,0 +1,66 @@ +use cgp::prelude::*; + +#[test] +pub fn test_clone_getter() { + #[cgp_type] + pub trait HasNameType { + type Name; + } + + #[cgp_getter { + provider: NameGetter, + }] + pub trait HasName: HasNameType { + fn name(&self) -> Self::Name; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub name: String, + } + + delegate_components! { + AppComponents { + NameTypeProviderComponent: UseType, + NameGetterComponent: UseField, + } + } + + let context = App { + name: "Alice".to_owned(), + }; + + assert_eq!(context.name(), "Alice"); +} + +#[test] +pub fn test_clone_auto_getter() { + #[cgp_type] + pub trait HasNameType { + type Name; + } + + #[cgp_auto_getter] + pub trait HasName: HasNameType { + fn name(&self) -> Self::Name; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub name: String, + } + + delegate_components! { + AppComponents { + NameTypeProviderComponent: UseType, + } + } + + let context = App { + name: "Alice".to_owned(), + }; + + assert_eq!(context.name(), "Alice"); +} diff --git a/crates/cgp-tests/src/tests/getter/mod.rs b/crates/cgp-tests/src/tests/getter/mod.rs new file mode 100644 index 00000000..0e162952 --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/mod.rs @@ -0,0 +1,6 @@ +pub mod abstract_type; +pub mod clone; +pub mod mref; +pub mod option; +pub mod slice; +pub mod string; diff --git a/crates/cgp-tests/src/tests/getter/mref.rs b/crates/cgp-tests/src/tests/getter/mref.rs new file mode 100644 index 00000000..6e04945e --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/mref.rs @@ -0,0 +1,44 @@ +use cgp::prelude::*; + +#[test] +pub fn test_mref_getter() { + #[cgp_getter { + provider: FooGetter, + }] + pub trait HasFoo { + fn foo(&self) -> MRef<'_, String>; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub bar: String, + } + + delegate_components! { + AppComponents { + FooGetterComponent: UseField, + } + } + + let context = App { bar: "foo".into() }; + + assert_eq!(context.foo().as_ref(), "foo"); +} + +#[test] +pub fn test_mref_auto_getter() { + #[cgp_auto_getter] + pub trait HasFoo { + fn foo(&self) -> MRef<'_, String>; + } + + #[derive(HasField)] + pub struct App { + pub foo: String, + } + + let context = App { foo: "foo".into() }; + + assert_eq!(context.foo().as_ref(), "foo"); +} diff --git a/crates/cgp-tests/src/tests/getter/option.rs b/crates/cgp-tests/src/tests/getter/option.rs new file mode 100644 index 00000000..96930493 --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/option.rs @@ -0,0 +1,49 @@ +use cgp::prelude::*; + +#[test] +pub fn test_option_getter() { + #[cgp_getter { + provider: FooGetter, + }] + pub trait HasFoo { + fn foo(&self) -> Option<&String>; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub bar: Option, + } + + delegate_components! { + AppComponents { + FooGetterComponent: UseField, + } + } + + let context = App { + bar: Some("foo".to_owned()), + }; + + assert_eq!(context.foo(), Some(&"foo".to_owned())); +} + +#[test] +pub fn test_option_auto_getter() { + #[cgp_auto_getter] + pub trait HasFoo { + fn foo(&self) -> Option<&String>; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub foo: Option, + } + + let context = App { + foo: Some("foo".to_owned()), + }; + + assert_eq!(context.foo(), Some(&"foo".to_owned())); +} diff --git a/crates/cgp-tests/src/tests/getter/slice.rs b/crates/cgp-tests/src/tests/getter/slice.rs new file mode 100644 index 00000000..f7d2e3e1 --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/slice.rs @@ -0,0 +1,44 @@ +use cgp::prelude::*; + +#[test] +pub fn test_slice_getter() { + #[cgp_getter { + provider: FooGetter, + }] + pub trait HasFoo { + fn foo(&self) -> &[u8]; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub bar: Vec, + } + + delegate_components! { + AppComponents { + FooGetterComponent: UseField, + } + } + + let context = App { bar: vec![1, 2, 3] }; + + assert_eq!(context.foo(), &[1, 2, 3]); +} + +#[test] +pub fn test_slice_auto_getter() { + #[cgp_auto_getter] + pub trait HasFoo { + fn foo(&self) -> &[u8]; + } + + #[derive(HasField)] + pub struct App { + pub foo: Vec, + } + + let context = App { foo: vec![1, 2, 3] }; + + assert_eq!(context.foo(), &[1, 2, 3]); +} diff --git a/crates/cgp-tests/src/tests/getter/string.rs b/crates/cgp-tests/src/tests/getter/string.rs new file mode 100644 index 00000000..f4f44f8b --- /dev/null +++ b/crates/cgp-tests/src/tests/getter/string.rs @@ -0,0 +1,49 @@ +use cgp::prelude::*; + +#[test] +pub fn test_string_getter() { + #[cgp_getter { + provider: FooGetter, + }] + pub trait HasFoo { + fn foo(&self) -> &str; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub bar: String, + } + + delegate_components! { + AppComponents { + FooGetterComponent: UseField, + } + } + + let context = App { + bar: "abc".to_owned(), + }; + + assert_eq!(context.foo(), "abc"); +} + +#[test] +pub fn test_string_auto_getter() { + #[cgp_auto_getter] + pub trait HasFoo { + fn foo(&self) -> &str; + } + + #[cgp_context(AppComponents)] + #[derive(HasField)] + pub struct App { + pub foo: String, + } + + let context = App { + foo: "abc".to_owned(), + }; + + assert_eq!(context.foo(), "abc"); +} diff --git a/crates/cgp-tests/src/tests/mod.rs b/crates/cgp-tests/src/tests/mod.rs index c1e95197..b435dd25 100644 --- a/crates/cgp-tests/src/tests/mod.rs +++ b/crates/cgp-tests/src/tests/mod.rs @@ -1,2 +1,3 @@ +pub mod getter; pub mod has_fields; pub mod symbol;