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 {
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 {
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 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 #[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 #[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 #[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}