use {
super::AssociatedTypes,
crate::{
core::Result,
documentation::templates::DocumentationBuilder,
generate_inferable_brand_name,
generate_name,
},
proc_macro2::TokenStream,
quote::quote,
};
pub fn trait_kind_worker(input: AssociatedTypes) -> Result<TokenStream> {
let name = generate_name(&input)?;
let ib_name = generate_inferable_brand_name(&input)?;
let assoc_types_tokens = input.associated_types.iter().map(|assoc| {
let ident = &assoc.signature.name;
let generics = &assoc.signature.generics;
let output_bounds = &assoc.signature.output_bounds;
let attrs = &assoc.signature.attributes;
let output_bounds_tokens =
if output_bounds.is_empty() { quote!() } else { quote!(: #output_bounds) };
quote! {
#(#attrs)*
type #ident #generics #output_bounds_tokens;
}
});
let doc_string = DocumentationBuilder::new(&name, &input.associated_types).build();
let ib_doc_summary = format!("Maps a concrete type back to its canonical brand for `{name}`.",);
let ib_doc_detail = "\n\nOnly implemented for types where the brand is unambiguous (one brand\n\
per concrete type). Types reachable through multiple brands (e.g.,\n\
`Result<A, E>` at arity 1) do not implement this trait and require\n\
explicit brand specification via turbofish.\n\
\n\
A blanket implementation for references (`&T`) delegates to `T`'s\n\
implementation, enabling brand inference for both owned and borrowed\n\
containers.";
let ib_blanket_doc = format!(
"Blanket implementation delegating brand inference through references.\n\
\n\
Enables brand inference for borrowed containers (`&Vec<A>`, `&Option<A>`,\n\
etc.) by delegating to the underlying type's `{ib_name}` implementation.",
);
Ok(quote! {
#[doc = #doc_string]
#[expect(non_camel_case_types, reason = "Generated name uses hash suffix for uniqueness")]
pub trait #name {
#(#assoc_types_tokens)*
}
#[doc = #ib_doc_summary]
#[doc = #ib_doc_detail]
#[expect(non_camel_case_types, reason = "Generated name uses hash suffix for uniqueness")]
#[diagnostic::on_unimplemented(
message = "`{Self}` does not have a unique brand and cannot use brand inference",
note = "use the `explicit::` variant with a turbofish to specify the brand manually"
)]
pub trait #ib_name {
type Brand: #name;
}
#[doc = #ib_blanket_doc]
impl<__IB_T: #ib_name + ?Sized> #ib_name for &__IB_T {
type Brand = __IB_T::Brand;
}
})
}
#[cfg(test)]
#[expect(clippy::expect_used, reason = "Tests use panicking operations for brevity and clarity")]
mod tests {
use super::*;
fn parse_kind_input(input: &str) -> AssociatedTypes {
syn::parse_str(input).expect("Failed to parse KindInput")
}
#[test]
fn test_trait_kind_simple() {
let input = parse_kind_input("type Of<A>;");
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(output_str.contains("pub trait Kind_"));
assert!(output_str.contains("type Of < A > ;"));
}
#[test]
fn test_trait_kind_multiple() {
let input = parse_kind_input(
"
type Of<'a, T>: Display;
type SendOf<U>: Send;
",
);
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(output_str.contains("pub trait Kind_"));
assert!(output_str.contains("type Of < 'a , T > : Display ;"));
assert!(output_str.contains("type SendOf < U > : Send ;"));
}
#[test]
fn test_trait_kind_complex() {
let input = parse_kind_input("type Of<'a, T: 'a + Clone>: Debug + Display;");
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(output_str.contains("type Of < 'a , T : 'a + Clone > : Debug + Display ;"));
}
#[test]
fn test_trait_kind_doc_type_param_bounds() {
let input = parse_kind_input("type Of<'a, A: 'a>: 'a;");
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(
output_str.contains(r#"**Type parameters** (1): `A: 'a`"#),
"Expected documentation to contain 'Type parameters (1): `A: 'a`', got: {output_str}"
);
assert!(
!output_str.contains(".bounds"),
"Documentation should not contain '.bounds', got: {output_str}"
);
assert!(
!output_str.contains("A: A"),
"Documentation should not contain 'A: A', got: {}",
output_str
);
}
#[test]
fn test_trait_kind_doc_type_param_no_bounds() {
let input = parse_kind_input("type Of<A>;");
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(
output_str.contains(r#"**Type parameters** (1): `A`"#),
"Expected documentation to contain 'Type parameters (1): `A`', got: {}",
output_str
);
}
#[test]
fn test_trait_kind_doc_impl_example() {
let input = parse_kind_input("type Of<'a, T>; type SendOf<U>;");
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(
output_str.contains("type Of<'a, T> = ConcreteType;"),
"Expected 'type Of<'a, T> = ConcreteType;' in documentation, got: {}",
output_str
);
assert!(
output_str.contains("type SendOf<U> = ConcreteType;"),
"Expected 'type SendOf<U> = ConcreteType;' in documentation, got: {}",
output_str
);
}
#[test]
fn test_trait_kind_doc_multiple_type_params() {
let input = parse_kind_input("type Of<'a, T: Clone, U: 'a + Send>: Debug;");
let output = trait_kind_worker(input).expect("trait_kind_worker failed");
let output_str = output.to_string();
assert!(
output_str.contains(r#"**Lifetimes** (1): `'a`"#),
"Expected documentation to contain lifetime 'a, got: {}",
output_str
);
assert!(
output_str.contains(r#"**Type parameters** (2):"#),
"Expected 2 type parameters, got: {}",
output_str
);
assert!(
output_str.contains("`T: Clone`"),
"Expected T: Clone in documentation, got: {}",
output_str
);
assert!(
output_str.contains("`U: 'a + Send`"),
"Expected U: 'a + Send in documentation, got: {}",
output_str
);
assert!(
output_str.contains(r#"**Output bounds**: `Debug`"#),
"Expected output bounds Debug, got: {}",
output_str
);
}
}