use prebindgen_registry::{
unfold::{steps_are_movable, PathStep},
Conversions,
};
use super::*;
pub(crate) fn emit_unfold_delivery(
ext: &Declarations,
registry: &Registry<KotlinMeta>,
plan: &prebindgen_registry::unfold::UnfoldPlan,
iface: Option<&IfaceSpec>,
call_expr: &TokenStream,
on_err: &TokenStream,
emit: &prebindgen_registry::Emit,
) -> TokenStream {
use prebindgen_registry::unfold::UnfoldShape;
let n = plan.leaves.len();
let obj_idents: Vec<syn::Ident> = (0..n).map(|i| format_ident!("__obj{}", i)).collect();
let fail = |msg: TokenStream| -> TokenStream {
quote! {
signal_binding_error(&mut env, &__error_sink, &__SINK_MID, __SINK_FQN, __SINK_DESCR, &#msg);
return #on_err;
}
};
let encode_leaves = |value: &TokenStream| -> (TokenStream, Vec<TokenStream>) {
encode_plan_leaves(ext, registry, plan, &obj_idents, value, &fail, emit)
};
let iface_statics = |spec: &IfaceSpec| -> TokenStream {
let fqn_lit = syn::LitStr::new(&spec.raw_slash_fqn(), Span::call_site());
let descr_lit = syn::LitStr::new(&spec.descr, Span::call_site());
quote! {
#[allow(non_upper_case_globals)]
static __CB_MID: ::prebindgen_jni_runtime::CachedIfaceMethod =
::prebindgen_jni_runtime::CachedIfaceMethod::new();
const __CB_FQN: &str = #fqn_lit;
const __CB_DESCR: &str = #descr_lit;
}
};
let builder_invoke = |arg_exprs: &[TokenStream]| -> TokenStream {
quote! {
match __CB_MID.call_object(
&mut env, __CB_FQN, "run", __CB_DESCR, &__builder, &[#(#arg_exprs),*],
) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
let _ = env.exception_describe();
let __e2 = <__JniErr as ::core::convert::From<String>>::from(__e.to_string());
signal_binding_error(&mut env, &__error_sink, &__SINK_MID, __SINK_FQN, __SINK_DESCR, &__e2.to_string());
#on_err
}
}
}
};
let emit_decompose = |value: &TokenStream| -> TokenStream {
let (leaves, arg_exprs) = encode_leaves(value);
let invoke = builder_invoke(&arg_exprs);
quote! { #leaves #invoke }
};
let opt_iterable = match &plan.shape {
UnfoldShape::Iterable(_) => Some(false),
UnfoldShape::Optional((), inner) if matches!(**inner, UnfoldShape::Iterable(_)) => {
Some(true)
}
_ => None,
};
if let Some(optional) = opt_iterable {
let statics =
iface_statics(iface.expect("folder interface spec derivable for a resolved plan"));
let fold_invoke = |arg_exprs: &[TokenStream]| -> TokenStream {
quote! {
__acc = match __CB_MID.call_object(
&mut env, __CB_FQN, "run", __CB_DESCR, &__fold,
&[jni::sys::jvalue { l: __acc.as_raw() }, #(#arg_exprs),*],
) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
let _ = env.exception_describe();
let __e2 = <__JniErr as ::core::convert::From<String>>::from(__e.to_string());
signal_binding_error(&mut env, &__error_sink, &__SINK_MID, __SINK_FQN, __SINK_DESCR, &__e2.to_string());
return #on_err;
}
};
}
};
let loop_body = if let Some(element) = plan.element.as_ref() {
let out_entry = registry
.reading(&element.key())
.and_then(|tr| registry.output_entry(&tr))
.unwrap_or_else(|| {
panic!(
"emit_unfold_delivery: Vec element `{}` has no registered output converter",
element.key()
)
});
let elem_conv = {
let step = |f: &syn::Ident, arg: TokenStream| {
quote! {
match #f(&mut env, #arg) {
::core::result::Result::Ok(__w) => __w,
::core::result::Result::Err(__e) => {
signal_binding_error(&mut env, &__error_sink, &__SINK_MID, __SINK_FQN, __SINK_DESCR, &__e.to_string());
return #on_err;
}
}
}
};
let mut body = TokenStream::new();
let mut previous = quote!(__elem);
for (order, (_, stage)) in out_entry.output_stage_order().enumerate() {
let next = format_ident!("__es{}", order);
let call = step(&stage.function.sig.ident, previous);
body.extend(quote! { let #next = #call; });
previous = quote!(#next);
}
let last = step(out_entry.converter_ident(), previous);
quote!({ #body #last })
};
let elem_wire = out_entry.destination.clone();
let elem_is_prim = matches!(jni_field_access(&elem_wire), Some((_, _, false)));
let enc = format_ident!("__enc");
let (bind_obj, arg_expr) = if elem_is_prim {
let letter = jni_field_access(&elem_wire).unwrap().1;
(
TokenStream::new(),
quote!(jni::sys::jvalue { #letter: __enc }),
)
} else {
let cast = cast_wire_to_jobject(&enc, &elem_wire, &fail);
(
quote! { let __obj: jni::objects::JObject = #cast; },
quote!(jni::sys::jvalue { l: __obj.as_raw() }),
)
};
let invoke = fold_invoke(&[arg_expr]);
quote! {
let __enc = #elem_conv;
#bind_obj
#invoke
}
} else {
let (leaves, arg_exprs) = encode_leaves("e!(__elem));
let invoke = fold_invoke(&arg_exprs);
quote! {
#leaves
#invoke
}
};
let fold = quote! {
let mut __acc = __acc;
for __elem in __vec.into_iter() {
#loop_body
}
__acc
};
return if optional {
quote! {
#statics
let __out = #call_expr;
match __out {
::core::option::Option::Some(__vec) => { #fold }
::core::option::Option::None => #on_err,
}
}
} else {
quote! {
#statics
let __vec = #call_expr;
#fold
}
};
}
match &plan.shape {
UnfoldShape::Base => {
let statics = iface_statics(
iface.expect("builder interface spec derivable for a registered declaration"),
);
let body = emit_decompose("e!(__out));
quote! {
#statics
let __out = #call_expr;
#body
}
}
UnfoldShape::Optional((), inner) => {
match **inner {
UnfoldShape::Base => {}
_ => panic!(
"emit_unfold_delivery: Optional inner must be Base (scalar) or \
Iterable (`Option<Vec<T>>`, handled above)"
),
}
let statics = iface_statics(
iface.expect("builder interface spec derivable for a registered declaration"),
);
let body = emit_decompose("e!(__inner));
quote! {
#statics
let __out = #call_expr;
match __out {
::core::option::Option::Some(__inner) => { #body }
::core::option::Option::None => #on_err,
}
}
}
UnfoldShape::Iterable(_) => {
unreachable!("Iterable delivery is handled by the `opt_iterable` branch above")
}
}
}
pub(crate) fn cast_wire_to_jobject(
enc: &syn::Ident,
wire: &syn::Type,
fail: &dyn Fn(TokenStream) -> TokenStream,
) -> TokenStream {
if is_jobject_wire(wire) {
quote!(#enc)
} else if matches!(jni_field_access(wire), Some((_, _, true))) {
quote!(#enc.into())
} else if let Some(helper) = box_helper_for_wire(wire) {
let on_fail = fail(quote!(__e));
quote! {
match ::prebindgen_jni_runtime::#helper(&mut env, #enc) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
#on_fail
}
}
}
} else {
panic!(
"jnigen unfold: leaf has unsupported wire `{}`",
wire.to_token_stream()
)
}
}
pub(crate) fn compose_step(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
step: &PathStep,
e: TokenStream,
) -> TokenStream {
match step {
PathStep::Call { ident, .. } => {
let m = qualify(ident);
quote!(#m::#ident(#e))
}
PathStep::Field { ident, .. } => quote!(&(#e).#ident),
}
}
fn fold_steps(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
steps: &[PathStep],
mut e: TokenStream,
mut owned: bool,
) -> TokenStream {
for step in steps {
if owned && matches!(step, PathStep::Call { .. }) {
e = quote!(&#e);
}
e = compose_step(qualify, step, e);
owned = step.yields_owned();
}
e
}
fn compose_value_form_call(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
call: &PathStep,
e: TokenStream,
e_owned: bool,
consuming: bool,
) -> TokenStream {
match (consuming, e_owned) {
(true, owned) => {
let (m, f) = (qualify(call.ident()), call.ident());
let arg = if owned { e } else { quote!((#e).clone()) };
quote!(#m::#f(#arg))
}
(false, true) => compose_step(qualify, call, quote!(&#e)),
(false, false) => compose_step(qualify, call, e),
}
}
fn project_leading_fields(
base: &TokenStream,
base_is_ref: bool,
path: &[PathStep],
) -> (TokenStream, usize) {
if base_is_ref {
return (base.clone(), 0);
}
let n = path.iter().take_while(|s| s.is_plain_field()).count();
if n == 0 {
return (quote!(&#base), 0);
}
let segs: Vec<&syn::Ident> = path[..n].iter().map(PathStep::ident).collect();
(quote!(&#base #(.#segs)*), n)
}
pub(crate) fn reach_leaf_flat(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
leaf: &prebindgen_registry::unfold::UnfoldLeaf,
path: &[PathStep],
base: TokenStream,
base_is_ref: bool,
consuming: bool,
) -> TokenStream {
use prebindgen_registry::unfold::LeafSource;
assert!(
!leaf.path.iter().rev().skip(1).any(PathStep::is_optional),
"jnigen unfold: leaf `{}` reaches through an optional step but is \
delivered as a single return value, which has no `None` arm — this \
shape needs callback delivery",
leaf.name,
);
let reached_is_ours = if leaf.identity {
!matches!(
leaf.out_ty.kind(),
prebindgen_registry::flat::TypeKind::Ref { .. }
)
} else {
consuming
};
if reached_is_ours && steps_are_movable(path) {
let segs: Vec<&syn::Ident> = path.iter().map(PathStep::ident).collect();
return quote!(#base #(.#segs)*);
}
let (e, lead) = project_leading_fields(&base, base_is_ref, path);
let e = fold_steps(qualify, &path[lead..], e, false);
if leaf.source == LeafSource::Field {
quote!((#e).clone())
} else {
e
}
}
pub(crate) struct Hoisted {
pub(crate) stmts: TokenStream,
bound: Vec<(Vec<PathStep>, syn::Ident)>,
consuming: Vec<bool>,
optional: Vec<bool>,
}
impl Hoisted {
fn innermost(&self, path: &[PathStep]) -> Option<(usize, Vec<PathStep>)> {
self.bound
.iter()
.enumerate()
.filter(|(_, (p, _))| p.len() < path.len() && path.starts_with(p))
.max_by_key(|(_, (p, _))| p.len())
.map(|(i, (p, _))| (i, path[p.len()..].to_vec()))
}
pub(crate) fn rebase(&self, path: &[PathStep]) -> Option<(syn::Ident, Vec<PathStep>, bool)> {
self.innermost(path)
.map(|(i, rest)| (self.bound[i].1.clone(), rest, self.consuming[i]))
}
pub(crate) fn conditional(
&self,
path: &[PathStep],
) -> Option<(usize, syn::Ident, syn::Ident, Vec<PathStep>)> {
let (i, rest) = self.innermost(path)?;
self.optional[i].then(|| (i, self.bound[i].1.clone(), format_ident!("__u{}", i), rest))
}
pub(crate) fn local(&self, i: usize) -> syn::Ident {
self.bound[i].1.clone()
}
pub(crate) fn consumed(&self, i: usize) -> bool {
self.consuming[i]
}
}
fn reach_optional(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
path: &[PathStep],
base: TokenStream,
base_is_ref: bool,
depth: usize,
body: &dyn Fn(TokenStream) -> TokenStream,
) -> TokenStream {
let (e, lead) = project_leading_fields(&base, base_is_ref, path);
match (lead..path.len()).find(|&i| path[i].is_optional()) {
None => body(fold_steps(qualify, &path[lead..], e, false)),
Some(k) => {
let opt_e = fold_steps(qualify, &path[lead..=k], e, false);
let bind = format_ident!("__hb{}", depth);
let rest = &path[k + 1..];
let inner = reach_optional(
qualify,
rest,
quote!(#bind),
rest.is_empty() || !path[k].yields_owned(),
depth + 1,
body,
);
let combinator = if rest.iter().any(PathStep::is_optional) {
format_ident!("and_then")
} else {
format_ident!("map")
};
quote! {
#opt_e.#combinator(|#bind| #inner)
}
}
}
}
pub(crate) fn bind_hoists(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
hoists: &[prebindgen_registry::unfold::Hoist],
value: &TokenStream,
by_ref: bool,
) -> Hoisted {
let mut out = Hoisted {
stmts: TokenStream::new(),
bound: Vec::new(),
consuming: Vec::new(),
optional: Vec::new(),
};
for (i, h) in hoists.iter().enumerate() {
let local = format_ident!("__vf{}", i);
if h.prefix.iter().any(PathStep::is_optional) {
let (last, lead) = h
.prefix
.split_last()
.expect("a hoist prefix ends in its value-form call");
let consuming = h.consuming;
let owned = lead.last().is_some_and(PathStep::yields_owned);
let expr = reach_optional(qualify, lead, value.clone(), by_ref, 0, &|reached| {
compose_value_form_call(qualify, last, reached, owned, consuming)
});
out.stmts.extend(quote! { let #local = #expr; });
out.bound.push((h.prefix.clone(), local));
out.consuming.push(h.consuming);
out.optional.push(true);
continue;
}
let (from, start, start_owned) = match out.rebase(&h.prefix) {
Some((outer, rest, _))
if h.consuming && rest[..rest.len() - 1].iter().all(PathStep::is_plain_field) =>
{
let lead = &rest[..rest.len() - 1];
let segs: Vec<&syn::Ident> = lead.iter().map(PathStep::ident).collect();
(h.prefix.len() - 1, quote!(#outer #(.#segs)*), true)
}
Some((outer, rest, _)) => {
let (e, lead) = project_leading_fields("e!(#outer), false, &rest);
(h.prefix.len() - rest.len() + lead, e, false)
}
None if by_ref => (0, value.clone(), false),
None => (0, value.clone(), true),
};
let last = h.prefix.len() - 1;
let head = &h.prefix[from..last];
let e = fold_steps(qualify, head, start, start_owned);
let e_owned = head.last().map_or(start_owned, PathStep::yields_owned);
let expr = compose_value_form_call(qualify, &h.prefix[last], e, e_owned, h.consuming);
out.stmts.extend(quote! { let #local = #expr; });
out.bound.push((h.prefix.clone(), local));
out.consuming.push(h.consuming);
out.optional.push(false);
}
out
}
pub(crate) fn bind_as_option(e: &TokenStream, bind: &syn::Ident) -> TokenStream {
quote! { let #bind: &::core::option::Option<_> = #e; }
}
fn reach_leaf(
qualify: &dyn Fn(&syn::Ident) -> syn::Path,
path: &[PathStep],
base: TokenStream,
base_is_ref: bool,
unwrap_last: bool,
depth: usize,
body: &dyn Fn(TokenStream) -> TokenStream,
) -> TokenStream {
let limit = if unwrap_last {
path.len()
} else {
path.len().saturating_sub(1)
};
let (e, lead) = project_leading_fields(&base, base_is_ref, path);
match (lead..limit).find(|&i| path[i].is_optional()) {
None => body(fold_steps(qualify, &path[lead..], e, false)),
Some(k) => {
let opt_e = fold_steps(qualify, &path[lead..=k], e, false);
let nested = format_ident!("__n{}", depth);
let inner = reach_leaf(
qualify,
&path[k + 1..],
quote!(#nested),
true,
unwrap_last,
depth + 1,
body,
);
if path[k].is_field() {
let opt_bind = format_ident!("__o{}", depth);
let coerce = bind_as_option(&opt_e, &opt_bind);
quote! {
{
#coerce
match #opt_bind {
::core::option::Option::Some(#nested) => { #inner }
::core::option::Option::None => jni::objects::JObject::null(),
}
}
}
} else {
quote! {
match #opt_e {
::core::option::Option::Some(#nested) => { #inner }
::core::option::Option::None => jni::objects::JObject::null(),
}
}
}
}
}
}
pub(crate) fn encode_plan_leaves(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
plan: &prebindgen_registry::unfold::UnfoldPlan,
obj_idents: &[syn::Ident],
value: &TokenStream,
fail: &dyn Fn(TokenStream) -> TokenStream,
emit: &prebindgen_registry::Emit,
) -> (TokenStream, Vec<TokenStream>) {
let qualify = |id: &syn::Ident| -> syn::Path { ext.fn_module(registry, id) };
let by_ref = plan.by_ref;
let n = plan.leaves.len();
let mut arg_exprs: Vec<TokenStream> = Vec::with_capacity(n);
for (idx, leaf) in plan.leaves.iter().enumerate() {
let obj_ident = &obj_idents[idx];
if leaf_is_prim(registry, leaf) {
arg_exprs.push(quote!(#obj_ident));
} else {
arg_exprs.push(quote!(jni::sys::jvalue { l: #obj_ident.as_raw() }));
}
}
let hoisted = bind_hoists(&qualify, &plan.hoists, value, by_ref);
let mut stmts = hoisted.stmts.clone();
let rebase =
|leaf: &prebindgen_registry::unfold::UnfoldLeaf| -> (TokenStream, bool, Vec<PathStep>, bool) {
if let Some((i, _, bind, rest)) = hoisted.conditional(&leaf.path) {
return (quote!(#bind), false, rest, hoisted.consumed(i));
}
match hoisted.rebase(&leaf.path) {
Some((local, rest, consuming)) => (quote!(#local), false, rest, consuming),
None => (value.clone(), by_ref, leaf.path.clone(), false),
}
};
let sum_segments: Vec<std::ops::Range<usize>> = (0..n)
.filter(|&i| plan.leaves[i].source == prebindgen_registry::unfold::LeafSource::SumTag)
.map(|start| {
let end = (start + 1..n)
.take_while(|&i| plan.leaves[i].group.is_some())
.last()
.map_or(start + 1, |i| i + 1);
start..end
})
.collect();
let mut cond_stmts: std::collections::BTreeMap<usize, TokenStream> = plan
.hoists
.iter()
.enumerate()
.filter_map(|(i, _)| {
plan.leaves
.iter()
.any(|l| hoisted.conditional(&l.path).is_some_and(|(j, ..)| j == i))
.then_some((i, TokenStream::new()))
})
.collect();
for seg in &sum_segments {
let leaf = &plan.leaves[seg.start];
let (base, base_is_ref, path, _) = rebase(leaf);
let (projected, lead) = project_leading_fields(&base, base_is_ref, &path);
let opt_at = (lead..path.len()).find(|&i| path[i].is_optional());
let (matched, gate) = match opt_at {
None => (fold_steps(&qualify, &path[lead..], projected, false), None),
Some(k) => {
let opt_e = fold_steps(&qualify, &path[lead..=k], projected, false);
let bind = format_ident!("__sg{}", seg.start);
assert!(
!path[k + 1..].iter().any(PathStep::is_optional),
"jnigen unfold: leaf `{}` reaches its sum through TWO optional \
steps — the segment gate has one `None` arm, so the second \
would be matched as if it were the sum itself",
leaf.name,
);
let inner = fold_steps(
&qualify,
&path[k + 1..],
quote!(#bind),
path[k].yields_owned(),
);
(inner, Some((k, opt_e, bind)))
}
};
let (group_stmts, group_args) = encode_sum_group(
ext,
registry,
&plan.leaves[seg.clone()],
&obj_idents[seg.clone()],
matched,
fail,
emit,
);
let group_stmts = match gate {
None => group_stmts,
Some((k, opt_e, bind)) => {
let ids: Vec<&syn::Ident> = obj_idents[seg.clone()].iter().collect();
let slots: Vec<Slot> = plan.leaves[seg.clone()]
.iter()
.map(|l| leaf_slot(registry, l))
.collect();
let tys = slots.iter().map(|s| &s.ty);
let defaults = slots.iter().map(|s| &s.default);
let (prelude, scrutinee) = if path[k].is_field() {
let opt_bind = format_ident!("__so{}", seg.start);
(bind_as_option(&opt_e, &opt_bind), quote!(#opt_bind))
} else {
(TokenStream::new(), opt_e)
};
quote! {
let (#(#ids,)*): (#(#tys,)*) = {
#prelude
match #scrutinee {
::core::option::Option::Some(#bind) => {
#group_stmts
(#(#ids,)*)
}
::core::option::Option::None => (#(#defaults,)*),
}
};
}
}
};
match hoisted.conditional(&leaf.path) {
Some((i, ..)) => cond_stmts
.get_mut(&i)
.expect("a conditional leaf's hoist has a bucket")
.extend(group_stmts),
None => stmts.extend(group_stmts),
}
for (k, e) in group_args.into_iter().enumerate() {
arg_exprs[seg.start + k] = e;
}
}
let in_sum = |i: usize| sum_segments.iter().any(|s| s.contains(&i));
let mut order: Vec<usize> = (0..n)
.filter(|&i| !plan.leaves[i].identity && !in_sum(i))
.collect();
order.extend((0..n).filter(|&i| plan.leaves[i].identity && !in_sum(i)));
for idx in order {
let leaf = &plan.leaves[idx];
let obj_ident = &obj_idents[idx];
let stmts: &mut TokenStream = match hoisted.conditional(&leaf.path) {
Some((i, ..)) => cond_stmts.get_mut(&i).expect("collected above"),
None => &mut stmts,
};
let (value, by_ref, path, consuming) = rebase(leaf);
let value = &value;
let out_entry = registry.output_entry(&leaf.out_ty).unwrap_or_else(|| {
panic!(
"jnigen unfold: leaf `{}` has no registered output converter",
leaf.out_ty.key()
)
});
let conv_fail = fail(quote!(__e.to_string()));
let conv_stages: Vec<syn::Ident> = out_entry
.output_stage_order()
.map(|(_, stage)| stage.function.sig.ident.clone())
.collect();
let conv_fn = out_entry.converter_ident().clone();
let conv = |input: TokenStream| -> TokenStream {
let step = |f: &syn::Ident, arg: TokenStream| {
quote! {
match #f(&mut env, #arg) {
::core::result::Result::Ok(__w) => __w,
::core::result::Result::Err(__e) => {
#conv_fail
}
}
}
};
if conv_stages.is_empty() {
return step(&conv_fn, input);
}
let mut body = TokenStream::new();
let mut previous = input;
for (order, stage_fn) in conv_stages.iter().enumerate() {
let next = format_ident!("__cs{}_{}", idx, order);
let call = step(stage_fn, previous);
body.extend(quote! { let #next = #call; });
previous = quote!(#next);
}
let last = step(&conv_fn, previous);
quote!({ #body #last })
};
let bind_obj = |obj_ident: &syn::Ident, expr: TokenStream| -> TokenStream {
quote! {
let #obj_ident: jni::objects::JObject = #expr;
}
};
if leaf.identity {
let proj = out_entry.metadata.projection.as_ref().unwrap_or_else(|| {
panic!(
"jnigen unfold: identity leaf `{}` has no projection — \
`.accessor_record_id()` requires a ptr_class type",
leaf.out_ty.key()
)
});
let owned_place: Option<TokenStream> = if !matches!(
leaf.out_ty.kind(),
prebindgen_registry::flat::TypeKind::Ref { .. }
) && steps_are_movable(&path)
{
let segs: Vec<&syn::Ident> = path.iter().map(PathStep::ident).collect();
Some(quote!(#value #(.#segs)*))
} else {
None
};
match proj.kind {
ProjectionKind::Handle => {
let handle_ident = format_ident!("__h{}", idx);
if let (Some(place), false) = (&owned_place, leaf.nullable) {
stmts.extend(quote! {
let #obj_ident: jni::sys::jvalue = jni::sys::jvalue {
j: std::boxed::Box::into_raw(std::boxed::Box::new(#place))
as jni::sys::jlong,
};
});
} else if let (Some(place), false) =
(&owned_place, path.last().is_some_and(PathStep::is_optional))
{
let box_fail = fail(quote!(__e.to_string()));
stmts.extend(bind_obj(
obj_ident,
quote! {{
let #handle_ident: jni::sys::jlong =
std::boxed::Box::into_raw(std::boxed::Box::new(#place))
as jni::sys::jlong;
match ::prebindgen_jni_runtime::box_jlong(&mut env, #handle_ident) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
#box_fail
}
}
}},
));
} else if let Some(place) = &owned_place {
let box_fail = fail(quote!(__e.to_string()));
stmts.extend(bind_obj(
obj_ident,
quote! {{
match #place {
::core::option::Option::Some(__n) => {
let #handle_ident: jni::sys::jlong =
std::boxed::Box::into_raw(std::boxed::Box::new(__n))
as jni::sys::jlong;
match ::prebindgen_jni_runtime::box_jlong(&mut env, #handle_ident) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
#box_fail
}
}
}
::core::option::Option::None => jni::objects::JObject::null(),
}
}},
));
} else if !leaf.nullable {
let expr = reach_leaf(
&qualify,
&path,
value.clone(),
by_ref,
true,
0,
&|reached| {
let __encoded = conv(quote!(#reached));
quote! {{
let #handle_ident: jni::sys::jlong = #__encoded;
jni::sys::jvalue { j: #handle_ident }
}}
},
);
stmts.extend(quote! {
let #obj_ident: jni::sys::jvalue = #expr;
});
} else {
let box_fail = fail(quote!(__e.to_string()));
let expr = reach_leaf(
&qualify,
&path,
value.clone(),
by_ref,
true,
0,
&|reached| {
let __encoded = conv(quote!(#reached));
quote! {{
let #handle_ident: jni::sys::jlong = #__encoded;
match ::prebindgen_jni_runtime::box_jlong(&mut env, #handle_ident) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
#box_fail
}
}
}}
},
);
stmts.extend(bind_obj(obj_ident, expr));
}
}
ProjectionKind::Unsigned64 => {
let enc_ident = format_ident!("__enc{}", idx);
let encode = |reached: TokenStream| {
let encoded = conv(reached);
quote! {{
let #enc_ident: jni::sys::jlong = #encoded;
jni::sys::jvalue { j: #enc_ident }
}}
};
if path.is_empty() && !by_ref {
let expr = encode(value.clone());
stmts.extend(quote! { let #obj_ident: jni::sys::jvalue = #expr; });
} else if !leaf.nullable {
let expr = reach_leaf(
&qualify,
&path,
value.clone(),
by_ref,
true,
0,
&|reached| encode(quote!(*#reached)),
);
stmts.extend(quote! { let #obj_ident: jni::sys::jvalue = #expr; });
} else {
let box_fail = fail(quote!(__e.to_string()));
let expr = reach_leaf(
&qualify,
&path,
value.clone(),
by_ref,
true,
0,
&|reached| {
let __encoded = conv(quote!(*#reached));
quote! {{
let #enc_ident: jni::sys::jlong = #__encoded;
match ::prebindgen_jni_runtime::box_jlong(&mut env, #enc_ident) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
#box_fail
}
}
}}
},
);
stmts.extend(bind_obj(obj_ident, expr));
}
}
}
continue;
}
use prebindgen_registry::unfold::LeafSource;
let reach = |body: &dyn Fn(TokenStream) -> TokenStream| -> TokenStream {
match &leaf.source {
LeafSource::Accessor => {
reach_leaf(&qualify, &path, value.clone(), by_ref, false, 0, body)
}
LeafSource::Field if consuming && path.iter().all(PathStep::is_plain_field) => {
let segs: Vec<&syn::Ident> = path.iter().map(PathStep::ident).collect();
body(quote!(#value #(.#segs)*))
}
LeafSource::Field if path.iter().all(PathStep::is_plain_field) => {
let segs: Vec<&syn::Ident> = path.iter().map(PathStep::ident).collect();
body(quote!(#value #(.#segs)*.clone()))
}
LeafSource::Field => reach_leaf(
&qualify,
&path,
value.clone(),
by_ref,
false,
0,
&|reached| body(quote!((#reached).clone())),
),
LeafSource::SumTag | LeafSource::VariantField { .. } => unreachable!(
"sum leaves are encoded by `encode_sum_leaves`, not reached by path"
),
}
};
let wire = out_entry.destination.clone();
let enc_ident = format_ident!("__enc{}", idx);
if leaf_is_prim(registry, leaf) {
let letter = jni_field_access(&wire)
.expect("leaf_is_prim guarantees a primitive wire")
.1;
let expr = reach(&|reached| {
let __encoded = conv(quote!(#reached));
quote! {{
let #enc_ident = #__encoded;
jni::sys::jvalue { #letter: #enc_ident }
}}
});
stmts.extend(quote! {
let #obj_ident: jni::sys::jvalue = #expr;
});
continue;
}
let cast = cast_wire_to_jobject(&enc_ident, &wire, fail);
let expr = reach(&|reached| {
let __encoded = conv(quote!(#reached));
quote! {{
let #enc_ident = #__encoded;
#cast
}}
});
stmts.extend(bind_obj(obj_ident, expr));
}
for (i, body) in cond_stmts {
let local = hoisted.local(i);
let bind = format_ident!("__u{}", i);
let idxs: Vec<usize> = (0..n)
.filter(|&k| {
hoisted
.conditional(&plan.leaves[k].path)
.is_some_and(|(j, ..)| j == i)
})
.collect();
let ids: Vec<&syn::Ident> = idxs.iter().map(|&k| &obj_idents[k]).collect();
let tys = idxs
.iter()
.map(|&k| leaf_slot(registry, &plan.leaves[k]).ty);
let defaults = idxs
.iter()
.map(|&k| leaf_slot(registry, &plan.leaves[k]).default);
stmts.extend(quote! {
let (#(#ids,)*): (#(#tys,)*) = match #local {
::core::option::Option::Some(#bind) => { #body (#(#ids,)*) }
::core::option::Option::None => (#(#defaults,)*),
};
});
}
(stmts, arg_exprs)
}
pub(crate) fn leaf_is_prim(
registry: &impl Conversions<KotlinMeta>,
leaf: &prebindgen_registry::unfold::UnfoldLeaf,
) -> bool {
if leaf.source == prebindgen_registry::unfold::LeafSource::SumTag {
return !leaf.nullable;
}
if leaf.nullable {
return false;
}
leaf_ty_is_prim(registry, &leaf.out_ty)
}
pub(crate) fn leaf_ty_is_prim(
registry: &impl Conversions<KotlinMeta>,
out_ty: &prebindgen_registry::flat::TypeRef,
) -> bool {
let Some(entry) = registry.output_entry(out_ty) else {
return false;
};
let proj_ok = match &entry.metadata.projection {
None => true,
Some(p) => matches!(p.kind, ProjectionKind::Handle | ProjectionKind::Unsigned64),
};
proj_ok && matches!(jni_field_access(&entry.destination), Some((_, _, false)))
}
#[cfg(test)]
mod tests {
use prebindgen_registry::unfold::{LeafSource, UnfoldLeaf};
use super::*;
use crate::test_util::reading as tref;
fn leaf(
out_ty: syn::Type,
path: Vec<PathStep>,
identity: bool,
source: LeafSource,
) -> UnfoldLeaf {
UnfoldLeaf {
name: "probe".to_string(),
path,
out_ty: tref(out_ty),
identity,
nullable: false,
source,
group: None,
}
}
fn qualify(id: &syn::Ident) -> syn::Path {
syn::parse_quote!(myflat::#id)
}
#[test]
fn a_movable_place_is_projected_as_a_move() {
for path in [
vec![PathStep::field(syn::parse_quote!(a), false)],
vec![
PathStep::field(syn::parse_quote!(a), false),
PathStep::field(syn::parse_quote!(b), false),
],
vec![
PathStep::field(syn::parse_quote!(a), false),
PathStep::field(syn::parse_quote!(b), true),
],
] {
assert!(steps_are_movable(&path), "fixture must be movable");
let l = leaf(
syn::parse_quote!(Owned),
path.clone(),
true,
LeafSource::Accessor,
);
let got = reach_leaf_flat(&qualify, &l, &path, quote!(__src), false, false).to_string();
assert!(
!got.contains('&') && !got.contains("clone"),
"a movable place is moved, not borrowed or cloned — got `{got}`"
);
}
}
#[test]
fn a_borrowed_out_ty_is_never_moved() {
let path = vec![PathStep::field(syn::parse_quote!(a), false)];
let l = leaf(
syn::parse_quote!(&Owned),
path.clone(),
true,
LeafSource::Accessor,
);
let got = reach_leaf_flat(&qualify, &l, &path, quote!(__src), false, false).to_string();
assert!(
got.contains('&'),
"a borrowed out_ty keeps its borrow — got `{got}`"
);
}
#[test]
fn a_field_leaf_is_cloned_out_of_its_place() {
let path = vec![
PathStep::field(syn::parse_quote!(a), false),
PathStep::field(syn::parse_quote!(b), false),
];
let l = leaf(
syn::parse_quote!(Owned),
path.clone(),
false,
LeafSource::Field,
);
let got = reach_leaf_flat(&qualify, &l, &path, quote!(__src), false, false).to_string();
assert!(
got.contains("clone"),
"a non-consuming field leaf clones rather than moves — got `{got}`"
);
}
#[test]
#[should_panic(expected = "which has no `None` arm")]
fn an_optional_step_in_a_stripped_prefix_is_still_refused() {
let full = vec![
PathStep::call(syn::parse_quote!(get_it), true, false),
PathStep::field(syn::parse_quote!(a), false),
];
let l = leaf(syn::parse_quote!(Owned), full, false, LeafSource::Accessor);
let rest = vec![PathStep::field(syn::parse_quote!(a), false)];
let _ = reach_leaf_flat(&qualify, &l, &rest, quote!(__vf0), false, false);
}
}