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 cdd89520..05a20150 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::{ToTokens, quote}; use syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse2}; -use crate::cgp_fn::{FunctionAttributes, substitute_abstract_type}; +use crate::cgp_fn::{FunctionAttributes, UseTypeSpec, substitute_abstract_type}; pub fn derive_item_trait( trait_ident: &Ident, @@ -27,17 +27,26 @@ pub fn derive_item_trait( item_trait.supertraits.extend(attributes.extend.clone()); if !attributes.use_type.is_empty() { - item_trait = parse2(substitute_abstract_type( - "e! { Self }, - &attributes.use_type, - 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())?); - } + item_trait = expand_use_type_attributes_on_trait(&item_trait, &attributes.use_type)?; + } + + Ok(item_trait) +} + +pub fn expand_use_type_attributes_on_trait( + item_trait: &ItemTrait, + use_type_specs: &[UseTypeSpec], +) -> syn::Result { + let mut item_trait: ItemTrait = parse2(substitute_abstract_type( + "e! { Self }, + use_type_specs, + item_trait.to_token_stream(), + ))?; + + for use_type in use_type_specs.iter() { + item_trait + .supertraits + .push(parse2(use_type.trait_path.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 d4946081..fd38f127 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -16,6 +16,7 @@ pub use attributes::*; pub use bounds::*; pub use derive::*; pub use fn_body::*; +pub use item_trait::*; 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 index b078e2d9..ec805469 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -57,12 +57,11 @@ pub fn substitute_abstract_type( } if token_is_colon { - if last_token_was_colon { - last_token_was_colon = true; - } else { - last_token_was_colon = true; - last_two_tokens_was_colon = false; - } + last_two_tokens_was_colon = last_token_was_colon; + last_token_was_colon = true; + } else { + last_two_tokens_was_colon = false; + last_token_was_colon = false; } } diff --git a/crates/cgp-macro-lib/src/derive_component/attributes.rs b/crates/cgp-macro-lib/src/derive_component/attributes.rs new file mode 100644 index 00000000..1b3c4ce2 --- /dev/null +++ b/crates/cgp-macro-lib/src/derive_component/attributes.rs @@ -0,0 +1,53 @@ +use core::mem; + +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Attribute, TypeParamBound}; + +use crate::cgp_fn::UseTypeSpec; + +pub fn parse_component_attributes( + attributes: &mut Vec, +) -> syn::Result { + let mut parsed_attributes = ComponentAttributes::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 == "use_type" { + let use_type_specs = attribute + .parse_args_with(Punctuated::::parse_terminated)?; + + for use_type_spec in use_type_specs.iter() { + for type_ident in use_type_spec.type_idents.iter() { + if let Some(equals) = &type_ident.equals { + return Err(syn::Error::new_spanned( + equals, + "Type equality constraints cannot be used in component trait definition", + )); + } + } + } + + parsed_attributes.use_type.extend(use_type_specs); + } else { + attributes.push(attribute); + } + } else { + attributes.push(attribute); + } + } + + Ok(parsed_attributes) +} + +#[derive(Default)] +pub struct ComponentAttributes { + pub extend: Vec, + pub use_type: Vec, +} diff --git a/crates/cgp-macro-lib/src/derive_component/derive.rs b/crates/cgp-macro-lib/src/derive_component/derive.rs index 0189994b..6eaee290 100644 --- a/crates/cgp-macro-lib/src/derive_component/derive.rs +++ b/crates/cgp-macro-lib/src/derive_component/derive.rs @@ -4,6 +4,7 @@ use syn::{ItemImpl, ItemStruct, ItemTrait, parse2}; use crate::derive_component::component_name::derive_component_name_struct; use crate::derive_component::consumer_impl::derive_consumer_impl; +use crate::derive_component::preprocess_consumer_trait; use crate::derive_component::provider_impl::derive_provider_impl; use crate::derive_component::provider_trait::derive_provider_trait; use crate::derive_component::use_context_impl::derive_use_context_impl; @@ -13,7 +14,7 @@ use crate::parse::ComponentSpec; pub fn derive_component_with_ast( spec: &ComponentSpec, - consumer_trait: ItemTrait, + mut consumer_trait: ItemTrait, ) -> syn::Result { let provider_name = &spec.provider_name; let context_type = &spec.context_type; @@ -21,6 +22,8 @@ pub fn derive_component_with_ast( let component_name = &spec.component_name; let component_params = &spec.component_params; + preprocess_consumer_trait(&mut consumer_trait)?; + let component_struct = derive_component_name_struct(component_name, component_params)?; let provider_trait = derive_provider_trait( diff --git a/crates/cgp-macro-lib/src/derive_component/mod.rs b/crates/cgp-macro-lib/src/derive_component/mod.rs index 30fe7f6a..314c34fc 100644 --- a/crates/cgp-macro-lib/src/derive_component/mod.rs +++ b/crates/cgp-macro-lib/src/derive_component/mod.rs @@ -1,8 +1,10 @@ +mod attributes; mod component_name; mod consumer_impl; mod delegate_fn; mod delegate_type; mod derive; +mod preprocess; mod provider_impl; mod provider_trait; mod signature_args; @@ -10,3 +12,4 @@ mod use_context_impl; mod use_delegate_impl; pub use derive::*; +pub use preprocess::*; diff --git a/crates/cgp-macro-lib/src/derive_component/preprocess.rs b/crates/cgp-macro-lib/src/derive_component/preprocess.rs new file mode 100644 index 00000000..93078214 --- /dev/null +++ b/crates/cgp-macro-lib/src/derive_component/preprocess.rs @@ -0,0 +1,17 @@ +use syn::ItemTrait; + +use crate::cgp_fn::expand_use_type_attributes_on_trait; +use crate::derive_component::attributes::parse_component_attributes; + +pub fn preprocess_consumer_trait(consumer_trait: &mut ItemTrait) -> syn::Result<()> { + let attributes = parse_component_attributes(&mut consumer_trait.attrs)?; + + consumer_trait.supertraits.extend(attributes.extend.clone()); + + if !attributes.use_type.is_empty() { + *consumer_trait = + expand_use_type_attributes_on_trait(consumer_trait, &attributes.use_type)?; + } + + Ok(()) +} 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 ba6a72c7..ee627762 100644 --- a/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs +++ b/crates/cgp-macro-lib/src/entrypoints/cgp_auto_getter.rs @@ -2,6 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Error, Ident, ItemTrait}; +use crate::derive_component::preprocess_consumer_trait; use crate::derive_getter::{derive_blanket_impl, parse_getter_fields}; pub fn cgp_auto_getter(attr: TokenStream, body: TokenStream) -> syn::Result { @@ -12,7 +13,9 @@ pub fn cgp_auto_getter(attr: TokenStream, body: TokenStream) -> syn::Result syn::Result(attr)?.entries }; - let consumer_trait: ItemTrait = syn::parse2(body)?; + let mut consumer_trait: ItemTrait = syn::parse2(body)?; + + preprocess_consumer_trait(&mut consumer_trait)?; let provider_entry = entries.entry("provider".to_owned()); diff --git a/crates/cgp-tests/tests/component_tests/cgp_impl/abstract_types/basic.rs b/crates/cgp-tests/tests/component_tests/abstract_types/basic.rs similarity index 60% rename from crates/cgp-tests/tests/component_tests/cgp_impl/abstract_types/basic.rs rename to crates/cgp-tests/tests/component_tests/abstract_types/basic.rs index 4df1090b..da5e7537 100644 --- a/crates/cgp-tests/tests/component_tests/cgp_impl/abstract_types/basic.rs +++ b/crates/cgp-tests/tests/component_tests/abstract_types/basic.rs @@ -1,5 +1,7 @@ +use std::convert::Infallible; use std::ops::Mul; +use cgp::core::error::ErrorTypeProviderComponent; use cgp::prelude::*; #[cgp_type] @@ -8,18 +10,19 @@ pub trait HasScalarType { } #[cgp_component(AreaCalculator)] -pub trait CanCalculateArea: HasScalarType { - fn area(&self) -> Self::Scalar; +#[use_type(HasScalarType::Scalar, HasErrorType::Error)] +pub trait CanCalculateArea { + fn area(&self) -> Result; } #[cgp_impl(new RectangleArea)] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType::Scalar, HasErrorType::Error)] impl AreaCalculator where Scalar: Mul + Clone, { - fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { - width * height + fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Result { + Ok(width * height) } } @@ -32,6 +35,8 @@ pub struct Rectangle { delegate_and_check_components! { CanUseRectangle for Rectangle; Rectangle { + ErrorTypeProviderComponent: + UseType, ScalarTypeProviderComponent: UseType, AreaCalculatorComponent: diff --git a/crates/cgp-tests/tests/component_tests/abstract_types/extend.rs b/crates/cgp-tests/tests/component_tests/abstract_types/extend.rs new file mode 100644 index 00000000..ae550963 --- /dev/null +++ b/crates/cgp-tests/tests/component_tests/abstract_types/extend.rs @@ -0,0 +1,49 @@ +use std::convert::Infallible; +use std::ops::Mul; + +use cgp::core::error::ErrorTypeProviderComponent; +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_component(AreaCalculator)] +#[extend(HasScalarType, HasErrorType)] +pub trait CanCalculateArea { + fn area(&self) -> Result; +} + +#[cgp_impl(new RectangleArea)] +#[uses(HasScalarType, HasErrorType)] +impl AreaCalculator +where + Self::Scalar: Mul + Clone, +{ + fn area( + &self, + #[implicit] width: Self::Scalar, + #[implicit] height: Self::Scalar, + ) -> Result { + Ok(width * height) + } +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_and_check_components! { + CanUseRectangle for Rectangle; + Rectangle { + ErrorTypeProviderComponent: + UseType, + ScalarTypeProviderComponent: + UseType, + AreaCalculatorComponent: + RectangleArea, + } +} diff --git a/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs b/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs new file mode 100644 index 00000000..840f511a --- /dev/null +++ b/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs @@ -0,0 +1,2 @@ +pub mod basic; +pub mod extend; diff --git a/crates/cgp-tests/tests/component_tests/cgp_impl/abstract_types/mod.rs b/crates/cgp-tests/tests/component_tests/cgp_impl/abstract_types/mod.rs deleted file mode 100644 index 38883ee0..00000000 --- a/crates/cgp-tests/tests/component_tests/cgp_impl/abstract_types/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod basic; diff --git a/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs b/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs index ebe001d3..46750cc2 100644 --- a/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs +++ b/crates/cgp-tests/tests/component_tests/cgp_impl/mod.rs @@ -1,4 +1,3 @@ -pub mod abstract_types; pub mod basic; pub mod implicit_args; pub mod implicit_context; diff --git a/crates/cgp-tests/tests/component_tests/mod.rs b/crates/cgp-tests/tests/component_tests/mod.rs index cc6245c5..9afc10e1 100644 --- a/crates/cgp-tests/tests/component_tests/mod.rs +++ b/crates/cgp-tests/tests/component_tests/mod.rs @@ -1,3 +1,4 @@ +pub mod abstract_types; pub mod cgp_component; pub mod cgp_impl; pub mod consumer_delegate; diff --git a/crates/cgp-tests/tests/getter_tests/abstract_type.rs b/crates/cgp-tests/tests/getter_tests/abstract_type/explicit.rs similarity index 100% rename from crates/cgp-tests/tests/getter_tests/abstract_type.rs rename to crates/cgp-tests/tests/getter_tests/abstract_type/explicit.rs diff --git a/crates/cgp-tests/tests/getter_tests/abstract_type/import.rs b/crates/cgp-tests/tests/getter_tests/abstract_type/import.rs new file mode 100644 index 00000000..43a1dfaa --- /dev/null +++ b/crates/cgp-tests/tests/getter_tests/abstract_type/import.rs @@ -0,0 +1,22 @@ +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar: Clone; +} + +#[cgp_auto_getter] +#[extend(HasScalarType)] +pub trait AutoRectangleFields { + fn width(&self) -> Self::Scalar; + + fn height(&self) -> Self::Scalar; +} + +#[cgp_getter(RectangleFieldsGetter)] +#[extend(HasScalarType)] +pub trait HasRectangleFields { + fn width(&self) -> Self::Scalar; + + fn height(&self) -> Self::Scalar; +} diff --git a/crates/cgp-tests/tests/getter_tests/abstract_type/mod.rs b/crates/cgp-tests/tests/getter_tests/abstract_type/mod.rs new file mode 100644 index 00000000..f4ccf34b --- /dev/null +++ b/crates/cgp-tests/tests/getter_tests/abstract_type/mod.rs @@ -0,0 +1,3 @@ +pub mod explicit; +pub mod import; +pub mod use_type; diff --git a/crates/cgp-tests/tests/getter_tests/abstract_type/use_type.rs b/crates/cgp-tests/tests/getter_tests/abstract_type/use_type.rs new file mode 100644 index 00000000..acbce19d --- /dev/null +++ b/crates/cgp-tests/tests/getter_tests/abstract_type/use_type.rs @@ -0,0 +1,22 @@ +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar: Clone; +} + +#[cgp_auto_getter] +#[use_type(HasScalarType::Scalar)] +pub trait AutoRectangleFields { + fn width(&self) -> Scalar; + + fn height(&self) -> Scalar; +} + +#[cgp_getter(RectangleFieldsGetter)] +#[use_type(HasScalarType::Scalar)] +pub trait HasRectangleFields { + fn width(&self) -> Scalar; + + fn height(&self) -> Scalar; +}