pub(crate) use kotlin_codegen::{
is_valid_kotlin_ident, mangle_kotlin_ident, mangle_kotlin_package,
};
use kotlin_codegen::{KtFun, KtType};
use super::*;
pub(crate) fn validate_symbols(ext: &Declarations, registry: &Registry<KotlinMeta>) -> Vec<String> {
let mut errors: Vec<String> = Vec::new();
let mut top_level: BTreeMap<(String, String), String> = BTreeMap::new();
let mut add_top_level =
|package: &str, name: &str, origin: String, errors: &mut Vec<String>| {
if let Some(prev) =
top_level.insert((package.to_string(), name.to_string()), origin.clone())
{
errors.push(format!(
"duplicate top-level Kotlin name `{name}` in package `{package}`: \
declared by both {prev} and {origin}",
));
}
};
let mut overloads: BTreeMap<(String, String, JvmSignature), String> = BTreeMap::new();
let mut add_overload = |scope: &str, f: &KtFun, origin: &str, errors: &mut Vec<String>| {
let sig = jvm_signature(f);
let key = (scope.to_string(), f.name.clone(), sig.clone());
if let Some(prev) = overloads.insert(key, origin.to_string()) {
errors.push(format!(
"conflicting Kotlin overload `{}{sig}` in {scope}: {prev} and {origin} \
have the same erased JVM signature — rename one via `.name(...)` or \
change a parameter type",
f.name,
));
}
};
let mut class_keys: Vec<&TypeKey> = ext
.types
.iter()
.filter(|(_, cfg)| cfg.name_spec.is_some())
.map(|(k, _)| k)
.collect();
class_keys.sort_by_key(|k| k.as_str().to_string());
for key in class_keys {
let cfg = &ext.types[key];
let spec = cfg.name_spec.as_ref().expect("filtered to Some");
let fqn = ext.fqn_of(spec);
let (package, short) = fqn.rsplit_once('.').unwrap_or(("", fqn.as_str()));
let origin = format!("class `{key}`");
check_ident(short, &origin, &mut errors);
add_top_level(package, short, origin.clone(), &mut errors);
if cfg.interface_enabled {
let iface = ext.interface_short_name_unchecked(
package,
short,
cfg.interface_name_override.as_deref(),
);
let iorigin = format!("interface of class `{key}`");
check_ident(&iface, &iorigin, &mut errors);
add_top_level(package, &iface, iorigin, &mut errors);
}
if let Some(sum_cfg) = cfg.sum() {
let mut seen: BTreeMap<String, String> = BTreeMap::from([(
short.to_string(),
format!("sealed class `{key}` itself (its variants' supertype)"),
)]);
if let Some(alts) = key
.ident()
.and_then(|i| declared_member_names(registry, &i))
{
for v in alts {
let name = ext.sum_variant_class_name(sum_cfg, &v);
let vorigin = format!("variant `{v}` of sealed class `{key}`");
check_ident(&name, &vorigin, &mut errors);
if let Some(prev) = seen.insert(name.clone(), vorigin.clone()) {
errors.push(format!(
"Kotlin name `{name}` is taken twice inside sealed class `{key}`: \
by {prev} and by {vorigin} — rename either with \
`variant!(...).name(\"...\")` or `sealed_class!(...).name(\"...\")`",
));
}
}
}
}
}
let harness = ext.jni_native_class_name();
check_ident(&harness, "the `JNINative` harness object", &mut errors);
add_top_level(
&ext.package,
&harness,
"the `JNINative` harness object".to_string(),
&mut errors,
);
let mut subpackages: Vec<&String> = ext.packages.keys().collect();
subpackages.sort();
for sub in subpackages {
let pkg_cfg = &ext.packages[sub];
let package = ext.package_name(sub);
let fn_scope = format!("package `{package}`");
for entry in &pkg_cfg.functions {
let name = ext.effective_function_name(sub, entry);
let origin = format!("function `{}`", entry.rust_ident);
check_ident(&name, &origin, &mut errors);
if let Some(item_fn) = registry.flat().function(&entry.rust_ident) {
if let Some(s) = build_wrapper_surface(ext, item_fn, registry, Some(&name), None) {
for ov in render_param_overloads(ext, item_fn, registry, &s.fun) {
add_overload(&fn_scope, &ov, &origin, &mut errors);
}
add_overload(&fn_scope, &s.fun, &origin, &mut errors);
}
}
}
for entry in pkg_cfg
.constants
.iter()
.chain(pkg_cfg.constant_functions.iter())
{
let name = entry
.kotlin_name_override
.clone()
.unwrap_or_else(|| mangle_kotlin_ident(&entry.rust_ident.to_string()));
let origin = format!("const `{}`", entry.rust_ident);
check_ident(&name, &origin, &mut errors);
add_top_level(&package, &name, origin, &mut errors);
}
for decl in &pkg_cfg.constant_exprs {
let origin = format!("expression constant `{}`", decl.kotlin_name);
check_ident(&decl.kotlin_name, &origin, &mut errors);
add_top_level(&package, &decl.kotlin_name, origin, &mut errors);
}
}
let mut member_keys: Vec<&TypeKey> = ext.class_members.keys().collect();
member_keys.sort_by_key(|k| k.as_str().to_string());
for key in member_keys {
for m in &ext.class_members[key] {
let name = ext.effective_method_name(key, m);
check_ident(&name, &format!("method `{}`", m.rust_ident), &mut errors);
let Some(item_fn) = registry.flat().function(&m.rust_ident) else {
continue;
};
let (scope, receiver) = match m.kind {
MemberKind::Method => (format!("class `{key}` methods"), Some(key)),
MemberKind::Constructor => (format!("class `{key}` factories"), None),
};
let origin = format!("member `{}`", m.rust_ident);
if let Some(s) = build_wrapper_surface(ext, item_fn, registry, Some(&name), receiver) {
for ov in render_param_overloads(ext, item_fn, registry, &s.fun) {
add_overload(&scope, &ov, &origin, &mut errors);
}
add_overload(&scope, &s.fun, &origin, &mut errors);
}
}
}
warn_derived_name_changes(ext, registry);
errors
}
fn check_ident(name: &str, origin: &str, errors: &mut Vec<String>) {
if !is_valid_kotlin_ident(name) {
errors.push(format!(
"`{name}` ({origin}) is not a valid Kotlin identifier — fix the `.name(...)` \
override or the name mangle hook that produced it",
));
}
}
fn warn_derived_name_changes(ext: &Declarations, registry: &Registry<KotlinMeta>) {
let warn = |raw: &str, mangled: &str, what: &str, owner: &str| {
if raw != mangled {
println!(
"cargo:warning=prebindgen: {what} `{raw}` of `{owner}` emitted as `{mangled}` \
(invalid Kotlin identifier sanitized)"
);
}
};
let mut class_keys: Vec<&TypeKey> = ext
.types
.iter()
.filter(|(_, cfg)| cfg.name_spec.is_some())
.map(|(k, _)| k)
.collect();
class_keys.sort_by_key(|k| k.as_str().to_string());
for key in class_keys {
let Some(ident) = key.ident() else {
continue;
};
if let Some(s) = registry.flat().struct_type(&ident) {
for f in &s.fields {
if let Some(fname) = &f.name {
let camel = kt_snake_to_camel(&fname.to_string());
warn(
&camel,
&mangle_kotlin_ident(&camel),
"field",
&ident.to_string(),
);
}
}
}
if let Some(names) = declared_member_names(registry, &ident) {
for v in names {
let screaming = crate::util::camel_to_screaming_snake(&v.to_string());
warn(
&screaming,
&mangle_kotlin_ident(&screaming),
"enum variant",
&ident.to_string(),
);
}
}
}
}
fn declared_member_names(
registry: &impl prebindgen_registry::Conversions<KotlinMeta>,
ident: &syn::Ident,
) -> Option<Vec<syn::Ident>> {
use prebindgen_registry::flat::Type;
match registry.flat().declared_type(ident)? {
Type::Enum(e) => Some(e.values.iter().map(|v| v.name.clone()).collect()),
Type::Variant(v) => Some(v.alternatives.iter().map(|a| a.name.clone()).collect()),
_ => None,
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct ErasedJvmType(String);
impl ErasedJvmType {
pub(crate) fn raw(s: impl Into<String>) -> Self {
ErasedJvmType(s.into())
}
}
impl std::fmt::Display for ErasedJvmType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct JvmSignature(Vec<ErasedJvmType>);
impl std::fmt::Display for JvmSignature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
for (i, t) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{t}")?;
}
write!(f, ")")
}
}
fn boxed_primitive(simple: &str) -> Option<&'static str> {
Some(match simple {
"Int" => "java.lang.Integer",
"Long" => "java.lang.Long",
"Short" => "java.lang.Short",
"Byte" => "java.lang.Byte",
"Char" => "java.lang.Character",
"Boolean" => "java.lang.Boolean",
"Float" => "java.lang.Float",
"Double" => "java.lang.Double",
_ => return None,
})
}
pub(crate) fn erase_kt_type(generics: &[String], ty: &KtType) -> ErasedJvmType {
use kt::KtType;
let token = match ty {
KtType::Function { params, .. } => format!("kotlin.Function{}", params.len()),
KtType::Named { fqn, nullable, .. } => {
let simple = ty.simple_name().unwrap_or(fqn);
if generics.iter().any(|g| g == fqn) {
"java.lang.Object".to_string()
} else if simple == "ULong" {
if *nullable {
"kotlin.ULong".to_string()
} else {
"Long".to_string()
}
} else if let Some(boxed) = boxed_primitive(simple) {
if *nullable {
boxed.to_string()
} else {
simple.to_string()
}
} else {
match simple {
"String" => "java.lang.String".to_string(),
"ByteArray" => "byte[]".to_string(),
"Any" => "java.lang.Object".to_string(),
"Unit" => "void".to_string(),
_ => fqn.clone(),
}
}
}
};
ErasedJvmType(token)
}
pub(crate) fn jvm_signature(f: &KtFun) -> JvmSignature {
JvmSignature(
f.params
.iter()
.map(|p| erase_kt_type(&f.generics, &p.ty))
.collect(),
)
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub(crate) struct NativeSymbol(String);
impl NativeSymbol {
pub fn new(sym: impl Into<String>) -> Self {
NativeSymbol(sym.into())
}
}
#[cfg(test)]
mod tests {
use kotlin_codegen as kt;
use super::erase_kt_type;
fn erase(generics: &[&str], ty: kt::KtType) -> String {
let gs: Vec<String> = generics.iter().map(|s| s.to_string()).collect();
erase_kt_type(&gs, &ty).to_string()
}
#[test]
fn jvm_erasure_rules() {
assert_eq!(erase(&[], kt::KtType::int()), "Int");
assert_eq!(
erase(&[], kt::KtType::int().nullable()),
"java.lang.Integer"
);
assert_ne!(
erase(&[], kt::KtType::int()),
erase(&[], kt::KtType::int().nullable()),
"Int and Int? must NOT clash"
);
assert_eq!(erase(&[], kt::KtType::cls("ULong")), "Long");
assert_eq!(
erase(&[], kt::KtType::cls("ULong").nullable()),
"kotlin.ULong"
);
assert_eq!(
erase(&[], kt::KtType::cls("ULong")),
erase(&[], kt::KtType::long()),
"ULong and Long share the same JVM carrier"
);
assert_eq!(erase(&[], kt::KtType::string()), "java.lang.String");
assert_eq!(
erase(&[], kt::KtType::string().nullable()),
"java.lang.String",
"String and String? share one descriptor"
);
assert_eq!(erase(&[], kt::KtType::byte_array()), "byte[]");
assert_eq!(erase(&[], kt::KtType::any()), "java.lang.Object");
assert_eq!(
erase(&[], kt::KtType::generic("List", [kt::KtType::int()])),
erase(&[], kt::KtType::generic("List", [kt::KtType::string()])),
);
assert_eq!(erase(&["A"], kt::KtType::var_("A")), "java.lang.Object");
assert_eq!(erase(&["R"], kt::KtType::var_r()), "java.lang.Object");
assert_eq!(erase(&[], kt::KtType::cls("io.test.Foo")), "io.test.Foo");
assert_ne!(
erase(&[], kt::KtType::cls("io.test.Foo")),
erase(&[], kt::KtType::cls("io.other.Foo")),
"distinct FQNs stay distinct"
);
}
}