diff --git a/crates/cgp-macro-lib/src/cgp_fn/apply_type.rs b/crates/cgp-macro-lib/src/cgp_fn/apply_type.rs index 22542627..d958f69a 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/apply_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/apply_type.rs @@ -1,30 +1,24 @@ -use quote::{ToTokens, quote}; -use syn::punctuated::Punctuated; -use syn::token::Plus; -use syn::{ItemImpl, TypeParamBound, parse2}; +use quote::ToTokens; +use syn::{ItemImpl, parse2}; -use crate::cgp_fn::{UseTypeSpec, derive_use_type_trait_bounds, substitute_abstract_type}; +use crate::cgp_fn::{UseTypeSpec, derive_use_type_predicates, substitute_abstract_types}; pub fn apply_use_type_attributes_to_item_impl( item_impl: &ItemImpl, use_type_specs: &[UseTypeSpec], ) -> syn::Result { - let mut item_impl: ItemImpl = parse2(substitute_abstract_type( - "e! { Self }, + let mut item_impl: ItemImpl = parse2(substitute_abstract_types( use_type_specs, item_impl.to_token_stream(), ))?; - let bounds = derive_use_type_trait_bounds("e! { Self }, use_type_specs)?; - let bounds = Punctuated::::from_iter(bounds); + let predicates = derive_use_type_predicates(use_type_specs)?; item_impl .generics .make_where_clause() .predicates - .push(parse2(quote! { - Self: #bounds - })?); + .extend(predicates); Ok(item_impl) } diff --git a/crates/cgp-macro-lib/src/cgp_fn/attributes.rs b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs index 01103697..0645c0fc 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/attributes.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/attributes.rs @@ -2,7 +2,7 @@ use core::mem; use syn::punctuated::Punctuated; use syn::token::Comma; -use syn::{Attribute, TypeParamBound}; +use syn::{Attribute, TypeParamBound, WherePredicate}; use crate::cgp_fn::{FunctionAttributes, UseTypeSpec}; use crate::parse::SimpleType; @@ -20,6 +20,10 @@ pub fn parse_function_attributes( let extend_bound = attribute .parse_args_with(Punctuated::::parse_terminated)?; parsed_attributes.extend.extend(extend_bound); + } else if ident == "extend_where" { + let where_predicates = attribute + .parse_args_with(Punctuated::::parse_terminated)?; + parsed_attributes.extend_where.extend(where_predicates); } else if ident == "uses" { let uses = attribute.parse_args_with(Punctuated::::parse_terminated)?; 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 a8868f01..ec369dd1 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/item_impl.rs @@ -48,6 +48,14 @@ pub fn derive_item_impl( } } + if !attributes.extend_where.is_empty() { + item_impl + .generics + .make_where_clause() + .predicates + .extend(attributes.extend_where.clone()); + } + if !implicit_args.is_empty() { let where_clause = item_impl.generics.make_where_clause(); let bounds = build_implicit_args_bounds(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 05a20150..eb953910 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 syn::{Generics, Ident, ItemFn, ItemTrait, TraitItemFn, parse_quote, parse2}; -use crate::cgp_fn::{FunctionAttributes, UseTypeSpec, substitute_abstract_type}; +use crate::cgp_fn::{FunctionAttributes, UseTypeSpec, substitute_abstract_types}; pub fn derive_item_trait( trait_ident: &Ident, @@ -16,16 +16,25 @@ pub fn derive_item_trait( semi_token: None, }; - let (_, type_generics, _) = generics.split_for_impl(); - let mut item_trait: ItemTrait = parse2(quote! { - pub trait #trait_ident #type_generics { + pub trait #trait_ident { #trait_item_fn } })?; + item_trait.generics = generics.clone(); + item_trait.generics.where_clause = None; + item_trait.supertraits.extend(attributes.extend.clone()); + if !attributes.extend_where.is_empty() { + item_trait + .generics + .make_where_clause() + .predicates + .extend(attributes.extend_where.clone()); + } + if !attributes.use_type.is_empty() { item_trait = expand_use_type_attributes_on_trait(&item_trait, &attributes.use_type)?; } @@ -37,13 +46,16 @@ 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 }, + let mut item_trait: ItemTrait = parse2(substitute_abstract_types( use_type_specs, item_trait.to_token_stream(), ))?; for use_type in use_type_specs.iter() { + if use_type.context_type != parse_quote! { Self } { + continue; + } + item_trait .supertraits .push(parse2(use_type.trait_path.to_token_stream())?); diff --git a/crates/cgp-macro-lib/src/cgp_fn/mod.rs b/crates/cgp-macro-lib/src/cgp_fn/mod.rs index fd38f127..6845f094 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/mod.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/mod.rs @@ -8,7 +8,7 @@ mod item_trait; mod parse_implicits; mod spec; mod substitute_type; -mod type_equality; +mod type_predicates; mod use_type; pub use apply_type::*; @@ -20,5 +20,5 @@ pub use item_trait::*; pub use parse_implicits::*; pub use spec::*; pub use substitute_type::*; -pub use type_equality::*; +pub use type_predicates::*; 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 a6bdd61d..aed51337 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/spec.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/spec.rs @@ -1,5 +1,5 @@ use syn::token::Mut; -use syn::{Ident, Type, TypeParamBound}; +use syn::{Ident, Type, TypeParamBound, WherePredicate}; use crate::cgp_fn::UseTypeSpec; use crate::derive_getter::FieldMode; @@ -17,6 +17,7 @@ pub struct ImplicitArgField { #[derive(Default)] pub struct FunctionAttributes { pub extend: Vec, + pub extend_where: 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 index ec805469..5e3214a5 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/substitute_type.rs @@ -3,11 +3,15 @@ use quote::quote; use crate::cgp_fn::UseTypeSpec; -pub fn substitute_abstract_type( - context_type: &TokenStream, - type_specs: &[UseTypeSpec], - body: TokenStream, -) -> TokenStream { +pub fn substitute_abstract_types(type_specs: &[UseTypeSpec], body: TokenStream) -> TokenStream { + let mut out = body; + for spec in type_specs.iter().rev() { + out = substitute_abstract_type(spec, out); + } + out +} + +pub fn substitute_abstract_type(type_specs: &UseTypeSpec, body: TokenStream) -> TokenStream { let mut out = TokenStream::new(); let mut last_token_was_colon = false; let mut last_two_tokens_was_colon = false; @@ -23,28 +27,20 @@ pub fn substitute_abstract_type( match token_tree { TokenTree::Group(group) => { - let new_stream = substitute_abstract_type(context_type, type_specs, group.stream()); + let new_stream = substitute_abstract_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_two_tokens_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 !last_two_tokens_was_colon + && let Some(replacement_ident) = type_specs.replace_ident(&ident) + { + let trait_path = &type_specs.trait_path; + let context_type = &type_specs.context_type; - if !replaced_ident { + out.extend(quote! { + < #context_type as #trait_path > :: #replacement_ident + }); + } else { out.extend([TokenTree::Ident(ident)]); } } diff --git a/crates/cgp-macro-lib/src/cgp_fn/type_equality.rs b/crates/cgp-macro-lib/src/cgp_fn/type_predicates.rs similarity index 62% rename from crates/cgp-macro-lib/src/cgp_fn/type_equality.rs rename to crates/cgp-macro-lib/src/cgp_fn/type_predicates.rs index fd289ffc..5dd9c7e7 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/type_equality.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/type_predicates.rs @@ -2,21 +2,29 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::punctuated::Punctuated; use syn::token::Comma; -use syn::{Ident, Type, TypeParamBound, parse2}; +use syn::{Ident, Type, WherePredicate, parse_quote, parse2}; use crate::cgp_fn::{UseTypeIdent, UseTypeSpec}; -pub fn derive_use_type_trait_bounds( - context_type: &TokenStream, - specs: &[UseTypeSpec], -) -> syn::Result> { - let mut bounds = Vec::new(); +pub fn derive_use_type_predicates(specs: &[UseTypeSpec]) -> syn::Result> { + let mut predicates = Vec::new(); for use_type in specs.iter() { - let type_equalities = find_type_equalities(use_type, context_type, specs)?; + let type_equalities = find_type_equalities(use_type, specs)?; + + let trait_path = &use_type.trait_path; + let mut context_type = use_type.context_type.clone(); + + if context_type != parse_quote!(Self) + && let Some(new_context_type) = find_type_alias(specs, &context_type)? + { + context_type = new_context_type; + } if type_equalities.is_empty() { - bounds.push(parse2(use_type.trait_path.to_token_stream())?); + predicates.push(parse2(quote! { + #context_type: #trait_path + })?); } else { let mut constraints: Punctuated = Punctuated::new(); @@ -26,21 +34,41 @@ pub fn derive_use_type_trait_bounds( }); } - let trait_path = &use_type.trait_path; - let bound = quote! { - #trait_path < #constraints > - }; + predicates.push(parse2(quote! { + #context_type: #trait_path < #constraints > + })?); + } + } + + Ok(predicates) +} + +fn find_type_alias(specs: &[UseTypeSpec], context_type: &Type) -> syn::Result> { + let Ok(context_ident) = parse2::(context_type.to_token_stream()) else { + return Ok(None); + }; + + for spec in specs { + for ident in spec.type_idents.iter() { + if ident.alias_ident() == &context_ident { + let new_context_type = &spec.context_type; + let type_ident = &ident.type_ident; + let trait_path = &spec.trait_path; - bounds.push(parse2(bound)?); + let new_type = parse2(quote! { + <#new_context_type as #trait_path>::#type_ident + })?; + + return Ok(Some(new_type)); + } } } - Ok(bounds) + Ok(None) } pub fn find_type_equalities( current_spec: &UseTypeSpec, - context_type: &TokenStream, specs: &[UseTypeSpec], ) -> syn::Result> { let mut equalities = Vec::new(); @@ -48,9 +76,7 @@ pub fn find_type_equalities( for current_type_ident in current_spec.type_idents.iter() { forbid_same_alias(current_type_ident, current_spec, specs)?; - if let Some(equality) = - find_type_equality(context_type, current_type_ident, current_spec, specs)? - { + if let Some(equality) = find_type_equality(current_type_ident, current_spec, specs)? { equalities.push(equality); } } @@ -83,7 +109,6 @@ fn forbid_same_alias( } fn find_type_equality( - context_type: &TokenStream, current_ident: &UseTypeIdent, current_spec: &UseTypeSpec, specs: &[UseTypeSpec], @@ -101,6 +126,7 @@ fn find_type_equality( let trait_path = &spec.trait_path; let current_type_ident = ¤t_ident.type_ident; let match_type_ident = &match_use_type.type_ident; + let context_type = &spec.context_type; let equal_target: Type = parse2(quote! { <#context_type as #trait_path>::#match_type_ident 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 d36949c1..f35c38fb 100644 --- a/crates/cgp-macro-lib/src/cgp_fn/use_type.rs +++ b/crates/cgp-macro-lib/src/cgp_fn/use_type.rs @@ -1,10 +1,12 @@ +use quote::ToTokens; use syn::parse::{Parse, ParseStream}; -use syn::token::{As, Brace, Colon, Comma, Eq, Gt, Lt}; -use syn::{Ident, Type, braced}; +use syn::token::{As, At, Brace, Colon, Comma, Eq, Gt, Lt}; +use syn::{Ident, Type, braced, parse_quote, parse2}; use crate::parse::SimpleType; pub struct UseTypeSpec { + pub context_type: Type, pub trait_path: SimpleType, pub type_idents: Vec, } @@ -37,31 +39,52 @@ impl UseTypeIdent { 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()?; + let body; + + let (context_type, body) = if input.peek(At) { + let _: At = input.parse()?; + + let context_type: SimpleType = input.parse()?; + let context_type = parse2(context_type.into_token_stream())?; + + let _: Colon = input.parse()?; + let _: Colon = input.parse()?; + + if input.peek(Brace) { + braced!(body in input); + (context_type, &body) + } else { + (context_type, input) + } + } else { + (parse_quote! { Self }, input) + }; + + let trait_path = if body.peek(Lt) { + let _: Lt = body.parse()?; + let trait_path: SimpleType = body.parse()?; + let _: Gt = body.parse()?; trait_path } else { - let name: Ident = input.parse()?; + let name: Ident = body.parse()?; SimpleType { name, generics: None, } }; - let _: Colon = input.parse()?; - let _: Colon = input.parse()?; + let _: Colon = body.parse()?; + let _: Colon = body.parse()?; - let type_idents: Vec = if input.peek(Brace) { + let type_idents: Vec = if body.peek(Brace) { let content; - braced!(content in input); + braced!(content in body); content .parse_terminated(UseTypeIdent::parse, Comma)? .into_iter() .collect() } else { - let ident: Ident = input.parse()?; + let ident: Ident = body.parse()?; vec![UseTypeIdent { type_ident: ident, as_alias: None, @@ -70,6 +93,7 @@ impl Parse for UseTypeSpec { }; Ok(Self { + context_type, trait_path, type_idents, }) diff --git a/crates/cgp-tests/tests/cgp_fn_tests/foreign_type.rs b/crates/cgp-tests/tests/cgp_fn_tests/foreign_type.rs new file mode 100644 index 00000000..91ef75ec --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/foreign_type.rs @@ -0,0 +1,37 @@ +use std::ops::Mul; + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_fn] +#[use_type(@Types::HasScalarType::Scalar)] +pub fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar +where + Scalar: Mul + Clone, +{ + let res: Scalar = width * height; + res +} + +pub struct Types; + +impl HasScalarType for Types { + type Scalar = f64; +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +pub trait CheckRectangle: RectangleArea {} +impl CheckRectangle for Rectangle {} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/foreign_type_equality.rs b/crates/cgp-tests/tests/cgp_fn_tests/foreign_type_equality.rs new file mode 100644 index 00000000..54b4a00b --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/foreign_type_equality.rs @@ -0,0 +1,58 @@ +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_type] +pub trait HasTypes { + type Types: HasScalarType; +} + +#[cgp_fn] +#[use_type( + HasTypes::Types, + @Types::HasScalarType::{Scalar = f64}, +)] +pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { + let res: f64 = width * height; + res +} + +pub trait HasFooType { + type Foo: Ord + Clone; +} + +pub trait HasBarType { + type Bar: HasFooType; +} + +#[cgp_fn] +#[use_type(HasFooType::Foo)] +pub fn do_foo(&self) -> Foo { + todo!() +} + +#[cgp_fn] +#[use_type(HasBarType::Bar, @Bar::HasFooType::Foo)] +pub fn do_bar(&self) -> Foo { + todo!() +} + +#[cgp_fn] +#[use_type( + HasFooType::Foo, + HasBarType::Bar, + @Bar::HasFooType::{Foo as BarFoo = Foo}, +)] +#[uses(DoFoo, DoBar)] +fn return_foo_or_bar(&self, flag: bool, #[implicit] foo: &Foo, #[implicit] bar: &BarFoo) -> Foo { + if flag { + let res: Foo = self.do_foo(); + if &res < foo { res } else { foo.clone() } + } else { + let res: BarFoo = self.do_bar(); + if &res < bar { res } else { bar.clone() } + } +} diff --git a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs index 2bbb504f..ce42cf2f 100644 --- a/crates/cgp-tests/tests/cgp_fn_tests/mod.rs +++ b/crates/cgp-tests/tests/cgp_fn_tests/mod.rs @@ -1,9 +1,12 @@ pub mod basic; pub mod call; pub mod extend; +pub mod foreign_type; +pub mod foreign_type_equality; pub mod generics; pub mod multi; pub mod mutable; +pub mod nested_foreign_type; pub mod type_equality; pub mod use_type; pub mod use_type_alias; diff --git a/crates/cgp-tests/tests/cgp_fn_tests/nested_foreign_type.rs b/crates/cgp-tests/tests/cgp_fn_tests/nested_foreign_type.rs new file mode 100644 index 00000000..5da55e01 --- /dev/null +++ b/crates/cgp-tests/tests/cgp_fn_tests/nested_foreign_type.rs @@ -0,0 +1,48 @@ +use std::ops::Mul; + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_type] +pub trait HasTypes { + type Types; +} + +#[cgp_fn] +#[use_type(HasTypes::Types, @Types::HasScalarType::Scalar)] +#[extend_where(Types: HasScalarType)] +pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar +where + Scalar: Mul + Clone, +{ + let res: Scalar = width * height; + res +} + +pub struct MyTypes; + +impl HasScalarType for MyTypes { + type Scalar = f64; +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +impl HasTypes for Rectangle { + type Types = MyTypes; +} + +pub trait CheckRectangle: RectangleArea +where + Self::Types: HasScalarType, +{ +} + +impl CheckRectangle for Rectangle {} diff --git a/crates/cgp-tests/tests/component_tests/abstract_types/foreign.rs b/crates/cgp-tests/tests/component_tests/abstract_types/foreign.rs new file mode 100644 index 00000000..5d745098 --- /dev/null +++ b/crates/cgp-tests/tests/component_tests/abstract_types/foreign.rs @@ -0,0 +1,54 @@ +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)] +#[use_type(@Types::HasScalarType::Scalar, HasErrorType::Error)] +pub trait CanCalculateArea { + fn area(&self) -> Result; +} + +#[cgp_impl(new RectangleArea)] +#[use_type(@Types::HasScalarType::Scalar, HasErrorType::Error)] +impl AreaCalculator +where + Scalar: Mul + Clone, +{ + fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Result { + Ok(width * height) + } +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +pub struct Types; + +impl HasScalarType for Types { + type Scalar = f64; +} + +delegate_components! { + Rectangle { + ErrorTypeProviderComponent: + UseType, + AreaCalculatorComponent: + RectangleArea, + } +} + +check_components! { + CanUseRectangle for Rectangle { + AreaCalculatorComponent: Types, + } +} diff --git a/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs b/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs index 840f511a..e206b362 100644 --- a/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs +++ b/crates/cgp-tests/tests/component_tests/abstract_types/mod.rs @@ -1,2 +1,3 @@ pub mod basic; pub mod extend; +pub mod foreign;