use {
super::AssociatedTypeBase,
crate::support::parsing::{
parse_generics,
parse_many,
parse_non_empty,
},
quote::ToTokens,
syn::{
Token,
parse::{
Parse,
ParseStream,
},
},
};
#[derive(Debug)]
pub struct AssociatedTypes {
pub associated_types: Vec<AssociatedType>,
}
#[derive(Debug)]
pub struct AssociatedType {
pub signature: AssociatedTypeBase,
pub semi_token: Token![;],
}
impl Parse for AssociatedTypes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let assoc_types: Vec<AssociatedType> = parse_many(input)?;
let assoc_types =
parse_non_empty(assoc_types, "Kind definition must have at least one associated type")?;
for assoc in &assoc_types {
parse_generics(&assoc.signature.generics)?;
}
Ok(AssociatedTypes {
associated_types: assoc_types,
})
}
}
impl ToTokens for AssociatedTypes {
fn to_tokens(
&self,
tokens: &mut proc_macro2::TokenStream,
) {
for assoc in &self.associated_types {
assoc.to_tokens(tokens);
}
}
}
impl ToTokens for AssociatedType {
fn to_tokens(
&self,
tokens: &mut proc_macro2::TokenStream,
) {
self.signature.to_tokens(tokens);
self.semi_token.to_tokens(tokens);
}
}
impl Parse for AssociatedType {
fn parse(input: ParseStream) -> syn::Result<Self> {
let signature = AssociatedTypeBase::parse_signature(input, |i| i.peek(Token![;]))?;
let semi_token: Token![;] = input.parse()?;
Ok(AssociatedType {
signature,
semi_token,
})
}
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::expect_used,
reason = "Tests use panicking operations for brevity and clarity"
)]
mod tests {
use {
super::*,
syn::parse_str,
};
#[test]
fn test_parse_kind_input_simple() {
let input = "type Of<'a, T>: Display;";
let parsed: AssociatedTypes = parse_str(input).expect("Failed to parse");
assert_eq!(parsed.associated_types.len(), 1);
let assoc = &parsed.associated_types[0];
assert_eq!(assoc.signature.name.to_string(), "Of");
assert_eq!(assoc.signature.generics.params.len(), 2);
assert_eq!(assoc.signature.output_bounds.len(), 1);
}
#[test]
fn test_parse_kind_input_multiple() {
let input = "
type Of<'a, T: 'a>: Display;
type SendOf<U>: Send;
";
let parsed: AssociatedTypes = parse_str(input).expect("Failed to parse");
assert_eq!(parsed.associated_types.len(), 2);
let assoc1 = &parsed.associated_types[0];
assert_eq!(assoc1.signature.name.to_string(), "Of");
assert_eq!(assoc1.signature.generics.params.len(), 2);
let assoc2 = &parsed.associated_types[1];
assert_eq!(assoc2.signature.name.to_string(), "SendOf");
assert_eq!(assoc2.signature.generics.params.len(), 1);
}
#[test]
fn test_parse_kind_input_empty() {
let input = "";
let result: syn::Result<AssociatedTypes> = parse_str(input);
assert!(result.is_err(), "Empty input should be rejected");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("at least one associated type"),
"Error message should mention requirement for associated types"
);
}
#[test]
fn test_parse_kind_input_const_generics() {
let input = "type Of<const N: usize>;";
let result: syn::Result<AssociatedTypes> = parse_str(input);
assert!(result.is_err(), "Const generics should be rejected");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Const generic parameters are not supported"),
"Error message should mention const generics not being supported"
);
}
#[test]
fn test_parse_kind_input_const_generics_in_second() {
let input = "
type Of<T>;
type Array<const N: usize>;
";
let result: syn::Result<AssociatedTypes> = parse_str(input);
assert!(result.is_err(), "Const generics should be rejected");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Const generic parameters are not supported"),
"Error message should mention const generics not being supported"
);
}
}