Skip to content

Commit 4008714

Browse files
authored
Refactor and move delegate_and_check_components! macro implementation to cgp-macro-core (#240)
* Slightly simplify delegate_and_check_components * Draft new delegate_and_check_components types * Allow attributes in delegate keys * Fix peek in DelegateKey * Refactor CheckParamsAttribute and remove delegate and check constructs * Implement ToCheckEntries for SingleDelegateKey * Implement KeyWithCheckParams * Implement ToKeysWithCheckParams for SingleDelegateKey * Implement CheckParamsAttribute::merge * Implement ToKeysWithCheckParams for MultiDelegateKey * Implement ToKeysWithCheckParams for DelegateEntries * Implement check_trait_ident * Implement ItemDelegateAndCheckComponents * Use ItemDelegateAndCheckComponents * Remove obsolete constructs * Add ValidateAttributes * Add more attributes validation * AI-revise code * AI revision
1 parent 2f83303 commit 4008714

21 files changed

Lines changed: 500 additions & 282 deletions

File tree

crates/macros/cgp-macro-core/src/types/check_components/entries.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use syn::token::Comma;
44

55
use crate::types::check_components::{CheckEntry, EvaluatedCheckEntry};
66

7+
#[derive(Default)]
78
pub struct CheckEntries {
89
pub entries: Punctuated<CheckEntry, Comma>,
910
}

crates/macros/cgp-macro-core/src/types/check_components/table.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub struct CheckComponentsTable {
1717
pub impl_generics: ImplGenerics,
1818
pub trait_name: Ident,
1919
pub context_type: Type,
20-
pub where_clause: WhereClause,
20+
pub where_clause: Option<WhereClause>,
2121
pub check_entries: CheckEntries,
2222
}
2323

@@ -141,21 +141,13 @@ impl Parse for CheckComponentsTable {
141141
let trait_name = if let Some(check_trait_name) = m_check_trait_name {
142142
check_trait_name
143143
} else {
144-
let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?;
145-
146-
Ident::new(
147-
&format!("__Check{}", context_type.ident),
148-
context_type.span(),
149-
)
144+
derive_check_trait_ident(&context_type, "__Check")?
150145
};
151146

152147
let where_clause = if input.peek(Where) {
153-
input.parse()?
148+
Some(input.parse()?)
154149
} else {
155-
WhereClause {
156-
where_token: Where(Span::call_site()),
157-
predicates: Punctuated::default(),
158-
}
150+
None
159151
};
160152

161153
let content;
@@ -174,6 +166,18 @@ impl Parse for CheckComponentsTable {
174166
}
175167
}
176168

169+
/// Derive a check trait identifier from a context type by prepending `prefix`
170+
/// to the context type's leading identifier, e.g. `__CheckPerson` or
171+
/// `__CanUsePerson` for the context type `Person`.
172+
pub fn derive_check_trait_ident(context_type: &Type, prefix: &str) -> syn::Result<Ident> {
173+
let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?;
174+
175+
Ok(Ident::new(
176+
&format!("{prefix}{}", context_type.ident),
177+
context_type.span(),
178+
))
179+
}
180+
177181
fn override_span<T>(span: &Span, body: &T) -> syn::Result<T>
178182
where
179183
T: Parse + ToTokens,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use syn::punctuated::Punctuated;
2+
use syn::spanned::Spanned;
3+
use syn::token::Comma;
4+
use syn::{Attribute, Error, Type};
5+
6+
#[derive(Clone)]
7+
pub enum CheckParamsAttribute {
8+
Default,
9+
Skip,
10+
Multi(Punctuated<Type, Comma>),
11+
}
12+
13+
impl CheckParamsAttribute {
14+
pub fn merge(&self, other: &Self) -> syn::Result<Self> {
15+
let res = match (self, other) {
16+
(Self::Default, other) => other.clone(),
17+
(other, Self::Default) => other.clone(),
18+
(Self::Skip, Self::Skip) => Self::Skip,
19+
(Self::Multi(params_a), Self::Multi(params_b)) => Self::Multi(Punctuated::from_iter(
20+
params_a.iter().chain(params_b.iter()).cloned(),
21+
)),
22+
(Self::Skip, Self::Multi(params)) | (Self::Multi(params), Self::Skip) => {
23+
return Err(Error::new(
24+
params.span(),
25+
"cannot combine #[skip_check] with #[check_params]",
26+
));
27+
}
28+
};
29+
30+
Ok(res)
31+
}
32+
33+
pub fn parse_attributes(attributes: &[Attribute]) -> syn::Result<Self> {
34+
if attributes.is_empty() {
35+
return Ok(Self::Default);
36+
}
37+
38+
if attributes.len() > 1 {
39+
return Err(Error::new(
40+
attributes[1].span(),
41+
"Expected at most one `#[check_params]` or `#[skip_check]` attribute",
42+
));
43+
}
44+
45+
let attribute = &attributes[0];
46+
47+
if attribute.path().is_ident("check_params") {
48+
let params = attribute.parse_args_with(Punctuated::parse_terminated)?;
49+
Ok(CheckParamsAttribute::Multi(params))
50+
} else if attribute.path().is_ident("skip_check") {
51+
attribute.meta.require_path_only().map_err(|_| {
52+
Error::new(
53+
attribute.span(),
54+
"`#[skip_check]` does not take any arguments",
55+
)
56+
})?;
57+
58+
Ok(CheckParamsAttribute::Skip)
59+
} else {
60+
Err(Error::new(
61+
attribute.span(),
62+
"Expected either `#[skip_check]` or `#[check_params]` attribute for specifying the check generics",
63+
))
64+
}
65+
}
66+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use syn::parse::{Parse, ParseStream};
2+
use syn::spanned::Spanned;
3+
use syn::{Error, Ident};
4+
5+
use crate::types::check_components::{
6+
CheckComponentsTable, CheckEntries, derive_check_trait_ident,
7+
};
8+
use crate::types::delegate_and_check_components::ToKeysWithCheckParams;
9+
use crate::types::delegate_component::DelegateTable;
10+
11+
pub struct ItemDelegateAndCheckComponents {
12+
pub table: DelegateTable,
13+
}
14+
15+
impl ItemDelegateAndCheckComponents {
16+
pub fn to_check_components(&self) -> syn::Result<CheckComponentsTable> {
17+
let trait_name = self.check_trait_ident()?;
18+
let check_entries = self.to_check_entries()?;
19+
20+
let check_table = CheckComponentsTable {
21+
check_providers: None,
22+
impl_generics: self.table.impl_generics.clone(),
23+
trait_name,
24+
context_type: self.table.table_type.clone(),
25+
where_clause: None,
26+
check_entries,
27+
};
28+
29+
Ok(check_table)
30+
}
31+
32+
pub fn to_check_entries(&self) -> syn::Result<CheckEntries> {
33+
let keys = self.table.entries.to_keys_with_check_params()?;
34+
35+
let mut entries = CheckEntries::default();
36+
37+
for key in keys {
38+
entries.entries.extend(key.to_check_entries().entries);
39+
}
40+
41+
Ok(entries)
42+
}
43+
44+
pub fn check_trait_ident(&self) -> syn::Result<Ident> {
45+
let attributes = &self.table.attributes;
46+
47+
if attributes.is_empty() {
48+
derive_check_trait_ident(&self.table.table_type, "__CanUse")
49+
} else if attributes.len() > 1 {
50+
Err(Error::new(
51+
attributes[1].span(),
52+
"Expected exactly one attribute for the check trait name",
53+
))
54+
} else {
55+
let attribute = &attributes[0];
56+
if !attribute.path().is_ident("check_trait") {
57+
return Err(syn::Error::new(
58+
attribute.span(),
59+
"Expected `#[check_trait]` attribute for specifying the check trait name",
60+
));
61+
}
62+
63+
let ident = attribute.parse_args()?;
64+
65+
Ok(ident)
66+
}
67+
}
68+
}
69+
70+
impl Parse for ItemDelegateAndCheckComponents {
71+
fn parse(input: ParseStream) -> syn::Result<Self> {
72+
let table = input.parse()?;
73+
74+
Ok(Self { table })
75+
}
76+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use syn::Type;
2+
use syn::punctuated::Punctuated;
3+
4+
use crate::types::check_components::{
5+
CheckEntries, CheckEntry, CheckKey, CheckValue, TypeWithGenerics,
6+
};
7+
use crate::types::delegate_and_check_components::CheckParamsAttribute;
8+
9+
pub struct KeyWithCheckParams {
10+
pub key_type: Type,
11+
pub check_params: CheckParamsAttribute,
12+
}
13+
14+
impl KeyWithCheckParams {
15+
pub fn to_check_entries(&self) -> CheckEntries {
16+
match &self.check_params {
17+
CheckParamsAttribute::Default => {
18+
let entry = CheckEntry {
19+
key: CheckKey::Single(self.key_type.clone()),
20+
value: None,
21+
};
22+
23+
CheckEntries {
24+
entries: Punctuated::from_iter([entry]),
25+
}
26+
}
27+
CheckParamsAttribute::Skip => CheckEntries::default(),
28+
CheckParamsAttribute::Multi(params) => {
29+
let mut entries = CheckEntries::default();
30+
31+
for param in params {
32+
entries.entries.push(CheckEntry {
33+
key: CheckKey::Single(self.key_type.clone()),
34+
value: Some(CheckValue::Single(Box::new(TypeWithGenerics::from(
35+
param.clone(),
36+
)))),
37+
})
38+
}
39+
40+
entries
41+
}
42+
}
43+
}
44+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
mod check_params;
2+
mod item;
3+
mod key_with_check_params;
4+
mod to_keys_with_check_params;
5+
6+
pub use check_params::*;
7+
pub use item::*;
8+
pub use key_with_check_params::*;
9+
pub use to_keys_with_check_params::*;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use crate::types::delegate_and_check_components::{CheckParamsAttribute, KeyWithCheckParams};
2+
use crate::types::delegate_component::{
3+
DelegateEntries, DelegateKey, DelegateMapping, MultiDelegateKey, SingleDelegateKey,
4+
ValidateAttributes,
5+
};
6+
7+
pub trait ToKeysWithCheckParams {
8+
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>>;
9+
}
10+
11+
impl ToKeysWithCheckParams for SingleDelegateKey {
12+
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>> {
13+
let check_params = CheckParamsAttribute::parse_attributes(&self.attributes)?;
14+
15+
// Note: any per-key `ImplGenerics` (`self.generics`) are not carried into
16+
// the check entry. The generated check impl only sees the table-level
17+
// generics, so a key that introduces its own generic parameters would
18+
// reference them unbound. Generic keys are therefore not yet supported in
19+
// the check half; use `#[skip_check]` for such keys if needed.
20+
let key = KeyWithCheckParams {
21+
check_params,
22+
key_type: self.ty.clone(),
23+
};
24+
25+
Ok(vec![key])
26+
}
27+
}
28+
29+
impl ToKeysWithCheckParams for MultiDelegateKey {
30+
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>> {
31+
let check_params = CheckParamsAttribute::parse_attributes(&self.attributes)?;
32+
33+
let mut out = Vec::new();
34+
35+
for key in &self.keys {
36+
let inner_res = key.to_keys_with_check_params()?;
37+
for inner in inner_res {
38+
let inner_params = check_params.merge(&inner.check_params)?;
39+
out.push(KeyWithCheckParams {
40+
key_type: inner.key_type,
41+
check_params: inner_params,
42+
})
43+
}
44+
}
45+
46+
Ok(out)
47+
}
48+
}
49+
50+
impl ToKeysWithCheckParams for DelegateKey {
51+
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>> {
52+
match self {
53+
DelegateKey::Single(key) => key.to_keys_with_check_params(),
54+
DelegateKey::Multi(key) => key.to_keys_with_check_params(),
55+
DelegateKey::Path(key) => {
56+
key.validate_attributes()?;
57+
Ok(Vec::new())
58+
}
59+
}
60+
}
61+
}
62+
63+
impl ToKeysWithCheckParams for DelegateMapping {
64+
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>> {
65+
match self {
66+
DelegateMapping::Normal(mapping) => mapping.key.to_keys_with_check_params(),
67+
DelegateMapping::Direct(mapping) => mapping.key.to_keys_with_check_params(),
68+
DelegateMapping::Redirect(mapping) => {
69+
// Redirect mappings do not support check params yet, so reject any
70+
// attribute on the key rather than silently ignoring it.
71+
mapping.key.validate_attributes()?;
72+
Ok(Vec::new())
73+
}
74+
}
75+
}
76+
}
77+
78+
impl ToKeysWithCheckParams for DelegateEntries {
79+
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>> {
80+
let mut out = Vec::new();
81+
82+
// Statement forms (`for`/`namespace`/`open`) are intentionally not checked
83+
// for now. They still produce delegate impls via `eval`, but no check
84+
// entries are generated for them. Since they cannot carry check params,
85+
// reject any attribute on their keys rather than silently ignoring it.
86+
for statement in &self.statements {
87+
statement.validate_attributes()?;
88+
}
89+
90+
for entry in &self.entries {
91+
out.extend(entry.to_keys_with_check_params()?);
92+
}
93+
94+
Ok(out)
95+
}
96+
}

crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use syn::Attribute;
12
use syn::parse::{Parse, ParseStream};
23
use syn::token::{At, Bracket};
34

@@ -16,12 +17,14 @@ pub enum DelegateKey {
1617
impl Parse for DelegateKey {
1718
fn parse(input: ParseStream) -> syn::Result<Self> {
1819
let fork = input.fork();
20+
21+
let _attributes = fork.call(Attribute::parse_outer)?;
1922
let _generics: ImplGenerics = fork.parse()?;
2023

2124
let key = if fork.peek(At) {
2225
let path = input.parse()?;
2326
Self::Path(path)
24-
} else if input.peek(Bracket) {
27+
} else if fork.peek(Bracket) {
2528
let keys = input.parse()?;
2629
Self::Multi(keys)
2730
} else {

0 commit comments

Comments
 (0)