Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use syn::token::Comma;

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

#[derive(Default)]
pub struct CheckEntries {
pub entries: Punctuated<CheckEntry, Comma>,
}
Expand Down
28 changes: 16 additions & 12 deletions crates/macros/cgp-macro-core/src/types/check_components/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WhereClause>,
pub check_entries: CheckEntries,
}

Expand Down Expand Up @@ -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;
Expand All @@ -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<Ident> {
let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?;

Ok(Ident::new(
&format!("{prefix}{}", context_type.ident),
context_type.span(),
))
}

fn override_span<T>(span: &Span, body: &T) -> syn::Result<T>
where
T: Parse + ToTokens,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Type, Comma>),
}

impl CheckParamsAttribute {
pub fn merge(&self, other: &Self) -> syn::Result<Self> {
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<Self> {
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",
))
}
}
}
Original file line number Diff line number Diff line change
@@ -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<CheckComponentsTable> {
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<CheckEntries> {
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<Ident> {
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<Self> {
let table = input.parse()?;

Ok(Self { table })
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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::*;
Original file line number Diff line number Diff line change
@@ -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<Vec<KeyWithCheckParams>>;
}

impl ToKeysWithCheckParams for SingleDelegateKey {
fn to_keys_with_check_params(&self) -> syn::Result<Vec<KeyWithCheckParams>> {
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<Vec<KeyWithCheckParams>> {
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<Vec<KeyWithCheckParams>> {
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<Vec<KeyWithCheckParams>> {
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<Vec<KeyWithCheckParams>> {
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)
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use syn::Attribute;
use syn::parse::{Parse, ParseStream};
use syn::token::{At, Bracket};

Expand All @@ -16,12 +17,14 @@ pub enum DelegateKey {
impl Parse for DelegateKey {
fn parse(input: ParseStream) -> syn::Result<Self> {
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 {
Expand Down
Loading
Loading