use {
super::AssociatedTypes,
crate::{
core::{
Result,
constants::macros::{
INFERABLE_BRAND_MACRO,
KIND_MACRO,
},
},
generate_inferable_brand_name,
generate_name,
},
proc_macro2::{
Delimiter,
TokenStream,
TokenTree,
},
quote::quote,
syn::{
AngleBracketedGenericArguments,
Ident,
Token,
Type,
parse::{
Parse,
ParseStream,
},
},
};
pub fn resolve_inferable_brand(input: TokenStream) -> std::result::Result<TokenStream, syn::Error> {
let tokens: Vec<TokenTree> = input.into_iter().collect();
let mut output = Vec::new();
let mut i = 0;
while i < tokens.len() {
if let Some([TokenTree::Ident(ident), TokenTree::Punct(bang), TokenTree::Group(group), ..]) =
tokens.get(i ..)
&& ident == INFERABLE_BRAND_MACRO
&& bang.as_char() == '!'
&& group.delimiter() == Delimiter::Parenthesis
{
let sig: AssociatedTypes = syn::parse2(group.stream())?;
let resolved = generate_inferable_brand_name(&sig)
.map_err(|e| syn::Error::new(ident.span(), e.to_string()))?;
output.push(TokenTree::Ident(resolved));
i += 3;
continue;
}
if let Some(TokenTree::Group(group)) = tokens.get(i) {
let resolved_inner = resolve_inferable_brand(group.stream())?;
let mut new_group = proc_macro2::Group::new(group.delimiter(), resolved_inner);
new_group.set_span(group.span());
output.push(TokenTree::Group(new_group));
} else if let Some(token) = tokens.get(i) {
output.push(token.clone());
}
i += 1;
}
Ok(output.into_iter().collect())
}
#[derive(Debug)]
pub struct ApplyInput {
pub brand: Type,
pub kind_input: AssociatedTypes,
pub assoc_name: Ident,
pub args: AngleBracketedGenericArguments,
}
impl Parse for ApplyInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![<]>()?;
let brand: Type = input.parse()?;
input.parse::<Token![as]>()?;
let kind_ident: Ident = input.parse()?;
if kind_ident != KIND_MACRO {
return Err(syn::Error::new(kind_ident.span(), format!("expected `{KIND_MACRO}`")));
}
input.parse::<Token![!]>()?;
let content;
syn::parenthesized!(content in input);
let kind_input: AssociatedTypes = content.parse()?;
input.parse::<Token![>]>()?;
input.parse::<Token![::]>()?;
let assoc_name: Ident = input.parse()?;
let args: AngleBracketedGenericArguments = input.parse()?;
Ok(ApplyInput {
brand,
kind_input,
assoc_name,
args,
})
}
}
pub fn apply_worker(input: ApplyInput) -> Result<TokenStream> {
let brand = &input.brand;
let kind_name = generate_name(&input.kind_input)?;
let assoc_name = &input.assoc_name;
let args = &input.args;
Ok(quote! {
<#brand as #kind_name>::#assoc_name #args
})
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::expect_used,
reason = "Tests use panicking operations for brevity and clarity"
)]
mod tests {
use {
super::*,
syn::parse_str,
};
#[test]
fn test_parse_apply_new_syntax() {
let input = "<OptionBrand as Kind!(type Of<'a, T>: 'a;)>::Of<'static, i32>";
let parsed: ApplyInput = parse_str(input).expect("Failed to parse ApplyInput");
assert_eq!(parsed.assoc_name.to_string(), "Of");
assert_eq!(parsed.kind_input.associated_types.len(), 1);
assert_eq!(parsed.args.args.len(), 2);
}
#[test]
fn test_apply_generation_new_syntax() {
let input = "<OptionBrand as Kind!(type Of<'a, T>: 'a;)>::Of<'static, i32>";
let parsed: ApplyInput = parse_str(input).expect("Failed to parse ApplyInput");
let output = apply_worker(parsed).expect("apply_worker failed");
let output_str = output.to_string();
assert!(output_str.contains("< OptionBrand as Kind_"));
assert!(output_str.contains(":: Of < 'static , i32 >"));
}
#[test]
fn test_resolve_inferable_brand_in_apply() {
let input: TokenStream = "<<FA as InferableBrand!(type Of<'a, A: 'a>: 'a;)>::Brand as Kind!(type Of<'a, T: 'a>: 'a;)>::Of<'a, B>"
.parse()
.unwrap();
let resolved = resolve_inferable_brand(input).expect("resolution failed");
let resolved_str = resolved.to_string();
assert!(
resolved_str.contains("InferableBrand_cdc7cd43dac7585f"),
"Expected resolved InferableBrand name, got: {resolved_str}"
);
let parsed: ApplyInput =
syn::parse2(resolved).expect("Failed to parse resolved stream as ApplyInput");
let output = apply_worker(parsed).expect("apply_worker failed");
let output_str = output.to_string();
assert!(
output_str.contains("InferableBrand_cdc7cd43dac7585f"),
"Expected InferableBrand in output, got: {output_str}"
);
}
}