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
29 changes: 29 additions & 0 deletions crates/macros/cgp-macro-core/src/types/check_components/entries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::Comma;

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

pub struct CheckEntries {
pub entries: Punctuated<CheckEntry, Comma>,
}

impl CheckEntries {
pub fn eval(&self) -> Vec<EvaluatedCheckEntry> {
let mut evaluated_entries = Vec::new();

for entry in &self.entries {
evaluated_entries.extend(entry.eval());
}

evaluated_entries
}
}

impl Parse for CheckEntries {
fn parse(input: ParseStream) -> syn::Result<Self> {
let entries: Punctuated<CheckEntry, Comma> = Punctuated::parse_terminated(input)?;

Ok(Self { entries })
}
}
82 changes: 82 additions & 0 deletions crates/macros/cgp-macro-core/src/types/check_components/entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::token::Colon;

use crate::types::check_components::{CheckKey, CheckValue, EvaluatedCheckEntry, TypeWithGenerics};

pub struct CheckEntry {
pub key: CheckKey,
pub value: Option<CheckValue>,
}

impl CheckEntry {
pub fn eval(&self) -> Vec<EvaluatedCheckEntry> {
let mut entries = Vec::new();

let keys = self.key.to_keys();

let component_types_count = keys.len();

for component_type in keys.iter() {
if let Some(value) = &self.value {
let values = value.to_values();

if values.is_empty() {
entries.push(EvaluatedCheckEntry {
key: component_type.clone(),
value: None,
span: component_type.span(),
})
} else {
let component_params_count = values.len();

for component_param in values.iter() {
let component_param_type = &component_param.ty;
let component_param_generics = &component_param.generics;

let span = if component_types_count >= component_params_count {
component_type.span()
} else {
component_param_type.span()
};

entries.push(EvaluatedCheckEntry {
key: component_type.clone(),
value: Some(TypeWithGenerics {
ty: component_param_type.clone(),
generics: component_param_generics.clone(),
}),
span,
})
}
}
} else {
entries.push(EvaluatedCheckEntry {
key: component_type.clone(),
value: None,
span: component_type.span(),
})
}
}

entries
}
}

impl Parse for CheckEntry {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key = input.parse()?;

if input.peek(Colon) {
let _: Colon = input.parse()?;
let value = input.parse()?;

Ok(Self {
key,
value: Some(value),
})
} else {
Ok(Self { key, value: None })
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use proc_macro2::Span;
use syn::Type;

use crate::types::check_components::TypeWithGenerics;

pub struct EvaluatedCheckEntry {
pub key: Type,
pub value: Option<TypeWithGenerics>,
pub span: Span,
}
33 changes: 33 additions & 0 deletions crates/macros/cgp-macro-core/src/types/check_components/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::{Bracket, Comma};
use syn::{Type, bracketed};

pub enum CheckKey {
Single(Type),
Multi(Punctuated<Type, Comma>),
}

impl CheckKey {
pub fn to_keys(&self) -> Vec<Type> {
match self {
Self::Single(key) => vec![key.clone()],
Self::Multi(keys) => Vec::from_iter(keys.iter().cloned()),
}
}
}

impl Parse for CheckKey {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Bracket) {
let content;
bracketed!(content in input);

let keys: Punctuated<Type, Comma> = Punctuated::parse_terminated(&content)?;
Ok(Self::Multi(keys))
} else {
let key: Type = input.parse()?;
Ok(Self::Single(key))
}
}
}
17 changes: 17 additions & 0 deletions crates/macros/cgp-macro-core/src/types/check_components/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mod entries;
mod entry;
mod evaluated_check_entry;
mod key;
mod table;
mod tables;
mod type_with_generics;
mod value;

pub use entries::*;
pub use entry::*;
pub use evaluated_check_entry::*;
pub use key::*;
pub use table::*;
pub use tables::*;
pub use type_with_generics::*;
pub use value::*;
190 changes: 190 additions & 0 deletions crates/macros/cgp-macro-core/src/types/check_components/table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use proc_macro2::Span;
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::{Comma, Lt, Pound, Where};
use syn::{Attribute, Ident, Item, ItemImpl, ItemTrait, Type, WhereClause, braced, parse2};

use crate::functions::merge_generics;
use crate::parse_internal;
use crate::types::check_components::{CheckEntries, EvaluatedCheckEntry, TypeWithGenerics};
use crate::types::generics::ImplGenerics;
use crate::types::ident::IdentWithTypeArgs;

pub struct CheckComponentsTable {
pub check_providers: Option<Punctuated<Type, Comma>>,
pub impl_generics: ImplGenerics,
pub trait_name: Ident,
pub context_type: Type,
pub where_clause: WhereClause,
pub check_entries: CheckEntries,
}

impl CheckComponentsTable {
pub fn to_items(&self) -> syn::Result<Vec<Item>> {
let (item_trait, item_impls) = self.eval()?;

let mut items = vec![item_trait.into()];
items.extend(item_impls.into_iter().map(Into::into));

Ok(items)
}

pub fn eval(&self) -> syn::Result<(ItemTrait, Vec<ItemImpl>)> {
let mut item_impls = Vec::new();
let unit: Type = parse_internal!(());

let context_type = &self.context_type;
let trait_name = &self.trait_name;
let impl_generics = &self.impl_generics;
let where_clause = &self.where_clause;

let item_trait: ItemTrait = if self.check_providers.is_some() {
parse_internal! {
trait #trait_name <__Component__, __Params__: ?Sized>: IsProviderFor<__Component__, #context_type, __Params__> {}
}
} else {
parse_internal! {
trait #trait_name <__Component__, __Params__: ?Sized>: CanUseComponent<__Component__, __Params__> {}
}
};

let evaluated_entries = self.check_entries.eval();

for entry in evaluated_entries {
let EvaluatedCheckEntry {
key: component_type,
value: component_params,
span,
} = entry;

let self_types = if let Some(check_providers) = &self.check_providers {
Vec::from_iter(check_providers.iter().cloned())
} else {
// Override the span of the context type so that any unsatisfied constraint
// error is highlighted on the component type instead
let context_type = override_span(&span, context_type)?;
vec![context_type]
};

let TypeWithGenerics {
ty: component_param,
generics: check_generics,
} = component_params.unwrap_or_else(|| unit.clone().into());

let generics = merge_generics(&check_generics.generics, &impl_generics.generics);

let impl_generics = generics.split_for_impl().0;

for self_type in self_types {
let item_impl: ItemImpl = parse_internal! {
impl #impl_generics
#trait_name < #component_type, #component_param >
for #self_type
#where_clause
{}
};

item_impls.push(item_impl);
}
}

Ok((item_trait, item_impls))
}
}

impl Parse for CheckComponentsTable {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut check_providers: Option<Punctuated<Type, Comma>> = None;
let mut m_check_trait_name: Option<Ident> = None;

if input.peek(Pound) {
let attributes = input.call(Attribute::parse_outer)?;

for attribute in attributes {
if attribute.path().is_ident("check_providers") {
let provider_types: Punctuated<Type, Comma> =
attribute.parse_args_with(Punctuated::parse_terminated)?;

check_providers
.get_or_insert_default()
.extend(provider_types);
} else if attribute.path().is_ident("check_trait") {
let check_trait_name: Ident = attribute.parse_args()?;

if m_check_trait_name.is_some() {
return Err(syn::Error::new(
attribute.span(),
"Multiple `#[check_trait]` attributes found. Expected at most one.",
));
}

m_check_trait_name = Some(check_trait_name);
} else {
return Err(syn::Error::new(
attribute.span(),
format!("Invalid attribute {}", attribute.to_token_stream()),
));
}
}
};

let impl_generics = if input.peek(Lt) {
input.parse()?
} else {
Default::default()
};

let context_type: Type = input.parse()?;

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(),
)
};

let where_clause = if input.peek(Where) {
input.parse()?
} else {
WhereClause {
where_token: Where(Span::call_site()),
predicates: Punctuated::default(),
}
};

let content;
braced!(content in input);

let entries: CheckEntries = content.parse()?;

Ok(Self {
check_providers,
impl_generics,
trait_name,
context_type,
where_clause,
check_entries: entries,
})
}
}

fn override_span<T>(span: &Span, body: &T) -> syn::Result<T>
where
T: Parse + ToTokens,
{
parse2(
body.to_token_stream()
.into_iter()
.map(|mut tree| {
tree.set_span(*span);
tree
})
.collect(),
)
}
Loading
Loading