From e82db3a399c9ad161a26403899aae95d796bc562 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 31 Jan 2026 19:22:07 +0100 Subject: [PATCH 1/6] Allow multiple specs in one `check_components!` macro call --- .../src/entrypoints/check_components.rs | 14 +++++++++----- .../cgp-macro-lib/src/parse/check_components.rs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) 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/parse/check_components.rs b/crates/cgp-macro-lib/src/parse/check_components.rs index bf89b962..d10df67c 100644 --- a/crates/cgp-macro-lib/src/parse/check_components.rs +++ b/crates/cgp-macro-lib/src/parse/check_components.rs @@ -7,6 +7,10 @@ use syn::{Ident, Type, WhereClause, braced, bracketed}; use crate::parse::ImplGenerics; +pub struct CheckComponentsSpecs { + pub specs: Vec, +} + pub struct CheckComponents { pub impl_generics: ImplGenerics, pub trait_name: Ident, @@ -29,6 +33,19 @@ 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 impl_generics = if input.peek(Lt) { From 75ec90e766f6a30f0b49183357422750e38b4cd0 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 31 Jan 2026 19:29:49 +0100 Subject: [PATCH 2/6] Parse `#[provider]` attribute in `check_components!` --- .../delegate_and_check_components.rs | 1 + .../src/parse/check_components.rs | 28 +++++++++++++++++-- .../cgp-tests/src/tests/check_components.rs | 3 ++ 3 files changed, 30 insertions(+), 2 deletions(-) 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 impl_generics: ImplGenerics, pub trait_name: Ident, pub context_type: Type, @@ -48,6 +49,28 @@ impl Parse for CheckComponentsSpecs { 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.to_string() != "provider" { + return Err(syn::Error::new( + command.span(), + "expected `provider` attribute", + )); + } + + let provider; + parenthesized!(provider in content); + let provider_ident: Ident = provider.parse()?; + Some(provider_ident) + } else { + None + }; + let impl_generics = if input.peek(Lt) { input.parse()? } else { @@ -75,6 +98,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..15654d78 100644 --- a/crates/cgp-tests/src/tests/check_components.rs +++ b/crates/cgp-tests/src/tests/check_components.rs @@ -61,6 +61,9 @@ pub fn test_basic_check_components() { ], FooGetterAtComponent: Index<3>, + } + + CanUseContext2 for Context { BarGetterAtComponent: [ (Index<0>, Index<1>), (Index<1>, Index<0>), From 305f969abb6871a59a4fd5f6e65ee803106dc6ee Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 31 Jan 2026 19:38:54 +0100 Subject: [PATCH 3/6] Derive check_provider --- .../src/check_components/derive.rs | 42 +++++++++++++++++++ .../src/parse/check_components.rs | 7 ++-- .../cgp-tests/src/tests/check_components.rs | 23 ++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/crates/cgp-macro-lib/src/check_components/derive.rs b/crates/cgp-macro-lib/src/check_components/derive.rs index 7c352a22..38d1d590 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(provider) = &spec.check_provider { + return derive_check_provider(spec, provider); + } + let mut item_impls = Vec::new(); let unit: Type = parse2(quote!(()))?; @@ -42,3 +46,41 @@ pub fn derive_check_components(spec: &CheckComponents) -> syn::Result<(ItemTrait Ok((item_trait, item_impls)) } + +pub fn derive_check_provider( + spec: &CheckComponents, + provider: &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); + + 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/parse/check_components.rs b/crates/cgp-macro-lib/src/parse/check_components.rs index 543c565d..733cce98 100644 --- a/crates/cgp-macro-lib/src/parse/check_components.rs +++ b/crates/cgp-macro-lib/src/parse/check_components.rs @@ -12,7 +12,7 @@ pub struct CheckComponentsSpecs { } pub struct CheckComponents { - pub check_provider: Option, + pub check_provider: Option, pub impl_generics: ImplGenerics, pub trait_name: Ident, pub context_type: Type, @@ -65,8 +65,9 @@ impl Parse for CheckComponents { let provider; parenthesized!(provider in content); - let provider_ident: Ident = provider.parse()?; - Some(provider_ident) + + let provider_type: Type = provider.parse()?; + Some(provider_type) } else { None }; diff --git a/crates/cgp-tests/src/tests/check_components.rs b/crates/cgp-tests/src/tests/check_components.rs index 15654d78..3cd9a2f2 100644 --- a/crates/cgp-tests/src/tests/check_components.rs +++ b/crates/cgp-tests/src/tests/check_components.rs @@ -78,6 +78,29 @@ pub fn test_basic_check_components() { (Index<7>, Index<8>), ] } + + #[provider(UseField)] + CanUseDummyField for Context { + FooGetterAtComponent: [ + Index<0>, + Index<1>, + ], + FooGetterAtComponent: + Index<3>, + BarGetterAtComponent: [ + (Index<0>, Index<1>), + (Index<1>, Index<0>), + ], + BarGetterAtComponent: + (Index<3>, Index<4>), + [ + FooGetterAtComponent, + BarGetterAtComponent, + ]: [ + (Index<5>, Index<6>), + (Index<7>, Index<8>), + ] + } } } From 423391ed3fbb60df67e42b9305f435b7c909376c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 31 Jan 2026 20:10:06 +0100 Subject: [PATCH 4/6] Allow checking multiple providers --- .../src/check_components/derive.rs | 26 ++++++++++--------- .../src/parse/check_components.rs | 7 ++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/cgp-macro-lib/src/check_components/derive.rs b/crates/cgp-macro-lib/src/check_components/derive.rs index 38d1d590..91ce6dc2 100644 --- a/crates/cgp-macro-lib/src/check_components/derive.rs +++ b/crates/cgp-macro-lib/src/check_components/derive.rs @@ -5,8 +5,8 @@ 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(provider) = &spec.check_provider { - return derive_check_provider(spec, provider); + if let Some(providers) = &spec.check_provider { + return derive_check_provider(spec, providers); } let mut item_impls = Vec::new(); @@ -49,7 +49,7 @@ pub fn derive_check_components(spec: &CheckComponents) -> syn::Result<(ItemTrait pub fn derive_check_provider( spec: &CheckComponents, - provider: &Type, + providers: &[Type], ) -> syn::Result<(ItemTrait, Vec)> { let mut item_impls = Vec::new(); let unit: Type = parse2(quote!(()))?; @@ -71,15 +71,17 @@ pub fn derive_check_provider( { let component_param = component_params.as_ref().unwrap_or(&unit); - let item_impl: ItemImpl = parse2(quote! { - impl #impl_generics - #trait_name < #component_type, #component_param > - for #provider - #where_clause - {} - })?; - - item_impls.push(item_impl); + 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/parse/check_components.rs b/crates/cgp-macro-lib/src/parse/check_components.rs index 733cce98..419f1ed9 100644 --- a/crates/cgp-macro-lib/src/parse/check_components.rs +++ b/crates/cgp-macro-lib/src/parse/check_components.rs @@ -12,7 +12,7 @@ pub struct CheckComponentsSpecs { } pub struct CheckComponents { - pub check_provider: Option, + pub check_provider: Option>, pub impl_generics: ImplGenerics, pub trait_name: Ident, pub context_type: Type, @@ -66,8 +66,9 @@ impl Parse for CheckComponents { let provider; parenthesized!(provider in content); - let provider_type: Type = provider.parse()?; - Some(provider_type) + let provider_types: Punctuated = Punctuated::parse_terminated(&provider)?; + + Some(provider_types.into_iter().collect()) } else { None }; From 4041d331d33b3cce61dff18eeb3dbbf6dbfc53de Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 31 Jan 2026 20:12:58 +0100 Subject: [PATCH 5/6] Rename attribute to `#[check_providers]` --- crates/cgp-macro-lib/src/parse/check_components.rs | 11 ++++++----- crates/cgp-tests/src/tests/check_components.rs | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/cgp-macro-lib/src/parse/check_components.rs b/crates/cgp-macro-lib/src/parse/check_components.rs index 419f1ed9..abfe2aa1 100644 --- a/crates/cgp-macro-lib/src/parse/check_components.rs +++ b/crates/cgp-macro-lib/src/parse/check_components.rs @@ -56,17 +56,18 @@ impl Parse for CheckComponents { bracketed!(content in input); let command: Ident = content.parse()?; - if command.to_string() != "provider" { + if command.to_string() != "check_providers" { return Err(syn::Error::new( command.span(), - "expected `provider` attribute", + "expected `check_providers` attribute", )); } - let provider; - parenthesized!(provider in content); + let raw_providers; + parenthesized!(raw_providers in content); - let provider_types: Punctuated = Punctuated::parse_terminated(&provider)?; + let provider_types: Punctuated = + Punctuated::parse_terminated(&raw_providers)?; Some(provider_types.into_iter().collect()) } else { diff --git a/crates/cgp-tests/src/tests/check_components.rs b/crates/cgp-tests/src/tests/check_components.rs index 3cd9a2f2..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! { @@ -79,7 +80,10 @@ pub fn test_basic_check_components() { ] } - #[provider(UseField)] + #[check_providers( + UseField, + UseField, + )] CanUseDummyField for Context { FooGetterAtComponent: [ Index<0>, From 100fafb56430ae7797d5f29873454d1c92f07acc Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 31 Jan 2026 20:13:27 +0100 Subject: [PATCH 6/6] Fix clippy --- crates/cgp-macro-lib/src/parse/check_components.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cgp-macro-lib/src/parse/check_components.rs b/crates/cgp-macro-lib/src/parse/check_components.rs index abfe2aa1..56cc6d10 100644 --- a/crates/cgp-macro-lib/src/parse/check_components.rs +++ b/crates/cgp-macro-lib/src/parse/check_components.rs @@ -56,7 +56,7 @@ impl Parse for CheckComponents { bracketed!(content in input); let command: Ident = content.parse()?; - if command.to_string() != "check_providers" { + if command != "check_providers" { return Err(syn::Error::new( command.span(), "expected `check_providers` attribute",