use kotlin_codegen::{KtCode, KtDecl, KtFun, KtFunInterface, KtFunSig, KtParam, KtType, KtVis};
use prebindgen_registry::{
unfold::{dedup_names, DeconId, LeafSource, UnfoldPlan},
Conversions,
};
use super::*;
pub(crate) const IFACE_METHOD: &str = "run";
pub(crate) const SINGLETON_FIELD: &str = "instance";
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum WrapKind {
None,
Handle(String),
HandleOwned(String),
Unsigned64 { niche_sentinel: Option<String> },
}
impl WrapKind {
pub fn class_fqn(&self) -> Option<&str> {
match self {
WrapKind::None | WrapKind::Unsigned64 { .. } => None,
WrapKind::Handle(f) | WrapKind::HandleOwned(f) => Some(f),
}
}
pub fn is_owned_handle(&self) -> bool {
matches!(self, WrapKind::HandleOwned(_))
}
pub fn wrap_expr(&self, arg: &str, raw_nullable: bool) -> String {
if let WrapKind::Unsigned64 { niche_sentinel } = self {
return match (raw_nullable, niche_sentinel) {
(true, Some(sentinel)) => {
format!("{arg}?.let {{ if (it == {sentinel}) null else it.toULong() }}")
}
(true, None) => format!("{arg}?.toULong()"),
(false, Some(sentinel)) => {
format!("if ({arg} == {sentinel}) null else {arg}.toULong()")
}
(false, None) => format!("{arg}.toULong()"),
};
}
match self.class_fqn() {
None => arg.to_string(),
Some(fqn) => {
let short = fqn.rsplit('.').next().unwrap_or(fqn);
if raw_nullable {
format!("{arg}?.let {{ {short}(it) }}")
} else {
format!("{short}({arg})")
}
}
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct IfaceParam {
pub name: String,
pub typed: KtType,
pub raw: KtType,
pub wrap: WrapKind,
}
impl IfaceParam {
fn same(name: String, ty: KtType) -> Self {
Self {
name,
typed: ty.clone(),
raw: ty,
wrap: WrapKind::None,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct TypedGroup {
pub name: String,
pub typed: KtType,
pub reassemble: Option<String>,
pub imports: Vec<String>,
pub leaf_count: usize,
pub close: Option<FoldStrategy>,
}
struct Bind {
line: String,
close: Option<String>,
}
fn nest_binds(mut code: KtCode, binds: &[Bind], call_args: &str) -> KtCode {
let Some((first, rest)) = binds.split_first() else {
return code.wline(format!("run({call_args})"));
};
code = code.line(first.line.clone());
match &first.close {
Some(close) => code.try_finally(
"",
nest_binds(KtCode::new(), rest, call_args),
KtCode::new().line(close.clone()),
),
None => nest_binds(code, rest, call_args),
}
}
fn nullable_close(strategy: FoldStrategy, nullable: bool) -> FoldStrategy {
match strategy {
FoldStrategy::Optional(..) => strategy,
inner if nullable => FoldStrategy::Optional(NullableKind::Boxed, Box::new(inner)),
inner => inner,
}
}
fn fill_placeholders(expr: &str, names: &[&str]) -> String {
let mut out = String::with_capacity(expr.len());
let mut rest = expr;
while let Some(pos) = rest.find('$') {
out.push_str(&rest[..pos]);
let digits: String = rest[pos + 1..]
.chars()
.take_while(char::is_ascii_digit)
.collect();
if digits.is_empty() {
out.push('$');
rest = &rest[pos + 1..];
continue;
}
let idx: usize = digits.parse().expect("digit run parses");
out.push_str(names.get(idx).copied().unwrap_or_else(|| {
panic!(
"reassembly placeholder ${idx} has no leaf in a {}-leaf group",
names.len()
)
}));
rest = &rest[pos + 1 + digits.len()..];
}
out.push_str(rest);
out
}
#[derive(Clone, Debug)]
pub(crate) struct IfaceSpec {
pub package: String,
pub name: String,
pub type_params: Vec<String>,
pub params: Vec<IfaceParam>,
pub ret: KtType,
pub descr: String,
pub typed_groups: Vec<TypedGroup>,
pub kdoc: Option<String>,
}
impl IfaceSpec {
fn assemble(
package: String,
name: String,
type_params: Vec<String>,
params: Vec<IfaceParam>,
ret: KtType,
) -> Self {
let descr = method_descr(¶ms, &ret, &type_params);
IfaceSpec {
package,
name,
type_params,
params,
ret,
descr,
kdoc: None,
typed_groups: Vec::new(),
}
}
pub fn fqn(&self) -> String {
if self.package.is_empty() {
self.name.clone()
} else {
format!("{}.{}", self.package, self.name)
}
}
pub fn kt_ref(&self, args: Vec<KtType>) -> KtType {
if args.is_empty() {
KtType::cls(self.fqn())
} else {
KtType::generic(self.fqn(), args)
}
}
pub fn needs_raw(&self) -> bool {
!self.typed_groups.is_empty() || self.params.iter().any(|p| p.wrap != WrapKind::None)
}
pub fn raw_name(&self) -> String {
if self.needs_raw() {
format!("{}Raw", self.name)
} else {
self.name.clone()
}
}
pub fn raw_fqn(&self) -> String {
if self.package.is_empty() {
self.raw_name()
} else {
format!("{}.{}", self.package, self.raw_name())
}
}
pub fn raw_slash_fqn(&self) -> String {
self.raw_fqn().replace('.', "/")
}
pub fn singleton_holder_name(&self) -> String {
format!("__{}Holder", self.raw_name())
}
pub fn singleton_holder_fqn(&self) -> String {
if self.package.is_empty() {
self.singleton_holder_name()
} else {
format!("{}.{}", self.package, self.singleton_holder_name())
}
}
pub fn singleton_holder_slash_fqn(&self) -> String {
self.singleton_holder_fqn().replace('.', "/")
}
pub fn capture_name(&self) -> String {
format!("{}Capture", self.raw_name())
}
pub fn capture_fqn(&self) -> String {
if self.package.is_empty() {
self.capture_name()
} else {
format!("{}.{}", self.package, self.capture_name())
}
}
pub fn to_capture_decl(&self) -> KtDecl {
let cap = self.capture_name();
let raw = self.raw_name();
let n_ze = self.params.len();
let mut fields = KtCode::new().line("@JvmField var failed: Boolean = false");
for (i, p) in self.params.iter().enumerate() {
let ty = p.raw.clone().nullable();
fields = fields.line(format!("@JvmField var ze{i}: {ty} = null"));
}
let run_params = self
.params
.iter()
.map(|p| format!("{}: {}", p.name, p.raw))
.collect::<Vec<_>>()
.join(", ");
let mut run_body = String::from("failed = true");
for (i, p) in self.params.iter().enumerate() {
run_body.push_str(&format!("; this.ze{i} = {}", p.name));
}
let mut reset = String::from("c.failed = false");
for i in 0..n_ze {
reset.push_str(&format!("; c.ze{i} = null"));
}
let code = KtCode::new().blk(format!("internal class {cap} : {raw}<Unit> {{"), |c| {
c.push(fields)
.wline(format!("override fun run({run_params}) {{ {run_body} }}"))
.blk("companion object {", |comp| {
comp.line(format!(
"private val TL: ThreadLocal<{cap}> = ThreadLocal.withInitial {{ {cap}() }}"
))
.blk(format!("@JvmStatic fun acquire(): {cap} {{"), |acq| {
acq.line("val c = TL.get()").wline(reset).line("return c")
})
})
});
KtDecl::Raw { name: cap, code }
}
pub fn to_decl(&self) -> KtFunInterface {
let mut m = KtFunSig::new(IFACE_METHOD).vis(KtVis::Public);
if self.typed_groups.is_empty() {
for p in &self.params {
m = m.param(KtParam::new(&p.name, p.typed.clone()));
}
} else {
for g in &self.typed_groups {
m = m.param(KtParam::new(&g.name, g.typed.clone()));
}
}
m = m.returns(self.ret.clone());
let mut i = KtFunInterface::new(&self.name, m).vis(KtVis::Public);
if let Some(doc) = &self.kdoc {
i = i.kdoc(doc.clone());
}
for tp in &self.type_params {
i = i.type_param(tp);
}
i
}
pub fn to_raw_decl(&self) -> KtFunInterface {
let mut m = KtFunSig::new(IFACE_METHOD).vis(KtVis::Public);
for p in &self.params {
m = m.param(KtParam::new(&p.name, p.raw.clone()));
}
m = m.returns(self.ret.clone());
let mut i = KtFunInterface::new(self.raw_name(), m).vis(KtVis::Public);
for tp in &self.type_params {
i = i.type_param(tp);
}
i
}
pub fn to_as_raw_fun(&self) -> KtFun {
let bare_generics: Vec<String> = self
.type_params
.iter()
.map(|tp| tp.strip_prefix("out ").unwrap_or(tp).trim().to_string())
.collect();
let gen_args = if bare_generics.is_empty() {
String::new()
} else {
format!("<{}>", bare_generics.join(", "))
};
let type_args: Vec<KtType> = bare_generics
.iter()
.map(|g| KtType::var_(g.as_str()))
.collect();
let recv = KtType::generic(&self.name, type_args.clone());
struct RunArg {
expr: String,
close: Option<FoldStrategy>,
nullable: bool,
reassembled: bool,
}
let run_args: Vec<RunArg> = if self.typed_groups.is_empty() {
self.params
.iter()
.map(|p| RunArg {
expr: p.wrap.wrap_expr(&p.name, p.raw.is_nullable()),
close: p.wrap.is_owned_handle().then_some(FoldStrategy::Base),
nullable: p.raw.is_nullable(),
reassembled: false,
})
.collect()
} else {
let mut args = Vec::with_capacity(self.typed_groups.len());
let mut at = 0usize;
for g in &self.typed_groups {
let names: Vec<&str> = self.params[at..at + g.leaf_count]
.iter()
.map(|p| p.name.as_str())
.collect();
match &g.reassemble {
Some(expr) => args.push(RunArg {
expr: fill_placeholders(expr, &names),
close: g.close.clone(),
nullable: g.typed.is_nullable(),
reassembled: true,
}),
None => {
let p = &self.params[at];
args.push(RunArg {
expr: p.wrap.wrap_expr(&p.name, p.raw.is_nullable()),
close: p.wrap.is_owned_handle().then_some(FoldStrategy::Base),
nullable: p.raw.is_nullable(),
reassembled: false,
});
}
}
at += g.leaf_count;
}
args
};
let any_owned = run_args.iter().any(|a| a.close.is_some());
let lambda_open = format!("{}{gen_args} {{", self.raw_name());
let body = if self.params.is_empty() {
KtCode::new().blk(lambda_open, |c| c.line("run()"))
} else if !any_owned {
let wrapped: Vec<&str> = run_args.iter().map(|a| a.expr.as_str()).collect();
KtCode::new().blk(lambda_open, |mut c| {
for (idx, name) in self.params.iter().map(|p| p.name.as_str()).enumerate() {
let suffix = if idx + 1 == self.params.len() {
" ->"
} else {
","
};
c = c.line(format!("{name}{suffix}"));
}
c.blk_with("run(", ")", |mut call| {
for (idx, expr) in wrapped.iter().enumerate() {
let suffix = if idx + 1 == wrapped.len() { "" } else { "," };
call = call.line(format!("{expr}{suffix}"));
}
call
})
})
} else {
let mut binds: Vec<Bind> = Vec::new();
let mut call_args: Vec<String> = Vec::with_capacity(run_args.len());
let mut owned_idx = 0usize;
let mut val_idx = 0usize;
for a in &run_args {
if let Some(strategy) = &a.close {
let local = format!("__own{owned_idx}");
owned_idx += 1;
call_args.push(local.clone());
binds.push(Bind {
line: format!("val {local} = {}", a.expr),
close: Some(render_handle_close(
&nullable_close(strategy.clone(), a.nullable),
&local,
)),
});
} else if a.reassembled {
let local = format!("__val{val_idx}");
val_idx += 1;
call_args.push(local.clone());
binds.push(Bind {
line: format!("val {local} = {}", a.expr),
close: None,
});
} else {
call_args.push(a.expr.clone());
}
}
KtCode::new().blk(lambda_open, |mut c| {
for (idx, name) in self.params.iter().map(|p| p.name.as_str()).enumerate() {
let suffix = if idx + 1 == self.params.len() {
" ->"
} else {
","
};
c = c.line(format!("{name}{suffix}"));
}
nest_binds(c, &binds, &call_args.join(", "))
})
};
let mut f = KtFun::new("asRaw").vis(KtVis::Public).receiver(recv);
for g in &bare_generics {
f = f.generic(g);
}
f = f.returns(KtType::generic(self.raw_name(), type_args));
f.expr_body(body)
}
}
#[cfg(test)]
mod tests;
fn kt_jvm_descriptor(ty: &KtType, type_params: &[String]) -> String {
let KtType::Named {
fqn,
args,
nullable,
} = ty
else {
panic!("kt_jvm_descriptor: function types cannot appear in a typed callback interface");
};
let simple = fqn.rsplit('.').next().unwrap_or(fqn);
if type_params
.iter()
.map(|p| p.strip_prefix("out ").unwrap_or(p).trim())
.any(|p| p == fqn)
{
return "Ljava/lang/Object;".to_string();
}
if !fqn.contains('.') {
if let Some(p) = JniPrim::from_kotlin_name(simple) {
return if *nullable {
p.box_descriptor().to_string()
} else {
p.descriptor().to_string()
};
}
if let Some(d) = crate::jni::wire_access::kotlin_array_descriptor(simple) {
return d.to_string();
}
return match simple {
"Unit" => "V".to_string(),
"String" => "Ljava/lang/String;".to_string(),
"List" | "MutableList" => "Ljava/util/List;".to_string(),
"Any" => "Ljava/lang/Object;".to_string(),
other => format!("L{other};"),
};
}
let _ = args;
format!("L{};", fqn.replace('.', "/"))
}
fn method_descr(params: &[IfaceParam], ret: &KtType, type_params: &[String]) -> String {
let mut d = String::from("(");
for p in params {
d.push_str(&kt_jvm_descriptor(&p.raw, type_params));
}
d.push(')');
d.push_str(&kt_jvm_descriptor(ret, type_params));
d
}
fn decon_base_name(short: &str, decon: Option<&DeconId>) -> String {
let upper_camel = |s: &str| -> String {
let camel = snake_to_camel(s);
let mut c = camel.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => camel,
}
};
match decon {
None | Some(DeconId::Default(_)) => short.to_string(),
Some(DeconId::PerFn(_, f)) => format!("{short}{}", upper_camel(f)),
}
}
fn subject_short(ty: &prebindgen_registry::flat::TypeRef) -> String {
use crate::util::{head_name, head_type};
let no_ref = ty.borrow_target().unwrap_or(ty);
if let prebindgen_registry::flat::TypeKind::Slice(elem) = no_ref.kind() {
return format!("{}List", subject_short(elem));
}
let peeled = head_type(ty);
head_name(peeled).unwrap_or_else(|| peeled.key().to_string().replace([' ', ':', '<', '>'], ""))
}
fn subject_package(ext: &Declarations, subject: &prebindgen_registry::flat::TypeRef) -> String {
let key = crate::util::head_type(subject).key();
ext.kotlin_fqn(&key)
.and_then(|fqn| fqn.rsplit_once('.').map(|(p, _)| p.to_string()))
.unwrap_or_else(|| ext.package.clone())
}
fn plan_leaf_params(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
leaves: &[prebindgen_registry::unfold::UnfoldLeaf],
) -> Option<Vec<IfaceParam>> {
let names = plan_leaf_names(leaves);
let mut out = Vec::with_capacity(leaves.len());
for (name, leaf) in names.into_iter().zip(leaves.iter()) {
out.push(plan_leaf_param(ext, registry, name, leaf)?);
}
Some(out)
}
fn plan_leaf_param(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
name: String,
leaf: &prebindgen_registry::unfold::UnfoldLeaf,
) -> Option<IfaceParam> {
use prebindgen_registry::unfold::LeafSource;
if leaf.source == LeafSource::SumTag {
let ty = KtType::int();
let ty = if leaf.nullable { ty.nullable() } else { ty };
return Some(IfaceParam::same(name, ty));
}
let inert_nullable = leaf.group.is_some() && !leaf_ty_is_prim(registry, &leaf.out_ty);
leaf_iface_param(
ext,
registry,
name,
&leaf.out_ty,
leaf.nullable || inert_nullable,
true,
)
}
fn leaf_iface_param(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
name: String,
out_ty: &prebindgen_registry::flat::TypeRef,
nullable: bool,
raw_handle: bool,
) -> Option<IfaceParam> {
let mut out_ty = out_ty;
if registry.output_entry(out_ty).is_none() {
if let prebindgen_registry::flat::TypeKind::Ref { inner, .. } = out_ty.kind() {
out_ty = inner;
}
}
let (builder_kt, _wire_kt, _wrap, is_value_projection) =
unfold_leaf_kt(ext, registry, out_ty, nullable, "x")?;
let proj = registry
.output_entry(out_ty)
.and_then(|e| e.metadata.projection.as_ref());
let nullable_kt = |t: KtType| {
if builder_kt.is_nullable() {
t.nullable()
} else {
t
}
};
if is_value_projection {
match proj?.kind {
ProjectionKind::Unsigned64 => {
let mut raw = projection_wire_return(proj?);
if nullable && !raw.is_nullable() {
raw = raw.nullable();
}
let niche_sentinel = if builder_kt.is_nullable() {
wrap_sentinel(proj?, nullable)
} else {
None
};
return Some(IfaceParam {
name,
typed: builder_kt.clone(),
raw,
wrap: WrapKind::Unsigned64 { niche_sentinel },
});
}
ProjectionKind::Handle => {}
}
}
if let Some(p) = proj.filter(|p| p.kind == ProjectionKind::Handle) {
let fqn = ext.kotlin_fqn(&p.leaf_key)?.to_string();
if raw_handle {
return Some(IfaceParam {
name,
typed: nullable_kt(KtType::cls(fqn.clone())),
raw: nullable_kt(KtType::long()),
wrap: WrapKind::Handle(fqn),
});
}
return Some(IfaceParam::same(name, nullable_kt(KtType::cls(fqn))));
}
if let KtType::Named { fqn: bk_fqn, .. } = &builder_kt {
if !bk_fqn.contains('.') {
if let Some(reg_fqn) = ext.kotlin_fqn(&out_ty.key()) {
let reg_short = reg_fqn.rsplit('.').next().unwrap_or(®_fqn);
if reg_fqn.contains('.') && reg_short == bk_fqn {
let raw = KtType::cls(reg_fqn.to_string());
let raw = if builder_kt.is_nullable() {
raw.nullable()
} else {
raw
};
return Some(IfaceParam {
name,
typed: builder_kt.clone(),
raw,
wrap: WrapKind::None,
});
}
}
}
}
Some(IfaceParam::same(name, builder_kt))
}
pub(crate) fn owned_handle_iface_param(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
name: String,
out_ty: &prebindgen_registry::flat::TypeRef,
nullable: bool,
) -> Option<IfaceParam> {
let proj = registry.output_entry(out_ty)?.metadata.projection.clone()?;
let fqn = ext.kotlin_fqn(&proj.leaf_key)?.to_string();
let typed = KtType::cls(fqn.clone());
let (typed, raw) = if nullable {
(typed.nullable(), KtType::long().nullable())
} else {
(typed, KtType::long())
};
Some(IfaceParam {
name,
typed,
raw,
wrap: WrapKind::HandleOwned(fqn),
})
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum SpecKey {
Callback(Vec<TypeKey>),
Builder(DeconId),
Folder(DeconId),
WholeFolder(TypeKey),
Handler(DeconId),
JniErrorHandler,
}
impl SpecKey {
pub fn callback(args: &[prebindgen_registry::flat::TypeRef]) -> Self {
SpecKey::Callback(args.iter().map(|a| a.key()).collect())
}
pub fn whole_folder(element: &prebindgen_registry::flat::TypeRef) -> Self {
SpecKey::WholeFolder(element.key())
}
}
pub(crate) fn fixed_decon_ids(
registry: &impl Conversions<KotlinMeta>,
) -> std::collections::HashSet<DeconId> {
let fixed: std::collections::HashSet<DeconId> = registry
.unfold_plans()
.values()
.chain(registry.callback_arg_plans().values())
.filter(|p| p.fixed_builder)
.filter_map(|p| p.decon.clone())
.collect();
debug_assert!(
!registry
.unfold_plans()
.values()
.chain(registry.callback_arg_plans().values())
.any(|p| !p.fixed_builder && p.decon.as_ref().is_some_and(|d| fixed.contains(d))),
"fixed and non-fixed plans share one DeconId — the typed interface \
cannot be shaped (or suppressed) for both"
);
fixed
}
pub(crate) fn fixed_leaf_element_keys(
registry: &Registry<KotlinMeta>,
) -> std::collections::HashSet<TypeKey> {
registry
.unfold_plans()
.values()
.chain(registry.callback_arg_plans().values())
.filter(|p| p.fixed_builder)
.filter_map(|p| p.element.as_ref())
.map(|el| el.key())
.collect()
}
fn derive_iface_spec(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
key: &SpecKey,
) -> Option<IfaceSpec> {
match key {
SpecKey::Callback(arg_keys) => {
let args: Vec<prebindgen_registry::flat::TypeRef> = arg_keys
.iter()
.map(|k| registry.reading(k))
.collect::<Option<_>>()?;
callback_iface_spec(ext, registry, &args)
}
SpecKey::Builder(d) => builder_iface_spec(ext, registry, d),
SpecKey::Folder(d) => {
let mut spec = folder_iface_spec(ext, registry, d)?;
if fixed_decon_ids(registry).contains(d) {
spec.typed_groups = fixed_folder_typed_groups(ext, registry, d)?;
}
Some(spec)
}
SpecKey::WholeFolder(el_key) => {
whole_folder_iface_spec(ext, registry, ®istry.reading(el_key)?)
}
SpecKey::Handler(d) => error_handler_iface_spec(ext, registry, d),
SpecKey::JniErrorHandler => Some(jni_error_handler_iface_spec(ext)),
}
}
impl Declarations {
pub(crate) fn iface_spec(
&self,
registry: &impl Conversions<KotlinMeta>,
key: &SpecKey,
) -> Option<std::sync::Arc<IfaceSpec>> {
let hit = self.iface_specs.borrow().get(key).cloned();
if let Some(hit) = hit {
#[cfg(debug_assertions)]
{
let fresh = derive_iface_spec(self, registry, key);
debug_assert_eq!(
fresh.as_ref().map(|s| format!("{s:?}")),
Some(format!("{:?}", *hit)),
"IfaceSpec derivation drifted for {key:?}"
);
}
return Some(hit);
}
let spec = std::sync::Arc::new(derive_iface_spec(self, registry, key)?);
self.iface_specs
.borrow_mut()
.insert(key.clone(), spec.clone());
Some(spec)
}
}
fn fixed_reassembly(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
source: &TypeKey,
leaves: &[prebindgen_registry::unfold::UnfoldLeaf],
class_fqn: &str,
) -> (String, Vec<String>) {
let slots: Vec<String> = (0..leaves.len()).map(|i| format!("${i}")).collect();
if !is_sum_leaves(leaves) {
let class_short = class_fqn.rsplit('.').next().unwrap_or(class_fqn);
return (
format!("{class_short}.fromParts({})", slots.join(", ")),
Vec::new(),
);
}
let params = plan_leaf_params(ext, registry, leaves).unwrap_or_default();
let mut imports: BTreeSet<String> = BTreeSet::new();
let (_, when) = ext.sum_reconstruct(registry, source, leaves, ¶ms, &slots, &mut imports);
(when, imports.into_iter().collect())
}
pub(crate) fn callback_iface_spec(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
cb_args: &[prebindgen_registry::flat::TypeRef],
) -> Option<IfaceSpec> {
struct GroupDesc {
name: String,
typed: Option<KtType>,
reassemble: Option<String>,
imports: Vec<String>,
leaf_count: usize,
close: Option<FoldStrategy>,
}
enum LeafDesc {
Plan(String, prebindgen_registry::unfold::UnfoldLeaf),
Whole {
name: String,
ty: prebindgen_registry::flat::TypeRef,
nullable: bool,
owned_handle: bool,
},
}
impl LeafDesc {
fn name(&self) -> &str {
match self {
LeafDesc::Plan(n, _) => n,
LeafDesc::Whole { name, .. } => name,
}
}
}
let mut leaf_tys: Vec<LeafDesc> = Vec::new();
let mut groups: Vec<GroupDesc> = Vec::new();
let mut any_fixed = false;
for (i, t) in cb_args.iter().enumerate() {
let plan = registry
.callback_arg_plans()
.get(&t.key())
.filter(|p| !super::render::is_iterable_fold(&p.shape));
if let Some(plan) = plan {
let leaf_names = plan_leaf_names(&plan.leaves);
for (n, l) in leaf_names.iter().zip(plan.leaves.iter()) {
leaf_tys.push(LeafDesc::Plan(n.clone(), l.clone()));
}
if plan.fixed_builder {
any_fixed = true;
let core = t.borrow_target().unwrap_or(t);
let fqn = ext.kotlin_fqn(&core.key())?;
let (reassemble, imports) =
fixed_reassembly(ext, registry, &core.key(), &plan.leaves, &fqn);
groups.push(GroupDesc {
name: whole_value_name(t, i),
typed: Some(KtType::cls(fqn.to_string())),
reassemble: Some(reassemble),
imports,
leaf_count: plan.leaves.len(),
close: crate::jni::struct_plan::type_close_strategy(ext, registry, core, 0),
});
} else {
let mut k = 0usize;
while k < plan.leaves.len() {
let leaf = &plan.leaves[k];
let seg = if leaf.source == LeafSource::SumTag {
(k + 1..plan.leaves.len())
.take_while(|&j| plan.leaves[j].group.is_some())
.last()
.map_or(k + 1, |j| j + 1)
} else {
k + 1
};
if leaf.source == LeafSource::SumTag {
any_fixed = true;
let fqn = ext.kotlin_fqn(&leaf.out_ty.key())?;
let (reassemble, imports) = fixed_reassembly(
ext,
registry,
&leaf.out_ty.key(),
&plan.leaves[k..seg],
&fqn,
);
groups.push(GroupDesc {
name: leaf_names[k]
.strip_suffix(&format!("__{SUM_TAG_LEAF}"))
.unwrap_or(&leaf_names[k])
.to_string(),
typed: Some({
let t = KtType::cls(fqn.to_string());
if leaf.nullable {
t.nullable()
} else {
t
}
}),
reassemble: Some(reassemble),
imports,
leaf_count: seg - k,
close: crate::jni::struct_plan::type_close_strategy(
ext,
registry,
&leaf.out_ty,
0,
),
});
} else {
groups.push(GroupDesc {
name: leaf_names[k].clone(),
typed: None,
reassemble: None,
imports: Vec::new(),
leaf_count: 1,
close: None,
});
}
k = seg;
}
}
} else {
let owned_handle = registry
.output_entry(t)
.and_then(|e| e.metadata.projection.as_ref())
.map(|p| p.kind == ProjectionKind::Handle)
.unwrap_or(false);
leaf_tys.push(LeafDesc::Whole {
name: whole_value_name(t, i),
ty: (*t).clone(),
nullable: t.optional_inner().is_some(),
owned_handle,
});
groups.push(GroupDesc {
name: whole_value_name(t, i),
typed: None,
reassemble: None,
imports: Vec::new(),
leaf_count: 1,
close: None,
});
}
}
let mut names: Vec<String> = leaf_tys.iter().map(|d| d.name().to_string()).collect();
dedup_names(&mut names);
let mut params = Vec::with_capacity(leaf_tys.len());
for (k, desc) in leaf_tys.iter().enumerate() {
let name = names[k].clone();
let param = match desc {
LeafDesc::Plan(_, leaf) => plan_leaf_param(ext, registry, name, leaf)?,
LeafDesc::Whole {
ty,
nullable,
owned_handle: true,
..
} => owned_handle_iface_param(ext, registry, name, ty, *nullable)?,
LeafDesc::Whole { ty, nullable, .. } => {
leaf_iface_param(ext, registry, name, ty, *nullable, false)?
}
};
params.push(param);
}
let typed_groups = if any_fixed {
let mut tg = Vec::with_capacity(groups.len());
let mut at = 0usize;
let mut group_names: Vec<String> = groups.iter().map(|g| g.name.clone()).collect();
dedup_names(&mut group_names);
for (gi, g) in groups.iter().enumerate() {
let typed = g.typed.clone().unwrap_or_else(|| params[at].typed.clone());
tg.push(TypedGroup {
name: group_names[gi].clone(),
typed,
reassemble: g.reassemble.clone(),
imports: g.imports.clone(),
leaf_count: g.leaf_count,
close: g.close.clone(),
});
at += g.leaf_count;
}
tg
} else {
Vec::new()
};
let name = if cb_args.is_empty() {
"VoidCallback".to_string()
} else {
format!(
"{}Callback",
cb_args
.iter()
.map(subject_short)
.collect::<Vec<_>>()
.join("")
)
};
let package = cb_args
.first()
.map(|t| subject_package(ext, t))
.unwrap_or_else(|| ext.package.clone());
Some(IfaceSpec {
typed_groups,
..IfaceSpec::assemble(package, name, vec![], params, KtType::unit())
})
}
pub(crate) fn builder_iface_spec(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
decon: &DeconId,
) -> Option<IfaceSpec> {
let spec = registry.decon_plans().get(decon)?;
let params = plan_leaf_params(ext, registry, &spec.leaves)?;
let name = format!(
"{}Builder",
decon_base_name(&subject_short(&spec.source), Some(decon))
);
let package = subject_package(ext, &spec.source);
Some(IfaceSpec::assemble(
package,
name,
vec!["out R".to_string()],
params,
KtType::var_r(),
))
}
pub(crate) fn folder_iface_spec(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
decon: &DeconId,
) -> Option<IfaceSpec> {
let spec = registry.decon_plans().get(decon)?;
let mut params: Vec<IfaceParam> = vec![IfaceParam::same("acc".to_string(), KtType::var_("A"))];
params.extend(plan_leaf_params(ext, registry, &spec.leaves)?);
let name = format!(
"{}Folder",
decon_base_name(&subject_short(&spec.source), Some(decon))
);
let package = subject_package(ext, &spec.source);
Some(IfaceSpec::assemble(
package,
name,
vec!["A".to_string()],
params,
KtType::var_("A"),
))
}
pub(crate) fn whole_folder_iface_spec(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
element: &prebindgen_registry::flat::TypeRef,
) -> Option<IfaceSpec> {
let mut params: Vec<IfaceParam> = vec![IfaceParam::same("acc".to_string(), KtType::var_("A"))];
params.push(leaf_iface_param(
ext,
registry,
"element".to_string(),
element,
false,
true,
)?);
let name = format!("{}Folder", subject_short(element));
let package = subject_package(ext, element);
Some(IfaceSpec::assemble(
package,
name,
vec!["A".to_string()],
params,
KtType::var_("A"),
))
}
pub(crate) fn folder_iface_for_plan(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
plan: &UnfoldPlan,
) -> Option<std::sync::Arc<IfaceSpec>> {
debug_assert!(
plan.shape.has_iterable_layer(),
"folder_iface_for_plan requires an Iterable (or Option<Iterable>) plan"
);
match (&plan.element, &plan.decon) {
(Some(el), _) => ext.iface_spec(registry, &SpecKey::whole_folder(el)),
(None, Some(d)) => ext.iface_spec(registry, &SpecKey::Folder(d.clone())),
(None, None) => None,
}
}
pub(crate) fn fixed_folder_typed_groups(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
decon: &DeconId,
) -> Option<Vec<TypedGroup>> {
let spec = registry.decon_plans().get(decon)?;
let fqn = ext.kotlin_fqn(&spec.source.key())?;
let (reassemble, imports) =
fixed_reassembly(ext, registry, &spec.source.key(), &spec.leaves, &fqn);
Some(vec![
TypedGroup {
name: "acc".to_string(),
typed: KtType::var_("A"),
reassemble: None,
imports: Vec::new(),
leaf_count: 1,
close: None,
},
TypedGroup {
name: "element".to_string(),
typed: KtType::cls(fqn.to_string()),
reassemble: Some(reassemble),
imports,
leaf_count: spec.leaves.len(),
close: crate::jni::struct_plan::type_close_strategy(ext, registry, &spec.source, 0),
},
])
}
pub(crate) fn error_handler_iface_spec(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
decon: &DeconId,
) -> Option<IfaceSpec> {
let spec = registry.decon_plans().get(decon)?;
let params: Vec<IfaceParam> = plan_leaf_params(ext, registry, &spec.leaves)?;
let name = format!(
"{}Handler",
decon_base_name(&subject_short(&spec.source), Some(decon))
);
let package = subject_package(ext, &spec.source);
let source_short = subject_short(&spec.source);
let mut iface = IfaceSpec::assemble(
package,
name,
vec!["out R".to_string()],
params,
KtType::var_r(),
);
iface.kdoc = Some(format!(
"Domain-error callback: called only when the native function returns `Err` — the\n\
parameters carry the decomposed `{source_short}`. Binding/system failures go to the\n\
separate `onBindingError` (`JniErrorHandler`) channel instead, so there is no `je`\n\
discriminator here. The wrapper returns whatever `run` returns;\n\
throwing from `run` is safe (it executes after the native call has returned)."
));
Some(iface)
}
pub(crate) fn jni_error_handler_iface_spec(ext: &Declarations) -> IfaceSpec {
let params = vec![IfaceParam::same(
"je".to_string(),
KtType::string().nullable(),
)];
let mut iface = IfaceSpec::assemble(
ext.package.clone(),
"JniErrorHandler".to_string(),
vec!["out R".to_string()],
params,
KtType::var_r(),
);
iface.kdoc = Some(
"Binding-error callback — every wrapper's binding/system failure channel (any\n\
converter in the chain may fail, or a handle may be closed). `je` is the\n\
failure message. For an infallible wrapper this is the sole `onError`; a\n\
fallible wrapper takes it as `onBindingError` alongside the typed domain\n\
handler. The wrapper returns whatever `run` returns;\n\
throwing from `run` is safe (it executes after the native call has returned)."
.to_string(),
);
iface
}
pub(crate) struct ErrorIfaces {
pub binding: std::sync::Arc<IfaceSpec>,
pub domain: Option<std::sync::Arc<IfaceSpec>>,
}
pub(crate) fn onerror_iface_spec(
ext: &Declarations,
registry: &Registry<KotlinMeta>,
fn_ident: &syn::Ident,
) -> Option<ErrorIfaces> {
let binding = ext.iface_spec(registry, &SpecKey::JniErrorHandler)?;
let domain = match registry.error_plans().get(fn_ident) {
Some(plan) => {
let decon = plan
.decon
.clone()
.expect("error plans are always record-built (decon is Some)");
Some(ext.iface_spec(registry, &SpecKey::Handler(decon))?)
}
None => None,
};
Some(ErrorIfaces { binding, domain })
}