use prebindgen_registry::Conversions;
use super::*;
pub(crate) const SUM_TAG_LEAF: &str = "tag";
pub(crate) fn synth_sum_leaves(
ext: &Declarations,
sum_cfg: &SumConfig,
sum: &prebindgen_registry::flat::Variant,
) -> Vec<prebindgen_registry::unfold::UnfoldLeaf> {
use prebindgen_registry::unfold::{LeafSource, UnfoldLeaf};
let mut leaves = vec![UnfoldLeaf {
name: SUM_TAG_LEAF.to_string(),
path: Vec::new(),
out_ty: sum.type_ref().clone(),
identity: false,
nullable: false,
source: LeafSource::SumTag,
group: None,
}];
for alt in &sum.alternatives {
let kotlin_name = ext.sum_variant_class_name(sum_cfg, &alt.name);
for field in &alt.fields {
let prop = sum_field_prop_name(&field.member());
leaves.push(UnfoldLeaf {
name: sum_slot_fragment(&kotlin_name, &prop),
path: Vec::new(),
out_ty: field.ty.clone(),
identity: false,
nullable: false,
source: LeafSource::VariantField {
variant: alt.name.clone(),
member: field.member(),
},
group: Some(sum_tag(alt)),
});
}
}
leaves
}
pub(crate) struct Slot {
pub(crate) prim: bool,
pub(crate) ty: TokenStream,
pub(crate) default: TokenStream,
}
pub(crate) fn leaf_slot(
registry: &impl Conversions<KotlinMeta>,
leaf: &prebindgen_registry::unfold::UnfoldLeaf,
) -> Slot {
use prebindgen_registry::unfold::LeafSource;
if !leaf_is_prim(registry, leaf) {
return Slot {
prim: false,
ty: quote!(jni::objects::JObject),
default: quote!(jni::objects::JObject::null()),
};
}
let (sig, letter) = if leaf.source == LeafSource::SumTag {
("I", format_ident!("i"))
} else {
let wire = registry
.output_entry(&leaf.out_ty)
.expect("leaf_is_prim implies a resolved output entry")
.destination
.clone();
let (sig, letter, _) =
jni_field_access(&wire).expect("leaf_is_prim guarantees a primitive wire");
(sig, letter)
};
let zero = primitive_default_for_descriptor(sig);
Slot {
prim: true,
ty: quote!(jni::sys::jvalue),
default: quote!(jni::sys::jvalue { #letter: #zero }),
}
}
pub(crate) fn is_sum_leaves(leaves: &[prebindgen_registry::unfold::UnfoldLeaf]) -> bool {
use prebindgen_registry::unfold::LeafSource;
leaves.iter().any(|l| l.source == LeafSource::SumTag)
}
pub(crate) fn encode_sum_group(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
leaves: &[prebindgen_registry::unfold::UnfoldLeaf],
obj_idents: &[syn::Ident],
matched: TokenStream,
fail: &dyn Fn(TokenStream) -> TokenStream,
emit: &prebindgen_registry::Emit,
) -> (TokenStream, Vec<TokenStream>) {
use prebindgen_registry::unfold::LeafSource;
let tag_leaf = leaves
.iter()
.find(|l| l.source == LeafSource::SumTag)
.expect("a sum segment carries its selector leaf");
let prebindgen_registry::flat::TypeKind::Named { id, .. } = tag_leaf.out_ty.unwrapped().kind()
else {
panic!(
"jnigen sum unfold: selector type `{}` is not a named type",
tag_leaf.out_ty.key()
)
};
let ident = id.ident().unwrap_or_else(|| {
panic!(
"jnigen sum unfold: selector type `{}` is not a single identifier",
id.name
)
});
let module = ext.fn_module(registry, &ident);
let source: syn::Path = syn::parse_quote!(#module::#ident);
let slots: Vec<Slot> = leaves.iter().map(|l| leaf_slot(registry, l)).collect();
let arg_exprs: Vec<TokenStream> = leaves
.iter()
.enumerate()
.map(|(idx, _)| {
let id = &obj_idents[idx];
if slots[idx].prim {
quote!(#id)
} else {
quote!(jni::sys::jvalue { l: #id.as_raw() })
}
})
.collect();
let decls: TokenStream = leaves
.iter()
.enumerate()
.map(|(idx, _)| {
let id = &obj_idents[idx];
let ty = &slots[idx].ty;
quote! { let #id: #ty; }
})
.collect();
let tag_idx = leaves
.iter()
.position(|l| l.source == LeafSource::SumTag)
.expect("a sum plan carries its selector leaf");
let tag_id = &obj_idents[tag_idx];
let Some(prebindgen_registry::flat::Type::Variant(sum)) = registry.flat().declared_type(&ident)
else {
panic!("jnigen sum unfold: no indexed sum `{ident}` for the decomposed sum")
};
let arms: Vec<TokenStream> = sum
.alternatives
.iter()
.map(|alt| {
let tag = sum_tag(alt);
let group: Vec<usize> = leaves
.iter()
.enumerate()
.filter(|(_, l)| l.group == Some(tag))
.map(|(i, _)| i)
.collect();
let binds: Vec<syn::Ident> = group
.iter()
.enumerate()
.map(|(k, _)| format_ident!("__sv{}", k))
.collect();
let vident = &alt.name;
let parts: Vec<TokenStream> = alt
.fields
.iter()
.zip(&binds)
.map(|(f, b)| f.bind(b))
.collect();
let pattern = emit.shape(alt, quote!(#source::#vident), &parts);
let live: TokenStream = group
.iter()
.zip(&binds)
.map(|(&idx, bind)| {
encode_group_leaf(
registry,
&leaves[idx],
&obj_idents[idx],
slots[idx].prim,
bind,
fail,
)
})
.collect();
let inert: TokenStream = (0..leaves.len())
.filter(|i| *i != tag_idx && !group.contains(i))
.map(|i| {
let id = &obj_idents[i];
let d = &slots[i].default;
quote! { #id = #d; }
})
.collect();
let tag_lit = proc_macro2::Literal::i32_unsuffixed(tag);
let set_tag = if slots[tag_idx].prim {
quote! { #tag_id = jni::sys::jvalue { i: #tag_lit }; }
} else {
let box_fail = fail(quote!(__e));
quote! {
#tag_id = match ::prebindgen_jni_runtime::box_jint(&mut env, #tag_lit) {
::core::result::Result::Ok(__o) => __o,
::core::result::Result::Err(__e) => {
#box_fail
}
};
}
};
quote! {
#pattern => {
#live
#set_tag
#inert
}
}
})
.collect();
let stmts = quote! {
#decls
match #matched { #(#arms)* }
};
(stmts, arg_exprs)
}
fn encode_group_leaf(
registry: &impl Conversions<KotlinMeta>,
leaf: &prebindgen_registry::unfold::UnfoldLeaf,
obj_ident: &syn::Ident,
prim: bool,
bind: &syn::Ident,
fail: &dyn Fn(TokenStream) -> TokenStream,
) -> TokenStream {
let out_entry = registry.output_entry(&leaf.out_ty).unwrap_or_else(|| {
panic!(
"jnigen sum unfold: payload leaf `{}` (`{}`) has no registered output converter",
leaf.name,
leaf.out_ty.key()
)
});
let wire = out_entry.destination.clone();
let conv_fail = fail(quote!(__e.to_string()));
let enc = format_ident!("__enc_{}", obj_ident);
let mut encode = TokenStream::new();
let mut previous = quote!(#bind.clone());
for (order, (_, stage)) in out_entry.output_stage_order().enumerate() {
let stage_fn = &stage.function.sig.ident;
let next = format_ident!("__enc_{}_s{}", obj_ident, order);
encode.extend(quote! {
let #next = match #stage_fn(&mut env, #previous) {
::core::result::Result::Ok(__w) => __w,
::core::result::Result::Err(__e) => {
#conv_fail
}
};
});
previous = quote!(#next);
}
let conv = out_entry.converter_ident();
encode.extend(quote! {
let #enc = match #conv(&mut env, #previous) {
::core::result::Result::Ok(__w) => __w,
::core::result::Result::Err(__e) => {
#conv_fail
}
};
});
if prim {
let letter = jni_field_access(&wire)
.expect("leaf_is_prim guarantees a primitive wire")
.1;
quote! {
#encode
#obj_ident = jni::sys::jvalue { #letter: #enc };
}
} else {
let cast = cast_wire_to_jobject(&enc, &wire, fail);
quote! {
#encode
#obj_ident = #cast;
}
}
}