use kotlin_codegen::KtType;
use prebindgen_registry::{Building, Conversions, Crossing, RegistryBuilder};
use super::*;
fn generated_converter_attr() -> syn::Attribute {
syn::parse_quote!(#[allow(
non_snake_case,
unused_mut,
unused_variables,
unused_braces,
// A representation-agnostic converter says the same thing for every
// spelling, so the plain spelling gets the degenerate form of it: a
// reflexive `.into()`, a deref that is a no-op, parens around a value
// that needed none. Suppressing per-shape would mean asking which
// spelling this is, which is the guessing #270 removed.
unused_parens,
dead_code,
clippy::useless_conversion,
clippy::needless_question_mark,
clippy::let_and_return,
clippy::nonminimal_bool,
clippy::eq_op
)])
}
impl Declarations {
pub(crate) fn build_input_fn_composed(
&self,
rust: &syn::Type,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
) -> syn::ItemFn {
let spelled = rust.to_token_stream();
self.build_input_fn_parts(&spelled, &spelled, wire, body, exc)
}
pub(crate) fn build_input_fn_of(
&self,
rust: &prebindgen_registry::flat::TypeRef,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
emit: &prebindgen_registry::Emit,
) -> syn::ItemFn {
let spelled = emit.spell(rust);
let rust_with_lifetime = match rust.kind() {
prebindgen_registry::flat::TypeKind::Ref {
lifetime: None,
mutable,
inner,
} => {
let inner = emit.spell(inner);
let m = if *mutable { quote!(mut) } else { quote!() };
quote!(&'env #m #inner)
}
_ => spelled.clone(),
};
self.build_input_fn_parts(&spelled, &rust_with_lifetime, wire, body, exc)
}
pub(crate) fn build_input_fn_parts(
&self,
rust: &TokenStream,
rust_with_lifetime: &TokenStream,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
) -> syn::ItemFn {
let name = input_name(rust, wire);
let wire_with_lifetime = annotate_jobject_with_lifetime(wire, "v");
let err_type = exc.cloned().unwrap_or_else(default_err_type);
let ret_body = body_for_exc(body, exc);
let gen_allow = generated_converter_attr();
if matches!(wire, syn::Type::Ptr(_)) {
syn::parse_quote!(
#gen_allow
pub(crate) unsafe fn #name<'env>(env: &mut jni::JNIEnv<'env>, v: #wire) -> ::core::result::Result<#rust_with_lifetime, #err_type> {
#ret_body
}
)
} else {
syn::parse_quote!(
#gen_allow
pub(crate) unsafe fn #name<'env, 'v>(env: &mut jni::JNIEnv<'env>, v: &#wire_with_lifetime) -> ::core::result::Result<#rust_with_lifetime, #err_type> {
#ret_body
}
)
}
}
pub(crate) fn build_output_fn(
&self,
rust: &syn::Type,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
) -> syn::ItemFn {
self.build_output_fn_parts(&rust.to_token_stream(), wire, body, exc)
}
pub(crate) fn build_output_fn_of(
&self,
rust: &prebindgen_registry::flat::TypeRef,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
emit: &prebindgen_registry::Emit,
) -> syn::ItemFn {
self.build_output_fn_parts(&emit.spell(rust), wire, body, exc)
}
pub(crate) fn build_output_fn_parts(
&self,
rust: &TokenStream,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
) -> syn::ItemFn {
let name = output_name(rust, wire);
let wire_with_lifetime = annotate_jobject_with_lifetime(wire, "a");
let err_type = exc.cloned().unwrap_or_else(default_err_type);
let ret_body = body_for_exc(body, exc);
let gen_allow = generated_converter_attr();
syn::parse_quote!(
#gen_allow
pub(crate) unsafe fn #name<'a>(env: &mut jni::JNIEnv<'a>, v: #rust) -> ::core::result::Result<#wire_with_lifetime, #err_type> {
#ret_body
}
)
}
fn str_ref_output(&self) -> ConverterImpl<KotlinMeta> {
let outer_ty: syn::Type = syn::parse_quote!(&str);
let wire: syn::Type = syn::parse_quote!(jni::objects::JString);
let body: syn::Expr = syn::parse_quote!({
env.new_string(v).map_err(|e| {
<__JniErr as ::core::convert::From<String>>::from(format!("encode_str: {}", e))
})?
});
let kotlin_name =
self.override_kotlin_name(&TypeKey::from_type(&outer_ty), Some(KtType::string()));
let niches = default_niches_for_wire(&wire);
ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn(&outer_ty, &wire, &body, None),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
}
}
fn cow_bytes_output(
&self,
ty: &prebindgen_registry::flat::TypeRef,
) -> Option<ConverterImpl<KotlinMeta>> {
let prebindgen_registry::flat::TypeKind::Cow { inner, .. } = ty.kind() else {
return None;
};
if inner.key().as_str() != "[u8]" {
return None;
}
let norm_ty: syn::Type = syn::parse_quote!(::std::borrow::Cow<'_, [u8]>);
let wire: syn::Type = syn::parse_quote!(jni::objects::JByteArray);
let body: syn::Expr = syn::parse_quote!({
env.byte_array_from_slice(&v).map_err(|e| {
<__JniErr as ::core::convert::From<String>>::from(format!(
"encode_byte_array: {}",
e
))
})?
});
let kotlin_name = self.override_kotlin_name(&ty.key(), Some(KtType::byte_array()));
let niches = default_niches_for_wire(&wire);
Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn(&norm_ty, &wire, &body, None),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
})
}
pub fn opaque_handle_input(
&self,
reading: &prebindgen_registry::flat::TypeRef,
emit: &prebindgen_registry::Emit,
) -> ConverterImpl<KotlinMeta> {
let wire: syn::Type = syn::parse_quote!(jni::sys::jlong);
let ty = emit.spell(reading);
let name = input_name(&ty, &wire);
let gen_allow = generated_converter_attr();
let function: syn::ItemFn = syn::parse_quote!(
#gen_allow
pub(crate) unsafe fn #name<'env, 'v>(
env: &mut jni::JNIEnv<'env>,
v: &jni::sys::jlong,
) -> ::core::result::Result<OwnedObject<#ty>, __JniErr> {
if *v == 0 || (*v & 1) == 1 {
return ::core::result::Result::Err(
<__JniErr as ::core::convert::From<String>>::from(
"Operation on a closed native handle.".to_string(),
),
);
}
Ok(unsafe { OwnedObject::from_raw(*v as *const #ty) })
}
);
ConverterImpl {
subs: vec![],
function,
destination: wire,
pre_stages: vec![],
niches: Niches::one(syn::parse_quote!(0i64), syn::parse_quote!(*v == 0)),
metadata: self.opaque_leaf_meta(reading.key()),
}
}
fn opaque_leaf_meta(&self, key: TypeKey) -> KotlinMeta {
KotlinMeta {
projection: Some(Projection {
leaf_key: key,
owned: true,
strategy: FoldStrategy::Base,
kind: ProjectionKind::Handle,
niche_sentinels: Vec::new(),
}),
..self.framework_meta(Some(KtType::cls("Long")))
}
}
fn unsigned64_leaf_meta(&self) -> KotlinMeta {
KotlinMeta {
projection: Some(Projection {
leaf_key: TypeKey::parse("u64").expect("builtin type key"),
owned: false,
strategy: FoldStrategy::Base,
kind: ProjectionKind::Unsigned64,
niche_sentinels: Vec::new(),
}),
..self.framework_meta(Some(KtType::long()))
}
}
pub(crate) fn override_kotlin_name(
&self,
key: &TypeKey,
inherited: Option<KtType>,
) -> Option<KtType> {
if let Some(cfg) = self.types.get(key) {
if !cfg.is_opaque() {
if let Some(spec) = &cfg.name_spec {
return Some(KtType::cls(self.fqn_of(spec)));
}
}
}
inherited
}
pub fn input_converter_name(&self, rust: &syn::Type, wire: &syn::Type) -> syn::Ident {
input_name(&rust.to_token_stream(), wire)
}
pub fn output_converter_name(&self, rust: &syn::Type, wire: &syn::Type) -> syn::Ident {
output_name(&rust.to_token_stream(), wire)
}
fn emitted_source_type_names(
&self,
registry: &Registry<KotlinMeta>,
) -> std::collections::HashMap<String, syn::Path> {
let mut names = std::collections::HashMap::new();
let mut add = |key: &TypeKey| {
if let Some(short) = rust_short_name_opt(key) {
let Ok(ident) = syn::parse_str::<syn::Ident>(&short) else {
return;
};
let module = registry
.origin_module(&ident)
.unwrap_or_else(|| self.default_module(registry));
names.insert(short, module);
}
};
for key in self.types.keys() {
add(key);
}
for (key, _) in self.rust_side_only_types().collect::<Vec<_>>() {
add(&key);
}
for decl in &self.convert_decls {
add(decl.key());
}
names
}
fn qualify_item(&self, item: &mut syn::Item, registry: &Registry<KotlinMeta>) {
let source_names = self.emitted_source_type_names(registry);
let length_names: std::collections::HashMap<String, syn::Path> = registry
.named_item_idents()
.map(|ident| {
let module = registry
.origin_module(ident)
.unwrap_or_else(|| self.default_module(registry));
(ident.to_string(), module)
})
.collect();
let mut visitor = QualifyEmittedTypes {
source_names: &source_names,
length_names: &length_names,
};
syn::visit_mut::VisitMut::visit_item_mut(&mut visitor, item);
}
pub fn opaque_handle_output(
&self,
reading: &prebindgen_registry::flat::TypeRef,
emit: &prebindgen_registry::Emit,
) -> ConverterImpl<KotlinMeta> {
let wire: syn::Type = syn::parse_quote!(jni::sys::jlong);
let body: syn::Expr =
syn::parse_quote!(std::boxed::Box::into_raw(std::boxed::Box::new(v)) as i64);
ConverterImpl {
subs: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
pre_stages: vec![],
niches: Niches::one(syn::parse_quote!(0i64), syn::parse_quote!(*v == 0)),
metadata: self.opaque_leaf_meta(reading.key()),
}
}
}
pub(crate) fn build_signal_binding_error_item() -> syn::Item {
syn::parse_quote!(
#[allow(non_snake_case, dead_code)]
pub(crate) fn signal_binding_error(
env: &mut jni::JNIEnv,
sink: &jni::objects::JObject,
mid: &::prebindgen_jni_runtime::CachedIfaceMethod,
fqn: &str,
descr: &str,
je: &str,
) {
if env.exception_check().unwrap_or(false) {
return;
}
let __je: jni::objects::JObject = match env.new_string(je) {
Ok(s) => s.into(),
Err(e) => {
tracing::error!("signal_binding_error: new_string failed: {}", e);
return;
}
};
let __args = [jni::sys::jvalue { l: __je.as_raw() }];
if let Err(e) = mid.call_object(env, fqn, "run", descr, sink, &__args) {
tracing::error!("signal_binding_error: error-callback invoke failed: {}", e);
}
}
)
}
pub(crate) fn build_signal_domain_error_item() -> syn::Item {
syn::parse_quote!(
#[allow(non_snake_case, dead_code)]
pub(crate) fn signal_domain_error(
env: &mut jni::JNIEnv,
sink: &jni::objects::JObject,
mid: &::prebindgen_jni_runtime::CachedIfaceMethod,
fqn: &str,
descr: &str,
ze: &[jni::sys::jvalue],
) {
if env.exception_check().unwrap_or(false) {
return;
}
if let Err(e) = mid.call_object(env, fqn, "run", descr, sink, ze) {
tracing::error!("signal_domain_error: error-callback invoke failed: {}", e);
}
}
)
}
pub(crate) fn build_handle_destructor_items(
ext: &Declarations,
registry: &Registry<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Vec<syn::Item> {
let mut named: Vec<(String, syn::Item)> = Vec::new();
for (key, cfg) in &ext.types {
if !cfg.is_opaque() {
continue;
}
let Some(reading) = registry.reading(key) else {
continue;
};
if registry.input_entry(&reading).is_none() && registry.output_entry(&reading).is_none() {
continue;
}
let ty = emit.spell(&reading);
let class_fqn = cfg
.name_spec
.as_ref()
.map(|s| ext.fqn_of(s))
.unwrap_or_else(|| {
panic!(
"build_handle_destructor_items: opaque handle `{}` has no \
name spec to derive a destructor symbol from",
key.as_str()
)
});
let class_short = class_fqn.rsplit('.').next().unwrap_or(&class_fqn);
let class_package = class_fqn.rsplit_once('.').map(|(pkg, _)| pkg).unwrap_or("");
let free_ptr = ext.mangle_method(class_package, class_short, "freePtr");
let symbol = super::symbol::native_symbol(class_package, class_short, &free_ptr);
let ident = syn::Ident::new(&symbol, Span::call_site());
let item: syn::Item = syn::parse_quote!(
const _: () = {
if ::core::mem::align_of::<#ty>() < 2 {
panic!(
"opaque handle types must have alignment >= 2 (bit 0 is the closed tag)"
);
}
};
);
named.push((format!("{symbol}__align_assert"), item));
let item: syn::Item = syn::parse_quote!(
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
pub(crate) unsafe extern "C" fn #ident(
_env: jni::JNIEnv,
_class: jni::objects::JClass,
ptr: jni::sys::jlong,
) {
if ptr != 0 && (ptr & 1) == 0 {
drop(Box::from_raw(ptr as *mut #ty));
}
}
);
named.push((symbol, item));
}
named.sort_by(|a, b| a.0.cmp(&b.0));
named.into_iter().map(|(_, item)| item).collect()
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum WrapperShape {
Borrow { mutable: bool },
OptionRef { mutable: bool },
Sequence,
Optional,
}
struct WrapperOps {
name: &'static str,
read: Option<fn(TokenStream) -> TokenStream>,
build: Option<fn(TokenStream) -> TokenStream>,
}
const WRAPPER_OPS: &[WrapperOps] = &[
WrapperOps {
name: "Box",
read: Some(|e| quote!(*#e)),
build: Some(|e| quote!(::std::boxed::Box::new(#e))),
},
WrapperOps {
name: "Cow",
read: None,
build: None,
},
];
fn wrapper_ops(name: &str) -> Option<&'static WrapperOps> {
WRAPPER_OPS.iter().find(|w| w.name == name)
}
pub(crate) enum Produced<'a> {
Reading(&'a prebindgen_registry::flat::TypeRef),
Composed(Box<syn::Type>),
}
impl Produced<'_> {
fn bridge_layers(&self) -> Option<Vec<&'static WrapperOps>> {
match self {
Produced::Reading(r) => r.erased_wrappers().into_iter().map(wrapper_ops).collect(),
Produced::Composed(_) => Some(Vec::new()),
}
}
fn is_canonical(&self) -> bool {
self.bridge_layers().is_some_and(|l| l.is_empty())
}
fn spell(&self, emit: &prebindgen_registry::Emit) -> TokenStream {
match self {
Produced::Reading(r) => emit.spell(r),
Produced::Composed(t) => t.to_token_stream(),
}
}
fn key(&self) -> TypeKey {
match self {
Produced::Reading(r) => r.key(),
Produced::Composed(t) => TypeKey::from_type(t),
}
}
fn borrow(&self) -> Option<(&prebindgen_registry::flat::TypeRef, bool)> {
match self {
Produced::Reading(r) => match r.kind() {
prebindgen_registry::flat::TypeKind::Ref { mutable, inner, .. } => {
Some((inner, *mutable))
}
_ => None,
},
Produced::Composed(_) => None,
}
}
}
impl Declarations {
fn build_input_fn_produced(
&self,
produced: &Produced<'_>,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
emit: &prebindgen_registry::Emit,
) -> syn::ItemFn {
match produced {
Produced::Reading(r) => self.build_input_fn_of(r, wire, body, exc, emit),
Produced::Composed(t) => self.build_input_fn_composed(t, wire, body, exc),
}
}
fn build_output_fn_produced(
&self,
produced: &Produced<'_>,
wire: &syn::Type,
body: &syn::Expr,
exc: Option<&syn::Type>,
emit: &prebindgen_registry::Emit,
) -> syn::ItemFn {
match produced {
Produced::Reading(r) => self.build_output_fn_of(r, wire, body, exc, emit),
Produced::Composed(t) => self.build_output_fn(t, wire, body, exc),
}
}
}
fn read_as_canonical(produced: &Produced<'_>) -> Option<TokenStream> {
let layers = produced.bridge_layers()?;
let mut e = quote!(v);
for w in layers {
e = (w.read?)(e);
}
Some(e)
}
fn build_from_canonical(produced: &Produced<'_>, value: TokenStream) -> Option<TokenStream> {
let layers = produced.bridge_layers()?;
let mut e = value;
for w in layers.into_iter().rev() {
e = (w.build?)(e);
}
Some(e)
}
pub(crate) fn read_through_erased_wrappers(
ty: &prebindgen_registry::flat::TypeRef,
e: TokenStream,
) -> Option<TokenStream> {
let mut out = e;
for name in ty.erased_wrappers() {
out = (wrapper_ops(name)?.read?)(out);
}
Some(out)
}
pub(crate) fn build_through_erased_wrappers(
ty: &prebindgen_registry::flat::TypeRef,
value: TokenStream,
) -> Option<TokenStream> {
build_through_wrappers(&ty.erased_wrappers(), value)
}
pub(crate) fn build_through_wrappers(
names: &[&'static str],
value: TokenStream,
) -> Option<TokenStream> {
let mut out = value;
for name in names.iter().rev() {
out = (wrapper_ops(name)?.build?)(out);
}
Some(out)
}
impl Declarations {
fn input_borrow(
&self,
shape: WrapperShape,
produced: &Produced<'_>,
t1: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
) -> Option<ConverterImpl<KotlinMeta>> {
let WrapperShape::Borrow { .. } = shape else {
return None;
};
if !produced.is_canonical() {
return None;
}
let inner = registry.input_entry(t1)?;
let outer_ty = produced.key();
let kotlin_name = self.override_kotlin_name(&outer_ty, inner.metadata.kotlin_name.clone());
let projection = inner
.metadata
.projection
.clone()
.map(|h| Projection { owned: false, ..h });
Some(ConverterImpl {
subs: vec![],
destination: inner.destination.clone(),
function: inner.function.clone(),
pre_stages: vec![],
niches: inner.niches.clone(),
metadata: KotlinMeta {
kotlin_name,
value_rust_type: None,
projection,
},
})
}
fn input_option_ref(
&self,
shape: WrapperShape,
produced: &Produced<'_>,
t1: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let t1_ty = emit.spell(t1);
let WrapperShape::OptionRef { .. } = shape else {
return None;
};
if !produced.is_canonical() {
return None;
}
let inner = registry.input_entry(t1)?;
if !inner.metadata.is_direct_handle() {
return None;
}
let inner_wire = inner.destination.clone();
let inner_conv = inner.function.sig.ident.clone();
let outer_ty = produced.key();
let outer_spelled = produced.spell(emit);
let name = input_name(&outer_spelled, &inner_wire);
let gen_allow = generated_converter_attr();
let function: syn::ItemFn = syn::parse_quote!(
#gen_allow
pub(crate) unsafe fn #name<'env, 'v>(
env: &mut jni::JNIEnv<'env>,
v: &#inner_wire,
) -> ::core::result::Result<Option<OwnedObject<#t1_ty>>, __JniErr> {
Ok({
if *v == 0 { None } else { Some(#inner_conv(env, v)?) }
})
}
);
let kotlin_name = self.override_kotlin_name(&outer_ty, inner.metadata.kotlin_name.clone());
let projection = inner.metadata.projection.clone().map(|h| Projection {
owned: false,
strategy: FoldStrategy::Optional(NullableKind::Niche, Box::new(h.strategy)),
..h
});
Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function,
destination: inner_wire,
niches: Niches::empty(),
metadata: KotlinMeta {
kotlin_name,
value_rust_type: None,
projection,
},
})
}
fn input_vec(
&self,
shape: WrapperShape,
produced: &Produced<'_>,
t1: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let t1_ty = emit.spell(t1);
if shape != WrapperShape::Sequence {
return None;
}
let inner = registry.input_entry(t1)?;
reject_vec_of_handle(&inner.metadata.projection, t1);
let inner_wire = inner.destination.clone();
if !is_jobject_shaped_wire(&inner_wire) {
return None;
}
let inner_conv = crate::jni::emit::composed_inner_input(inner, quote::quote!(&__elem_wire));
let outer_ty = produced.key();
let build = build_from_canonical(produced, quote::quote!(__out))?;
let wire: syn::Type = syn::parse_quote!(jni::objects::JObject);
let body: syn::Expr = syn::parse_quote!({
let __list = jni::objects::JList::from_env(env, v)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("Vec<_>: list-from-env: {}", e)))?;
let mut __it = __list.iter(env)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("Vec<_>: list-iter: {}", e)))?;
let mut __out: Vec<#t1_ty> = Vec::new();
while let Some(__obj) = __it.next(env)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("Vec<_>: list-next: {}", e)))?
{
let __elem_wire: #inner_wire = __obj.into();
let __elem: #t1_ty = #inner_conv;
__out.push(__elem);
}
#build
});
let inner_kotlin = inner.metadata.kotlin_name.clone()?;
let kotlin_name = self.override_kotlin_name(
&outer_ty,
Some(KtType::generic("List", [inner_kotlin])),
);
Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_produced(produced, &wire, &body, None, emit),
destination: wire,
niches: Niches::empty(),
metadata: KotlinMeta {
kotlin_name,
value_rust_type: None,
projection: None,
},
})
}
fn input_option(
&self,
shape: WrapperShape,
produced: &Produced<'_>,
t1: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let t1_ty = emit.spell(t1);
if shape == WrapperShape::Optional {
let inner = registry.input_entry(t1)?;
if inner.metadata.is_direct_handle() {
let inner_wire = inner.destination.clone();
let outer_ty = produced.key();
let outer_spelled = produced.spell(emit);
let build = build_from_canonical(produced, quote::quote!(__v))?;
let name = input_name(&outer_spelled, &inner_wire);
let gen_allow = generated_converter_attr();
let function: syn::ItemFn = syn::parse_quote!(
#gen_allow
pub(crate) unsafe fn #name<'env, 'v>(
env: &mut jni::JNIEnv<'env>,
v: &#inner_wire,
) -> ::core::result::Result<#outer_spelled, __JniErr> {
Ok({
let __v: ::core::option::Option<#t1_ty> = if *v == 0 {
None
} else if (*v & 1) == 1 {
return ::core::result::Result::Err(
<__JniErr as ::core::convert::From<String>>::from(
"Operation on a closed native handle.".to_string(),
),
);
} else {
Some(*std::boxed::Box::from_raw(*v as *mut #t1_ty))
};
#build
})
}
);
let kotlin_name =
self.override_kotlin_name(&outer_ty, inner.metadata.kotlin_name.clone());
let projection = inner.metadata.projection.clone().map(|h| Projection {
owned: true,
strategy: FoldStrategy::Optional(NullableKind::Niche, Box::new(h.strategy)),
..h
});
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function,
destination: inner_wire,
niches: Niches::empty(),
metadata: KotlinMeta {
kotlin_name,
value_rust_type: None,
projection,
},
});
}
}
if shape == WrapperShape::Optional {
let outer_ty = produced.key();
let build = build_from_canonical(produced, quote::quote!(__v))?;
let (wire, inner_body, niches) = option_input(t1, registry, emit)?;
let body: syn::Expr = syn::parse_quote!({
let __v: ::core::option::Option<#t1_ty> = #inner_body;
#build
});
let inherited = registry
.input_entry(t1)
.and_then(|e| e.metadata.kotlin_name.clone());
let kotlin_name = self.override_kotlin_name(&outer_ty, inherited);
let nullable_kind = nullable_kind_for(&wire, t1, registry);
let projection = registry
.input_entry(t1)
.and_then(|e| e.metadata.projection.clone())
.map(|h| Projection {
strategy: FoldStrategy::Optional(nullable_kind, Box::new(h.strategy)),
..h
});
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_produced(produced, &wire, &body, None, emit),
destination: wire,
niches,
metadata: KotlinMeta {
projection,
..self.framework_meta(kotlin_name)
},
});
}
None
}
fn is_leaf_vec_element(&self, elem: &prebindgen_registry::flat::TypeRef) -> bool {
match self.types.get(&elem.key()) {
Some(cfg) => cfg.is_opaque(),
None => {
matches!(elem.kind(), prebindgen_registry::flat::TypeKind::String)
|| elem.key().as_str() == "u64"
}
}
}
}
impl JniGenBuilder {
pub fn build(self) -> Result<JniGen, prebindgen_registry::WriteRustError> {
let flat = self
.sources
.clone()
.build()
.map_err(prebindgen_registry::ScanError::from)?;
let registry = prebindgen_registry::Registry::builder(flat)?;
self.build_with(registry)
}
pub(crate) fn build_with(
self,
registry: prebindgen_registry::RegistryBuilder<KotlinMeta>,
) -> Result<JniGen, prebindgen_registry::WriteRustError> {
let decls = self.decls;
let registry = decls
.declare_into(registry)?
.validate_with(&decls)?
.convert_with(|crossing, built, emit| decls.convert_crossing(crossing, built, emit))?
.build()?;
decls
.validate_resolved(®istry)
.map_err(|message| prebindgen_registry::ScanError::AdapterInvariant { message })?;
Ok(JniGen { decls, registry })
}
}
impl Declarations {
fn convert_crossing(
&self,
crossing: &Crossing,
built: &Building<'_, KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let (dir, key) = crossing;
let reading = built.reading(key)?;
match dir {
Direction::Input => self.select_input_type(&reading, built, emit).or_else(|| {
let prebindgen_registry::flat::TypeKind::Callback { args } =
reading.unwrapped().kind()
else {
return None;
};
self.dispatch_fn_input(args, built, emit)
}),
Direction::Output => self.select_output_type(&reading, built, emit),
}
}
pub fn declare_into(
&self,
mut registry: RegistryBuilder<KotlinMeta>,
) -> Result<RegistryBuilder<KotlinMeta>, prebindgen_registry::ScanError> {
for (item_fn, origin) in self.collect_local_functions() {
registry = registry.local_function(item_fn, origin)?;
}
for ident in self.declared_functions() {
registry = registry.export(&ident);
}
for ident in self.helper_functions() {
registry = registry.reference(&ident);
}
registry = registry.declares_consts();
for ident in self.declared_consts().into_iter().flatten() {
registry = registry.export_const(&ident);
}
for ty in self.declared_types().into_values() {
registry = registry.export_type(ty);
}
for ident in self.accessor_functions() {
registry = registry.accessor(&ident);
}
for (ident, receiver) in self.method_receivers() {
registry = registry.method_receiver(&ident, receiver);
}
for ty in self.required_output_types() {
registry = registry.cross(Direction::Output, &ty);
}
let mut convert_edges: Vec<(Crossing, Crossing)> = Vec::new();
for decl in &self.convert_decls {
if let Some(ty) = self.convert_target(decl.key(), ®istry, Direction::Input) {
registry = registry.cross(Direction::Input, &ty);
convert_edges.push((
(Direction::Input, decl.key().clone()),
(Direction::Input, TypeKey::from_type(&ty)),
));
}
if let Some(ty) = self.convert_target(decl.key(), ®istry, Direction::Output) {
registry = registry.cross(Direction::Output, &ty);
convert_edges.push((
(Direction::Output, decl.key().clone()),
(Direction::Output, TypeKey::from_type(&ty)),
));
}
}
for (from, on) in convert_edges {
registry = registry.depends(from, on);
}
let decompositions = prebindgen_registry::Decompositions {
expansions: Some(self.build_expansions()),
deconstructors: Some(self.build_deconstructors(®istry)),
value_structs: self.build_value_struct_decons(®istry),
sums: self.build_sum_decons(®istry),
leaf_vec_elements: self.build_leaf_vec_fold_elements(®istry),
replaces: self.boundary_only_types(),
};
registry = registry.decompose(decompositions);
Ok(registry)
}
}
impl Declarations {
pub(crate) fn build_value_struct_decons(
&self,
registry: &impl Conversions<KotlinMeta>,
) -> Vec<prebindgen_registry::unfold::ValueDecon> {
let mut out = Vec::new();
for item_struct in registry.flat().types().filter_map(|t| match t {
prebindgen_registry::flat::Type::Struct(s) => Some(s),
_ => None,
}) {
let reading = item_struct.type_ref();
let key = reading.key();
let is_data_class = matches!(
self.type_kind(registry, &reading.key()),
TypeKind::DataStruct { cfg: Some(c), .. } if c.name_spec.is_some()
);
if !is_data_class {
continue;
}
if let Some(leaves) =
crate::jni::synth_value_struct_leaves(self, registry, item_struct, &[], "", 0)
{
if !leaves.is_empty() {
out.push(prebindgen_registry::unfold::ValueDecon {
key,
source: reading.clone(),
leaves,
});
}
}
}
out
}
pub(crate) fn build_sum_decons(
&self,
registry: &impl Conversions<KotlinMeta>,
) -> Vec<prebindgen_registry::unfold::SumDecon> {
let mut keys: Vec<&TypeKey> = self.types.keys().collect();
keys.sort_by(|a, b| a.as_str().cmp(b.as_str()));
let mut out = Vec::new();
for key in keys {
let Some(sum_cfg) = self.types[key].sum() else {
continue;
};
let Some(ident) = self.types[key].rust_type.key().ident() else {
continue;
};
let Some(prebindgen_registry::flat::Type::Variant(sum)) =
registry.flat().declared_type(&ident)
else {
continue;
};
out.push(prebindgen_registry::unfold::SumDecon {
key: key.clone(),
source: sum.type_ref().clone(),
leaves: crate::jni::synth_sum_leaves(self, sum_cfg, sum),
});
}
out
}
pub(crate) fn build_leaf_vec_fold_elements(
&self,
registry: &impl Conversions<KotlinMeta>,
) -> Vec<TypeKey> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
let mut consider = |bare: &prebindgen_registry::flat::TypeRef| {
if seen.insert(bare.key()) && self.is_leaf_vec_element(bare) {
out.push(bare.key());
}
};
for f in registry.flat().functions() {
{
let after_opt = match f.ret.kind() {
prebindgen_registry::flat::TypeKind::Optional(inner) => inner,
_ => &f.ret,
};
if let prebindgen_registry::flat::TypeKind::Vec(elem) = after_opt.kind() {
consider(peel_one_borrow(elem));
}
}
for p in &f.params {
let Some(args) = p.ty.callback_args() else {
continue;
};
for arg in args {
if let prebindgen_registry::flat::TypeKind::Slice(elem) =
peel_one_borrow(arg).kind()
{
consider(peel_one_borrow(elem));
}
}
}
}
out
}
}
fn peel_one_borrow(t: &prebindgen_registry::flat::TypeRef) -> &prebindgen_registry::flat::TypeRef {
match t.kind() {
prebindgen_registry::flat::TypeKind::Ref { inner, .. } => inner,
_ => t,
}
}
fn flat_unit_enum<'r>(
registry: &'r impl Conversions<KotlinMeta>,
name: &syn::Ident,
declarator: &str,
) -> Option<&'r prebindgen_registry::flat::Enum> {
match registry.flat().declared_type(name)? {
prebindgen_registry::flat::Type::Enum(e) => Some(e),
prebindgen_registry::flat::Type::Variant(v) => {
let offender = v
.alternatives
.iter()
.find(|a| !a.is_empty())
.map(|a| a.name.to_string())
.unwrap_or_default();
panic!(
"`{name}` is a data-carrying enum (variant `{offender}` has fields): declare \
it with `sealed_class!({name})`, not `{declarator}!({name})` — \
`{declarator}` crosses the boundary as a bare discriminant and has no room \
for a payload"
)
}
_ => None,
}
}
impl Declarations {
fn dispatch_fn_input(
&self,
args: &[prebindgen_registry::flat::TypeRef],
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let outer_ty = build_fn_type(args, emit);
let (wire, body) = callback_input(self, args, registry, emit)?;
let niches = default_niches_for_wire(&wire);
Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_composed(&outer_ty, &wire, &body, None),
destination: wire,
niches,
metadata: self.framework_meta(Some(KtType::any())),
})
}
}
impl Prebindgen for Declarations {
type Metadata = KotlinMeta;
fn validate(&self, binding: &Building<'_, Self::Metadata>) -> Result<(), String> {
prebindgen_registry::warn_unclaimed(binding.flat(), &self.claimed());
for (key, members) in &self.class_members {
for m in members {
let Some(func) = binding.flat().function(&m.rust_ident) else {
continue;
};
match m.kind {
MemberKind::Method => {
let has_receiver =
func.params.iter().any(|p| &peel_receiver_key(&p.ty) == key);
if !has_receiver {
let took: Vec<String> =
func.params.iter().map(|p| p.ty.to_string()).collect();
return Err(format!(
"class `{}` method `{}`: no parameter of type `{}` — an \
instance method's receiver must appear in the signature \
(took: {})",
key.as_str(),
m.rust_ident,
key.as_str(),
if took.is_empty() {
"no parameters".to_string()
} else {
took.join(", ")
}
));
}
}
MemberKind::Constructor => {
let core = func.ret.fallible_parts().map_or(&func.ret, |(ok, _)| ok);
if &peel_receiver_key(core) != key {
return Err(format!(
"class `{}` constructor `{}`: must return `{}` or \
`Result<{}, E>` — it returns `{}`",
key.as_str(),
m.rust_ident,
key.as_str(),
key.as_str(),
func.ret
));
}
}
}
}
}
for ident in self.declared_functions() {
let Some(func) = binding.flat().function(&ident) else {
continue;
};
if let Some((ok, _)) = func.ret.fallible_parts() {
{
let core = crate::util::head_type(ok);
if matches!(self.type_kind(binding, &core.key()), TypeKind::Sum) {
return Err(format!(
"fn `{ident}`: `Result<{}, _>` — a sealed_class value is not \
supported in the success position of a fallible return. A sum \
crosses decomposed (a tag plus one leaf group per variant) \
through a builder callback, which the `Result` lane does not \
have — a fallible return keeps its whole-value converter so a \
factory can still hand back a handle. Return `{}` directly and \
report failure through the error channel, or model the failure \
as one of the sum's own variants",
ok, ok,
));
}
}
}
if let Some((_, err_ty)) = func.ret.fallible_parts() {
{
let core = crate::util::head_type(err_ty);
let declared = self
.return_expand_decls
.iter()
.any(|d| *d.key() == err_ty.key());
if !declared && matches!(self.type_kind(binding, &core.key()), TypeKind::Sum) {
return Err(format!(
"fn `{ident}`: `Result<_, {}>` — `{}` is declared `sealed_class!`, \
but nothing decomposes it in the error position, so it would be \
delivered as `e.to_string()` on the plain binding-error channel \
rather than as the sealed hierarchy (and would silently require \
`{}: Display`). Declare `expand_return!({})` with the fields to \
deliver, so the error crosses through the typed domain-handler \
channel; or, if a text message really is what you want, drop the \
`sealed_class!` declaration for this type",
err_ty,
core,
err_ty,
err_ty,
));
}
}
}
for p in &func.params {
let Some(args) = p.ty.callback_args() else {
continue;
};
for arg in args {
let prebindgen_registry::flat::TypeKind::Slice(elem) =
peel_one_borrow(arg).kind()
else {
continue;
};
let elem = peel_one_borrow(elem);
if matches!(self.type_kind(binding, &elem.key()), TypeKind::Sum) {
return Err(format!(
"fn `{ident}`: `impl Fn(&[{}])` — a slice of a sealed_class value \
is not supported as a callback argument. A sum crosses as a tag \
plus one leaf group per variant, and folding a *sequence* of \
those into the foreign list needs the element folder a `Vec<{}>` \
RETURN provides; declare the callback over one value \
(`impl Fn({})`) or return `Vec<{}>` instead",
elem, elem, elem, elem,
));
}
}
}
}
Ok(())
}
fn validate_resolved(&self, registry: &Registry<KotlinMeta>) -> Result<(), String> {
validate_bindings(self, registry)
}
fn prerequisites(
&self,
registry: &Registry<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Vec<syn::Item> {
let error_type = framework_error_type();
let alias: syn::Item = syn::parse_quote!(
#[allow(dead_code)]
pub(crate) type __JniErr = #error_type;
);
let mut items = vec![alias];
items.extend(owned_object_prerequisite_items());
items.push(build_signal_binding_error_item());
items.push(build_signal_domain_error_item());
let _ = registry;
items.extend(build_handle_destructor_items(self, registry, emit));
items.extend(build_vec_build_helper_items(self, registry, emit));
let mut glob_modules = registry.all_source_modules();
if glob_modules.is_empty() {
glob_modules.push(self.default_module(registry));
}
for decl in self.packages.values().flat_map(|p| &p.constant_exprs) {
validate_constant_expr(self, &decl.kotlin_name, &decl.ty);
let getter = const_expr_getter_fn(&decl.kotlin_name, &decl.ty, registry);
let expr = &decl.expr;
let callee: syn::Expr = syn::parse_quote!({
#(
#[allow(unused_imports)]
use #glob_modules::*;
)*
#expr
});
let wrapper =
emit_jni_function_wrapper_with_callee(self, &getter, registry, Some(callee), emit);
items.push(syn::parse2::<syn::Item>(wrapper).expect(
"constant_expr: generated getter wrapper is a single item by construction",
));
}
items
}
fn post_process_item(
&self,
item: &mut syn::Item,
registry: &Registry<KotlinMeta>,
_emit: &prebindgen_registry::Emit,
) {
self.qualify_item(item, registry);
}
fn on_function(
&self,
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> TokenStream {
emit_jni_function_wrapper(self, f, registry, emit)
}
fn on_struct(
&self,
_s: &prebindgen_registry::flat::Struct,
_registry: &Registry<KotlinMeta>,
_emit: &prebindgen_registry::Emit,
) -> TokenStream {
TokenStream::new()
}
fn on_variant(
&self,
_v: &prebindgen_registry::flat::Variant,
_registry: &Registry<KotlinMeta>,
_emit: &prebindgen_registry::Emit,
) -> TokenStream {
TokenStream::new()
}
fn on_enum(
&self,
_e: &prebindgen_registry::flat::Enum,
_registry: &Registry<KotlinMeta>,
_emit: &prebindgen_registry::Emit,
) -> TokenStream {
TokenStream::new()
}
fn on_const(
&self,
c: &prebindgen_registry::flat::Constant,
registry: &Registry<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> TokenStream {
reject_handle_const(self, c);
let getter = const_getter_fn(c);
let const_ident = &c.name;
let source_module = self.fn_module(registry, const_ident);
let callee: syn::Expr = syn::parse_quote!(#source_module::#const_ident);
let wrapper =
emit_jni_function_wrapper_with_callee(self, &getter, registry, Some(callee), emit);
let alias = emit.const_alias(c, &source_module);
quote! {
#alias
#wrapper
}
}
}
impl Declarations {
pub(crate) fn input_terminal(
&self,
reading: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let key = reading.key();
if let Some(cfg) = self.types.get(&key) {
if cfg.is_opaque() {
return Some(self.opaque_handle_input(reading, emit));
}
}
if let Some(spec) = crate::jni::prim_array::prim_array_of(reading) {
let body = crate::jni::prim_array::input_body(reading, &spec, emit);
let wire = spec.wire.clone();
let kotlin_name = self.override_kotlin_name(&reading.key(), Some(spec.kotlin.clone()));
let niches = default_niches_for_wire(&wire);
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
if let Some(cfg) = self.types.get(&key) {
if cfg.is_enum_class() {
if let Some(name) = reading.key().ident() {
if let Some(e) = flat_unit_enum(registry, &name, "enum_class") {
let (wire, body) = enum_input_body(self, registry, e);
let niches = default_niches_for_wire(&wire);
let kotlin_name =
cfg.name_spec.as_ref().map(|s| KtType::cls(self.fqn_of(s)));
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
}
}
}
if let Some(conv) = self.lookup_input(reading, registry, emit) {
return Some(conv);
}
if reading.key().as_str() == "str" {
let wire: syn::Type = syn::parse_quote!(jni::objects::JString);
let body: syn::Expr = syn::parse_quote!({
let s = env.get_string(v).map_err(|e| {
<__JniErr as ::core::convert::From<String>>::from(format!(
"decode_string: {}",
e
))
})?;
s.into()
});
let rust_ty: syn::Type = syn::parse_quote!(String);
let kotlin_name = self.override_kotlin_name(&reading.key(), Some(KtType::string()));
let niches = default_niches_for_wire(&wire);
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_composed(&rust_ty, &wire, &body, None),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
if matches!(
reading.unwrapped().kind(),
prebindgen_registry::flat::TypeKind::Str | prebindgen_registry::flat::TypeKind::String
) {
let wire: syn::Type = syn::parse_quote!(jni::objects::JString);
let body: syn::Expr = syn::parse_quote!({
let s = env.get_string(v).map_err(|e| {
<__JniErr as ::core::convert::From<String>>::from(format!(
"decode_string: {}",
e
))
})?;
::std::string::String::from(s).into()
});
let kotlin_name = self.override_kotlin_name(&reading.key(), Some(KtType::string()));
let niches = default_niches_for_wire(&wire);
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
if let Some((wire, body)) = primitive_input(&reading.key()) {
let niches = default_niches_for_wire(&wire);
let kotlin_name = kotlin_for_wire(&wire);
let metadata = if reading.key().as_str() == "u64" {
self.unsigned64_leaf_meta()
} else {
self.framework_meta(kotlin_name)
};
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata,
});
}
if let Some(name) = reading.key().ident() {
if self.types.get(&key).is_some_and(|c| c.sum().is_some()) {
if let Some(prebindgen_registry::flat::Type::Variant(v)) =
registry.flat().declared_type(&name)
{
let (wire, body) = sum_input_body(self, v, registry, emit)?;
let niches = default_niches_for_wire(&wire);
let kotlin_name = self
.types
.get(&key)
.and_then(|c| c.name_spec.as_ref())
.map(|s| KtType::cls(self.fqn_of(s)));
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
}
if let Some(s) = registry.flat().struct_type(&name) {
let (wire, body) = struct_input_body(self, s, registry, emit)?;
let niches = default_niches_for_wire(&wire);
let kotlin_name = self
.types
.get(&key)
.and_then(|c| c.name_spec.as_ref())
.map(|s| KtType::cls(self.fqn_of(s)));
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
}
None
}
pub(crate) fn output_transparent_bridge(
&self,
reading: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
if reading.erased_wrappers().is_empty() {
return None;
}
let stripped = reading.stripped_key();
if reading.borrow_target().is_some() {
return None;
}
let inner = registry.reading(&stripped)?;
let entry = registry.output_entry(&inner)?;
let wire = entry.destination.clone();
let read = read_through_erased_wrappers(reading, quote!(v))?;
let inner_call = crate::jni::emit::composed_inner_output(entry, quote!(__inner));
let body: syn::Expr = syn::parse_quote!({
let __inner = #read;
#inner_call
});
Some(ConverterImpl {
subs: vec![stripped],
pre_stages: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches: entry.niches.clone(),
metadata: entry.metadata.clone(),
})
}
pub(crate) fn input_transparent_bridge(
&self,
reading: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
if reading.erased_wrappers().is_empty() {
return None;
}
let stripped = reading.stripped_key();
if reading.borrow_target().is_some() {
return None;
}
let inner = registry.reading(&stripped)?;
let entry = registry.input_entry(&inner)?;
let wire = entry.destination.clone();
let built = build_through_erased_wrappers(reading, quote!(__inner))?;
let inner_call = crate::jni::emit::composed_inner_input(entry, quote!(v));
let body: syn::Expr = syn::parse_quote!({
let __inner = #inner_call;
#built
});
Some(ConverterImpl {
subs: vec![stripped],
pre_stages: vec![],
function: self.build_input_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches: entry.niches.clone(),
metadata: entry.metadata.clone(),
})
}
pub(crate) fn input_wrapper_shape(
&self,
shape: WrapperShape,
produced: &Produced<'_>,
t1: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
self.input_borrow(shape, produced, t1, registry)
.or_else(|| self.input_option_ref(shape, produced, t1, registry, emit))
.or_else(|| self.input_vec(shape, produced, t1, registry, emit))
.or_else(|| self.input_option(shape, produced, t1, registry, emit))
}
pub(crate) fn output_terminal(
&self,
reading: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let key = reading.key();
if let Some(cfg) = self.types.get(&key) {
if cfg.is_opaque() {
return Some(self.opaque_handle_output(reading, emit));
}
}
if let Some(spec) = crate::jni::prim_array::prim_array_of(reading) {
let body = crate::jni::prim_array::output_body(&spec);
let wire = spec.wire.clone();
let kotlin_name = self.override_kotlin_name(&reading.key(), Some(spec.kotlin.clone()));
let niches = default_niches_for_wire(&wire);
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
if let Some(cfg) = self.types.get(&key) {
if cfg.is_enum_class() {
if let Some(name) = reading.key().ident() {
if let Some(e) = flat_unit_enum(registry, &name, "enum_class") {
let (wire, body) = enum_output_body(self, e);
let niches = default_niches_for_wire(&wire);
let kotlin_name =
cfg.name_spec.as_ref().map(|s| KtType::cls(self.fqn_of(s)));
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
}
}
}
if let Some(conv) = self.lookup_output(reading, registry, emit) {
return Some(conv);
}
if reading.key().as_str() == "str" {
return Some(self.str_ref_output());
}
if matches!(
reading.unwrapped().kind(),
prebindgen_registry::flat::TypeKind::Str | prebindgen_registry::flat::TypeKind::String
) {
let wire: syn::Type = syn::parse_quote!(jni::objects::JString);
let body: syn::Expr = syn::parse_quote!({
env.new_string(&*v).map_err(|e| {
<__JniErr as ::core::convert::From<String>>::from(format!("encode_str: {}", e))
})?
});
let kotlin_name = self.override_kotlin_name(&reading.key(), Some(KtType::string()));
let niches = default_niches_for_wire(&wire);
let function = match reading.kind() {
prebindgen_registry::flat::TypeKind::Cow { .. } => {
let norm: syn::Type = syn::parse_quote!(::std::borrow::Cow<'_, str>);
self.build_output_fn(&norm, &wire, &body, None)
}
_ => self.build_output_fn_of(reading, &wire, &body, None, emit),
};
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function,
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
if let Some(conv) = self.cow_bytes_output(reading) {
return Some(conv);
}
if matches!(
reading.unwrapped().kind(),
prebindgen_registry::flat::TypeKind::Unit
) {
let wire: syn::Type = syn::parse_quote!(());
let body: syn::Expr = syn::parse_quote!(v);
return Some(ConverterImpl {
subs: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
pre_stages: vec![],
niches: Niches::empty(),
metadata: KotlinMeta::default(),
});
}
if let Some((wire, body)) = primitive_output(&reading.key()) {
let niches = default_niches_for_wire(&wire);
let kotlin_name = kotlin_for_wire(&wire);
let metadata = if reading.key().as_str() == "u64" {
self.unsigned64_leaf_meta()
} else {
self.framework_meta(kotlin_name)
};
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata,
});
}
if let Some(name) = reading.key().ident() {
if let Some(s) = registry.flat().struct_type(&name) {
let (wire, body) = struct_output_body(self, s, registry)?;
let niches = default_niches_for_wire(&wire);
let kotlin_name = self
.types
.get(&key)
.and_then(|c| c.name_spec.as_ref())
.map(|s| KtType::cls(self.fqn_of(s)));
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn_of(reading, &wire, &body, None, emit),
destination: wire,
niches,
metadata: self.framework_meta(kotlin_name),
});
}
}
None
}
pub(crate) fn output_wrapper_shape(
&self,
shape: WrapperShape,
produced: &Produced<'_>,
t1: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let t1_ty = emit.spell(t1);
if let Some((_, false)) = produced.borrow() {
if self.types.get(&t1.key()).is_some_and(|c| c.is_opaque()) {
let wire: syn::Type = syn::parse_quote!(jni::sys::jlong);
let body: syn::Expr = syn::parse_quote!(std::boxed::Box::into_raw(
std::boxed::Box::new(v.clone())
) as i64);
return Some(ConverterImpl {
subs: vec![],
function: self.build_output_fn_produced(produced, &wire, &body, None, emit),
destination: wire,
pre_stages: vec![],
niches: Niches::one(syn::parse_quote!(0i64), syn::parse_quote!(*v == 0)),
metadata: self.opaque_leaf_meta(t1.key()),
});
}
}
if let Some((_, false)) = produced.borrow() {
if t1.key().as_str() == "str" {
return Some(self.str_ref_output());
}
}
if shape == WrapperShape::Optional {
let outer_ty = produced.key();
let canonical: syn::Type = syn::parse_quote!(Option<#t1_ty>);
let read = read_as_canonical(produced)?;
let (wire, inner_body, niches) = option_output(t1, registry)?;
let body: syn::Expr = syn::parse_quote!({
let v: #canonical = #read;
#inner_body
});
let inherited = registry
.output_entry(t1)
.and_then(|e| e.metadata.kotlin_name.clone());
let kotlin_name = self.override_kotlin_name(&outer_ty, inherited);
let nullable_kind = nullable_kind_for_output(&wire, t1, registry);
let projection = registry
.output_entry(t1)
.and_then(|e| e.metadata.projection.clone())
.map(|h| Projection {
strategy: FoldStrategy::Optional(nullable_kind, Box::new(h.strategy)),
..h
});
let kotlin_name = if projection.is_none() {
kotlin_name.map(|n| if n.is_nullable() { n } else { n.nullable() })
} else {
kotlin_name
};
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn_produced(produced, &wire, &body, None, emit),
destination: wire,
niches,
metadata: KotlinMeta {
projection,
..self.framework_meta(kotlin_name)
},
});
}
if shape == WrapperShape::Sequence {
let inner = registry.output_entry(t1)?;
let inner_wire = inner.destination.clone();
if !is_jobject_shaped_wire(&inner_wire) {
return None;
}
let inner_conv = crate::jni::emit::composed_inner_output(inner, quote::quote!(__elem));
let outer_ty = produced.key();
let canonical: syn::Type = syn::parse_quote!(Vec<#t1_ty>);
let read = read_as_canonical(produced)?;
let wire: syn::Type = syn::parse_quote!(jni::objects::JObject);
let body: syn::Expr = syn::parse_quote!({
let v: #canonical = #read;
let __list_obj = env
.new_object("java/util/ArrayList", "()V", &[])
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("Vec<_>: new ArrayList: {}", e)))?;
let __list = jni::objects::JList::from_env(env, &__list_obj)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("Vec<_>: list-from-env: {}", e)))?;
for __elem in v.into_iter() {
let __elem_wire = #inner_conv;
let __elem_obj: jni::objects::JObject = __elem_wire.into();
__list.add(env, &__elem_obj)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("Vec<_>: list-add: {}", e)))?;
}
__list_obj
});
let inner_kotlin = inner.metadata.kotlin_name.clone()?;
let kotlin_name = self.override_kotlin_name(
&outer_ty,
Some(KtType::generic("List", [inner_kotlin])),
);
let projection = inner.metadata.projection.clone().map(|h| Projection {
strategy: FoldStrategy::Iterable(Box::new(h.strategy)),
..h
});
let niches = default_niches_for_wire(&wire);
return Some(ConverterImpl {
subs: vec![],
pre_stages: vec![],
function: self.build_output_fn_produced(produced, &wire, &body, None, emit),
destination: wire,
niches,
metadata: KotlinMeta {
kotlin_name,
value_rust_type: None,
projection,
},
});
}
None
}
pub(crate) fn output_slice(
&self,
elem: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<KotlinMeta>> {
let inner = registry.output_entry(elem)?;
let elem_key = elem.key();
let elem = emit.spell(elem);
let inner_wire = inner.destination.clone();
if !is_jobject_shaped_wire(&inner_wire) {
return None;
}
let inner_conv = crate::jni::emit::composed_inner_output(
inner,
quote::quote!(::core::clone::Clone::clone(__elem)),
);
let outer_ty: syn::Type = syn::parse_quote!(&[#elem]);
let wire: syn::Type = syn::parse_quote!(jni::objects::JObject);
let body: syn::Expr = syn::parse_quote!({
let __list_obj = env
.new_object("java/util/ArrayList", "()V", &[])
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("&[_]: new ArrayList: {}", e)))?;
let __list = jni::objects::JList::from_env(env, &__list_obj)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("&[_]: list-from-env: {}", e)))?;
for __elem in v.iter() {
let __elem_wire = #inner_conv;
let __elem_obj: jni::objects::JObject = __elem_wire.into();
__list.add(env, &__elem_obj)
.map_err(|e| <__JniErr as ::core::convert::From<String>>::from(format!("&[_]: list-add: {}", e)))?;
}
__list_obj
});
let inner_kotlin = inner.metadata.kotlin_name.clone()?;
let kotlin_name = self.override_kotlin_name(
&TypeKey::from_type(&outer_ty),
Some(KtType::generic("List", [inner_kotlin])),
);
let projection = inner.metadata.projection.clone().map(|h| Projection {
strategy: FoldStrategy::Iterable(Box::new(h.strategy)),
..h
});
let niches = default_niches_for_wire(&wire);
Some(ConverterImpl {
subs: vec![elem_key],
pre_stages: vec![],
function: self.build_output_fn(&outer_ty, &wire, &body, None),
destination: wire,
niches,
metadata: KotlinMeta {
kotlin_name,
value_rust_type: None,
projection,
},
})
}
}
impl Declarations {
pub(crate) fn declared_functions(&self) -> std::collections::HashSet<syn::Ident> {
let mut out = std::collections::HashSet::new();
for pkg in self.packages.values() {
for m in &pkg.functions {
out.insert(m.rust_ident.clone());
}
for m in &pkg.constant_functions {
out.insert(m.rust_ident.clone());
}
}
out.extend(
self.class_members
.values()
.flatten()
.map(|m| m.rust_ident.clone()),
);
out
}
pub(crate) fn accessor_functions(&self) -> std::collections::HashSet<syn::Ident> {
self.field_accessor_fns()
}
pub(crate) fn method_receivers(&self) -> std::collections::HashMap<syn::Ident, TypeKey> {
self.class_members
.iter()
.flat_map(|(key, ms)| {
ms.iter()
.filter(|m| m.kind == MemberKind::Method)
.map(move |m| (m.rust_ident.clone(), key.clone()))
})
.collect()
}
pub(crate) fn ignored_functions(&self) -> std::collections::HashSet<syn::Ident> {
self.ignored_fns.clone()
}
pub(crate) fn ignored_name_predicates(&self) -> Vec<prebindgen_registry::NamePredicate> {
self.ignored_name_predicates.clone()
}
pub(crate) fn helper_functions(&self) -> std::collections::HashSet<syn::Ident> {
let declared = self.declared_functions();
self.convert_fns()
.chain(self.boundary_referenced_fns())
.filter(|f| !declared.contains(f))
.collect()
}
pub(crate) fn declared_consts(&self) -> Option<std::collections::HashSet<syn::Ident>> {
let mut out = std::collections::HashSet::new();
for pkg in self.packages.values() {
for c in &pkg.constants {
out.insert(c.rust_ident.clone());
}
}
Some(out)
}
pub(crate) fn ignored_consts(&self) -> std::collections::HashSet<syn::Ident> {
self.ignored_const_idents.clone()
}
pub(crate) fn required_output_types(&self) -> Vec<syn::Type> {
self.packages
.values()
.flat_map(|p| p.constant_exprs.iter().map(|e| e.ty.clone()))
.collect()
}
pub(crate) fn declared_types(&self) -> std::collections::HashMap<TypeKey, Origin<syn::Type>> {
self.types
.iter()
.map(|(k, c)| (k.clone(), c.rust_type.clone()))
.collect()
}
pub(crate) fn ignored_types(&self) -> std::collections::HashSet<TypeKey> {
self.ignored_class_types.clone()
}
pub(crate) fn claimed(&self) -> prebindgen_registry::Claimed {
let mut functions = self.declared_functions();
functions.extend(self.helper_functions());
let mut types: std::collections::HashSet<TypeKey> =
self.declared_types().into_keys().collect();
types.extend(self.boundary_only_types().into_keys());
prebindgen_registry::Claimed {
functions,
types,
consts: self.declared_consts(),
ignored_functions: self.ignored_functions(),
ignored_types: self.ignored_types(),
ignored_consts: self.ignored_consts(),
ignored_name_predicates: self.ignored_name_predicates(),
}
}
pub(crate) fn boundary_only_types(
&self,
) -> std::collections::HashMap<TypeKey, Origin<syn::Type>> {
self.rust_side_only_types()
.chain(
self.types
.iter()
.filter(|(_, c)| c.sum().is_some())
.map(|(k, c)| (k.clone(), c.rust_type.clone())),
)
.collect()
}
}
#[cfg(test)]
mod wrapper_ops_tests {
use super::*;
#[test]
fn every_erased_wrapper_has_ops() {
let missing: Vec<&str> = prebindgen_registry::flat::TRANSPARENT_WRAPPERS
.iter()
.copied()
.filter(|w| wrapper_ops(w).is_none())
.collect();
assert!(
missing.is_empty(),
"the model erases {missing:?}, and this adapter has no `WrapperOps` row for them — \
add one (`read`/`build` may be `None` when the representation does not allow it, \
which refuses the shape instead of mis-generating it)"
);
}
#[test]
fn no_ops_for_a_wrapper_the_model_keeps() {
let stray: Vec<&str> = WRAPPER_OPS
.iter()
.map(|w| w.name)
.filter(|n| !prebindgen_registry::flat::TRANSPARENT_WRAPPERS.contains(n))
.collect();
assert!(
stray.is_empty(),
"`WRAPPER_OPS` rows for non-erased {stray:?}"
);
}
#[test]
fn a_rebuild_puts_the_wrappers_back_inside_out() {
let ty = crate::test_util::reading(syn::parse_quote!(Box<Box<Option<String>>>));
assert_eq!(ty.erased_wrappers(), ["Box", "Box"]);
let built = build_through_erased_wrappers(&ty, quote!(v)).expect("Box builds");
assert_eq!(
built.to_string().replace(' ', ""),
":: std :: boxed :: Box :: new (:: std :: boxed :: Box :: new (v))".replace(' ', ""),
);
let read = read_through_erased_wrappers(&ty, quote!(v)).expect("Box reads");
assert_eq!(read.to_string().replace(' ', ""), "**v");
let plain = crate::test_util::reading(syn::parse_quote!(Option<String>));
assert!(plain.erased_wrappers().is_empty());
for e in [
build_through_erased_wrappers(&plain, quote!(v)),
read_through_erased_wrappers(&plain, quote!(v)),
] {
assert_eq!(e.expect("identity").to_string(), "v");
}
}
#[test]
fn a_cow_declines_a_rebuild_by_policy() {
let ty = crate::test_util::reading(syn::parse_quote!(Cow<'_, str>));
assert_eq!(ty.erased_wrappers(), ["Cow"]);
assert!(build_through_erased_wrappers(&ty, quote!(v)).is_none());
assert!(read_through_erased_wrappers(&ty, quote!(v)).is_none());
let nested = crate::test_util::reading(syn::parse_quote!(Box<Cow<'_, str>>));
assert_eq!(nested.erased_wrappers(), ["Box", "Cow"]);
assert!(build_through_erased_wrappers(&nested, quote!(v)).is_none());
}
}