use {
crate::{
core::constants::macros::{
APPLY_MACRO,
IMPL_KIND_MACRO,
KIND_MACRO,
TRAIT_KIND_MACRO,
},
hkt::AssociatedType,
},
proc_macro2::Ident,
quote::quote,
syn::GenericParam,
};
pub struct DocumentationBuilder<'a> {
name: &'a Ident,
assoc_types: &'a [AssociatedType],
}
impl<'a> DocumentationBuilder<'a> {
pub fn new(
name: &'a Ident,
assoc_types: &'a [AssociatedType],
) -> Self {
Self {
name,
assoc_types,
}
}
pub fn build(self) -> String {
let sections = [
self.build_summary(),
self.build_overview(),
self.build_associated_types_section(),
self.build_implementation_section(),
self.build_naming_section(),
self.build_see_also_section(),
];
sections.join("\n\n")
}
fn build_summary(&self) -> String {
let assoc_types_summary = self
.assoc_types
.iter()
.map(|assoc| {
let ident = &assoc.signature.name;
let generics = &assoc.signature.generics;
let output_bounds = &assoc.signature.output_bounds;
let output_bounds_tokens =
if output_bounds.is_empty() { quote!() } else { quote!(: #output_bounds) };
let s = quote!(#ident #generics #output_bounds_tokens).to_string();
let cleaned = s
.replace(" < ", "<")
.replace(" >", ">")
.replace(" , ", ", ")
.replace(" : ", ": ");
format!("`{cleaned}`")
})
.collect::<Vec<_>>()
.join("; ");
match self.assoc_types.len() {
0 => "`Kind` trait.".to_string(),
1 => format!("`Kind` with associated type: {assoc_types_summary}."),
_ => format!("`Kind` with associated types: {assoc_types_summary}."),
}
}
fn build_overview(&self) -> String {
format!(
"Higher-Kinded Type (HKT) trait auto-generated by [`{TRAIT_KIND_MACRO}!`](crate::{TRAIT_KIND_MACRO}!), \
representing type constructors that can be applied to generic parameters to produce \
concrete types."
)
.to_string()
}
fn build_associated_types_section(&self) -> String {
let mut section = String::from("# Associated Types");
for assoc in self.assoc_types {
section.push_str("\n\n");
section.push_str(&self.format_assoc_type(assoc));
}
section
}
fn format_assoc_type(
&self,
assoc: &AssociatedType,
) -> String {
let ident = &assoc.signature.name;
let mut l_count = 0;
let mut t_count = 0;
let mut lifetimes_doc = Vec::new();
let mut types_doc = Vec::new();
for param in &assoc.signature.generics.params {
match param {
GenericParam::Lifetime(lt) => {
l_count += 1;
lifetimes_doc.push(format!("`{}`", lt.lifetime));
}
GenericParam::Type(ty) => {
t_count += 1;
let bounds_str = if ty.bounds.is_empty() {
String::new()
} else {
let bounds = &ty.bounds;
format!(": {}", quote!(#bounds))
};
types_doc.push(format!("`{}{}`", ty.ident, bounds_str));
}
_ => {}
}
}
let output_bounds = &assoc.signature.output_bounds;
let output_bounds_doc = if output_bounds.is_empty() {
"None".to_string()
} else {
format!("`{}`", quote!(#output_bounds))
};
format!(
r#"### `type {ident}`
* **Lifetimes** ({l_count}): {}
* **Type parameters** ({t_count}): {}
* **Output bounds**: {output_bounds_doc}"#,
if l_count == 0 { "None".to_string() } else { lifetimes_doc.join(", ") },
if t_count == 0 { "None".to_string() } else { types_doc.join(", ") }
)
}
fn build_implementation_section(&self) -> String {
let impl_example_body = self
.assoc_types
.iter()
.map(|assoc| {
let ident = &assoc.signature.name;
let generics = &assoc.signature.generics;
let s = quote!(type #ident #generics = ConcreteType;).to_string();
s.replace(" < ", "<")
.replace(" >", ">")
.replace(" , ", ", ")
.replace(" ;", ";")
.replace(" :", ":")
})
.collect::<Vec<_>>()
.join("\n ");
format!(
r#"# Implementation
To implement this trait for your type constructor, use the [`{IMPL_KIND_MACRO}!`](crate::{IMPL_KIND_MACRO}!) macro:
```ignore
{IMPL_KIND_MACRO}! {{
for BrandType {{
{impl_example_body}
}}
}}
```"#
)
}
fn build_naming_section(&self) -> String {
let name = self.name;
format!(
r#"# Naming
The trait name `{name}` is a deterministic hash of the canonical signature, ensuring that semantically equivalent signatures always map to the same trait."#
)
}
fn build_see_also_section(&self) -> String {
format!(
r#"# See Also
* [`{KIND_MACRO}!`](crate::{KIND_MACRO}!) - Macro to generate the name of a Kind trait
* [`{IMPL_KIND_MACRO}!`](crate::{IMPL_KIND_MACRO}!) - Macro to implement a Kind trait for a brand
* [`{APPLY_MACRO}!`](crate::{APPLY_MACRO}!) - Macro to apply a Kind to generic arguments"#
)
.to_string()
}
}
#[cfg(test)]
mod tests {
use {
super::*,
syn::parse_quote,
};
#[test]
fn test_documentation_builder_single_assoc() {
let name: Ident = parse_quote!(Kind_12345678);
let assoc_type: AssociatedType = parse_quote!(
type Of<A>;
);
let assoc_types = vec![assoc_type];
let builder = DocumentationBuilder::new(&name, &assoc_types);
let doc = builder.build();
assert!(doc.contains("`Kind` with associated type:"));
assert!(doc.contains("# Associated Types"));
assert!(doc.contains("### `type Of`"));
assert!(doc.contains("# Implementation"));
assert!(doc.contains("# Naming"));
assert!(doc.contains("# See Also"));
}
#[test]
fn test_documentation_builder_multiple_assoc() {
let name: Ident = parse_quote!(Kind_87654321);
let assoc1: AssociatedType = parse_quote!(
type Of<A>;
);
let assoc2: AssociatedType = parse_quote!(
type SendOf<B>: Send;
);
let assoc_types = vec![assoc1, assoc2];
let builder = DocumentationBuilder::new(&name, &assoc_types);
let doc = builder.build();
assert!(doc.contains("`Kind` with associated types:"));
assert!(doc.contains("### `type Of`"));
assert!(doc.contains("### `type SendOf`"));
}
#[test]
fn test_documentation_builder_with_bounds() {
let name: Ident = parse_quote!(Kind_test);
let assoc_type: AssociatedType = parse_quote!(
type Of<'a, T: 'a + Clone>: Debug;
);
let assoc_types = vec![assoc_type];
let builder = DocumentationBuilder::new(&name, &assoc_types);
let doc = builder.build();
assert!(doc.contains("**Lifetimes** (1):"));
assert!(doc.contains("**Type parameters** (1):"));
assert!(doc.contains("**Output bounds**:"));
}
}