use {
crate::{
AssociatedTypes,
core::{
constants::markers::{
INFERABLE_BRAND_PREFIX,
KIND_PREFIX,
},
error_handling::{
Error,
UnsupportedFeature,
},
},
support::type_visitor::TypeVisitor,
},
quote::{
format_ident,
quote,
},
std::collections::BTreeMap,
syn::{
GenericArgument,
GenericParam,
Generics,
Ident,
PathArguments,
ReturnType,
Token,
Type,
TypeParamBound,
punctuated::Punctuated,
},
};
type Result<T> = std::result::Result<T, Error>;
pub struct Canonicalizer {
lifetime_map: BTreeMap<String, usize>,
type_map: BTreeMap<String, usize>,
}
impl Canonicalizer {
pub fn new(generics: &Generics) -> Self {
let mut lifetime_map = BTreeMap::new();
let mut type_map = BTreeMap::new();
let mut l_idx = 0;
let mut t_idx = 0;
for param in &generics.params {
match param {
GenericParam::Lifetime(lt) => {
lifetime_map.insert(lt.lifetime.ident.to_string(), l_idx);
l_idx += 1;
}
GenericParam::Type(ty) => {
type_map.insert(ty.ident.to_string(), t_idx);
t_idx += 1;
}
GenericParam::Const(_) => {
}
}
}
Self {
lifetime_map,
type_map,
}
}
pub fn canonicalize_bound(
&mut self,
bound: &TypeParamBound,
) -> Result<String> {
match bound {
TypeParamBound::Lifetime(lt) => {
let idx = self
.lifetime_map
.get(<.ident.to_string())
.ok_or_else(|| Error::internal(format!("Unknown lifetime: {}", lt.ident)))?;
Ok(format!("l{idx}"))
}
TypeParamBound::Trait(tr) => {
let mut path_parts = Vec::new();
for seg in &tr.path.segments {
let ident = seg.ident.to_string();
let segment_str = match &seg.arguments {
PathArguments::None => ident,
PathArguments::AngleBracketed(args) => {
let mut args_vec = Vec::new();
for arg in &args.args {
args_vec.push(self.canonicalize_generic_arg(arg)?);
}
let args_str = args_vec.join(",");
format!("{ident}<{args_str}>")
}
PathArguments::Parenthesized(args) => {
let mut inputs_vec = Vec::new();
for t in &args.inputs {
inputs_vec.push(self.canonicalize_type(t)?);
}
let inputs = inputs_vec.join(",");
let output = match &args.output {
ReturnType::Default => "()".to_string(),
ReturnType::Type(_, ty) => self.canonicalize_type(ty)?,
};
format!("{ident}({inputs})->{output}")
}
};
path_parts.push(segment_str);
}
let path = path_parts.join("::");
Ok(format!("t{path}"))
}
TypeParamBound::Verbatim(_tokens) =>
Err(Error::Unsupported(UnsupportedFeature::VerbatimBounds {
span: proc_macro2::Span::call_site(),
})),
_ => Err(Error::Unsupported(UnsupportedFeature::BoundType {
description: "Unknown bound type variant".to_string(),
span: proc_macro2::Span::call_site(),
})),
}
}
pub fn canonicalize_bounds(
&mut self,
bounds: &Punctuated<TypeParamBound, Token![+]>,
) -> Result<String> {
let mut parts: Vec<String> = Vec::new();
for b in bounds {
parts.push(self.canonicalize_bound(b)?);
}
parts.sort(); Ok(parts.join(""))
}
fn canonicalize_generic_arg(
&mut self,
arg: &GenericArgument,
) -> Result<String> {
match arg {
GenericArgument::Type(ty) => self.canonicalize_type(ty),
GenericArgument::Lifetime(lt) => {
if let Some(idx) = self.lifetime_map.get(<.ident.to_string()) {
Ok(format!("l{idx}"))
} else {
Ok(lt.ident.to_string())
}
}
GenericArgument::AssocType(assoc) =>
Ok(format!("{}={}", assoc.ident, self.canonicalize_type(&assoc.ty)?)),
GenericArgument::Const(expr) => Ok(quote!(#expr).to_string().replace(" ", "")),
GenericArgument::AssocConst(_) | GenericArgument::Constraint(_) =>
Err(Error::Unsupported(UnsupportedFeature::GenericArgument {
description: "Associated const or constraint".to_string(),
span: proc_macro2::Span::call_site(),
})),
_ => Err(Error::Unsupported(UnsupportedFeature::GenericArgument {
description: "Unknown generic argument variant".to_string(),
span: proc_macro2::Span::call_site(),
})),
}
}
}
impl TypeVisitor for Canonicalizer {
type Output = Result<String>;
fn default_output(&self) -> Self::Output {
Err(Error::Unsupported(UnsupportedFeature::ComplexTypes {
description: "Unsupported type variant in canonicalization".to_string(),
span: proc_macro2::Span::call_site(),
}))
}
fn visit_path(
&mut self,
type_path: &syn::TypePath,
) -> Self::Output {
if let Some(ident) = type_path.path.get_ident()
&& let Some(idx) = self.type_map.get(&ident.to_string())
{
return Ok(format!("T{idx}"));
}
let mut path_parts = Vec::new();
for seg in &type_path.path.segments {
let ident = seg.ident.to_string();
let segment_str = match &seg.arguments {
PathArguments::None => ident,
PathArguments::AngleBracketed(args) => {
let mut args_vec = Vec::new();
for a in &args.args {
args_vec.push(self.canonicalize_generic_arg(a)?);
}
let args_str = args_vec.join(",");
format!("{ident}<{args_str}>")
}
PathArguments::Parenthesized(args) => {
let mut inputs_vec = Vec::new();
for t in &args.inputs {
inputs_vec.push(self.visit(t)?);
}
let inputs = inputs_vec.join(",");
let output = match &args.output {
ReturnType::Default => "()".to_string(),
ReturnType::Type(_, ty) => self.visit(ty)?,
};
format!("{ident}({inputs})->{output}")
}
};
path_parts.push(segment_str);
}
Ok(path_parts.join("::"))
}
fn visit_reference(
&mut self,
type_ref: &syn::TypeReference,
) -> Self::Output {
let lt = if let Some(lt) = &type_ref.lifetime {
if let Some(idx) = self.lifetime_map.get(<.ident.to_string()) {
format!("l{idx} ")
} else {
format!("{} ", lt.ident)
}
} else {
"".to_string()
};
let mutability = if type_ref.mutability.is_some() { "mut " } else { "" };
Ok(format!("&{lt}{mutability}{}", self.visit(&type_ref.elem)?))
}
fn visit_tuple(
&mut self,
tuple: &syn::TypeTuple,
) -> Self::Output {
let mut elems_vec = Vec::new();
for t in &tuple.elems {
elems_vec.push(self.visit(t)?);
}
let elems = elems_vec.join(",");
Ok(format!("({elems})"))
}
fn visit_slice(
&mut self,
slice: &syn::TypeSlice,
) -> Self::Output {
Ok(format!("[{}]", self.visit(&slice.elem)?))
}
fn visit_array(
&mut self,
array: &syn::TypeArray,
) -> Self::Output {
let len = quote!(#array.len).to_string().replace(" ", "");
Ok(format!("[{};{len}]", self.visit(&array.elem)?))
}
fn visit_other(
&mut self,
ty: &syn::Type,
) -> Self::Output {
match ty {
Type::Never(_) => Ok("!".to_string()),
Type::Infer(_) => Ok("_".to_string()),
Type::BareFn(_) | Type::ImplTrait(_) | Type::TraitObject(_) =>
Err(Error::Unsupported(UnsupportedFeature::ComplexTypes {
description: format!("Type {} in canonicalization", quote!(#ty)),
span: proc_macro2::Span::call_site(),
})),
_ => self.default_output(),
}
}
}
impl Canonicalizer {
fn canonicalize_type(
&mut self,
ty: &Type,
) -> Result<String> {
self.visit(ty)
}
}
const RAPID_SECRETS: rapidhash::v3::RapidSecrets =
rapidhash::v3::RapidSecrets::seed(0x1234567890abcdef);
pub fn rapidhash(data: &[u8]) -> u64 {
rapidhash::v3::rapidhash_v3_seeded(data, &RAPID_SECRETS)
}
pub fn generate_assoc_signature(signature: &crate::hkt::AssociatedTypeBase) -> Result<String> {
let mut canon = Canonicalizer::new(&signature.generics);
let mut l_count = 0;
let mut t_count = 0;
let mut type_bounds_parts = Vec::new();
for param in &signature.generics.params {
match param {
GenericParam::Lifetime(_) => l_count += 1,
GenericParam::Type(ty) => {
if !ty.bounds.is_empty() {
let bounds_str = canon.canonicalize_bounds(&ty.bounds)?;
type_bounds_parts.push(format!("B{t_count}{bounds_str}"));
}
t_count += 1;
}
_ => {}
}
}
let mut parts = vec![signature.name.to_string(), format!("L{l_count}"), format!("T{t_count}")];
parts.extend(type_bounds_parts);
if !signature.output_bounds.is_empty() {
let bounds_str = canon.canonicalize_bounds(&signature.output_bounds)?;
parts.push(format!("O{bounds_str}"));
}
Ok(parts.join("_"))
}
pub fn hash_assoc_signature(signature: &crate::hkt::AssociatedTypeBase) -> Result<u64> {
let repr = generate_assoc_signature(signature)?;
Ok(rapidhash(repr.as_bytes()))
}
pub fn generate_hash(input: &AssociatedTypes) -> Result<u64> {
let mut assoc_types: Vec<_> = input.associated_types.iter().collect();
assoc_types.sort_by_key(|a| a.signature.name.to_string());
let mut canonical_parts = Vec::new();
for assoc in assoc_types {
canonical_parts.push(generate_assoc_signature(&assoc.signature)?);
}
let canonical_repr = canonical_parts.join("__");
Ok(rapidhash(canonical_repr.as_bytes()))
}
fn generate_prefixed_name(
prefix: &str,
input: &AssociatedTypes,
) -> Result<Ident> {
let hash = generate_hash(input)?;
Ok(format_ident!("{prefix}{:016x}", hash))
}
pub fn generate_name(input: &AssociatedTypes) -> Result<Ident> {
generate_prefixed_name(KIND_PREFIX, input)
}
pub fn generate_inferable_brand_name(input: &AssociatedTypes) -> Result<Ident> {
generate_prefixed_name(INFERABLE_BRAND_PREFIX, input)
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::expect_used,
reason = "Tests use panicking operations for brevity and clarity"
)]
mod tests {
use {
super::*,
syn::parse_quote,
};
#[test]
fn test_canonicalize_simple_bound() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Clone);
assert_eq!(canon.canonicalize_bound(&bound).unwrap(), "tClone");
}
#[test]
fn test_canonicalize_path_bound() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(std::fmt::Debug);
assert_eq!(canon.canonicalize_bound(&bound).unwrap(), "tstd::fmt::Debug");
}
#[test]
fn test_canonicalize_generic_bound() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Iterator<Item = String>);
assert_eq!(canon.canonicalize_bound(&bound).unwrap(), "tIterator<Item=String>");
}
#[test]
fn test_canonicalize_fn_bound() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Fn(i32) -> bool);
assert_eq!(canon.canonicalize_bound(&bound).unwrap(), "tFn(i32)->bool");
}
#[test]
fn test_canonicalize_lifetime_bound() {
let generics: Generics = parse_quote!(<'a>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!('a);
assert_eq!(canon.canonicalize_bound(&bound).unwrap(), "l0");
}
#[test]
fn test_canonicalize_bounds_sorting() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bounds1: Punctuated<TypeParamBound, Token![+]> = parse_quote!(Clone + std::fmt::Debug);
let bounds2: Punctuated<TypeParamBound, Token![+]> = parse_quote!(std::fmt::Debug + Clone);
assert_eq!(
canon.canonicalize_bounds(&bounds1).unwrap(),
canon.canonicalize_bounds(&bounds2).unwrap()
);
}
#[test]
fn test_canonicalize_type_param_mapping() {
let generics: Generics = parse_quote!(<T, U>);
let mut canon = Canonicalizer::new(&generics);
let bound_t: TypeParamBound = parse_quote!(AsRef<T>);
assert_eq!(canon.canonicalize_bound(&bound_t).unwrap(), "tAsRef<T0>");
let bound_u: TypeParamBound = parse_quote!(AsRef<U>);
assert_eq!(canon.canonicalize_bound(&bound_u).unwrap(), "tAsRef<T1>");
}
#[test]
fn test_canonicalize_type_param_independence() {
let generics1: Generics = parse_quote!(<A>);
let mut canon1 = Canonicalizer::new(&generics1);
let bound1: TypeParamBound = parse_quote!(AsRef<A>);
let generics2: Generics = parse_quote!(<B>);
let mut canon2 = Canonicalizer::new(&generics2);
let bound2: TypeParamBound = parse_quote!(AsRef<B>);
assert_eq!(canon1.canonicalize_bound(&bound1).unwrap(), "tAsRef<T0>");
assert_eq!(canon2.canonicalize_bound(&bound2).unwrap(), "tAsRef<T0>");
}
#[test]
fn test_canonicalize_nested_generic() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Iterator<Item = Option<Vec<String>>>);
let result = canon.canonicalize_bound(&bound).unwrap();
assert!(result.contains("Iterator"));
assert!(result.contains("Option<Vec<String>>"));
}
#[test]
fn test_canonicalize_deeply_nested_with_params() {
let generics: Generics = parse_quote!(<T>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(AsRef<Vec<Option<T>>>);
let result = canon.canonicalize_bound(&bound).unwrap();
assert_eq!(result, "tAsRef<Vec<Option<T0>>>");
}
#[test]
fn test_canonicalize_multiple_generic_params() {
let generics: Generics = parse_quote!(<E>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Into<Result<String, E>>);
let result = canon.canonicalize_bound(&bound).unwrap();
assert_eq!(result, "tInto<Result<String,T0>>");
}
#[test]
fn test_canonicalize_fn_complex() {
let generics: Generics = parse_quote!(<T>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Fn(T, String) -> Option<T>);
let result = canon.canonicalize_bound(&bound).unwrap();
assert_eq!(result, "tFn(T0,String)->Option<T0>");
}
#[test]
fn test_canonicalize_fn_no_return() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Fn(i32));
let result = canon.canonicalize_bound(&bound).unwrap();
assert_eq!(result, "tFn(i32)->()");
}
#[test]
fn test_canonicalize_fnmut() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(FnMut(String) -> i32);
let result = canon.canonicalize_bound(&bound).unwrap();
assert_eq!(result, "tFnMut(String)->i32");
}
#[test]
fn test_canonicalize_fnonce() {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(FnOnce() -> String);
let result = canon.canonicalize_bound(&bound).unwrap();
assert_eq!(result, "tFnOnce()->String");
}
#[test]
fn test_canonicalize_multiple_lifetimes() {
let generics: Generics = parse_quote!(<'a, 'b, 'c>);
let mut canon = Canonicalizer::new(&generics);
let bound_a: TypeParamBound = parse_quote!('a);
let bound_b: TypeParamBound = parse_quote!('b);
let bound_c: TypeParamBound = parse_quote!('c);
assert_eq!(canon.canonicalize_bound(&bound_a).unwrap(), "l0");
assert_eq!(canon.canonicalize_bound(&bound_b).unwrap(), "l1");
assert_eq!(canon.canonicalize_bound(&bound_c).unwrap(), "l2");
}
#[test]
fn test_canonicalize_lifetime_independence() {
let generics1: Generics = parse_quote!(<'a>);
let mut canon1 = Canonicalizer::new(&generics1);
let generics2: Generics = parse_quote!(<'x>);
let mut canon2 = Canonicalizer::new(&generics2);
let bound1: TypeParamBound = parse_quote!('a);
let bound2: TypeParamBound = parse_quote!('x);
assert_eq!(
canon1.canonicalize_bound(&bound1).unwrap(),
canon2.canonicalize_bound(&bound2).unwrap()
);
}
fn parse_kind_input(input: &str) -> AssociatedTypes {
syn::parse_str(input).expect("Failed to parse KindInput")
}
#[test]
fn test_generate_name_determinism() {
let input1 = parse_kind_input("type Of<'a, A: 'a>: 'a;");
let name1 = generate_name(&input1).unwrap();
let input2 = parse_kind_input("type Of<'a, A: 'a>: 'a;");
let name2 = generate_name(&input2).unwrap();
assert_eq!(name1, name2);
assert!(name1.to_string().starts_with("Kind_"));
}
#[test]
fn test_generate_name_different_inputs() {
let input1 = parse_kind_input("type Of<'a, A: 'a>: 'a;");
let name1 = generate_name(&input1).unwrap();
let input2 = parse_kind_input("type Of<A>;");
let name2 = generate_name(&input2).unwrap();
assert_ne!(name1, name2);
}
#[test]
fn test_generate_name_order_independence() {
let input1 = parse_kind_input(
"
type Of<'a, T>: Display;
type SendOf<U>: Send;
",
);
let name1 = generate_name(&input1).unwrap();
let input2 = parse_kind_input(
"
type SendOf<U>: Send;
type Of<'a, T>: Display;
",
);
let name2 = generate_name(&input2).unwrap();
assert_eq!(name1, name2);
}
#[test]
fn test_generate_name_complex_bounds() {
let input = parse_kind_input("type Of<'a, A: Clone + Send>: Clone + Send;");
let name = generate_name(&input).unwrap();
assert!(name.to_string().starts_with("Kind_"));
let input2 = parse_kind_input("type Of<'a, A: Clone + Send>: Clone + Send;");
let name2 = generate_name(&input2).unwrap();
assert_eq!(name, name2);
}
#[test]
fn test_generate_name_bound_order_independence() {
let input1 = parse_kind_input("type Of<A: Clone + Send>;");
let input2 = parse_kind_input("type Of<A: Send + Clone>;");
let name1 = generate_name(&input1).unwrap();
let name2 = generate_name(&input2).unwrap();
assert_eq!(name1, name2);
}
}