use kotlin_codegen::{KtCode, KtFun, KtParam, KtType};
fn array_bearing(ty: &KtType) -> bool {
match ty {
KtType::Named { fqn, args, .. } => {
is_kotlin_array(fqn.rsplit('.').next().unwrap_or(fqn)) || args.iter().any(array_bearing)
}
KtType::Function { .. } => false,
}
}
fn is_kotlin_array(name: &str) -> bool {
crate::jni::wire_access::kotlin_array_descriptor(name).is_some()
}
fn element_of(ty: &KtType) -> Option<&KtType> {
match ty {
KtType::Named { args, .. } if args.len() == 1 => Some(&args[0]),
_ => None,
}
}
fn eq_expr(a: &str, b: &str, ty: &KtType) -> String {
if !array_bearing(ty) {
return format!("{a} == {b}");
}
if element_of(ty).is_none() {
return format!("{a}.contentEquals({b})");
}
let elem = element_of(ty).expect("checked above");
let inner = eq_expr("__x", "__y", elem);
let cmp = format!(
"{a}.size == {b}.size && {a}.indices.all {{ __i -> \
val __x = {a}[__i]; val __y = {b}[__i]; {inner} }}"
);
if ty.is_nullable() {
format!("(({a} == null && {b} == null) || ({a} != null && {b} != null && {cmp}))")
} else {
format!("({cmp})")
}
}
fn hash_expr(x: &str, ty: &KtType) -> String {
if !array_bearing(ty) {
return if ty.is_nullable() {
format!("({x}?.hashCode() ?: 0)")
} else {
format!("{x}.hashCode()")
};
}
match element_of(ty) {
None if ty.is_nullable() => format!("({x}?.contentHashCode() ?: 0)"),
None => format!("{x}.contentHashCode()"),
Some(elem) => {
let inner = hash_expr("__e", elem);
let fold = format!("{x}.fold(1) {{ __acc, __e -> 31 * __acc + {inner} }}");
if ty.is_nullable() {
format!("({x}?.let {{ __l -> {} }} ?: 0)", fold.replace(x, "__l"))
} else {
format!("({fold})")
}
}
}
}
fn str_expr(x: &str, ty: &KtType) -> String {
if !array_bearing(ty) {
return format!("${{{x}}}");
}
match element_of(ty) {
None if ty.is_nullable() => format!("${{{x}?.contentToString()}}"),
None => format!("${{{x}.contentToString()}}"),
Some(elem) => {
let inner = str_expr("__e", elem);
let join = format!("{x}.joinToString(\", \", \"[\", \"]\") {{ __e -> \"{inner}\" }}");
if ty.is_nullable() {
format!("${{{x}?.let {{ __l -> {} }}}}", join.replace(x, "__l"))
} else {
format!("${{{join}}}")
}
}
}
}
pub(crate) fn content_equality_members(
class_name: &str,
props: &[(String, KtType)],
) -> Option<Vec<KtFun>> {
if !props.iter().any(|(_, ty)| array_bearing(ty)) {
return None;
}
let comparisons: Vec<String> = props
.iter()
.map(|(name, ty)| eq_expr(name, &format!("other.{name}"), ty))
.collect();
let equals_body = KtCode::new()
.line("if (this === other) return true")
.line(format!("if (other !is {class_name}) return false"))
.line(format!("return {}", comparisons.join(" && ")));
let equals = KtFun::new("equals")
.modifier("override")
.param(KtParam::new("other", KtType::any().nullable()))
.returns(KtType::boolean())
.body(equals_body);
let first = hash_expr(&props[0].0, &props[0].1);
let hash_body = if props.len() == 1 {
KtCode::new().line(format!("return {first}"))
} else {
let mut b = KtCode::new().line(format!("var result = {first}"));
for (name, ty) in &props[1..] {
b = b.line(format!("result = 31 * result + {}", hash_expr(name, ty)));
}
b.line("return result")
};
let hash_code = KtFun::new("hashCode")
.modifier("override")
.returns(KtType::int())
.body(hash_body);
let rendered: Vec<String> = props
.iter()
.map(|(name, ty)| format!("{name}={}", str_expr(name, ty)))
.collect();
let to_string = KtFun::new("toString")
.modifier("override")
.returns(KtType::string())
.expr_body(KtCode::new().line(format!("\"{class_name}({})\"", rendered.join(", "))));
Some(vec![equals, hash_code, to_string])
}