diff --git a/crates/macros/cgp-macro-core/src/types/check_components/entries.rs b/crates/macros/cgp-macro-core/src/types/check_components/entries.rs index 956ebc7f..8955c635 100644 --- a/crates/macros/cgp-macro-core/src/types/check_components/entries.rs +++ b/crates/macros/cgp-macro-core/src/types/check_components/entries.rs @@ -4,6 +4,7 @@ use syn::token::Comma; use crate::types::check_components::{CheckEntry, EvaluatedCheckEntry}; +#[derive(Default)] pub struct CheckEntries { pub entries: Punctuated, } diff --git a/crates/macros/cgp-macro-core/src/types/check_components/table.rs b/crates/macros/cgp-macro-core/src/types/check_components/table.rs index bac27732..a8dad6d4 100644 --- a/crates/macros/cgp-macro-core/src/types/check_components/table.rs +++ b/crates/macros/cgp-macro-core/src/types/check_components/table.rs @@ -17,7 +17,7 @@ pub struct CheckComponentsTable { pub impl_generics: ImplGenerics, pub trait_name: Ident, pub context_type: Type, - pub where_clause: WhereClause, + pub where_clause: Option, pub check_entries: CheckEntries, } @@ -141,21 +141,13 @@ impl Parse for CheckComponentsTable { let trait_name = if let Some(check_trait_name) = m_check_trait_name { check_trait_name } else { - let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?; - - Ident::new( - &format!("__Check{}", context_type.ident), - context_type.span(), - ) + derive_check_trait_ident(&context_type, "__Check")? }; let where_clause = if input.peek(Where) { - input.parse()? + Some(input.parse()?) } else { - WhereClause { - where_token: Where(Span::call_site()), - predicates: Punctuated::default(), - } + None }; let content; @@ -174,6 +166,18 @@ impl Parse for CheckComponentsTable { } } +/// Derive a check trait identifier from a context type by prepending `prefix` +/// to the context type's leading identifier, e.g. `__CheckPerson` or +/// `__CanUsePerson` for the context type `Person`. +pub fn derive_check_trait_ident(context_type: &Type, prefix: &str) -> syn::Result { + let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?; + + Ok(Ident::new( + &format!("{prefix}{}", context_type.ident), + context_type.span(), + )) +} + fn override_span(span: &Span, body: &T) -> syn::Result where T: Parse + ToTokens, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/check_params.rs b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/check_params.rs new file mode 100644 index 00000000..cd7b6d52 --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/check_params.rs @@ -0,0 +1,66 @@ +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::token::Comma; +use syn::{Attribute, Error, Type}; + +#[derive(Clone)] +pub enum CheckParamsAttribute { + Default, + Skip, + Multi(Punctuated), +} + +impl CheckParamsAttribute { + pub fn merge(&self, other: &Self) -> syn::Result { + let res = match (self, other) { + (Self::Default, other) => other.clone(), + (other, Self::Default) => other.clone(), + (Self::Skip, Self::Skip) => Self::Skip, + (Self::Multi(params_a), Self::Multi(params_b)) => Self::Multi(Punctuated::from_iter( + params_a.iter().chain(params_b.iter()).cloned(), + )), + (Self::Skip, Self::Multi(params)) | (Self::Multi(params), Self::Skip) => { + return Err(Error::new( + params.span(), + "cannot combine #[skip_check] with #[check_params]", + )); + } + }; + + Ok(res) + } + + pub fn parse_attributes(attributes: &[Attribute]) -> syn::Result { + if attributes.is_empty() { + return Ok(Self::Default); + } + + if attributes.len() > 1 { + return Err(Error::new( + attributes[1].span(), + "Expected at most one `#[check_params]` or `#[skip_check]` attribute", + )); + } + + let attribute = &attributes[0]; + + if attribute.path().is_ident("check_params") { + let params = attribute.parse_args_with(Punctuated::parse_terminated)?; + Ok(CheckParamsAttribute::Multi(params)) + } else if attribute.path().is_ident("skip_check") { + attribute.meta.require_path_only().map_err(|_| { + Error::new( + attribute.span(), + "`#[skip_check]` does not take any arguments", + ) + })?; + + Ok(CheckParamsAttribute::Skip) + } else { + Err(Error::new( + attribute.span(), + "Expected either `#[skip_check]` or `#[check_params]` attribute for specifying the check generics", + )) + } + } +} diff --git a/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/item.rs b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/item.rs new file mode 100644 index 00000000..1716338a --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/item.rs @@ -0,0 +1,76 @@ +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::{Error, Ident}; + +use crate::types::check_components::{ + CheckComponentsTable, CheckEntries, derive_check_trait_ident, +}; +use crate::types::delegate_and_check_components::ToKeysWithCheckParams; +use crate::types::delegate_component::DelegateTable; + +pub struct ItemDelegateAndCheckComponents { + pub table: DelegateTable, +} + +impl ItemDelegateAndCheckComponents { + pub fn to_check_components(&self) -> syn::Result { + let trait_name = self.check_trait_ident()?; + let check_entries = self.to_check_entries()?; + + let check_table = CheckComponentsTable { + check_providers: None, + impl_generics: self.table.impl_generics.clone(), + trait_name, + context_type: self.table.table_type.clone(), + where_clause: None, + check_entries, + }; + + Ok(check_table) + } + + pub fn to_check_entries(&self) -> syn::Result { + let keys = self.table.entries.to_keys_with_check_params()?; + + let mut entries = CheckEntries::default(); + + for key in keys { + entries.entries.extend(key.to_check_entries().entries); + } + + Ok(entries) + } + + pub fn check_trait_ident(&self) -> syn::Result { + let attributes = &self.table.attributes; + + if attributes.is_empty() { + derive_check_trait_ident(&self.table.table_type, "__CanUse") + } else if attributes.len() > 1 { + Err(Error::new( + attributes[1].span(), + "Expected exactly one attribute for the check trait name", + )) + } else { + let attribute = &attributes[0]; + if !attribute.path().is_ident("check_trait") { + return Err(syn::Error::new( + attribute.span(), + "Expected `#[check_trait]` attribute for specifying the check trait name", + )); + } + + let ident = attribute.parse_args()?; + + Ok(ident) + } + } +} + +impl Parse for ItemDelegateAndCheckComponents { + fn parse(input: ParseStream) -> syn::Result { + let table = input.parse()?; + + Ok(Self { table }) + } +} diff --git a/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/key_with_check_params.rs b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/key_with_check_params.rs new file mode 100644 index 00000000..84c9c054 --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/key_with_check_params.rs @@ -0,0 +1,44 @@ +use syn::Type; +use syn::punctuated::Punctuated; + +use crate::types::check_components::{ + CheckEntries, CheckEntry, CheckKey, CheckValue, TypeWithGenerics, +}; +use crate::types::delegate_and_check_components::CheckParamsAttribute; + +pub struct KeyWithCheckParams { + pub key_type: Type, + pub check_params: CheckParamsAttribute, +} + +impl KeyWithCheckParams { + pub fn to_check_entries(&self) -> CheckEntries { + match &self.check_params { + CheckParamsAttribute::Default => { + let entry = CheckEntry { + key: CheckKey::Single(self.key_type.clone()), + value: None, + }; + + CheckEntries { + entries: Punctuated::from_iter([entry]), + } + } + CheckParamsAttribute::Skip => CheckEntries::default(), + CheckParamsAttribute::Multi(params) => { + let mut entries = CheckEntries::default(); + + for param in params { + entries.entries.push(CheckEntry { + key: CheckKey::Single(self.key_type.clone()), + value: Some(CheckValue::Single(Box::new(TypeWithGenerics::from( + param.clone(), + )))), + }) + } + + entries + } + } + } +} diff --git a/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/mod.rs b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/mod.rs new file mode 100644 index 00000000..a567e40a --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/mod.rs @@ -0,0 +1,9 @@ +mod check_params; +mod item; +mod key_with_check_params; +mod to_keys_with_check_params; + +pub use check_params::*; +pub use item::*; +pub use key_with_check_params::*; +pub use to_keys_with_check_params::*; diff --git a/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/to_keys_with_check_params.rs b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/to_keys_with_check_params.rs new file mode 100644 index 00000000..983ebf42 --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/delegate_and_check_components/to_keys_with_check_params.rs @@ -0,0 +1,96 @@ +use crate::types::delegate_and_check_components::{CheckParamsAttribute, KeyWithCheckParams}; +use crate::types::delegate_component::{ + DelegateEntries, DelegateKey, DelegateMapping, MultiDelegateKey, SingleDelegateKey, + ValidateAttributes, +}; + +pub trait ToKeysWithCheckParams { + fn to_keys_with_check_params(&self) -> syn::Result>; +} + +impl ToKeysWithCheckParams for SingleDelegateKey { + fn to_keys_with_check_params(&self) -> syn::Result> { + let check_params = CheckParamsAttribute::parse_attributes(&self.attributes)?; + + // Note: any per-key `ImplGenerics` (`self.generics`) are not carried into + // the check entry. The generated check impl only sees the table-level + // generics, so a key that introduces its own generic parameters would + // reference them unbound. Generic keys are therefore not yet supported in + // the check half; use `#[skip_check]` for such keys if needed. + let key = KeyWithCheckParams { + check_params, + key_type: self.ty.clone(), + }; + + Ok(vec![key]) + } +} + +impl ToKeysWithCheckParams for MultiDelegateKey { + fn to_keys_with_check_params(&self) -> syn::Result> { + let check_params = CheckParamsAttribute::parse_attributes(&self.attributes)?; + + let mut out = Vec::new(); + + for key in &self.keys { + let inner_res = key.to_keys_with_check_params()?; + for inner in inner_res { + let inner_params = check_params.merge(&inner.check_params)?; + out.push(KeyWithCheckParams { + key_type: inner.key_type, + check_params: inner_params, + }) + } + } + + Ok(out) + } +} + +impl ToKeysWithCheckParams for DelegateKey { + fn to_keys_with_check_params(&self) -> syn::Result> { + match self { + DelegateKey::Single(key) => key.to_keys_with_check_params(), + DelegateKey::Multi(key) => key.to_keys_with_check_params(), + DelegateKey::Path(key) => { + key.validate_attributes()?; + Ok(Vec::new()) + } + } + } +} + +impl ToKeysWithCheckParams for DelegateMapping { + fn to_keys_with_check_params(&self) -> syn::Result> { + match self { + DelegateMapping::Normal(mapping) => mapping.key.to_keys_with_check_params(), + DelegateMapping::Direct(mapping) => mapping.key.to_keys_with_check_params(), + DelegateMapping::Redirect(mapping) => { + // Redirect mappings do not support check params yet, so reject any + // attribute on the key rather than silently ignoring it. + mapping.key.validate_attributes()?; + Ok(Vec::new()) + } + } + } +} + +impl ToKeysWithCheckParams for DelegateEntries { + fn to_keys_with_check_params(&self) -> syn::Result> { + let mut out = Vec::new(); + + // Statement forms (`for`/`namespace`/`open`) are intentionally not checked + // for now. They still produce delegate impls via `eval`, but no check + // entries are generated for them. Since they cannot carry check params, + // reject any attribute on their keys rather than silently ignoring it. + for statement in &self.statements { + statement.validate_attributes()?; + } + + for entry in &self.entries { + out.extend(entry.to_keys_with_check_params()?); + } + + Ok(out) + } +} diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs index 04df43d4..05b9e0db 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs @@ -1,3 +1,4 @@ +use syn::Attribute; use syn::parse::{Parse, ParseStream}; use syn::token::{At, Bracket}; @@ -16,12 +17,14 @@ pub enum DelegateKey { impl Parse for DelegateKey { fn parse(input: ParseStream) -> syn::Result { let fork = input.fork(); + + let _attributes = fork.call(Attribute::parse_outer)?; let _generics: ImplGenerics = fork.parse()?; let key = if fork.peek(At) { let path = input.parse()?; Self::Path(path) - } else if input.peek(Bracket) { + } else if fork.peek(Bracket) { let keys = input.parse()?; Self::Multi(keys) } else { diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs index c571ae0b..c6e86b3a 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs @@ -1,22 +1,25 @@ -use syn::bracketed; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::Comma; +use syn::{Attribute, bracketed}; use crate::types::delegate_component::{EvalDelegateKey, EvaluatedDelegateKey, SingleDelegateKey}; #[derive(Debug, Clone)] pub struct MultiDelegateKey { + pub attributes: Vec, pub keys: Punctuated, } impl Parse for MultiDelegateKey { fn parse(input: ParseStream) -> syn::Result { + let attributes = input.call(Attribute::parse_outer)?; + let body; bracketed!(body in input); let keys = Punctuated::parse_terminated(&body)?; - Ok(Self { keys }) + Ok(Self { attributes, keys }) } } diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs index 1b5c426f..17702b92 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs @@ -1,3 +1,4 @@ +use syn::Attribute; use syn::parse::{Parse, ParseStream}; use syn::token::At; @@ -9,6 +10,7 @@ use crate::types::path::PathHead; #[derive(Debug, Clone)] pub struct PathDelegateKey { + pub attributes: Vec, pub generics: ImplGenerics, pub at: At, pub path: PathHead, @@ -16,11 +18,18 @@ pub struct PathDelegateKey { impl Parse for PathDelegateKey { fn parse(input: ParseStream) -> syn::Result { + let attributes = input.call(Attribute::parse_outer)?; + let generics = input.parse()?; let at = input.parse()?; let path = input.parse()?; - Ok(Self { generics, at, path }) + Ok(Self { + attributes, + generics, + at, + path, + }) } } diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs index 6e234165..7655a038 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs @@ -1,21 +1,27 @@ -use syn::Type; use syn::parse::{Parse, ParseStream}; +use syn::{Attribute, Type}; use crate::types::delegate_component::{EvalDelegateKey, EvaluatedDelegateKey}; use crate::types::generics::ImplGenerics; #[derive(Debug, Clone)] pub struct SingleDelegateKey { + pub attributes: Vec, pub generics: ImplGenerics, pub ty: Type, } impl Parse for SingleDelegateKey { fn parse(input: ParseStream) -> syn::Result { + let attributes = input.call(Attribute::parse_outer)?; let generics = input.parse()?; let ty = input.parse()?; - Ok(Self { generics, ty }) + Ok(Self { + attributes, + generics, + ty, + }) } } diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mod.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mod.rs index 30440c2c..939e8f20 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mod.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mod.rs @@ -3,6 +3,7 @@ mod key; mod mapping; mod statement; mod table; +mod validate_attributes; mod value; pub use entries::*; @@ -10,4 +11,5 @@ pub use key::*; pub use mapping::*; pub use statement::*; pub use table::*; +pub use validate_attributes::*; pub use value::*; diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs index 5a2b8dcc..54aa2776 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::ToTokens; use syn::parse::{Parse, ParseStream}; -use syn::{ItemImpl, Type, braced}; +use syn::{Attribute, ItemImpl, Type, braced}; use crate::functions::parse_internal; use crate::traits::ParseOptionalKeyword; @@ -13,6 +13,7 @@ use crate::types::keyword::Keyword; use crate::types::keywords::New; pub struct DelegateTable { + pub attributes: Vec, pub impl_generics: ImplGenerics, pub new: Option>, pub table_type: Type, @@ -26,6 +27,8 @@ pub struct EvaluatedDelegateTable { impl Parse for DelegateTable { fn parse(input: ParseStream) -> syn::Result { + let attributes = input.call(Attribute::parse_outer)?; + let impl_generics = input.parse()?; let new = input.parse_optional_keyword()?; @@ -40,6 +43,7 @@ impl Parse for DelegateTable { }; Ok(Self { + attributes, impl_generics, new, table_type, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs new file mode 100644 index 00000000..2cd071ba --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs @@ -0,0 +1,123 @@ +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::{Attribute, Error}; + +use crate::types::delegate_component::{ + DelegateEntries, DelegateKey, DelegateMapping, DelegateStatement, DelegateTable, + ForDelegateStatement, MultiDelegateKey, PathDelegateKey, SingleDelegateKey, +}; + +/** + Validate that the attributes in the delegate table constructs are valid. + + At the moment, no attribute is supported, so all attributes are rejected. +*/ +pub trait ValidateAttributes { + fn validate_attributes(&self) -> syn::Result<()>; +} + +pub fn reject_non_empty_attributes(attributes: &[Attribute]) -> syn::Result<()> { + if !attributes.is_empty() { + let attribute = &attributes[0]; + Err(Error::new( + attribute.span(), + format!( + "unsupported attribute: {}", + attribute.path().to_token_stream() + ), + )) + } else { + Ok(()) + } +} + +impl ValidateAttributes for SingleDelegateKey { + fn validate_attributes(&self) -> syn::Result<()> { + reject_non_empty_attributes(&self.attributes) + } +} + +impl ValidateAttributes for MultiDelegateKey { + fn validate_attributes(&self) -> syn::Result<()> { + reject_non_empty_attributes(&self.attributes)?; + + for key in &self.keys { + key.validate_attributes()?; + } + + Ok(()) + } +} + +impl ValidateAttributes for PathDelegateKey { + fn validate_attributes(&self) -> syn::Result<()> { + reject_non_empty_attributes(&self.attributes)?; + + Ok(()) + } +} + +impl ValidateAttributes for DelegateKey { + fn validate_attributes(&self) -> syn::Result<()> { + match self { + DelegateKey::Single(key) => key.validate_attributes(), + DelegateKey::Multi(key) => key.validate_attributes(), + DelegateKey::Path(key) => key.validate_attributes(), + } + } +} + +impl ValidateAttributes for DelegateMapping { + fn validate_attributes(&self) -> syn::Result<()> { + match self { + DelegateMapping::Normal(mapping) => mapping.key.validate_attributes(), + DelegateMapping::Direct(mapping) => mapping.key.validate_attributes(), + DelegateMapping::Redirect(mapping) => mapping.key.validate_attributes(), + } + } +} + +impl ValidateAttributes for ForDelegateStatement { + fn validate_attributes(&self) -> syn::Result<()> { + for mapping in &self.mappings { + mapping.key.validate_attributes()?; + } + + Ok(()) + } +} + +impl ValidateAttributes for DelegateStatement { + fn validate_attributes(&self) -> syn::Result<()> { + match self { + // `namespace` and `open` statements carry no keys that can hold attributes. + DelegateStatement::Namespace(_) | DelegateStatement::Open(_) => Ok(()), + DelegateStatement::For(statement) => statement.validate_attributes(), + } + } +} + +impl ValidateAttributes for DelegateEntries { + fn validate_attributes(&self) -> syn::Result<()> { + // Keys nested inside statement forms (`for`/`namespace`/`open`) do not + // support attributes, so reject any rather than silently discarding them. + for statement in &self.statements { + statement.validate_attributes()?; + } + + for entry in &self.entries { + entry.validate_attributes()?; + } + + Ok(()) + } +} + +impl ValidateAttributes for DelegateTable { + fn validate_attributes(&self) -> syn::Result<()> { + reject_non_empty_attributes(&self.attributes)?; + self.entries.validate_attributes()?; + + Ok(()) + } +} diff --git a/crates/macros/cgp-macro-core/src/types/mod.rs b/crates/macros/cgp-macro-core/src/types/mod.rs index 83876489..024cae30 100644 --- a/crates/macros/cgp-macro-core/src/types/mod.rs +++ b/crates/macros/cgp-macro-core/src/types/mod.rs @@ -7,6 +7,7 @@ pub mod cgp_getter; pub mod cgp_impl; pub mod cgp_provider; pub mod check_components; +pub mod delegate_and_check_components; pub mod delegate_component; pub mod empty_struct; pub mod field; diff --git a/crates/macros/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs b/crates/macros/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs index 7f087056..8bba31bc 100644 --- a/crates/macros/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs +++ b/crates/macros/cgp-macro-lib/src/entrypoints/delegate_and_check_components.rs @@ -1,93 +1,20 @@ -use cgp_macro_core::types::check_components::{ - CheckComponentsTable, CheckEntries, CheckEntry, CheckKey, CheckValue, TypeWithGenerics, -}; -use cgp_macro_core::types::generics::ImplGenerics; -use proc_macro2::{Span, TokenStream}; +use cgp_macro_core::types::delegate_and_check_components::ItemDelegateAndCheckComponents; +use proc_macro2::TokenStream; use quote::quote; -use syn::punctuated::Punctuated; -use syn::token::{Comma, Where}; -use syn::{Type, WhereClause, parse2}; - -use crate::delegate_components::impl_delegate_components; -use crate::parse::{DelegateAndCheckSpec, DelegateEntry, DelegateKey}; +use syn::parse2; pub fn delegate_and_check_components(body: TokenStream) -> syn::Result { - let spec: DelegateAndCheckSpec = parse2(body)?; - - let mut check_entries = Punctuated::new(); - - for entry in &spec.entries { - for key in &entry.keys { - let component_type = &key.component_type; - - match &key.check_params { - Some(check_params) => { - // Emit one check entry per param so that a single-key/single-param - // entry resolves the error span to the component type (via eval()'s - // `component_types_count >= component_params_count` heuristic), and so - // that an empty param list (i.e. `#[skip_check]`) emits no check at all. - for check_param in check_params { - check_entries.push(CheckEntry { - key: CheckKey::Single(component_type.clone()), - value: Some(CheckValue::Single(Box::new(TypeWithGenerics::from( - check_param.clone(), - )))), - }); - } - } - None => { - check_entries.push(CheckEntry { - key: CheckKey::Single(component_type.clone()), - value: None, - }); - } - } - } - } - - let delegate_entries: Punctuated, Comma> = spec - .entries - .into_iter() - .map(|entry| { - let keys = entry - .keys - .into_iter() - .map(|key| DelegateKey { - ty: key.component_type, - generics: ImplGenerics::default(), - }) - .collect(); - - DelegateEntry { - keys, - value: entry.value, - mode: entry.mode, - } - }) - .collect(); + let item: ItemDelegateAndCheckComponents = parse2(body)?; - let mut out = - impl_delegate_components(&spec.context_type, &spec.impl_generics, &delegate_entries)?; + let check_table = item.to_check_components()?; - let check_spec = CheckComponentsTable { - check_providers: None, - impl_generics: spec.impl_generics, - trait_name: spec.trait_name, - context_type: spec.context_type, - where_clause: WhereClause { - where_token: Where(Span::call_site()), - predicates: Punctuated::default(), - }, - check_entries: CheckEntries { - entries: check_entries, - }, - }; + let evaluated_table = item.table.eval()?; - let items = check_spec.to_items()?; + let check_items = check_table.to_items()?; - out.extend(quote! { - #( #items )* - }); + Ok(quote! { + #evaluated_table - Ok(out) + #( #check_items )* + }) } diff --git a/crates/macros/cgp-macro-lib/src/entrypoints/delegate_components.rs b/crates/macros/cgp-macro-lib/src/entrypoints/delegate_components.rs index ba10cd0e..43dffa5a 100644 --- a/crates/macros/cgp-macro-lib/src/entrypoints/delegate_components.rs +++ b/crates/macros/cgp-macro-lib/src/entrypoints/delegate_components.rs @@ -1,4 +1,4 @@ -use cgp_macro_core::types::delegate_component::DelegateTable; +use cgp_macro_core::types::delegate_component::{DelegateTable, ValidateAttributes}; use proc_macro2::TokenStream; use quote::ToTokens; use syn::parse2; @@ -6,6 +6,10 @@ use syn::parse2; pub fn delegate_components(body: TokenStream) -> syn::Result { let table: DelegateTable = parse2(body.clone())?; + // `delegate_components!` does not support any attributes on the table or its + // keys, so reject them instead of silently parsing and discarding them. + table.validate_attributes()?; + let evaluated_table = table.eval()?; Ok(evaluated_table.to_token_stream()) diff --git a/crates/macros/cgp-macro-lib/src/parse/delegate_and_check_components.rs b/crates/macros/cgp-macro-lib/src/parse/delegate_and_check_components.rs deleted file mode 100644 index 0c11087d..00000000 --- a/crates/macros/cgp-macro-lib/src/parse/delegate_and_check_components.rs +++ /dev/null @@ -1,158 +0,0 @@ -use core::iter; - -use cgp_macro_core::types::generics::ImplGenerics; -use cgp_macro_core::types::ident::IdentWithTypeArgs; -use quote::ToTokens; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::token::{Bracket, Comma, Lt, Pound}; -use syn::{Attribute, Ident, Type, braced, bracketed, parse2}; - -use crate::parse::{DelegateMode, DelegateValue}; - -pub struct DelegateAndCheckSpec { - pub impl_generics: ImplGenerics, - pub trait_name: Ident, - pub context_type: Type, - pub entries: Punctuated, -} - -#[derive(Clone)] -pub struct DelegateAndCheckEntry { - pub keys: Punctuated, - pub mode: DelegateMode, - pub value: DelegateValue, -} - -#[derive(Clone)] -pub struct DelegateAndCheckKey { - pub component_type: Type, - pub check_params: Option>, -} - -impl Parse for DelegateAndCheckSpec { - fn parse(input: ParseStream) -> syn::Result { - let impl_generics = if input.peek(Lt) { - input.parse()? - } else { - Default::default() - }; - - let m_trait_name = parse_check_trait_name(input)?; - - let context_type: Type = input.parse()?; - - let trait_name = match m_trait_name { - Some(ident) => ident, - None => { - let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?; - Ident::new( - &format!("__CanUse{}", context_type.ident), - context_type.span(), - ) - } - }; - - let entries = { - let body; - braced!(body in input); - Punctuated::parse_terminated(&body)? - }; - - Ok(Self { - impl_generics, - trait_name, - context_type, - entries, - }) - } -} - -impl Parse for DelegateAndCheckEntry { - fn parse(input: ParseStream) -> syn::Result { - let check_params = parse_check_params(input)?; - - let mut keys = if input.peek(Bracket) { - let body; - bracketed!(body in input); - Punctuated::parse_terminated(&body)? - } else { - let key: DelegateAndCheckKey = input.parse()?; - Punctuated::from_iter(iter::once(key)) - }; - - if let Some(check_params) = check_params { - for key in &mut keys { - key.check_params - .get_or_insert_default() - .extend(check_params.clone()); - } - } - - let mode = input.parse()?; - - let value = input.parse()?; - - Ok(Self { keys, mode, value }) - } -} - -impl Parse for DelegateAndCheckKey { - fn parse(input: ParseStream) -> syn::Result { - let check_params = parse_check_params(input)?; - - let component_type: Type = input.parse()?; - Ok(Self { - component_type, - check_params, - }) - } -} - -pub fn parse_check_trait_name(input: ParseStream) -> syn::Result> { - if input.peek(Pound) { - let attributes = input.call(Attribute::parse_outer)?; - - let [attribute]: [Attribute; 1] = attributes - .try_into() - .map_err(|_| input.error("Expected exactly one attribute for the check trait name"))?; - - if !attribute.path().is_ident("check_trait") { - return Err(syn::Error::new( - attribute.span(), - "Expected `#[check_trait]` attribute for specifying the check trait name", - )); - } - - let ident: Ident = attribute.parse_args()?; - Ok(Some(ident)) - } else { - Ok(None) - } -} - -pub fn parse_check_params(input: ParseStream) -> syn::Result>> { - if input.peek(Pound) { - let attributes = input.call(Attribute::parse_outer)?; - - let [attribute]: [Attribute; 1] = attributes - .try_into() - .map_err(|_| input.error("Expected exactly one key attribute"))?; - - let check_params = if attribute.path().is_ident("check_params") { - attribute.parse_args_with(Punctuated::parse_terminated)? - } else if attribute.path().is_ident("skip_check") { - Punctuated::new() - } else { - return Err(syn::Error::new( - attribute.span(), - "Expected either `#[skip_check]` or `#[check_params]` attribute for specifying the check generics", - )); - }; - - Ok(Some(check_params)) - } else { - Ok(None) - } -} diff --git a/crates/macros/cgp-macro-lib/src/parse/mod.rs b/crates/macros/cgp-macro-lib/src/parse/mod.rs index a2690f75..ea12a5f9 100644 --- a/crates/macros/cgp-macro-lib/src/parse/mod.rs +++ b/crates/macros/cgp-macro-lib/src/parse/mod.rs @@ -1,11 +1,9 @@ mod define_preset; -mod delegate_and_check_components; mod delegate_components; mod path; mod type_spec; pub use define_preset::*; -pub use delegate_and_check_components::*; pub use delegate_components::*; pub use path::*; pub use type_spec::*; diff --git a/crates/tests/cgp-tests/src/tests/check_components.rs b/crates/tests/cgp-tests/src/tests/check_components.rs index 5daf1824..d4841431 100644 --- a/crates/tests/cgp-tests/src/tests/check_components.rs +++ b/crates/tests/cgp-tests/src/tests/check_components.rs @@ -654,14 +654,14 @@ mod basic_check_components { >: CanUseComponent<__Component__, __Params__> {} impl __CanUseContext for Context {} impl __CanUseContext for Context {} - impl __CanUseContext> for Context {} - impl __CanUseContext> for Context {} impl __CanUseContext, Index<6>)> for Context {} impl __CanUseContext, Index<8>)> for Context {} - impl __CanUseContext, Index<1>)> for Context {} - impl __CanUseContext, Index<0>)> for Context {} + impl __CanUseContext> for Context {} + impl __CanUseContext> for Context {} impl __CanUseContext, Index<6>)> for Context {} impl __CanUseContext, Index<8>)> for Context {} + impl __CanUseContext, Index<1>)> for Context {} + impl __CanUseContext, Index<0>)> for Context {} "#) } } diff --git a/crates/tests/cgp-tests/src/tests/use_delegate/getter.rs b/crates/tests/cgp-tests/src/tests/use_delegate/getter.rs index f40b5064..d40017b1 100644 --- a/crates/tests/cgp-tests/src/tests/use_delegate/getter.rs +++ b/crates/tests/cgp-tests/src/tests/use_delegate/getter.rs @@ -496,6 +496,8 @@ mod derive_delegate { expand_my_context(output) { insta::assert_snapshot!(output, @r#" + pub struct FooTypes; + pub struct FooGetters; impl DelegateComponent for MyContext { type Delegate = UseDelegate; } @@ -508,7 +510,18 @@ mod derive_delegate { FooTypes, >: IsProviderFor, {} - pub struct FooTypes; + impl DelegateComponent for MyContext { + type Delegate = UseDelegate; + } + impl< + __Context__, + __Params__, + > IsProviderFor for MyContext + where + UseDelegate< + FooGetters, + >: IsProviderFor, + {} impl DelegateComponent> for FooTypes { type Delegate = UseType; } @@ -525,19 +538,6 @@ mod derive_delegate { where UseType: IsProviderFor, __Context__, __Params__>, {} - impl DelegateComponent for MyContext { - type Delegate = UseDelegate; - } - impl< - __Context__, - __Params__, - > IsProviderFor for MyContext - where - UseDelegate< - FooGetters, - >: IsProviderFor, - {} - pub struct FooGetters; impl DelegateComponent> for FooGetters { type Delegate = UseField; }