use prebindgen_registry::Conversions;
use super::*;
pub(crate) fn handle_field_fqn(ext: &Declarations, h: &Projection) -> String {
fn assert_scalar(s: &FoldStrategy) {
match s {
FoldStrategy::Base => {}
FoldStrategy::Optional(_, inner) => assert_scalar(inner),
FoldStrategy::Iterable(_) => panic!(
"struct handle field: collection (Vec<Handle>) layers are not yet \
supported by the struct encode/decode bridge — add array codegen \
to struct_output_body/struct_input_body to lift this guard"
),
}
}
assert_scalar(&h.strategy);
ext.kotlin_fqn(&h.leaf_key)
.map(|v| v.to_string())
.unwrap_or_else(|| {
panic!(
"struct handle field: leaf `{}` has no Kotlin FQN registered \
(ptr_class)",
h.leaf_key
)
})
}
pub(crate) struct EncSlot {
ident: proc_macro2::Ident,
wire_ty: TokenStream,
descriptor: String,
is_object: bool,
default: TokenStream,
}
pub(crate) fn primitive_default_for_descriptor(sig: &str) -> TokenStream {
match sig {
"Z" => quote!(0u8),
"B" => quote!(0i8),
"C" => quote!(0u16),
"S" => quote!(0i16),
"I" => quote!(0i32),
"J" => quote!(0i64),
"F" => quote!(0.0f32),
"D" => quote!(0.0f64),
_ => quote!(jni::objects::JObject::null()),
}
}
pub(crate) fn synth_value_struct_leaves(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
s: &prebindgen_registry::flat::Struct,
path_prefix: &[prebindgen_registry::unfold::PathStep],
name_prefix: &str,
depth: usize,
) -> Option<Vec<prebindgen_registry::unfold::UnfoldLeaf>> {
use prebindgen_registry::unfold::{LeafSource, PathStep, UnfoldLeaf};
if depth > 16 {
return None;
}
let mut leaves: Vec<UnfoldLeaf> = Vec::new();
for field in &s.fields {
let fname = field.name.as_ref()?.clone();
let camel = mangle_kotlin_ident(&kt_snake_to_camel(&fname.to_string()));
let leaf_name = if name_prefix.is_empty() {
camel
} else {
format!("{name_prefix}__{camel}")
};
let mut path = path_prefix.to_vec();
path.push(PathStep::field(fname, false));
let probe = field.ty.optional_inner().unwrap_or(&field.ty);
let nested = match ext.type_kind(registry, &probe.key()) {
TypeKind::Handle | TypeKind::Enum | TypeKind::Sum => return None,
TypeKind::DataStruct { st, cfg: Some(_) } => Some(st.clone()),
_ => None,
};
if let Some(child) = nested {
if field.ty.optional_inner().is_some()
|| matches!(field.ty.kind(), prebindgen_registry::flat::TypeKind::Vec(_))
{
return None;
}
let child_leaves =
synth_value_struct_leaves(ext, registry, &child, &path, &leaf_name, depth + 1)?;
leaves.extend(child_leaves);
continue;
}
leaves.push(UnfoldLeaf {
name: leaf_name,
path,
out_ty: field.ty.clone(),
identity: false,
nullable: false,
source: LeafSource::Field,
group: None,
});
}
Some(leaves)
}
pub(crate) fn flatten_struct_encode(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
s: &prebindgen_registry::flat::Struct,
access: &TokenStream,
prefix: &str,
depth: usize,
env_expr: &TokenStream,
) -> Option<(TokenStream, Vec<EncSlot>)> {
let plan = build_struct_plan(ext, registry, s, depth)?;
Some(encode_plan(&plan, access, prefix, depth, env_expr))
}
fn encode_plan(
plan: &StructPlan,
access: &TokenStream,
prefix: &str,
depth: usize,
env_expr: &TokenStream,
) -> (TokenStream, Vec<EncSlot>) {
let mut preludes = TokenStream::new();
let mut slots: Vec<EncSlot> = Vec::new();
for f in &plan.fields {
let fname = &f.fname;
let base = format!("{}_{}", prefix, fname);
let value = quote! { #access.#fname };
let (pre, sl) = encode_field(&f.kind, &value, &base, depth, env_expr);
preludes.extend(pre);
slots.extend(sl);
}
(preludes, slots)
}
fn encode_field(
kind: &PlanFieldKind,
value: &TokenStream,
base: &str,
depth: usize,
env_expr: &TokenStream,
) -> (TokenStream, Vec<EncSlot>) {
let mut preludes = TokenStream::new();
let mut slots: Vec<EncSlot> = Vec::new();
{
let id = format_ident!("__{}", base);
let conv_value = |conv: &ConvChain| -> TokenStream { conv.call(env_expr, value, base) };
match kind {
PlanFieldKind::Projection { conv, proj, .. } => {
let value_expr = conv_value(conv);
match proj.kind {
ProjectionKind::Handle => {
preludes.extend(quote! { let #id: jni::sys::jlong = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::sys::jlong),
descriptor: "J".to_string(),
is_object: false,
default: quote!(0i64),
});
}
ProjectionKind::Unsigned64 => match proj.strategy {
FoldStrategy::Base => {
preludes.extend(quote! { let #id: jni::sys::jlong = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::sys::jlong),
descriptor: "J".to_string(),
is_object: false,
default: quote!(0i64),
});
}
FoldStrategy::Optional(NullableKind::Niche, _) => {
preludes.extend(quote! { let #id: jni::sys::jlong = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::sys::jlong),
descriptor: "J".to_string(),
is_object: false,
default: quote!(0i64),
});
}
FoldStrategy::Optional(NullableKind::Boxed, _) => {
preludes
.extend(quote! { let #id: jni::objects::JObject = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::objects::JObject),
descriptor: "Ljava/lang/Long;".to_string(),
is_object: true,
default: quote!(jni::objects::JObject::null()),
});
}
FoldStrategy::Iterable(_) => unreachable!(
"projection collection fields are rejected by build_struct_plan"
),
},
}
}
PlanFieldKind::Enum { conv, .. } => {
let value_expr = conv_value(conv);
preludes.extend(quote! { let #id: jni::sys::jint = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::sys::jint),
descriptor: "I".to_string(),
is_object: false,
default: quote!(0i32),
});
}
PlanFieldKind::OptionEnum { conv, .. } => {
let value_expr = conv_value(conv);
preludes.extend(quote! { let #id: jni::objects::JObject = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::objects::JObject),
descriptor: "Ljava/lang/Integer;".to_string(),
is_object: true,
default: quote!(jni::objects::JObject::null()),
});
}
PlanFieldKind::Nested {
optional,
plan: child,
..
} => {
if !*optional {
let (child_pre, child_slots) =
encode_plan(child, value, base, depth + 1, env_expr);
preludes.extend(child_pre);
slots.extend(child_slots);
} else {
let cbind = format_ident!("__c{}", depth);
let child_access = quote! { #cbind };
let (child_pre, child_slots) =
encode_plan(child, &child_access, base, depth + 1, env_expr);
let flag_id = format_ident!("__{}_present", base);
let outer_ids: Vec<proc_macro2::Ident> = (0..child_slots.len())
.map(|i| format_ident!("__{}_o{}", base, i))
.collect();
let outer_tys: Vec<TokenStream> =
child_slots.iter().map(|sl| sl.wire_ty.clone()).collect();
let inner_ids: Vec<proc_macro2::Ident> =
child_slots.iter().map(|sl| sl.ident.clone()).collect();
let defaults: Vec<TokenStream> =
child_slots.iter().map(|sl| sl.default.clone()).collect();
let obind = format_ident!("__on{}", depth);
let coerce = bind_as_option("e!(&#value), &obind);
preludes.extend(quote! {
let #flag_id: jni::sys::jboolean;
#( let #outer_ids: #outer_tys; )*
#coerce
match #obind {
::core::option::Option::Some(#cbind) => {
#child_pre
#flag_id = 1u8;
#( #outer_ids = #inner_ids; )*
}
::core::option::Option::None => {
#flag_id = 0u8;
#( #outer_ids = #defaults; )*
}
}
});
slots.push(EncSlot {
ident: flag_id,
wire_ty: quote!(jni::sys::jboolean),
descriptor: "Z".to_string(),
is_object: false,
default: quote!(0u8),
});
for (i, sl) in child_slots.iter().enumerate() {
slots.push(EncSlot {
ident: outer_ids[i].clone(),
wire_ty: sl.wire_ty.clone(),
descriptor: sl.descriptor.clone(),
is_object: sl.is_object,
default: sl.default.clone(),
});
}
}
}
PlanFieldKind::Sum {
source,
optional,
variants,
..
} => {
let tag_id = format_ident!("__{}__tag", base);
struct Arm {
pattern: TokenStream,
preludes: TokenStream,
slots: Vec<EncSlot>,
}
let mut arms: Vec<Arm> = Vec::new();
for v in variants {
let vident = &v.rust_ident;
let binds: Vec<syn::Ident> = (0..v.fields.len())
.map(|i| format_ident!("__s{}_{}", depth, i))
.collect();
let mut vpre = TokenStream::new();
let mut vslots: Vec<EncSlot> = Vec::new();
for (f, bind) in v.fields.iter().zip(&binds) {
let fbase = format!("{base}_{}", f.slot);
let bind_expr = quote!(#bind);
let (p, s) = encode_field(&f.kind, &bind_expr, &fbase, depth + 1, env_expr);
vpre.extend(p);
vslots.extend(s);
}
let pattern = match v.fields.first().map(|f| &f.member) {
None => quote!(#source::#vident),
Some(syn::Member::Named(_)) => {
let pairs = v.fields.iter().zip(&binds).map(|(f, b)| {
let syn::Member::Named(n) = &f.member else {
unreachable!("variant field shapes are uniform")
};
quote!(#n: #b)
});
quote!(#source::#vident { #(#pairs),* })
}
Some(syn::Member::Unnamed(_)) => quote!(#source::#vident(#(#binds),*)),
};
arms.push(Arm {
pattern,
preludes: vpre,
slots: vslots,
});
}
let all: Vec<&EncSlot> = arms.iter().flat_map(|a| a.slots.iter()).collect();
let outer_ids: Vec<proc_macro2::Ident> = (0..all.len())
.map(|i| format_ident!("__{}_g{}", base, i))
.collect();
let outer_tys: Vec<TokenStream> = all.iter().map(|s| s.wire_ty.clone()).collect();
let defaults: Vec<TokenStream> = all.iter().map(|s| s.default.clone()).collect();
let mut offset = 0usize;
let arm_code: Vec<TokenStream> = arms
.iter()
.enumerate()
.map(|(tag, a)| {
let n = a.slots.len();
let live_outer = &outer_ids[offset..offset + n];
let live_inner: Vec<proc_macro2::Ident> =
a.slots.iter().map(|s| s.ident.clone()).collect();
let inert_outer: Vec<proc_macro2::Ident> = outer_ids
.iter()
.enumerate()
.filter(|(i, _)| *i < offset || *i >= offset + n)
.map(|(_, id)| id.clone())
.collect();
let inert_defaults: Vec<TokenStream> = defaults
.iter()
.enumerate()
.filter(|(i, _)| *i < offset || *i >= offset + n)
.map(|(_, d)| d.clone())
.collect();
offset += n;
let pattern = &a.pattern;
let pre = &a.preludes;
let tag_lit = proc_macro2::Literal::i32_unsuffixed(tag as i32);
quote! {
#pattern => {
#pre
#tag_id = #tag_lit;
#( #live_outer = #live_inner; )*
#( #inert_outer = #inert_defaults; )*
}
}
})
.collect();
let decls = quote! {
let #tag_id: jni::sys::jint;
#( let #outer_ids: #outer_tys; )*
};
if !*optional {
preludes.extend(quote! {
#decls
match &#value { #(#arm_code)* }
});
} else {
let flag_id = format_ident!("__{}_present", base);
let sbind = format_ident!("__o{}", depth);
let inner_arms: Vec<TokenStream> =
arm_code.iter().map(|a| quote! { #a }).collect();
let obind = format_ident!("__oc{}", depth);
let coerce = bind_as_option("e!(&#value), &obind);
preludes.extend(quote! {
let #flag_id: jni::sys::jboolean;
#decls
#coerce
match #obind {
::core::option::Option::Some(#sbind) => {
#flag_id = 1u8;
match #sbind { #(#inner_arms)* }
}
::core::option::Option::None => {
#flag_id = 0u8;
#tag_id = 0i32;
#( #outer_ids = #defaults; )*
}
}
});
slots.push(EncSlot {
ident: flag_id,
wire_ty: quote!(jni::sys::jboolean),
descriptor: "Z".to_string(),
is_object: false,
default: quote!(0u8),
});
}
slots.push(EncSlot {
ident: tag_id,
wire_ty: quote!(jni::sys::jint),
descriptor: "I".to_string(),
is_object: false,
default: quote!(0i32),
});
for (i, sl) in all.iter().enumerate() {
slots.push(EncSlot {
ident: outer_ids[i].clone(),
wire_ty: sl.wire_ty.clone(),
descriptor: sl.descriptor.clone(),
is_object: sl.is_object,
default: sl.default.clone(),
});
}
}
PlanFieldKind::Leaf {
conv,
wire,
form,
descriptor,
..
} => {
let value_expr = conv_value(conv);
match form {
LeafForm::Prim => {
preludes.extend(quote! { let #id: #wire = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(#wire),
descriptor: descriptor.clone(),
is_object: false,
default: primitive_default_for_descriptor(descriptor),
});
}
LeafForm::IntoObject => {
preludes.extend(
quote! { let #id: jni::objects::JObject = #value_expr.into(); },
);
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::objects::JObject),
descriptor: descriptor.clone(),
is_object: true,
default: quote!(jni::objects::JObject::null()),
});
}
LeafForm::Object => {
preludes.extend(quote! { let #id: jni::objects::JObject = #value_expr; });
slots.push(EncSlot {
ident: id,
wire_ty: quote!(jni::objects::JObject),
descriptor: descriptor.clone(),
is_object: true,
default: quote!(jni::objects::JObject::null()),
});
}
}
}
}
}
(preludes, slots)
}
pub(crate) fn struct_output_body(
ext: &Declarations,
s: &prebindgen_registry::flat::Struct,
registry: &impl Conversions<KotlinMeta>,
) -> Option<(syn::Type, syn::Expr)> {
let struct_name = s.name.to_string();
let struct_ident = &s.name;
let struct_ty: syn::Type = syn::parse_quote!(#struct_ident);
let registered_fqn = ext
.types
.get(&TypeKey::from_type(&struct_ty))
.and_then(|cfg| cfg.name_spec.as_ref())
.map(|s| ext.fqn_of(s));
let java_class_prefix = ext.java_class_prefix();
let java_class_name = if let Some(fqn) = registered_fqn {
fqn.replace('.', "/")
} else if java_class_prefix.is_empty() {
struct_name.clone()
} else {
format!("{}/{}", java_class_prefix, struct_name)
};
let access = quote!(v);
let (preludes, slots) = flatten_struct_encode(ext, registry, s, &access, "", 0, "e!(env))?;
let mut sig = String::from("(");
let mut args: Vec<TokenStream> = Vec::new();
for sl in &slots {
sig.push_str(&sl.descriptor);
let id = &sl.ident;
if sl.is_object {
args.push(quote!(jni::objects::JValue::Object(&#id)));
} else {
args.push(quote!(jni::objects::JValue::from(#id)));
}
}
sig.push_str(&format!(")L{};", java_class_name));
let factory_sig_lit = syn::LitStr::new(&sig, Span::call_site());
let body: syn::Expr = syn::parse_quote!({
#preludes
let __obj = env.call_static_method(
#java_class_name,
"fromParts",
#factory_sig_lit,
&[#(#args),*],
)
.and_then(|__v| __v.l())
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("encode struct via fromParts: {}", e)))?;
__obj
});
Some((syn::parse_quote!(jni::objects::JObject), body))
}
pub(crate) fn struct_module_path(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
name: &syn::Ident,
) -> syn::Path {
ext.fn_module(registry, name)
}