Skip to main content

weaveffi_gen_android/
lib.rs

1use anyhow::Result;
2use camino::Utf8Path;
3use std::fmt::Write as _;
4use weaveffi_core::codegen::Generator;
5use weaveffi_core::utils::c_symbol_name;
6use weaveffi_ir::ir::{Api, EnumDef, StructDef, TypeRef};
7
8pub struct AndroidGenerator;
9
10impl Generator for AndroidGenerator {
11    fn name(&self) -> &'static str {
12        "android"
13    }
14
15    fn generate(&self, api: &Api, out_dir: &Utf8Path) -> Result<()> {
16        let dir = out_dir.join("android");
17        std::fs::create_dir_all(&dir)?;
18
19        std::fs::write(
20            dir.join("settings.gradle"),
21            "rootProject.name = 'weaveffi'\n",
22        )?;
23        std::fs::write(dir.join("build.gradle"), BUILD_GRADLE)?;
24
25        let src_dir = dir.join("src/main/kotlin/com/weaveffi");
26        std::fs::create_dir_all(&src_dir)?;
27        let kotlin = render_kotlin(api);
28        std::fs::write(src_dir.join("WeaveFFI.kt"), kotlin)?;
29
30        let jni_dir = dir.join("src/main/cpp");
31        std::fs::create_dir_all(&jni_dir)?;
32        std::fs::write(jni_dir.join("CMakeLists.txt"), CMAKE)?;
33        let jni_c = render_jni_c(api);
34        std::fs::write(jni_dir.join("weaveffi_jni.c"), jni_c)?;
35
36        Ok(())
37    }
38}
39
40const BUILD_GRADLE: &str = r#"plugins {
41    id 'com.android.library'
42    id 'org.jetbrains.kotlin.android' version '1.9.22' apply false
43}
44
45android {
46    namespace 'com.weaveffi'
47    compileSdk 34
48    defaultConfig { minSdk 24 }
49}
50"#;
51
52const CMAKE: &str = r#"cmake_minimum_required(VERSION 3.22)
53project(weaveffi)
54add_library(weaveffi SHARED weaveffi_jni.c)
55target_include_directories(weaveffi PRIVATE ../../../../c)
56"#;
57
58fn kotlin_type(t: &TypeRef) -> String {
59    match t {
60        TypeRef::I32 => "Int".to_string(),
61        TypeRef::U32 => "Long".to_string(),
62        TypeRef::I64 => "Long".to_string(),
63        TypeRef::F64 => "Double".to_string(),
64        TypeRef::Bool => "Boolean".to_string(),
65        TypeRef::StringUtf8 => "String".to_string(),
66        TypeRef::Bytes => "ByteArray".to_string(),
67        TypeRef::Handle => "Long".to_string(),
68        TypeRef::Struct(_) => "Long".to_string(),
69        TypeRef::Enum(_) => "Int".to_string(),
70        TypeRef::Optional(inner) => format!("{}?", kotlin_type(inner)),
71        TypeRef::List(inner) => kotlin_list_type(inner),
72    }
73}
74
75fn kotlin_list_type(inner: &TypeRef) -> String {
76    match inner {
77        TypeRef::I32 | TypeRef::Enum(_) => "IntArray".to_string(),
78        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
79            "LongArray".to_string()
80        }
81        TypeRef::F64 => "DoubleArray".to_string(),
82        TypeRef::Bool => "BooleanArray".to_string(),
83        TypeRef::StringUtf8 => "Array<String>".to_string(),
84        TypeRef::Bytes => "Array<ByteArray>".to_string(),
85        TypeRef::Optional(_) | TypeRef::List(_) => "LongArray".to_string(),
86    }
87}
88
89fn jni_param_type(t: &TypeRef) -> String {
90    match t {
91        TypeRef::I32 | TypeRef::Enum(_) => "jint".to_string(),
92        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => "jlong".to_string(),
93        TypeRef::F64 => "jdouble".to_string(),
94        TypeRef::Bool => "jboolean".to_string(),
95        TypeRef::StringUtf8 => "jstring".to_string(),
96        TypeRef::Bytes => "jbyteArray".to_string(),
97        TypeRef::Optional(inner) => match inner.as_ref() {
98            TypeRef::StringUtf8 => "jstring".to_string(),
99            TypeRef::Bytes => "jbyteArray".to_string(),
100            _ => "jobject".to_string(),
101        },
102        TypeRef::List(inner) => jni_array_type(inner),
103    }
104}
105
106fn jni_array_type(inner: &TypeRef) -> String {
107    match inner {
108        TypeRef::I32 | TypeRef::Enum(_) => "jintArray".to_string(),
109        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
110            "jlongArray".to_string()
111        }
112        TypeRef::F64 => "jdoubleArray".to_string(),
113        TypeRef::Bool => "jbooleanArray".to_string(),
114        _ => "jobjectArray".to_string(),
115    }
116}
117
118fn jni_ret_type(t: Option<&TypeRef>) -> String {
119    match t {
120        None => "void".to_string(),
121        Some(t) => jni_param_type(t),
122    }
123}
124
125fn c_type_for_return(t: &TypeRef) -> &'static str {
126    match t {
127        TypeRef::I32 | TypeRef::Enum(_) => "int32_t",
128        TypeRef::U32 => "uint32_t",
129        TypeRef::I64 => "int64_t",
130        TypeRef::F64 => "double",
131        TypeRef::Bool => "bool",
132        TypeRef::Handle => "weaveffi_handle_t",
133        TypeRef::StringUtf8 => "const char*",
134        TypeRef::Bytes => "const uint8_t*",
135        TypeRef::Struct(_) | TypeRef::Optional(_) | TypeRef::List(_) => "void*",
136    }
137}
138
139fn jni_default_return(t: Option<&TypeRef>) -> &'static str {
140    match t {
141        None => "",
142        Some(TypeRef::I32 | TypeRef::Enum(_)) => "return 0;",
143        Some(TypeRef::U32 | TypeRef::I64 | TypeRef::Handle) => "return 0;",
144        Some(TypeRef::F64) => "return 0.0;",
145        Some(TypeRef::Bool) => "return JNI_FALSE;",
146        Some(TypeRef::StringUtf8) => "return NULL;",
147        Some(TypeRef::Bytes) => "return NULL;",
148        Some(TypeRef::Struct(_)) => "return 0;",
149        Some(TypeRef::Optional(_) | TypeRef::List(_)) => "return NULL;",
150    }
151}
152
153fn jni_cast_for(t: &TypeRef) -> &'static str {
154    match t {
155        TypeRef::I32 | TypeRef::Enum(_) => "(jint)",
156        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle => "(jlong)",
157        TypeRef::F64 => "(jdouble)",
158        TypeRef::Struct(_) => "(jlong)(intptr_t)",
159        _ => "",
160    }
161}
162
163fn render_kotlin(api: &Api) -> String {
164    let mut kotlin = String::from("package com.weaveffi\n\nclass WeaveFFI {\n    companion object {\n        init { System.loadLibrary(\"weaveffi\") }\n\n");
165    for m in &api.modules {
166        for f in &m.functions {
167            let params_sig: Vec<String> = f
168                .params
169                .iter()
170                .map(|p| format!("{}: {}", p.name, kotlin_type(&p.ty)))
171                .collect();
172            let ret = f
173                .returns
174                .as_ref()
175                .map(kotlin_type)
176                .unwrap_or_else(|| "Unit".to_string());
177            let _ = writeln!(
178                kotlin,
179                "        @JvmStatic external fun {}({}): {}",
180                f.name,
181                params_sig.join(", "),
182                ret
183            );
184        }
185    }
186    kotlin.push_str("    }\n}\n");
187    for m in &api.modules {
188        for e in &m.enums {
189            render_kotlin_enum(&mut kotlin, e);
190        }
191        for s in &m.structs {
192            render_kotlin_struct(&mut kotlin, s);
193        }
194    }
195    kotlin
196}
197
198fn render_kotlin_enum(out: &mut String, e: &EnumDef) {
199    let _ = writeln!(out);
200    let _ = writeln!(out, "enum class {}(val value: Int) {{", e.name);
201    for (i, v) in e.variants.iter().enumerate() {
202        let comma = if i < e.variants.len() - 1 { "," } else { ";" };
203        let _ = writeln!(out, "    {}({}){}", v.name, v.value, comma);
204    }
205    let _ = writeln!(out);
206    let _ = writeln!(out, "    companion object {{");
207    let _ = writeln!(
208        out,
209        "        fun fromValue(value: Int): {} = entries.first {{ it.value == value }}",
210        e.name
211    );
212    let _ = writeln!(out, "    }}");
213    let _ = writeln!(out, "}}");
214}
215
216fn render_jni_c(api: &Api) -> String {
217    let mut jni_c = String::from("#include <jni.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stddef.h>\n#include \"weaveffi.h\"\n\n");
218    for m in &api.modules {
219        for f in &m.functions {
220            let jret = jni_ret_type(f.returns.as_ref());
221            let mut jparams: Vec<String> = vec!["JNIEnv* env".into(), "jclass clazz".into()];
222            for p in &f.params {
223                jparams.push(format!("{} {}", jni_param_type(&p.ty), p.name));
224            }
225            let _ = writeln!(
226                jni_c,
227                "JNIEXPORT {} JNICALL Java_com_weaveffi_WeaveFFI_{}({}) {{",
228                jret,
229                f.name,
230                jparams.join(", ")
231            );
232            let _ = writeln!(jni_c, "    weaveffi_error err = {{0, NULL}};");
233
234            for p in &f.params {
235                write_param_acquire(&mut jni_c, &p.name, &p.ty);
236            }
237
238            let c_sym = c_symbol_name(&m.name, &f.name);
239            let mut call_args: Vec<String> = Vec::new();
240            for p in &f.params {
241                build_c_call_args(&mut call_args, &p.name, &p.ty, &m.name);
242            }
243
244            let needs_out_len = matches!(f.returns, Some(TypeRef::Bytes) | Some(TypeRef::List(_)));
245            if needs_out_len {
246                let _ = writeln!(jni_c, "    size_t out_len = 0;");
247            }
248
249            if let Some(ret_type) = f.returns.as_ref() {
250                write_return_handling(
251                    &mut jni_c,
252                    ret_type,
253                    &c_sym,
254                    &call_args,
255                    f.returns.as_ref(),
256                    &f.params,
257                    &m.name,
258                );
259            } else {
260                let _ = writeln!(jni_c, "    {}({}, &err);", c_sym, call_args.join(", "));
261                write_error_check(&mut jni_c, f.returns.as_ref());
262                release_jni_resources(&mut jni_c, &f.params);
263                let _ = writeln!(jni_c, "    return;");
264            }
265
266            let _ = writeln!(jni_c, "}}\n");
267        }
268    }
269    for m in &api.modules {
270        for s in &m.structs {
271            render_jni_struct(&mut jni_c, &m.name, s);
272        }
273    }
274    jni_c
275}
276
277fn write_param_acquire(out: &mut String, name: &str, ty: &TypeRef) {
278    match ty {
279        TypeRef::StringUtf8 => {
280            let _ = writeln!(
281                out,
282                "    const char* {n}_chars = (*env)->GetStringUTFChars(env, {n}, NULL);",
283                n = name
284            );
285            let _ = writeln!(
286                out,
287                "    jsize {n}_len = (*env)->GetStringUTFLength(env, {n});",
288                n = name
289            );
290        }
291        TypeRef::Bytes => {
292            let _ = writeln!(out, "    jboolean {n}_is_copy = 0;", n = name);
293            let _ = writeln!(
294                out,
295                "    jbyte* {n}_elems = (*env)->GetByteArrayElements(env, {n}, &{n}_is_copy);",
296                n = name
297            );
298            let _ = writeln!(
299                out,
300                "    jsize {n}_len = (*env)->GetArrayLength(env, {n});",
301                n = name
302            );
303        }
304        TypeRef::Optional(inner) => write_optional_acquire(out, name, inner),
305        TypeRef::List(inner) => write_list_acquire(out, name, inner),
306        _ => {}
307    }
308}
309
310fn write_optional_acquire(out: &mut String, name: &str, inner: &TypeRef) {
311    match inner {
312        TypeRef::StringUtf8 => {
313            let _ = writeln!(out, "    const char* {n}_chars = NULL;", n = name);
314            let _ = writeln!(out, "    jsize {n}_len = 0;", n = name);
315            let _ = writeln!(out, "    if ({n} != NULL) {{", n = name);
316            let _ = writeln!(
317                out,
318                "        {n}_chars = (*env)->GetStringUTFChars(env, {n}, NULL);",
319                n = name
320            );
321            let _ = writeln!(
322                out,
323                "        {n}_len = (*env)->GetStringUTFLength(env, {n});",
324                n = name
325            );
326            let _ = writeln!(out, "    }}");
327        }
328        TypeRef::Bytes => {
329            let _ = writeln!(out, "    jbyte* {n}_elems = NULL;", n = name);
330            let _ = writeln!(out, "    jsize {n}_len = 0;", n = name);
331            let _ = writeln!(out, "    if ({n} != NULL) {{", n = name);
332            let _ = writeln!(
333                out,
334                "        {n}_elems = (*env)->GetByteArrayElements(env, {n}, NULL);",
335                n = name
336            );
337            let _ = writeln!(
338                out,
339                "        {n}_len = (*env)->GetArrayLength(env, {n});",
340                n = name
341            );
342            let _ = writeln!(out, "    }}");
343        }
344        TypeRef::I32 | TypeRef::Enum(_) => {
345            let _ = writeln!(out, "    int32_t {n}_val = 0;", n = name);
346            let _ = writeln!(out, "    const int32_t* {n}_ptr = NULL;", n = name);
347            let _ = writeln!(out, "    if ({n} != NULL) {{", n = name);
348            let _ = writeln!(
349                out,
350                "        jclass {n}_cls = (*env)->FindClass(env, \"java/lang/Integer\");",
351                n = name
352            );
353            let _ = writeln!(
354                out,
355                "        jmethodID {n}_mid = (*env)->GetMethodID(env, {n}_cls, \"intValue\", \"()I\");",
356                n = name
357            );
358            let _ = writeln!(
359                out,
360                "        {n}_val = (int32_t)(*env)->CallIntMethod(env, {n}, {n}_mid);",
361                n = name
362            );
363            let _ = writeln!(out, "        {n}_ptr = &{n}_val;", n = name);
364            let _ = writeln!(out, "    }}");
365        }
366        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
367            let _ = writeln!(out, "    int64_t {n}_val = 0;", n = name);
368            let _ = writeln!(out, "    const int64_t* {n}_ptr = NULL;", n = name);
369            let _ = writeln!(out, "    if ({n} != NULL) {{", n = name);
370            let _ = writeln!(
371                out,
372                "        jclass {n}_cls = (*env)->FindClass(env, \"java/lang/Long\");",
373                n = name
374            );
375            let _ = writeln!(
376                out,
377                "        jmethodID {n}_mid = (*env)->GetMethodID(env, {n}_cls, \"longValue\", \"()J\");",
378                n = name
379            );
380            let _ = writeln!(
381                out,
382                "        {n}_val = (int64_t)(*env)->CallLongMethod(env, {n}, {n}_mid);",
383                n = name
384            );
385            let _ = writeln!(out, "        {n}_ptr = &{n}_val;", n = name);
386            let _ = writeln!(out, "    }}");
387        }
388        TypeRef::F64 => {
389            let _ = writeln!(out, "    double {n}_val = 0.0;", n = name);
390            let _ = writeln!(out, "    const double* {n}_ptr = NULL;", n = name);
391            let _ = writeln!(out, "    if ({n} != NULL) {{", n = name);
392            let _ = writeln!(
393                out,
394                "        jclass {n}_cls = (*env)->FindClass(env, \"java/lang/Double\");",
395                n = name
396            );
397            let _ = writeln!(
398                out,
399                "        jmethodID {n}_mid = (*env)->GetMethodID(env, {n}_cls, \"doubleValue\", \"()D\");",
400                n = name
401            );
402            let _ = writeln!(
403                out,
404                "        {n}_val = (*env)->CallDoubleMethod(env, {n}, {n}_mid);",
405                n = name
406            );
407            let _ = writeln!(out, "        {n}_ptr = &{n}_val;", n = name);
408            let _ = writeln!(out, "    }}");
409        }
410        TypeRef::Bool => {
411            let _ = writeln!(out, "    bool {n}_val = false;", n = name);
412            let _ = writeln!(out, "    const bool* {n}_ptr = NULL;", n = name);
413            let _ = writeln!(out, "    if ({n} != NULL) {{", n = name);
414            let _ = writeln!(
415                out,
416                "        jclass {n}_cls = (*env)->FindClass(env, \"java/lang/Boolean\");",
417                n = name
418            );
419            let _ = writeln!(
420                out,
421                "        jmethodID {n}_mid = (*env)->GetMethodID(env, {n}_cls, \"booleanValue\", \"()Z\");",
422                n = name
423            );
424            let _ = writeln!(
425                out,
426                "        {n}_val = (bool)(*env)->CallBooleanMethod(env, {n}, {n}_mid);",
427                n = name
428            );
429            let _ = writeln!(out, "        {n}_ptr = &{n}_val;", n = name);
430            let _ = writeln!(out, "    }}");
431        }
432        TypeRef::Optional(_) | TypeRef::List(_) => {}
433    }
434}
435
436fn write_list_acquire(out: &mut String, name: &str, inner: &TypeRef) {
437    match inner {
438        TypeRef::I32 | TypeRef::Enum(_) => {
439            let _ = writeln!(
440                out,
441                "    jint* {n}_elems = (*env)->GetIntArrayElements(env, {n}, NULL);",
442                n = name
443            );
444            let _ = writeln!(
445                out,
446                "    jsize {n}_len = (*env)->GetArrayLength(env, {n});",
447                n = name
448            );
449        }
450        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
451            let _ = writeln!(
452                out,
453                "    jlong* {n}_elems = (*env)->GetLongArrayElements(env, {n}, NULL);",
454                n = name
455            );
456            let _ = writeln!(
457                out,
458                "    jsize {n}_len = (*env)->GetArrayLength(env, {n});",
459                n = name
460            );
461        }
462        TypeRef::F64 => {
463            let _ = writeln!(
464                out,
465                "    jdouble* {n}_elems = (*env)->GetDoubleArrayElements(env, {n}, NULL);",
466                n = name
467            );
468            let _ = writeln!(
469                out,
470                "    jsize {n}_len = (*env)->GetArrayLength(env, {n});",
471                n = name
472            );
473        }
474        TypeRef::Bool => {
475            let _ = writeln!(
476                out,
477                "    jboolean* {n}_elems = (*env)->GetBooleanArrayElements(env, {n}, NULL);",
478                n = name
479            );
480            let _ = writeln!(
481                out,
482                "    jsize {n}_len = (*env)->GetArrayLength(env, {n});",
483                n = name
484            );
485        }
486        _ => {
487            let _ = writeln!(
488                out,
489                "    jsize {n}_len = (*env)->GetArrayLength(env, {n});",
490                n = name
491            );
492        }
493    }
494}
495
496fn build_c_call_args(args: &mut Vec<String>, name: &str, ty: &TypeRef, module: &str) {
497    match ty {
498        TypeRef::StringUtf8 => {
499            args.push(format!("(const uint8_t*){n}_chars", n = name));
500            args.push(format!("(size_t){n}_len", n = name));
501        }
502        TypeRef::Bytes => {
503            args.push(format!("(const uint8_t*){n}_elems", n = name));
504            args.push(format!("(size_t){n}_len", n = name));
505        }
506        TypeRef::Bool => args.push(format!("(bool)({} == JNI_TRUE)", name)),
507        TypeRef::I32 => args.push(format!("(int32_t){}", name)),
508        TypeRef::U32 => args.push(format!("(uint32_t){}", name)),
509        TypeRef::I64 => args.push(format!("(int64_t){}", name)),
510        TypeRef::F64 => args.push(format!("(double){}", name)),
511        TypeRef::Handle => args.push(format!("(weaveffi_handle_t){}", name)),
512        TypeRef::Struct(sname) => {
513            args.push(format!(
514                "(const weaveffi_{}_{}*)(intptr_t){}",
515                module, sname, name
516            ));
517        }
518        TypeRef::Enum(_) => args.push(format!("(int32_t){}", name)),
519        TypeRef::Optional(inner) => match inner.as_ref() {
520            TypeRef::StringUtf8 => {
521                args.push(format!("(const uint8_t*){n}_chars", n = name));
522                args.push(format!("(size_t){n}_len", n = name));
523            }
524            TypeRef::Bytes => {
525                args.push(format!("(const uint8_t*){n}_elems", n = name));
526                args.push(format!("(size_t){n}_len", n = name));
527            }
528            _ => args.push(format!("{}_ptr", name)),
529        },
530        TypeRef::List(inner) => {
531            match inner.as_ref() {
532                TypeRef::I32 | TypeRef::Enum(_) => {
533                    args.push(format!("(const int32_t*){n}_elems", n = name));
534                }
535                TypeRef::U32 => {
536                    args.push(format!("(const uint32_t*){n}_elems", n = name));
537                }
538                TypeRef::I64 => {
539                    args.push(format!("(const int64_t*){n}_elems", n = name));
540                }
541                TypeRef::F64 => {
542                    args.push(format!("(const double*){n}_elems", n = name));
543                }
544                TypeRef::Bool => {
545                    args.push(format!("(const bool*){n}_elems", n = name));
546                }
547                TypeRef::Handle => {
548                    args.push(format!("(const weaveffi_handle_t*){n}_elems", n = name));
549                }
550                _ => {
551                    args.push(format!("(const void*){n}_elems", n = name));
552                }
553            }
554            args.push(format!("(size_t){n}_len", n = name));
555        }
556    }
557}
558
559#[allow(clippy::too_many_arguments)]
560fn write_return_handling(
561    jni_c: &mut String,
562    ret_type: &TypeRef,
563    c_sym: &str,
564    call_args: &[String],
565    returns: Option<&TypeRef>,
566    params: &[weaveffi_ir::ir::Param],
567    module: &str,
568) {
569    let args_str = call_args.join(", ");
570    match ret_type {
571        TypeRef::StringUtf8 => {
572            let _ = writeln!(jni_c, "    const char* rv = {}({}, &err);", c_sym, args_str);
573            write_error_check(jni_c, returns);
574            let _ = writeln!(jni_c, "    jstring out = rv ? (*env)->NewStringUTF(env, rv) : (*env)->NewStringUTF(env, \"\");");
575            let _ = writeln!(jni_c, "    weaveffi_free_string(rv);");
576            release_jni_resources(jni_c, params);
577            let _ = writeln!(jni_c, "    return out;");
578        }
579        TypeRef::Bytes => {
580            let _ = writeln!(
581                jni_c,
582                "    const uint8_t* rv = {}({}, &out_len, &err);",
583                c_sym, args_str
584            );
585            write_error_check(jni_c, returns);
586            let _ = writeln!(
587                jni_c,
588                "    jbyteArray out = (*env)->NewByteArray(env, (jsize)out_len);"
589            );
590            let _ = writeln!(jni_c, "    if (out && rv) {{ (*env)->SetByteArrayRegion(env, out, 0, (jsize)out_len, (const jbyte*)rv); }}");
591            let _ = writeln!(
592                jni_c,
593                "    weaveffi_free_bytes((uint8_t*)rv, (size_t)out_len);"
594            );
595            release_jni_resources(jni_c, params);
596            let _ = writeln!(jni_c, "    return out;");
597        }
598        TypeRef::Bool => {
599            let _ = writeln!(jni_c, "    bool rv = {}({}, &err);", c_sym, args_str);
600            write_error_check(jni_c, returns);
601            release_jni_resources(jni_c, params);
602            let _ = writeln!(jni_c, "    return rv ? JNI_TRUE : JNI_FALSE;");
603        }
604        TypeRef::Struct(name) => {
605            let c_ty = format!("weaveffi_{}_{}", module, name);
606            let _ = writeln!(jni_c, "    {}* rv = {}({}, &err);", c_ty, c_sym, args_str);
607            write_error_check(jni_c, returns);
608            release_jni_resources(jni_c, params);
609            let _ = writeln!(jni_c, "    return (jlong)(intptr_t)rv;");
610        }
611        TypeRef::Optional(inner) => {
612            write_optional_return(jni_c, inner, c_sym, &args_str, returns, params, module);
613        }
614        TypeRef::List(inner) => {
615            write_list_return(jni_c, inner, c_sym, &args_str, returns, params);
616        }
617        ret_type => {
618            let c_ty = c_type_for_return(ret_type);
619            let jcast = jni_cast_for(ret_type);
620            let _ = writeln!(jni_c, "    {} rv = {}({}, &err);", c_ty, c_sym, args_str);
621            write_error_check(jni_c, returns);
622            release_jni_resources(jni_c, params);
623            let _ = writeln!(jni_c, "    return {} rv;", jcast);
624        }
625    }
626}
627
628fn write_optional_return(
629    out: &mut String,
630    inner: &TypeRef,
631    c_sym: &str,
632    args_str: &str,
633    returns: Option<&TypeRef>,
634    params: &[weaveffi_ir::ir::Param],
635    _module: &str,
636) {
637    match inner {
638        TypeRef::StringUtf8 => {
639            let _ = writeln!(out, "    const char* rv = {}({}, &err);", c_sym, args_str);
640            write_error_check(out, returns);
641            release_jni_resources(out, params);
642            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
643            let _ = writeln!(out, "    jstring result = (*env)->NewStringUTF(env, rv);");
644            let _ = writeln!(out, "    weaveffi_free_string(rv);");
645            let _ = writeln!(out, "    return result;");
646        }
647        TypeRef::I32 | TypeRef::Enum(_) => {
648            let _ = writeln!(
649                out,
650                "    const int32_t* rv = {}({}, &err);",
651                c_sym, args_str
652            );
653            write_error_check(out, returns);
654            release_jni_resources(out, params);
655            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
656            let _ = writeln!(
657                out,
658                "    jclass cls = (*env)->FindClass(env, \"java/lang/Integer\");"
659            );
660            let _ = writeln!(
661                out,
662                "    jmethodID mid = (*env)->GetStaticMethodID(env, cls, \"valueOf\", \"(I)Ljava/lang/Integer;\");"
663            );
664            let _ = writeln!(
665                out,
666                "    return (*env)->CallStaticObjectMethod(env, cls, mid, (jint)*rv);"
667            );
668        }
669        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
670            let _ = writeln!(
671                out,
672                "    const int64_t* rv = (const int64_t*){}({}, &err);",
673                c_sym, args_str
674            );
675            write_error_check(out, returns);
676            release_jni_resources(out, params);
677            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
678            let _ = writeln!(
679                out,
680                "    jclass cls = (*env)->FindClass(env, \"java/lang/Long\");"
681            );
682            let _ = writeln!(
683                out,
684                "    jmethodID mid = (*env)->GetStaticMethodID(env, cls, \"valueOf\", \"(J)Ljava/lang/Long;\");"
685            );
686            let _ = writeln!(
687                out,
688                "    return (*env)->CallStaticObjectMethod(env, cls, mid, (jlong)*rv);"
689            );
690        }
691        TypeRef::F64 => {
692            let _ = writeln!(out, "    const double* rv = {}({}, &err);", c_sym, args_str);
693            write_error_check(out, returns);
694            release_jni_resources(out, params);
695            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
696            let _ = writeln!(
697                out,
698                "    jclass cls = (*env)->FindClass(env, \"java/lang/Double\");"
699            );
700            let _ = writeln!(
701                out,
702                "    jmethodID mid = (*env)->GetStaticMethodID(env, cls, \"valueOf\", \"(D)Ljava/lang/Double;\");"
703            );
704            let _ = writeln!(
705                out,
706                "    return (*env)->CallStaticObjectMethod(env, cls, mid, (jdouble)*rv);"
707            );
708        }
709        TypeRef::Bool => {
710            let _ = writeln!(out, "    const bool* rv = {}({}, &err);", c_sym, args_str);
711            write_error_check(out, returns);
712            release_jni_resources(out, params);
713            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
714            let _ = writeln!(
715                out,
716                "    jclass cls = (*env)->FindClass(env, \"java/lang/Boolean\");"
717            );
718            let _ = writeln!(
719                out,
720                "    jmethodID mid = (*env)->GetStaticMethodID(env, cls, \"valueOf\", \"(Z)Ljava/lang/Boolean;\");"
721            );
722            let _ = writeln!(
723                out,
724                "    return (*env)->CallStaticObjectMethod(env, cls, mid, *rv ? JNI_TRUE : JNI_FALSE);"
725            );
726        }
727        _ => {
728            let _ = writeln!(out, "    void* rv = {}({}, &err);", c_sym, args_str);
729            write_error_check(out, returns);
730            release_jni_resources(out, params);
731            let _ = writeln!(out, "    return (jobject)rv;");
732        }
733    }
734}
735
736fn write_list_return(
737    out: &mut String,
738    inner: &TypeRef,
739    c_sym: &str,
740    args_str: &str,
741    returns: Option<&TypeRef>,
742    params: &[weaveffi_ir::ir::Param],
743) {
744    match inner {
745        TypeRef::I32 | TypeRef::Enum(_) => {
746            let _ = writeln!(
747                out,
748                "    const int32_t* rv = {}({}, &out_len, &err);",
749                c_sym, args_str
750            );
751            write_error_check(out, returns);
752            release_jni_resources(out, params);
753            let _ = writeln!(
754                out,
755                "    jintArray result = (*env)->NewIntArray(env, (jsize)out_len);"
756            );
757            let _ = writeln!(out, "    if (result && rv) {{ (*env)->SetIntArrayRegion(env, result, 0, (jsize)out_len, (const jint*)rv); }}");
758            let _ = writeln!(out, "    return result;");
759        }
760        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
761            let _ = writeln!(
762                out,
763                "    const int64_t* rv = (const int64_t*){}({}, &out_len, &err);",
764                c_sym, args_str
765            );
766            write_error_check(out, returns);
767            release_jni_resources(out, params);
768            let _ = writeln!(
769                out,
770                "    jlongArray result = (*env)->NewLongArray(env, (jsize)out_len);"
771            );
772            let _ = writeln!(out, "    if (result && rv) {{ (*env)->SetLongArrayRegion(env, result, 0, (jsize)out_len, (const jlong*)rv); }}");
773            let _ = writeln!(out, "    return result;");
774        }
775        TypeRef::F64 => {
776            let _ = writeln!(
777                out,
778                "    const double* rv = {}({}, &out_len, &err);",
779                c_sym, args_str
780            );
781            write_error_check(out, returns);
782            release_jni_resources(out, params);
783            let _ = writeln!(
784                out,
785                "    jdoubleArray result = (*env)->NewDoubleArray(env, (jsize)out_len);"
786            );
787            let _ = writeln!(out, "    if (result && rv) {{ (*env)->SetDoubleArrayRegion(env, result, 0, (jsize)out_len, (const jdouble*)rv); }}");
788            let _ = writeln!(out, "    return result;");
789        }
790        TypeRef::Bool => {
791            let _ = writeln!(
792                out,
793                "    const bool* rv = {}({}, &out_len, &err);",
794                c_sym, args_str
795            );
796            write_error_check(out, returns);
797            release_jni_resources(out, params);
798            let _ = writeln!(
799                out,
800                "    jbooleanArray result = (*env)->NewBooleanArray(env, (jsize)out_len);"
801            );
802            let _ = writeln!(out, "    if (result && rv) {{ (*env)->SetBooleanArrayRegion(env, result, 0, (jsize)out_len, (const jboolean*)rv); }}");
803            let _ = writeln!(out, "    return result;");
804        }
805        _ => {
806            let _ = writeln!(
807                out,
808                "    const void* rv = {}({}, &out_len, &err);",
809                c_sym, args_str
810            );
811            write_error_check(out, returns);
812            release_jni_resources(out, params);
813            let _ = writeln!(out, "    return NULL;");
814        }
815    }
816}
817
818fn write_error_check(out: &mut String, ret_type: Option<&TypeRef>) {
819    let _ = writeln!(out, "    if (err.code != 0) {{");
820    let _ = writeln!(
821        out,
822        "        jclass exClass = (*env)->FindClass(env, \"java/lang/RuntimeException\");"
823    );
824    let _ = writeln!(
825        out,
826        "        const char* msg = err.message ? err.message : \"WeaveFFI error\";"
827    );
828    let _ = writeln!(out, "        (*env)->ThrowNew(env, exClass, msg);");
829    let _ = writeln!(out, "        weaveffi_error_clear(&err);");
830    let _ = writeln!(out, "        {}", jni_default_return(ret_type));
831    let _ = writeln!(out, "    }}");
832}
833
834fn release_jni_resources(out: &mut String, params: &[weaveffi_ir::ir::Param]) {
835    for p in params {
836        match &p.ty {
837            TypeRef::StringUtf8 => {
838                let _ = writeln!(
839                    out,
840                    "    (*env)->ReleaseStringUTFChars(env, {n}, {n}_chars);",
841                    n = p.name
842                );
843            }
844            TypeRef::Bytes => {
845                let _ = writeln!(
846                    out,
847                    "    (*env)->ReleaseByteArrayElements(env, {n}, {n}_elems, 0);",
848                    n = p.name
849                );
850            }
851            TypeRef::Optional(inner) => match inner.as_ref() {
852                TypeRef::StringUtf8 => {
853                    let _ = writeln!(
854                        out,
855                        "    if ({n} != NULL) {{ (*env)->ReleaseStringUTFChars(env, {n}, {n}_chars); }}",
856                        n = p.name
857                    );
858                }
859                TypeRef::Bytes => {
860                    let _ = writeln!(
861                        out,
862                        "    if ({n} != NULL && {n}_elems != NULL) {{ (*env)->ReleaseByteArrayElements(env, {n}, {n}_elems, 0); }}",
863                        n = p.name
864                    );
865                }
866                _ => {}
867            },
868            TypeRef::List(inner) => match inner.as_ref() {
869                TypeRef::I32 | TypeRef::Enum(_) => {
870                    let _ = writeln!(
871                        out,
872                        "    (*env)->ReleaseIntArrayElements(env, {n}, {n}_elems, 0);",
873                        n = p.name
874                    );
875                }
876                TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
877                    let _ = writeln!(
878                        out,
879                        "    (*env)->ReleaseLongArrayElements(env, {n}, {n}_elems, 0);",
880                        n = p.name
881                    );
882                }
883                TypeRef::F64 => {
884                    let _ = writeln!(
885                        out,
886                        "    (*env)->ReleaseDoubleArrayElements(env, {n}, {n}_elems, 0);",
887                        n = p.name
888                    );
889                }
890                TypeRef::Bool => {
891                    let _ = writeln!(
892                        out,
893                        "    (*env)->ReleaseBooleanArrayElements(env, {n}, {n}_elems, 0);",
894                        n = p.name
895                    );
896                }
897                _ => {}
898            },
899            _ => {}
900        }
901    }
902}
903
904fn to_pascal_case(s: &str) -> String {
905    s.split('_')
906        .map(|part| {
907            let mut c = part.chars();
908            match c.next() {
909                None => String::new(),
910                Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
911            }
912        })
913        .collect()
914}
915
916fn kotlin_getter_type(t: &TypeRef) -> String {
917    match t {
918        TypeRef::Struct(name) | TypeRef::Enum(name) => name.clone(),
919        other => kotlin_type(other),
920    }
921}
922
923fn render_kotlin_struct(out: &mut String, s: &StructDef) {
924    let _ = writeln!(out);
925    let _ = writeln!(
926        out,
927        "class {} internal constructor(private var handle: Long) : java.io.Closeable {{",
928        s.name
929    );
930    let _ = writeln!(out, "    companion object {{");
931    let _ = writeln!(out, "        init {{ System.loadLibrary(\"weaveffi\") }}");
932    let _ = writeln!(out);
933
934    let create_params: Vec<String> = s
935        .fields
936        .iter()
937        .map(|f| format!("{}: {}", f.name, kotlin_type(&f.ty)))
938        .collect();
939    let _ = writeln!(
940        out,
941        "        @JvmStatic external fun nativeCreate({}): Long",
942        create_params.join(", ")
943    );
944    let _ = writeln!(
945        out,
946        "        @JvmStatic external fun nativeDestroy(handle: Long)"
947    );
948    for f in &s.fields {
949        let pascal = to_pascal_case(&f.name);
950        let _ = writeln!(
951            out,
952            "        @JvmStatic external fun nativeGet{}(handle: Long): {}",
953            pascal,
954            kotlin_type(&f.ty)
955        );
956    }
957
958    let param_names: Vec<&str> = s.fields.iter().map(|f| f.name.as_str()).collect();
959    let _ = writeln!(out);
960    let _ = writeln!(
961        out,
962        "        fun create({}): {} = {}(nativeCreate({}))",
963        create_params.join(", "),
964        s.name,
965        s.name,
966        param_names.join(", ")
967    );
968    let _ = writeln!(out, "    }}");
969    let _ = writeln!(out);
970
971    for f in &s.fields {
972        let pascal = to_pascal_case(&f.name);
973        let kt_type = kotlin_getter_type(&f.ty);
974        match &f.ty {
975            TypeRef::Struct(name) => {
976                let _ = writeln!(
977                    out,
978                    "    val {}: {} get() = {}(nativeGet{}(handle))",
979                    f.name, kt_type, name, pascal
980                );
981            }
982            _ => {
983                let _ = writeln!(
984                    out,
985                    "    val {}: {} get() = nativeGet{}(handle)",
986                    f.name, kt_type, pascal
987                );
988            }
989        }
990    }
991    let _ = writeln!(out);
992
993    let _ = writeln!(out, "    override fun close() {{");
994    let _ = writeln!(out, "        if (handle != 0L) {{");
995    let _ = writeln!(out, "            nativeDestroy(handle)");
996    let _ = writeln!(out, "            handle = 0L");
997    let _ = writeln!(out, "        }}");
998    let _ = writeln!(out, "    }}");
999    let _ = writeln!(out);
1000    let _ = writeln!(out, "    protected fun finalize() {{");
1001    let _ = writeln!(out, "        close()");
1002    let _ = writeln!(out, "    }}");
1003    let _ = writeln!(out, "}}");
1004}
1005
1006fn render_jni_struct(out: &mut String, module_name: &str, s: &StructDef) {
1007    let prefix = format!("weaveffi_{}_{}", module_name, s.name);
1008
1009    // nativeCreate
1010    {
1011        let mut jparams: Vec<String> = vec!["JNIEnv* env".into(), "jclass clazz".into()];
1012        for f in &s.fields {
1013            jparams.push(format!("{} {}", jni_param_type(&f.ty), f.name));
1014        }
1015        let _ = writeln!(
1016            out,
1017            "JNIEXPORT jlong JNICALL Java_com_weaveffi_{}_nativeCreate({}) {{",
1018            s.name,
1019            jparams.join(", ")
1020        );
1021        let _ = writeln!(out, "    weaveffi_error err = {{0, NULL}};");
1022
1023        for f in &s.fields {
1024            write_param_acquire(out, &f.name, &f.ty);
1025        }
1026
1027        let mut call_args: Vec<String> = Vec::new();
1028        for f in &s.fields {
1029            build_c_call_args(&mut call_args, &f.name, &f.ty, module_name);
1030        }
1031
1032        if call_args.is_empty() {
1033            let _ = writeln!(out, "    {}* rv = {}_create(&err);", prefix, prefix);
1034        } else {
1035            let _ = writeln!(
1036                out,
1037                "    {}* rv = {}_create({}, &err);",
1038                prefix,
1039                prefix,
1040                call_args.join(", ")
1041            );
1042        }
1043        write_error_check(out, Some(&TypeRef::Handle));
1044
1045        for f in &s.fields {
1046            release_jni_resources_single(out, &f.name, &f.ty);
1047        }
1048
1049        let _ = writeln!(out, "    return (jlong)(intptr_t)rv;");
1050        let _ = writeln!(out, "}}\n");
1051    }
1052
1053    // nativeDestroy
1054    {
1055        let _ = writeln!(
1056            out,
1057            "JNIEXPORT void JNICALL Java_com_weaveffi_{}_nativeDestroy(JNIEnv* env, jclass clazz, jlong handle) {{",
1058            s.name
1059        );
1060        let _ = writeln!(
1061            out,
1062            "    {}_destroy(({}*)(intptr_t)handle);",
1063            prefix, prefix
1064        );
1065        let _ = writeln!(out, "}}\n");
1066    }
1067
1068    // nativeGet{Field} for each field
1069    for f in &s.fields {
1070        let pascal = to_pascal_case(&f.name);
1071        let jret = jni_ret_type(Some(&f.ty));
1072        let getter_c = format!("{}_get_{}", prefix, f.name);
1073
1074        let _ = writeln!(
1075            out,
1076            "JNIEXPORT {} JNICALL Java_com_weaveffi_{}_nativeGet{}(JNIEnv* env, jclass clazz, jlong handle) {{",
1077            jret, s.name, pascal
1078        );
1079
1080        match &f.ty {
1081            TypeRef::StringUtf8 => {
1082                let _ = writeln!(
1083                    out,
1084                    "    const char* rv = {}((const {}*)(intptr_t)handle);",
1085                    getter_c, prefix
1086                );
1087                let _ = writeln!(
1088                    out,
1089                    "    jstring jout = rv ? (*env)->NewStringUTF(env, rv) : (*env)->NewStringUTF(env, \"\");"
1090                );
1091                let _ = writeln!(out, "    weaveffi_free_string(rv);");
1092                let _ = writeln!(out, "    return jout;");
1093            }
1094            TypeRef::Bytes => {
1095                let _ = writeln!(out, "    size_t out_len = 0;");
1096                let _ = writeln!(
1097                    out,
1098                    "    const uint8_t* rv = {}((const {}*)(intptr_t)handle, &out_len);",
1099                    getter_c, prefix
1100                );
1101                let _ = writeln!(
1102                    out,
1103                    "    jbyteArray jout = (*env)->NewByteArray(env, (jsize)out_len);"
1104                );
1105                let _ = writeln!(
1106                    out,
1107                    "    if (jout && rv) {{ (*env)->SetByteArrayRegion(env, jout, 0, (jsize)out_len, (const jbyte*)rv); }}"
1108                );
1109                let _ = writeln!(
1110                    out,
1111                    "    weaveffi_free_bytes((uint8_t*)rv, (size_t)out_len);"
1112                );
1113                let _ = writeln!(out, "    return jout;");
1114            }
1115            TypeRef::Bool => {
1116                let _ = writeln!(
1117                    out,
1118                    "    bool rv = {}((const {}*)(intptr_t)handle);",
1119                    getter_c, prefix
1120                );
1121                let _ = writeln!(out, "    return rv ? JNI_TRUE : JNI_FALSE;");
1122            }
1123            TypeRef::Struct(name) => {
1124                let _ = writeln!(
1125                    out,
1126                    "    const weaveffi_{}_{}* rv = {}((const {}*)(intptr_t)handle);",
1127                    module_name, name, getter_c, prefix
1128                );
1129                let _ = writeln!(out, "    return (jlong)(intptr_t)rv;");
1130            }
1131            TypeRef::Optional(inner) => {
1132                write_struct_optional_getter(out, inner, &getter_c, &prefix);
1133            }
1134            TypeRef::List(inner) => {
1135                write_struct_list_getter(out, inner, &getter_c, &prefix);
1136            }
1137            other => {
1138                let c_ty = c_type_for_return(other);
1139                let jcast = jni_cast_for(other);
1140                let _ = writeln!(
1141                    out,
1142                    "    {} rv = {}((const {}*)(intptr_t)handle);",
1143                    c_ty, getter_c, prefix
1144                );
1145                let _ = writeln!(out, "    return {}rv;", jcast);
1146            }
1147        }
1148
1149        let _ = writeln!(out, "}}\n");
1150    }
1151}
1152
1153fn write_struct_optional_getter(out: &mut String, inner: &TypeRef, getter_c: &str, prefix: &str) {
1154    match inner {
1155        TypeRef::StringUtf8 => {
1156            let _ = writeln!(
1157                out,
1158                "    const char* rv = {}((const {}*)(intptr_t)handle);",
1159                getter_c, prefix
1160            );
1161            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
1162            let _ = writeln!(out, "    jstring jout = (*env)->NewStringUTF(env, rv);");
1163            let _ = writeln!(out, "    weaveffi_free_string(rv);");
1164            let _ = writeln!(out, "    return jout;");
1165        }
1166        TypeRef::I32 | TypeRef::Enum(_) => {
1167            let _ = writeln!(
1168                out,
1169                "    const int32_t* rv = {}((const {}*)(intptr_t)handle);",
1170                getter_c, prefix
1171            );
1172            let _ = writeln!(out, "    if (rv == NULL) {{ return NULL; }}");
1173            let _ = writeln!(
1174                out,
1175                "    jclass cls = (*env)->FindClass(env, \"java/lang/Integer\");"
1176            );
1177            let _ = writeln!(
1178                out,
1179                "    jmethodID mid = (*env)->GetStaticMethodID(env, cls, \"valueOf\", \"(I)Ljava/lang/Integer;\");"
1180            );
1181            let _ = writeln!(
1182                out,
1183                "    return (*env)->CallStaticObjectMethod(env, cls, mid, (jint)*rv);"
1184            );
1185        }
1186        TypeRef::Struct(_) | TypeRef::Handle => {
1187            let _ = writeln!(
1188                out,
1189                "    const void* rv = {}((const {}*)(intptr_t)handle);",
1190                getter_c, prefix
1191            );
1192            let _ = writeln!(out, "    if (!rv) {{ return 0; }}");
1193            let _ = writeln!(out, "    return (jlong)(intptr_t)rv;");
1194        }
1195        _ => {
1196            let _ = writeln!(
1197                out,
1198                "    const void* rv = {}((const {}*)(intptr_t)handle);",
1199                getter_c, prefix
1200            );
1201            let _ = writeln!(out, "    if (!rv) {{ return 0; }}");
1202            let _ = writeln!(out, "    return (jlong)(intptr_t)rv;");
1203        }
1204    }
1205}
1206
1207fn write_struct_list_getter(out: &mut String, inner: &TypeRef, getter_c: &str, prefix: &str) {
1208    match inner {
1209        TypeRef::I32 | TypeRef::Enum(_) => {
1210            let _ = writeln!(out, "    size_t out_len = 0;");
1211            let _ = writeln!(
1212                out,
1213                "    const int32_t* rv = {}((const {}*)(intptr_t)handle, &out_len);",
1214                getter_c, prefix
1215            );
1216            let _ = writeln!(
1217                out,
1218                "    jintArray jout = (*env)->NewIntArray(env, (jsize)out_len);"
1219            );
1220            let _ = writeln!(out, "    if (jout && rv) {{ (*env)->SetIntArrayRegion(env, jout, 0, (jsize)out_len, (const jint*)rv); }}");
1221            let _ = writeln!(out, "    return jout;");
1222        }
1223        TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
1224            let _ = writeln!(out, "    size_t out_len = 0;");
1225            let _ = writeln!(
1226                out,
1227                "    const int64_t* rv = (const int64_t*){}((const {}*)(intptr_t)handle, &out_len);",
1228                getter_c, prefix
1229            );
1230            let _ = writeln!(
1231                out,
1232                "    jlongArray jout = (*env)->NewLongArray(env, (jsize)out_len);"
1233            );
1234            let _ = writeln!(out, "    if (jout && rv) {{ (*env)->SetLongArrayRegion(env, jout, 0, (jsize)out_len, (const jlong*)rv); }}");
1235            let _ = writeln!(out, "    return jout;");
1236        }
1237        TypeRef::F64 => {
1238            let _ = writeln!(out, "    size_t out_len = 0;");
1239            let _ = writeln!(
1240                out,
1241                "    const double* rv = (const double*){}((const {}*)(intptr_t)handle, &out_len);",
1242                getter_c, prefix
1243            );
1244            let _ = writeln!(
1245                out,
1246                "    jdoubleArray jout = (*env)->NewDoubleArray(env, (jsize)out_len);"
1247            );
1248            let _ = writeln!(out, "    if (jout && rv) {{ (*env)->SetDoubleArrayRegion(env, jout, 0, (jsize)out_len, (const jdouble*)rv); }}");
1249            let _ = writeln!(out, "    return jout;");
1250        }
1251        TypeRef::Bool => {
1252            let _ = writeln!(out, "    size_t out_len = 0;");
1253            let _ = writeln!(
1254                out,
1255                "    const int32_t* rv = (const int32_t*){}((const {}*)(intptr_t)handle, &out_len);",
1256                getter_c, prefix
1257            );
1258            let _ = writeln!(
1259                out,
1260                "    jbooleanArray jout = (*env)->NewBooleanArray(env, (jsize)out_len);"
1261            );
1262            let _ = writeln!(out, "    if (jout && rv) {{");
1263            let _ = writeln!(out, "        for (jsize i = 0; i < (jsize)out_len; i++) {{");
1264            let _ = writeln!(
1265                out,
1266                "            jboolean val = rv[i] ? JNI_TRUE : JNI_FALSE;"
1267            );
1268            let _ = writeln!(
1269                out,
1270                "            (*env)->SetBooleanArrayRegion(env, jout, i, 1, &val);"
1271            );
1272            let _ = writeln!(out, "        }}");
1273            let _ = writeln!(out, "    }}");
1274            let _ = writeln!(out, "    return jout;");
1275        }
1276        _ => {
1277            let _ = writeln!(out, "    size_t out_len = 0;");
1278            let _ = writeln!(
1279                out,
1280                "    const void* rv = {}((const {}*)(intptr_t)handle, &out_len);",
1281                getter_c, prefix
1282            );
1283            let _ = writeln!(out, "    (void)rv; (void)out_len;");
1284            let _ = writeln!(out, "    return NULL;");
1285        }
1286    }
1287}
1288
1289fn release_jni_resources_single(out: &mut String, name: &str, ty: &TypeRef) {
1290    match ty {
1291        TypeRef::StringUtf8 => {
1292            let _ = writeln!(
1293                out,
1294                "    (*env)->ReleaseStringUTFChars(env, {n}, {n}_chars);",
1295                n = name
1296            );
1297        }
1298        TypeRef::Bytes => {
1299            let _ = writeln!(
1300                out,
1301                "    (*env)->ReleaseByteArrayElements(env, {n}, {n}_elems, 0);",
1302                n = name
1303            );
1304        }
1305        TypeRef::Optional(inner) => match inner.as_ref() {
1306            TypeRef::StringUtf8 => {
1307                let _ = writeln!(
1308                    out,
1309                    "    if ({n} != NULL) {{ (*env)->ReleaseStringUTFChars(env, {n}, {n}_chars); }}",
1310                    n = name
1311                );
1312            }
1313            TypeRef::Bytes => {
1314                let _ = writeln!(
1315                    out,
1316                    "    if ({n} != NULL && {n}_elems != NULL) {{ (*env)->ReleaseByteArrayElements(env, {n}, {n}_elems, 0); }}",
1317                    n = name
1318                );
1319            }
1320            _ => {}
1321        },
1322        TypeRef::List(inner) => match inner.as_ref() {
1323            TypeRef::I32 | TypeRef::Enum(_) => {
1324                let _ = writeln!(
1325                    out,
1326                    "    (*env)->ReleaseIntArrayElements(env, {n}, {n}_elems, 0);",
1327                    n = name
1328                );
1329            }
1330            TypeRef::U32 | TypeRef::I64 | TypeRef::Handle | TypeRef::Struct(_) => {
1331                let _ = writeln!(
1332                    out,
1333                    "    (*env)->ReleaseLongArrayElements(env, {n}, {n}_elems, 0);",
1334                    n = name
1335                );
1336            }
1337            TypeRef::F64 => {
1338                let _ = writeln!(
1339                    out,
1340                    "    (*env)->ReleaseDoubleArrayElements(env, {n}, {n}_elems, 0);",
1341                    n = name
1342                );
1343            }
1344            TypeRef::Bool => {
1345                let _ = writeln!(
1346                    out,
1347                    "    (*env)->ReleaseBooleanArrayElements(env, {n}, {n}_elems, 0);",
1348                    n = name
1349                );
1350            }
1351            _ => {}
1352        },
1353        _ => {}
1354    }
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359    use super::*;
1360    use weaveffi_ir::ir::{EnumDef, EnumVariant, Function, Module, Param, StructDef, StructField};
1361
1362    fn make_api(modules: Vec<Module>) -> Api {
1363        Api {
1364            version: "0.1.0".to_string(),
1365            modules,
1366        }
1367    }
1368
1369    fn make_struct_api() -> Api {
1370        make_api(vec![Module {
1371            name: "contacts".to_string(),
1372            functions: vec![],
1373            structs: vec![StructDef {
1374                name: "Contact".to_string(),
1375                doc: None,
1376                fields: vec![
1377                    StructField {
1378                        name: "name".to_string(),
1379                        ty: TypeRef::StringUtf8,
1380                        doc: None,
1381                    },
1382                    StructField {
1383                        name: "age".to_string(),
1384                        ty: TypeRef::I32,
1385                        doc: None,
1386                    },
1387                ],
1388            }],
1389            enums: vec![],
1390            errors: None,
1391        }])
1392    }
1393
1394    #[test]
1395    fn kotlin_struct_class_declaration() {
1396        let api = make_struct_api();
1397        let kt = render_kotlin(&api);
1398        assert!(
1399            kt.contains("class Contact internal constructor(private var handle: Long) : java.io.Closeable {"),
1400            "missing struct class declaration: {kt}"
1401        );
1402    }
1403
1404    #[test]
1405    fn kotlin_struct_companion_native_create() {
1406        let api = make_struct_api();
1407        let kt = render_kotlin(&api);
1408        assert!(
1409            kt.contains("@JvmStatic external fun nativeCreate(name: String, age: Int): Long"),
1410            "missing nativeCreate: {kt}"
1411        );
1412    }
1413
1414    #[test]
1415    fn kotlin_struct_companion_native_destroy() {
1416        let api = make_struct_api();
1417        let kt = render_kotlin(&api);
1418        assert!(
1419            kt.contains("@JvmStatic external fun nativeDestroy(handle: Long)"),
1420            "missing nativeDestroy: {kt}"
1421        );
1422    }
1423
1424    #[test]
1425    fn kotlin_struct_companion_native_getters() {
1426        let api = make_struct_api();
1427        let kt = render_kotlin(&api);
1428        assert!(
1429            kt.contains("@JvmStatic external fun nativeGetName(handle: Long): String"),
1430            "missing nativeGetName: {kt}"
1431        );
1432        assert!(
1433            kt.contains("@JvmStatic external fun nativeGetAge(handle: Long): Int"),
1434            "missing nativeGetAge: {kt}"
1435        );
1436    }
1437
1438    #[test]
1439    fn kotlin_struct_factory_method() {
1440        let api = make_struct_api();
1441        let kt = render_kotlin(&api);
1442        assert!(
1443            kt.contains(
1444                "fun create(name: String, age: Int): Contact = Contact(nativeCreate(name, age))"
1445            ),
1446            "missing create factory: {kt}"
1447        );
1448    }
1449
1450    #[test]
1451    fn kotlin_struct_property_getters() {
1452        let api = make_struct_api();
1453        let kt = render_kotlin(&api);
1454        assert!(
1455            kt.contains("val name: String get() = nativeGetName(handle)"),
1456            "missing name property: {kt}"
1457        );
1458        assert!(
1459            kt.contains("val age: Int get() = nativeGetAge(handle)"),
1460            "missing age property: {kt}"
1461        );
1462    }
1463
1464    #[test]
1465    fn kotlin_struct_closeable() {
1466        let api = make_struct_api();
1467        let kt = render_kotlin(&api);
1468        assert!(
1469            kt.contains("override fun close()"),
1470            "missing close method: {kt}"
1471        );
1472        assert!(
1473            kt.contains("nativeDestroy(handle)"),
1474            "missing nativeDestroy call in close: {kt}"
1475        );
1476        assert!(kt.contains("handle = 0L"), "missing handle zeroing: {kt}");
1477    }
1478
1479    #[test]
1480    fn kotlin_struct_finalize() {
1481        let api = make_struct_api();
1482        let kt = render_kotlin(&api);
1483        assert!(
1484            kt.contains("protected fun finalize()"),
1485            "missing finalize: {kt}"
1486        );
1487    }
1488
1489    #[test]
1490    fn kotlin_struct_loads_library() {
1491        let api = make_struct_api();
1492        let kt = render_kotlin(&api);
1493        let struct_section = kt.split("class Contact").nth(1).unwrap();
1494        assert!(
1495            struct_section.contains("System.loadLibrary(\"weaveffi\")"),
1496            "struct companion missing loadLibrary: {kt}"
1497        );
1498    }
1499
1500    #[test]
1501    fn jni_struct_native_create() {
1502        let api = make_struct_api();
1503        let jni = render_jni_c(&api);
1504        assert!(
1505            jni.contains("Java_com_weaveffi_Contact_nativeCreate"),
1506            "missing JNI nativeCreate: {jni}"
1507        );
1508        assert!(
1509            jni.contains("weaveffi_contacts_Contact_create("),
1510            "missing C create call: {jni}"
1511        );
1512    }
1513
1514    #[test]
1515    fn jni_struct_native_destroy() {
1516        let api = make_struct_api();
1517        let jni = render_jni_c(&api);
1518        assert!(
1519            jni.contains("Java_com_weaveffi_Contact_nativeDestroy"),
1520            "missing JNI nativeDestroy: {jni}"
1521        );
1522        assert!(
1523            jni.contains("weaveffi_contacts_Contact_destroy("),
1524            "missing C destroy call: {jni}"
1525        );
1526    }
1527
1528    #[test]
1529    fn jni_struct_native_getters() {
1530        let api = make_struct_api();
1531        let jni = render_jni_c(&api);
1532        assert!(
1533            jni.contains("Java_com_weaveffi_Contact_nativeGetName"),
1534            "missing JNI nativeGetName: {jni}"
1535        );
1536        assert!(
1537            jni.contains("weaveffi_contacts_Contact_get_name("),
1538            "missing C get_name call: {jni}"
1539        );
1540        assert!(
1541            jni.contains("Java_com_weaveffi_Contact_nativeGetAge"),
1542            "missing JNI nativeGetAge: {jni}"
1543        );
1544        assert!(
1545            jni.contains("weaveffi_contacts_Contact_get_age("),
1546            "missing C get_age call: {jni}"
1547        );
1548    }
1549
1550    #[test]
1551    fn jni_struct_string_getter_frees() {
1552        let api = make_struct_api();
1553        let jni = render_jni_c(&api);
1554        assert!(
1555            jni.contains("weaveffi_free_string(rv)"),
1556            "missing free_string in getter: {jni}"
1557        );
1558    }
1559
1560    #[test]
1561    fn jni_struct_create_handles_string_param() {
1562        let api = make_struct_api();
1563        let jni = render_jni_c(&api);
1564        assert!(
1565            jni.contains("GetStringUTFChars(env, name, NULL)"),
1566            "missing string acquisition in create: {jni}"
1567        );
1568        assert!(
1569            jni.contains("ReleaseStringUTFChars(env, name, name_chars)"),
1570            "missing string release in create: {jni}"
1571        );
1572    }
1573
1574    #[test]
1575    fn jni_struct_create_error_check() {
1576        let api = make_struct_api();
1577        let jni = render_jni_c(&api);
1578        let create_section: &str = jni
1579            .split("Java_com_weaveffi_Contact_nativeCreate")
1580            .nth(1)
1581            .unwrap();
1582        assert!(
1583            create_section.contains("if (err.code != 0)"),
1584            "missing error check in create: {jni}"
1585        );
1586    }
1587
1588    #[test]
1589    fn kotlin_struct_with_bytes_field() {
1590        let api = make_api(vec![Module {
1591            name: "storage".to_string(),
1592            functions: vec![],
1593            structs: vec![StructDef {
1594                name: "Blob".to_string(),
1595                doc: None,
1596                fields: vec![StructField {
1597                    name: "data".to_string(),
1598                    ty: TypeRef::Bytes,
1599                    doc: None,
1600                }],
1601            }],
1602            enums: vec![],
1603            errors: None,
1604        }]);
1605
1606        let kt = render_kotlin(&api);
1607        assert!(
1608            kt.contains("val data: ByteArray get() = nativeGetData(handle)"),
1609            "missing bytes property: {kt}"
1610        );
1611
1612        let jni = render_jni_c(&api);
1613        assert!(
1614            jni.contains("weaveffi_storage_Blob_get_data("),
1615            "missing bytes getter C call: {jni}"
1616        );
1617        assert!(
1618            jni.contains("weaveffi_free_bytes("),
1619            "missing free_bytes in getter: {jni}"
1620        );
1621    }
1622
1623    #[test]
1624    fn kotlin_struct_with_nested_struct_field() {
1625        let api = make_api(vec![Module {
1626            name: "geo".to_string(),
1627            functions: vec![],
1628            structs: vec![StructDef {
1629                name: "Line".to_string(),
1630                doc: None,
1631                fields: vec![StructField {
1632                    name: "start".to_string(),
1633                    ty: TypeRef::Struct("Point".into()),
1634                    doc: None,
1635                }],
1636            }],
1637            enums: vec![],
1638            errors: None,
1639        }]);
1640
1641        let kt = render_kotlin(&api);
1642        assert!(
1643            kt.contains("val start: Point get() = Point(nativeGetStart(handle))"),
1644            "missing nested struct property: {kt}"
1645        );
1646
1647        let jni = render_jni_c(&api);
1648        assert!(
1649            jni.contains("weaveffi_geo_Line_get_start("),
1650            "missing nested struct getter C call: {jni}"
1651        );
1652    }
1653
1654    #[test]
1655    fn kotlin_type_for_struct_returns_long() {
1656        assert_eq!(kotlin_type(&TypeRef::Struct("Contact".into())), "Long");
1657    }
1658
1659    #[test]
1660    fn kotlin_getter_type_for_struct_returns_name() {
1661        assert_eq!(
1662            kotlin_getter_type(&TypeRef::Struct("Contact".into())),
1663            "Contact"
1664        );
1665    }
1666
1667    #[test]
1668    fn to_pascal_case_converts_snake_case() {
1669        assert_eq!(to_pascal_case("first_name"), "FirstName");
1670        assert_eq!(to_pascal_case("name"), "Name");
1671        assert_eq!(to_pascal_case("is_active"), "IsActive");
1672    }
1673
1674    #[test]
1675    fn function_with_struct_param_jni() {
1676        let api = make_api(vec![Module {
1677            name: "contacts".to_string(),
1678            functions: vec![Function {
1679                name: "save".to_string(),
1680                params: vec![Param {
1681                    name: "contact".to_string(),
1682                    ty: TypeRef::Struct("Contact".into()),
1683                }],
1684                returns: None,
1685                doc: None,
1686                r#async: false,
1687            }],
1688            structs: vec![],
1689            enums: vec![],
1690            errors: None,
1691        }]);
1692
1693        let kt = render_kotlin(&api);
1694        assert!(
1695            kt.contains("contact: Long"),
1696            "missing struct param as Long: {kt}"
1697        );
1698
1699        let jni = render_jni_c(&api);
1700        assert!(
1701            jni.contains("(const weaveffi_contacts_Contact*)(intptr_t)contact"),
1702            "missing struct param cast: {jni}"
1703        );
1704    }
1705
1706    #[test]
1707    fn function_returning_struct_jni() {
1708        let api = make_api(vec![Module {
1709            name: "contacts".to_string(),
1710            functions: vec![Function {
1711                name: "create".to_string(),
1712                params: vec![Param {
1713                    name: "age".to_string(),
1714                    ty: TypeRef::I32,
1715                }],
1716                returns: Some(TypeRef::Struct("Contact".into())),
1717                doc: None,
1718                r#async: false,
1719            }],
1720            structs: vec![],
1721            enums: vec![],
1722            errors: None,
1723        }]);
1724
1725        let jni = render_jni_c(&api);
1726        assert!(
1727            jni.contains("weaveffi_contacts_Contact* rv"),
1728            "missing struct return type: {jni}"
1729        );
1730        assert!(
1731            jni.contains("return (jlong)(intptr_t)rv;"),
1732            "missing struct return cast: {jni}"
1733        );
1734    }
1735
1736    // --- Enum tests ---
1737
1738    #[test]
1739    fn kotlin_enum_class_generated() {
1740        let api = make_api(vec![Module {
1741            name: "paint".to_string(),
1742            functions: vec![],
1743            structs: vec![],
1744            enums: vec![EnumDef {
1745                name: "Color".to_string(),
1746                doc: None,
1747                variants: vec![
1748                    EnumVariant {
1749                        name: "Red".to_string(),
1750                        value: 0,
1751                        doc: None,
1752                    },
1753                    EnumVariant {
1754                        name: "Green".to_string(),
1755                        value: 1,
1756                        doc: None,
1757                    },
1758                    EnumVariant {
1759                        name: "Blue".to_string(),
1760                        value: 2,
1761                        doc: None,
1762                    },
1763                ],
1764            }],
1765            errors: None,
1766        }]);
1767
1768        let kt = render_kotlin(&api);
1769        assert!(
1770            kt.contains("enum class Color(val value: Int) {"),
1771            "missing enum class: {kt}"
1772        );
1773        assert!(kt.contains("Red(0),"), "missing Red variant: {kt}");
1774        assert!(kt.contains("Green(1),"), "missing Green variant: {kt}");
1775        assert!(
1776            kt.contains("Blue(2);"),
1777            "missing Blue variant (with semicolon): {kt}"
1778        );
1779        assert!(
1780            kt.contains("companion object {"),
1781            "missing companion object: {kt}"
1782        );
1783        assert!(
1784            kt.contains("fun fromValue(value: Int): Color"),
1785            "missing fromValue: {kt}"
1786        );
1787    }
1788
1789    #[test]
1790    fn kotlin_type_for_enum_is_int() {
1791        assert_eq!(kotlin_type(&TypeRef::Enum("Color".into())), "Int");
1792    }
1793
1794    #[test]
1795    fn kotlin_getter_type_for_enum_returns_name() {
1796        assert_eq!(kotlin_getter_type(&TypeRef::Enum("Color".into())), "Color");
1797    }
1798
1799    #[test]
1800    fn function_with_enum_param_kotlin() {
1801        let api = make_api(vec![Module {
1802            name: "paint".to_string(),
1803            functions: vec![Function {
1804                name: "set_color".to_string(),
1805                params: vec![Param {
1806                    name: "color".to_string(),
1807                    ty: TypeRef::Enum("Color".into()),
1808                }],
1809                returns: None,
1810                doc: None,
1811                r#async: false,
1812            }],
1813            structs: vec![],
1814            enums: vec![],
1815            errors: None,
1816        }]);
1817
1818        let kt = render_kotlin(&api);
1819        assert!(kt.contains("color: Int"), "missing enum param as Int: {kt}");
1820    }
1821
1822    #[test]
1823    fn function_with_enum_param_jni() {
1824        let api = make_api(vec![Module {
1825            name: "paint".to_string(),
1826            functions: vec![Function {
1827                name: "set_color".to_string(),
1828                params: vec![Param {
1829                    name: "color".to_string(),
1830                    ty: TypeRef::Enum("Color".into()),
1831                }],
1832                returns: None,
1833                doc: None,
1834                r#async: false,
1835            }],
1836            structs: vec![],
1837            enums: vec![],
1838            errors: None,
1839        }]);
1840
1841        let jni = render_jni_c(&api);
1842        assert!(
1843            jni.contains("jint color"),
1844            "missing jint param in JNI: {jni}"
1845        );
1846        assert!(
1847            jni.contains("(int32_t)color"),
1848            "missing int32_t cast: {jni}"
1849        );
1850    }
1851
1852    #[test]
1853    fn function_returning_enum_jni() {
1854        let api = make_api(vec![Module {
1855            name: "paint".to_string(),
1856            functions: vec![Function {
1857                name: "get_color".to_string(),
1858                params: vec![],
1859                returns: Some(TypeRef::Enum("Color".into())),
1860                doc: None,
1861                r#async: false,
1862            }],
1863            structs: vec![],
1864            enums: vec![],
1865            errors: None,
1866        }]);
1867
1868        let jni = render_jni_c(&api);
1869        assert!(
1870            jni.contains("JNIEXPORT jint JNICALL"),
1871            "missing jint return in JNI: {jni}"
1872        );
1873        assert!(jni.contains("(jint)"), "missing jint cast: {jni}");
1874    }
1875
1876    // --- Optional tests ---
1877
1878    #[test]
1879    fn kotlin_type_for_optional_int() {
1880        assert_eq!(
1881            kotlin_type(&TypeRef::Optional(Box::new(TypeRef::I32))),
1882            "Int?"
1883        );
1884    }
1885
1886    #[test]
1887    fn kotlin_type_for_optional_string() {
1888        assert_eq!(
1889            kotlin_type(&TypeRef::Optional(Box::new(TypeRef::StringUtf8))),
1890            "String?"
1891        );
1892    }
1893
1894    #[test]
1895    fn function_with_optional_int_param_kotlin() {
1896        let api = make_api(vec![Module {
1897            name: "store".to_string(),
1898            functions: vec![Function {
1899                name: "find".to_string(),
1900                params: vec![Param {
1901                    name: "id".to_string(),
1902                    ty: TypeRef::Optional(Box::new(TypeRef::I32)),
1903                }],
1904                returns: None,
1905                doc: None,
1906                r#async: false,
1907            }],
1908            structs: vec![],
1909            enums: vec![],
1910            errors: None,
1911        }]);
1912
1913        let kt = render_kotlin(&api);
1914        assert!(kt.contains("id: Int?"), "missing optional Int? param: {kt}");
1915    }
1916
1917    #[test]
1918    fn function_with_optional_int_param_jni() {
1919        let api = make_api(vec![Module {
1920            name: "store".to_string(),
1921            functions: vec![Function {
1922                name: "find".to_string(),
1923                params: vec![Param {
1924                    name: "id".to_string(),
1925                    ty: TypeRef::Optional(Box::new(TypeRef::I32)),
1926                }],
1927                returns: None,
1928                doc: None,
1929                r#async: false,
1930            }],
1931            structs: vec![],
1932            enums: vec![],
1933            errors: None,
1934        }]);
1935
1936        let jni = render_jni_c(&api);
1937        assert!(jni.contains("jobject id"), "missing jobject param: {jni}");
1938        assert!(
1939            jni.contains("java/lang/Integer"),
1940            "missing Integer class lookup: {jni}"
1941        );
1942        assert!(
1943            jni.contains("intValue"),
1944            "missing intValue unbox call: {jni}"
1945        );
1946        assert!(jni.contains("id_ptr"), "missing id_ptr in C call: {jni}");
1947    }
1948
1949    #[test]
1950    fn function_with_optional_string_param_jni() {
1951        let api = make_api(vec![Module {
1952            name: "store".to_string(),
1953            functions: vec![Function {
1954                name: "find_name".to_string(),
1955                params: vec![Param {
1956                    name: "query".to_string(),
1957                    ty: TypeRef::Optional(Box::new(TypeRef::StringUtf8)),
1958                }],
1959                returns: None,
1960                doc: None,
1961                r#async: false,
1962            }],
1963            structs: vec![],
1964            enums: vec![],
1965            errors: None,
1966        }]);
1967
1968        let jni = render_jni_c(&api);
1969        assert!(
1970            jni.contains("jstring query"),
1971            "missing jstring param: {jni}"
1972        );
1973        assert!(
1974            jni.contains("if (query != NULL)"),
1975            "missing null check for optional string: {jni}"
1976        );
1977    }
1978
1979    #[test]
1980    fn function_returning_optional_int_jni() {
1981        let api = make_api(vec![Module {
1982            name: "store".to_string(),
1983            functions: vec![Function {
1984                name: "lookup".to_string(),
1985                params: vec![],
1986                returns: Some(TypeRef::Optional(Box::new(TypeRef::I32))),
1987                doc: None,
1988                r#async: false,
1989            }],
1990            structs: vec![],
1991            enums: vec![],
1992            errors: None,
1993        }]);
1994
1995        let jni = render_jni_c(&api);
1996        assert!(
1997            jni.contains("JNIEXPORT jobject JNICALL"),
1998            "missing jobject return: {jni}"
1999        );
2000        assert!(
2001            jni.contains("if (rv == NULL) { return NULL; }"),
2002            "missing NULL check: {jni}"
2003        );
2004        assert!(
2005            jni.contains("java/lang/Integer"),
2006            "missing Integer boxing: {jni}"
2007        );
2008        assert!(
2009            jni.contains("valueOf"),
2010            "missing valueOf boxing call: {jni}"
2011        );
2012    }
2013
2014    #[test]
2015    fn function_returning_optional_string_jni() {
2016        let api = make_api(vec![Module {
2017            name: "store".to_string(),
2018            functions: vec![Function {
2019                name: "get_name".to_string(),
2020                params: vec![],
2021                returns: Some(TypeRef::Optional(Box::new(TypeRef::StringUtf8))),
2022                doc: None,
2023                r#async: false,
2024            }],
2025            structs: vec![],
2026            enums: vec![],
2027            errors: None,
2028        }]);
2029
2030        let jni = render_jni_c(&api);
2031        assert!(
2032            jni.contains("JNIEXPORT jstring JNICALL"),
2033            "missing jstring return: {jni}"
2034        );
2035        assert!(
2036            jni.contains("if (rv == NULL) { return NULL; }"),
2037            "missing NULL check: {jni}"
2038        );
2039        assert!(jni.contains("NewStringUTF"), "missing NewStringUTF: {jni}");
2040    }
2041
2042    // --- List tests ---
2043
2044    #[test]
2045    fn kotlin_type_for_list_int() {
2046        assert_eq!(
2047            kotlin_type(&TypeRef::List(Box::new(TypeRef::I32))),
2048            "IntArray"
2049        );
2050    }
2051
2052    #[test]
2053    fn kotlin_type_for_list_string() {
2054        assert_eq!(
2055            kotlin_type(&TypeRef::List(Box::new(TypeRef::StringUtf8))),
2056            "Array<String>"
2057        );
2058    }
2059
2060    #[test]
2061    fn kotlin_type_for_list_enum() {
2062        assert_eq!(
2063            kotlin_type(&TypeRef::List(Box::new(TypeRef::Enum("Color".into())))),
2064            "IntArray"
2065        );
2066    }
2067
2068    #[test]
2069    fn function_with_list_int_param_kotlin() {
2070        let api = make_api(vec![Module {
2071            name: "batch".to_string(),
2072            functions: vec![Function {
2073                name: "process".to_string(),
2074                params: vec![Param {
2075                    name: "ids".to_string(),
2076                    ty: TypeRef::List(Box::new(TypeRef::I32)),
2077                }],
2078                returns: None,
2079                doc: None,
2080                r#async: false,
2081            }],
2082            structs: vec![],
2083            enums: vec![],
2084            errors: None,
2085        }]);
2086
2087        let kt = render_kotlin(&api);
2088        assert!(kt.contains("ids: IntArray"), "missing IntArray param: {kt}");
2089    }
2090
2091    #[test]
2092    fn function_with_list_int_param_jni() {
2093        let api = make_api(vec![Module {
2094            name: "batch".to_string(),
2095            functions: vec![Function {
2096                name: "process".to_string(),
2097                params: vec![Param {
2098                    name: "ids".to_string(),
2099                    ty: TypeRef::List(Box::new(TypeRef::I32)),
2100                }],
2101                returns: None,
2102                doc: None,
2103                r#async: false,
2104            }],
2105            structs: vec![],
2106            enums: vec![],
2107            errors: None,
2108        }]);
2109
2110        let jni = render_jni_c(&api);
2111        assert!(
2112            jni.contains("jintArray ids"),
2113            "missing jintArray param: {jni}"
2114        );
2115        assert!(
2116            jni.contains("GetIntArrayElements(env, ids, NULL)"),
2117            "missing GetIntArrayElements: {jni}"
2118        );
2119        assert!(
2120            jni.contains("ReleaseIntArrayElements(env, ids, ids_elems, 0)"),
2121            "missing ReleaseIntArrayElements: {jni}"
2122        );
2123    }
2124
2125    #[test]
2126    fn function_returning_list_int_jni() {
2127        let api = make_api(vec![Module {
2128            name: "batch".to_string(),
2129            functions: vec![Function {
2130                name: "get_ids".to_string(),
2131                params: vec![],
2132                returns: Some(TypeRef::List(Box::new(TypeRef::I32))),
2133                doc: None,
2134                r#async: false,
2135            }],
2136            structs: vec![],
2137            enums: vec![],
2138            errors: None,
2139        }]);
2140
2141        let jni = render_jni_c(&api);
2142        assert!(
2143            jni.contains("JNIEXPORT jintArray JNICALL"),
2144            "missing jintArray return: {jni}"
2145        );
2146        assert!(jni.contains("NewIntArray"), "missing NewIntArray: {jni}");
2147        assert!(
2148            jni.contains("SetIntArrayRegion"),
2149            "missing SetIntArrayRegion: {jni}"
2150        );
2151        assert!(jni.contains("out_len"), "missing out_len: {jni}");
2152    }
2153
2154    #[test]
2155    fn jni_param_type_enum_is_jint() {
2156        assert_eq!(jni_param_type(&TypeRef::Enum("Color".into())), "jint");
2157    }
2158
2159    #[test]
2160    fn jni_param_type_optional_int_is_jobject() {
2161        assert_eq!(
2162            jni_param_type(&TypeRef::Optional(Box::new(TypeRef::I32))),
2163            "jobject"
2164        );
2165    }
2166
2167    #[test]
2168    fn jni_param_type_optional_string_is_jstring() {
2169        assert_eq!(
2170            jni_param_type(&TypeRef::Optional(Box::new(TypeRef::StringUtf8))),
2171            "jstring"
2172        );
2173    }
2174
2175    #[test]
2176    fn jni_param_type_list_int_is_jintarray() {
2177        assert_eq!(
2178            jni_param_type(&TypeRef::List(Box::new(TypeRef::I32))),
2179            "jintArray"
2180        );
2181    }
2182
2183    #[test]
2184    fn jni_param_type_list_long_is_jlongarray() {
2185        assert_eq!(
2186            jni_param_type(&TypeRef::List(Box::new(TypeRef::I64))),
2187            "jlongArray"
2188        );
2189    }
2190
2191    #[test]
2192    fn generate_android_with_structs_and_enums() {
2193        let api = make_api(vec![Module {
2194            name: "contacts".to_string(),
2195            functions: vec![Function {
2196                name: "get_contact".to_string(),
2197                params: vec![Param {
2198                    name: "id".to_string(),
2199                    ty: TypeRef::I32,
2200                }],
2201                returns: Some(TypeRef::Struct("Contact".into())),
2202                doc: None,
2203                r#async: false,
2204            }],
2205            structs: vec![StructDef {
2206                name: "Contact".to_string(),
2207                doc: None,
2208                fields: vec![
2209                    StructField {
2210                        name: "name".to_string(),
2211                        ty: TypeRef::StringUtf8,
2212                        doc: None,
2213                    },
2214                    StructField {
2215                        name: "email".to_string(),
2216                        ty: TypeRef::StringUtf8,
2217                        doc: None,
2218                    },
2219                    StructField {
2220                        name: "age".to_string(),
2221                        ty: TypeRef::I32,
2222                        doc: None,
2223                    },
2224                ],
2225            }],
2226            enums: vec![EnumDef {
2227                name: "Color".to_string(),
2228                doc: None,
2229                variants: vec![
2230                    EnumVariant {
2231                        name: "Red".to_string(),
2232                        value: 0,
2233                        doc: None,
2234                    },
2235                    EnumVariant {
2236                        name: "Green".to_string(),
2237                        value: 1,
2238                        doc: None,
2239                    },
2240                    EnumVariant {
2241                        name: "Blue".to_string(),
2242                        value: 2,
2243                        doc: None,
2244                    },
2245                ],
2246            }],
2247            errors: None,
2248        }]);
2249
2250        let tmp = std::env::temp_dir().join("weaveffi_test_android_structs_and_enums");
2251        let _ = std::fs::remove_dir_all(&tmp);
2252        std::fs::create_dir_all(&tmp).unwrap();
2253        let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
2254
2255        AndroidGenerator.generate(&api, out_dir).unwrap();
2256
2257        let kotlin =
2258            std::fs::read_to_string(tmp.join("android/src/main/kotlin/com/weaveffi/WeaveFFI.kt"))
2259                .unwrap();
2260
2261        assert!(
2262            kotlin.contains("enum class Color(val value: Int) {"),
2263            "missing enum class: {kotlin}"
2264        );
2265        assert!(kotlin.contains("Red(0),"), "missing Red variant: {kotlin}");
2266        assert!(
2267            kotlin.contains("Green(1),"),
2268            "missing Green variant: {kotlin}"
2269        );
2270        assert!(
2271            kotlin.contains("Blue(2);"),
2272            "missing Blue variant with semicolon: {kotlin}"
2273        );
2274        assert!(
2275            kotlin.contains("fun fromValue(value: Int): Color"),
2276            "missing fromValue: {kotlin}"
2277        );
2278
2279        assert!(
2280            kotlin.contains(
2281                "class Contact internal constructor(private var handle: Long) : java.io.Closeable {"
2282            ),
2283            "missing struct class: {kotlin}"
2284        );
2285        assert!(
2286            kotlin.contains(
2287                "@JvmStatic external fun nativeCreate(name: String, email: String, age: Int): Long"
2288            ),
2289            "missing nativeCreate: {kotlin}"
2290        );
2291        assert!(
2292            kotlin.contains("val name: String get() = nativeGetName(handle)"),
2293            "missing name getter: {kotlin}"
2294        );
2295        assert!(
2296            kotlin.contains("val email: String get() = nativeGetEmail(handle)"),
2297            "missing email getter: {kotlin}"
2298        );
2299        assert!(
2300            kotlin.contains("val age: Int get() = nativeGetAge(handle)"),
2301            "missing age getter: {kotlin}"
2302        );
2303
2304        let jni = std::fs::read_to_string(tmp.join("android/src/main/cpp/weaveffi_jni.c")).unwrap();
2305
2306        assert!(
2307            jni.contains("Java_com_weaveffi_Contact_nativeGetName"),
2308            "missing JNI nativeGetName: {jni}"
2309        );
2310        assert!(
2311            jni.contains("weaveffi_contacts_Contact_get_name("),
2312            "missing C get_name call: {jni}"
2313        );
2314        assert!(
2315            jni.contains("Java_com_weaveffi_Contact_nativeGetEmail"),
2316            "missing JNI nativeGetEmail: {jni}"
2317        );
2318        assert!(
2319            jni.contains("weaveffi_contacts_Contact_get_email("),
2320            "missing C get_email call: {jni}"
2321        );
2322        assert!(
2323            jni.contains("Java_com_weaveffi_Contact_nativeGetAge"),
2324            "missing JNI nativeGetAge: {jni}"
2325        );
2326        assert!(
2327            jni.contains("weaveffi_contacts_Contact_get_age("),
2328            "missing C get_age call: {jni}"
2329        );
2330
2331        let _ = std::fs::remove_dir_all(&tmp);
2332    }
2333}