diff --git a/crates/cgp-macro-lib/src/check_components/derive.rs b/crates/cgp-macro-lib/src/check_components/derive.rs index 7c352a22..91ce6dc2 100644 --- a/crates/cgp-macro-lib/src/check_components/derive.rs +++ b/crates/cgp-macro-lib/src/check_components/derive.rs @@ -5,6 +5,10 @@ use crate::check_components::override_span; use crate::parse::{CheckComponents, CheckEntry}; pub fn derive_check_components(spec: &CheckComponents) -> syn::Result<(ItemTrait, Vec)> { + if let Some(providers) = &spec.check_provider { + return derive_check_provider(spec, providers); + } + let mut item_impls = Vec::new(); let unit: Type = parse2(quote!(()))?; @@ -42,3 +46,43 @@ pub fn derive_check_components(spec: &CheckComponents) -> syn::Result<(ItemTrait Ok((item_trait, item_impls)) } + +pub fn derive_check_provider( + spec: &CheckComponents, + providers: &[Type], +) -> syn::Result<(ItemTrait, Vec)> { + let mut item_impls = Vec::new(); + let unit: Type = parse2(quote!(()))?; + + let context_type = &spec.context_type; + let trait_name = &spec.trait_name; + let impl_generics = &spec.impl_generics; + let where_clause = &spec.where_clause; + + let item_trait = parse2(quote! { + trait #trait_name <__Component__, __Params__: ?Sized>: IsProviderFor<__Component__, #context_type, __Params__> {} + })?; + + for CheckEntry { + component_type, + component_params, + .. + } in spec.check_entries.entries.iter() + { + let component_param = component_params.as_ref().unwrap_or(&unit); + + for provider in providers { + let item_impl: ItemImpl = parse2(quote! { + impl #impl_generics + #trait_name < #component_type, #component_param > + for #provider + #where_clause + {} + })?; + + item_impls.push(item_impl); + } + } + + Ok((item_trait, item_impls)) +} diff --git a/crates/cgp-macro-lib/src/entrypoints/check_components.rs b/crates/cgp-macro-lib/src/entrypoints/check_components.rs index 2ed18a0d..78afaeba 100644 --- a/crates/cgp-macro-lib/src/entrypoints/check_components.rs +++ b/crates/cgp-macro-lib/src/entrypoints/check_components.rs @@ -3,15 +3,19 @@ use quote::{ToTokens, TokenStreamExt}; use syn::parse2; use crate::check_components::derive_check_components; -use crate::parse::CheckComponents; +use crate::parse::CheckComponentsSpecs; pub fn check_components(body: TokenStream) -> syn::Result { - let spec: CheckComponents = parse2(body)?; + let spec: CheckComponentsSpecs = parse2(body)?; - let (item_trait, item_impls) = derive_check_components(&spec)?; + let mut out = TokenStream::new(); - let mut out = item_trait.to_token_stream(); - out.append_all(item_impls); + for spec in spec.specs { + let (item_trait, item_impls) = derive_check_components(&spec)?; + + out.append_all(item_trait.to_token_stream()); + out.append_all(item_impls); + } Ok(out) } diff --git a/crates/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs b/crates/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs index dc36b81c..00ca7393 100644 --- a/crates/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs +++ b/crates/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs @@ -58,6 +58,7 @@ pub fn delegate_and_check_components(body: TokenStream) -> syn::Result, +} + pub struct CheckComponents { + pub check_provider: Option>, pub impl_generics: ImplGenerics, pub trait_name: Ident, pub context_type: Type, @@ -29,8 +34,46 @@ struct ParseCheckEntries { pub entries: Vec, } +impl Parse for CheckComponentsSpecs { + fn parse(input: ParseStream) -> syn::Result { + let mut specs = Vec::new(); + + while !input.is_empty() { + let spec: CheckComponents = input.parse()?; + specs.push(spec); + } + + Ok(Self { specs }) + } +} + impl Parse for CheckComponents { fn parse(input: ParseStream) -> syn::Result { + let check_provider = if input.peek(Pound) { + let _: Pound = input.parse()?; + + let content; + bracketed!(content in input); + + let command: Ident = content.parse()?; + if command != "check_providers" { + return Err(syn::Error::new( + command.span(), + "expected `check_providers` attribute", + )); + } + + let raw_providers; + parenthesized!(raw_providers in content); + + let provider_types: Punctuated = + Punctuated::parse_terminated(&raw_providers)?; + + Some(provider_types.into_iter().collect()) + } else { + None + }; + let impl_generics = if input.peek(Lt) { input.parse()? } else { @@ -58,6 +101,7 @@ impl Parse for CheckComponents { let entries: CheckEntries = content.parse()?; Ok(Self { + check_provider, impl_generics, trait_name, context_type, diff --git a/crates/cgp-tests/src/tests/check_components.rs b/crates/cgp-tests/src/tests/check_components.rs index a82ab51b..59ac55b1 100644 --- a/crates/cgp-tests/src/tests/check_components.rs +++ b/crates/cgp-tests/src/tests/check_components.rs @@ -34,6 +34,7 @@ pub fn test_basic_check_components() { #[derive(HasField)] pub struct Context { pub dummy: (), + pub extra_dummy: (), } delegate_components! { @@ -61,6 +62,35 @@ pub fn test_basic_check_components() { ], FooGetterAtComponent: Index<3>, + } + + CanUseContext2 for Context { + BarGetterAtComponent: [ + (Index<0>, Index<1>), + (Index<1>, Index<0>), + ], + BarGetterAtComponent: + (Index<3>, Index<4>), + [ + FooGetterAtComponent, + BarGetterAtComponent, + ]: [ + (Index<5>, Index<6>), + (Index<7>, Index<8>), + ] + } + + #[check_providers( + UseField, + UseField, + )] + CanUseDummyField for Context { + FooGetterAtComponent: [ + Index<0>, + Index<1>, + ], + FooGetterAtComponent: + Index<3>, BarGetterAtComponent: [ (Index<0>, Index<1>), (Index<1>, Index<0>),