use prebindgen_registry::{Conversions, RegistryBuilder};
use super::*;
#[test]
fn inline_output_gets_own_builder() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_name(t: &ZThing) -> String { unimplemented!() }",
"pub fn z_thing_size(t: &ZThing) -> i64 { unimplemented!() }",
"pub fn z_make_a() -> ZThing { unimplemented!() }",
"pub fn z_make_b() -> ZThing { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("thing")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_name).name("name"))
.method(prebindgen_registry::fun!(z_thing_size).name("size")),
)
.fun(prebindgen_registry::fun!(z_make_a))
.fun(
prebindgen_registry::fun!(z_make_b).expand_return(
prebindgen_registry::expand_return!(ZThing)
.field(prebindgen_registry::fun!(z_thing_name).name("name"))
.field(prebindgen_registry::fun!(z_thing_size).name("size"))
.field(prebindgen_registry::fun!(z_thing_name).name("name2")),
),
),
)
.expand(
prebindgen_registry::expand_return!(ZThing)
.field(prebindgen_registry::fun!(z_thing_name))
.field(prebindgen_registry::fun!(z_thing_size).name("size")),
);
let dir = unique_test_dir("jnigen_inline_out");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("io/test/jni/thing/ZThingBuilder"), "{rust}");
assert!(
rc.contains("io/test/jni/thing/ZThingZMakeBBuilder"),
"{rust}"
);
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let all: String = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n")
.split_whitespace()
.collect();
assert!(
all.contains("funinterfaceZThingBuilder<outR>{publicfunrun(name:String,size:Long):R"),
"{all}"
);
assert!(
all.contains(
"funinterfaceZThingZMakeBBuilder<outR>{publicfunrun(name:String,size:Long,name2:String):R"
),
"{all}"
);
assert!(all.contains("build:ZThingBuilder<R>"), "{all}");
assert!(all.contains("build:ZThingZMakeBBuilder<R>"), "{all}");
}
#[test]
fn error_unwrap_universal_records() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_err_message(e: &ZErr) -> String { unimplemented!() }",
"pub fn z_err_detail(e: &ZErr) -> Option<&ZDetail> { unimplemented!() }",
"pub fn z_detail_code(d: &ZDetail) -> i32 { unimplemented!() }",
"pub fn z_fallible() -> Result<i64, ZErr> { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("errors")
.class(
crate::ptr_class!(ZDetail)
.method(prebindgen_registry::fun!(z_detail_code).name("code")),
)
.class(
crate::ptr_class!(ZErr)
.method(prebindgen_registry::fun!(z_err_message).name("message"))
.method(prebindgen_registry::fun!(z_err_detail).name("detail")),
)
.fun(prebindgen_registry::fun!(z_fallible)),
)
.expand(
prebindgen_registry::expand_return!(ZDetail)
.field(prebindgen_registry::fun!(z_detail_code)),
)
.expand(
prebindgen_registry::expand_return!(ZErr)
.field_self()
.field(prebindgen_registry::fun!(z_err_message))
.field(prebindgen_registry::fun!(z_err_detail)),
);
let dir = unique_test_dir("jnigen_err_universal");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(
rc.contains("\"(JLjava/lang/String;Ljava/lang/Integer;)Ljava/lang/Object;\""),
"{rust}"
);
assert!(
rc.contains("\"(Ljava/lang/String;)Ljava/lang/Object;\""),
"{rust}"
);
assert!(rc.contains("std::boxed::Box::new(__de)"), "{rust}");
assert!(rc.contains("matchmyflat::z_err_detail(&__de)"), "{rust}");
assert!(rc.contains("signal_domain_error("), "{rust}");
assert!(!rc.contains("__ze_defaults"), "{rust}");
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let all: String = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n")
.split_whitespace()
.collect();
assert!(
all.contains(
"funinterfaceZErrHandler<outR>{publicfunrun(handle:ZErr,message:String,detail__code:Int?):R"
),
"{all}"
);
assert!(
all.contains(
"funinterfaceZErrHandlerRaw<outR>{publicfunrun(handle:Long,message:String,detail__code:Int?):R"
),
"{all}"
);
assert!(
all.contains("returnonError.run(ZErr(__dcap.ze0!!),__dcap.ze1!!,__dcap.ze2)"),
"{all}"
);
assert!(
all.contains("if(__bcap.failed)returnonBindingError.run(__bcap.ze0)"),
"{all}"
);
assert!(
all.contains("internalclassZErrHandlerRawCapture:ZErrHandlerRaw<Unit>"),
"{all}"
);
assert!(
all.contains("val__dcap=ZErrHandlerRawCapture.acquire()"),
"{all}"
);
assert!(
all.contains("val__bcap=JniErrorHandlerCapture.acquire()"),
"{all}"
);
assert!(all.contains("ThreadLocal.withInitial"), "{all}");
assert!(!all.contains("?:\"\""), "{all}");
}
#[test]
fn method_constructor_and_inline_field_self() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_name(t: &ZThing) -> String { unimplemented!() }",
"pub fn z_thing_rename(t: &ZThing, name: String) -> bool { unimplemented!() }",
"pub fn z_thing_make(name: String) -> ZThing { unimplemented!() }",
"pub fn z_get() -> ZThing { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("thing")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_name).name("name"))
.method(prebindgen_registry::fun!(z_thing_rename).name("rename"))
.constructor(prebindgen_registry::fun!(z_thing_make).name("make")),
)
.fun(
prebindgen_registry::fun!(z_get).expand_return(
prebindgen_registry::expand_return!(ZThing)
.field_self()
.field(prebindgen_registry::fun!(z_thing_name).name("name")),
),
),
);
let dir = unique_test_dir("jnigen_method_ctor");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let all: String = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n");
let flat: String = all.split_whitespace().collect();
assert!(flat.contains("publicfunrename(name:String"), "{all}");
assert!(all.contains("withSortedHandleLocks(this)"), "{all}");
assert!(flat.contains("publiccompanionobject"), "{all}");
assert!(flat.contains("publicfunmake(name:String"), "{all}");
assert!(
flat.contains("publicfunrun(handle:ZThing,name:String)"),
"{all}"
);
}
#[test]
fn rust_side_only_error_type() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_err_message(e: &ZErr) -> String { unimplemented!() }",
"pub fn z_fallible() -> Result<i64, ZErr> { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!("ops").fun(prebindgen_registry::fun!(z_fallible)))
.expand(
prebindgen_registry::expand_return!(ZErr)
.field(prebindgen_registry::fun!(z_err_message).name("message")),
);
let dir = unique_test_dir("jnigen_rust_side_only_err");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("myflat::z_err_message(&__de)"), "{rust}");
assert!(!rc.contains("ZErr_1freePtr"), "{rust}");
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let all: String = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n")
.split_whitespace()
.collect();
assert!(
all.contains("funinterfaceZErrHandler<outR>{publicfunrun(message:String):R"),
"{all}"
);
assert!(!all.contains("classZErr("), "{all}");
let base_file: String = paths
.iter()
.filter(|p| p.ends_with("io/test/jni.kt"))
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n")
.split_whitespace()
.collect();
assert!(base_file.contains("funinterfaceZErrHandler"), "{all}");
}
#[test]
fn rust_side_only_input_type() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_opts_new(retries: i32, verbose: bool) -> ZOpts { unimplemented!() }",
"pub fn z_run(opts: ZOpts) -> i64 { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!("ops").fun(prebindgen_registry::fun!(z_run)))
.expand(
prebindgen_registry::expand_param!(ZOpts)
.variant(prebindgen_registry::fun!(z_opts_new)),
);
let dir = unique_test_dir("jnigen_rust_side_only_in");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("myflat::z_opts_new("), "{rust}");
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let all: String = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n")
.split_whitespace()
.collect();
assert!(
all.contains("funzRun(optsRetries:Int,optsVerbose:Boolean"),
"{all}"
);
assert!(!all.contains("classZOpts("), "{all}");
}
#[test]
#[should_panic(expected = "has no class declaration")]
fn rust_side_only_variant_self_rejected() {
let loc = myflat_loc();
let f: syn::ItemFn =
syn::parse_str("pub fn z_run(opts: ZOpts) -> i64 { unimplemented!() }").unwrap();
let registry =
crate::test_util::reg_from_items(declare_referenced(vec![(syn::Item::Fn(f), loc)]))
.expect("index items");
let jni = JniGenBuilder::new()
.package(crate::package!("ops").fun(prebindgen_registry::fun!(z_run)))
.expand(prebindgen_registry::expand_param!(ZOpts).variant_self());
let dir = unique_test_dir("jnigen_rso_self_in");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let _ = jni
.build_with(registry)
.and_then(|gen| gen.write_rust(dir.join("gen.rs")));
}
#[test]
#[should_panic(expected = "has no class declaration")]
fn rust_side_only_field_self_rejected() {
let loc = myflat_loc();
let f: syn::ItemFn = syn::parse_str("pub fn z_make() -> ZThing { unimplemented!() }").unwrap();
let registry =
crate::test_util::reg_from_items(declare_referenced(vec![(syn::Item::Fn(f), loc)]))
.expect("index items");
let jni = JniGenBuilder::new()
.package(crate::package!("ops").fun(prebindgen_registry::fun!(z_make)))
.expand(prebindgen_registry::expand_return!(ZThing).field_self());
let dir = unique_test_dir("jnigen_rso_self_out");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let _ = jni
.build_with(registry)
.and_then(|gen| gen.write_rust(dir.join("gen.rs")));
}
#[test]
fn fn_expand_param_type_mismatch_rejected() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_make(name: String) -> ZThing { unimplemented!() }",
"pub fn z_use(t: ZThing) -> i64 { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new().package(
crate::package!("ops")
.class(crate::ptr_class!(ZThing).constructor(prebindgen_registry::fun!(z_thing_make)))
.class(crate::ptr_class!(ZOther))
.fun(
prebindgen_registry::fun!(z_use).expand_param(
"t",
prebindgen_registry::expand_param!(ZOther)
.variant(prebindgen_registry::fun!(z_thing_make)),
),
),
);
let dir = unique_test_dir("jnigen_fn_param_mismatch");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let err = jni
.build_with(registry)
.expect_err("type mismatch must fail");
let msg = format!("{err}");
assert!(msg.contains("ZOther") && msg.contains("ZThing"), "{msg}");
}
#[test]
fn fn_expand_return_type_mismatch_rejected() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_name(t: &ZThing) -> String { unimplemented!() }",
"pub fn z_make() -> ZThing { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new().package(
crate::package!("ops")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_name).name("name")),
)
.class(crate::ptr_class!(ZOther))
.fun(
prebindgen_registry::fun!(z_make)
.expand_return(prebindgen_registry::expand_return!(ZOther).field_self()),
),
);
let dir = unique_test_dir("jnigen_fn_return_mismatch");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let err = jni
.build_with(registry)
.expect_err("type mismatch must fail");
let msg = format!("{err}");
assert!(msg.contains("ZOther") && msg.contains("ZThing"), "{msg}");
}
#[test]
fn fn_expand_param_unknown_param_rejected() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_make(name: String) -> ZThing { unimplemented!() }",
"pub fn z_use(t: ZThing) -> i64 { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new().package(
crate::package!("ops")
.class(crate::ptr_class!(ZThing).constructor(prebindgen_registry::fun!(z_thing_make)))
.fun(
prebindgen_registry::fun!(z_use).expand_param(
"typo",
prebindgen_registry::expand_param!(ZThing)
.variant(prebindgen_registry::fun!(z_thing_make)),
),
),
);
let dir = unique_test_dir("jnigen_fn_param_unknown");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let err = jni
.build_with(registry)
.expect_err("unknown param must fail");
assert!(format!("{err}").contains("typo"), "{err}");
}
#[test]
#[should_panic(expected = "already has a return expand override")]
fn fn_expand_return_duplicate_rejected() {
let _ = prebindgen_registry::fun!(z_make)
.expand_return(prebindgen_registry::expand_return!(ZThing).field_self())
.expand_return(prebindgen_registry::expand_return!(ZThing).field_self());
}
#[test]
fn typo_in_expand_decl_is_hard_error() {
use prebindgen_registry::{ScanError, WriteRustError};
let loc = myflat_loc();
let f: syn::ItemFn =
syn::parse_str("pub fn z_fallible() -> Result<i64, ZErr> { unimplemented!() }").unwrap();
let registry =
crate::test_util::reg_from_items(declare_referenced(vec![(syn::Item::Fn(f), loc)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!("ops").fun(prebindgen_registry::fun!(z_fallible)))
.expand(
prebindgen_registry::expand_return!(ZErr)
.field(prebindgen_registry::fun!(z_err_mesage).name("message")),
);
let dir = unique_test_dir("jnigen_expand_typo_hard_error");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let err = jni
.build_with(registry)
.expect_err("typo'd expand accessor must fail the scan");
match err {
WriteRustError::Scan(ScanError::DeclaredNotFound { entries }) => {
assert_eq!(
entries,
vec![("helper function", "z_err_mesage".to_string())]
);
}
other => panic!("expected DeclaredNotFound, got {other:?}"),
}
}
#[test]
fn ignore_matching_acknowledges_naming_family() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_len(v: i64) -> i64 { unimplemented!() }",
"pub fn detail_const_a() -> i64 { unimplemented!() }",
"pub fn detail_const_b() -> i64 { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!("ops").fun(prebindgen_registry::fun!(z_len)))
.ignore(crate::matching(|name| name.starts_with("detail_const_")))
.ignore(prebindgen_registry::ty!(ZUnusedThing));
{
let preds = jni.decls.ignored_name_predicates();
assert_eq!(preds.len(), 1);
assert!(preds[0]("detail_const_a") && !preds[0]("z_len"));
assert!(jni
.decls
.ignored_types()
.contains(&TypeKey::parse("ZUnusedThing").expect("test type")));
}
let dir = unique_test_dir("jnigen_ignore_funs_where");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
assert!(rust.contains("Java_io_test_jni_JNINative_zLen"), "{rust}");
assert!(!rust.contains("detailConstA"), "{rust}");
}
#[test]
#[should_panic(expected = "expand overrides don't apply")]
fn ignore_fun_with_overrides_rejected() {
let _ = crate::IgnoreDecl::from(prebindgen_registry::fun!(z_thing).name("thing"));
}
#[test]
#[should_panic(expected = "value sources/.name() don't apply")]
fn ignore_const_with_source_rejected() {
let _ = crate::IgnoreDecl::from(crate::constant!(X).expr(
prebindgen_registry::ty!(i64),
prebindgen_registry::expr!(1 + 1),
));
}
#[test]
#[should_panic(expected = ".name()/expand overrides don't apply")]
fn expand_param_variant_with_name_rejected() {
let _ = prebindgen_registry::expand_param!(ZThing)
.variant(prebindgen_registry::fun!(z_thing_new).name("thing"));
}
#[test]
#[should_panic(expected = ".name()/expand overrides don't apply")]
fn expand_param_variant_with_expand_override_rejected() {
let _ = prebindgen_registry::expand_param!(ZThing).variant(
prebindgen_registry::fun!(z_thing_new)
.expand_return(prebindgen_registry::expand_return!(ZName).field_self()),
);
}
#[test]
#[should_panic(expected = "only .name() is honored")]
fn expand_return_field_with_expand_override_rejected() {
let _ = prebindgen_registry::expand_return!(ZThing).field(
prebindgen_registry::fun!(z_thing_name).expand_param(
"v",
prebindgen_registry::expand_param!(ZName).variant_self(),
),
);
}
#[test]
fn expand_return_field_with_name_accepted() {
let _ = prebindgen_registry::expand_return!(ZThing)
.field(prebindgen_registry::fun!(z_thing_name).name("label"));
}
#[test]
fn method_without_receiver_rejected() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_free_standing(v: i64) -> i64 { unimplemented!() }",
"pub fn z_make() -> ZThing { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("t").class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_free_standing))
.constructor(prebindgen_registry::fun!(z_make)),
),
);
let err = jni.build_with(registry).expect_err("receiver-less member");
let msg = format!("{err}");
assert!(
msg.contains("method `z_thing_free_standing`") && msg.contains("`ZThing`"),
"{msg}"
);
}
#[test]
fn constructor_with_wrong_return_rejected() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_len(t: &ZThing) -> i64 { unimplemented!() }",
"pub fn z_make_number() -> i64 { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("t").class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_len))
.constructor(prebindgen_registry::fun!(z_make_number)),
),
);
let err = jni.build_with(registry).expect_err("wrong ctor return");
let msg = format!("{err}");
assert!(
msg.contains("constructor `z_make_number`") && msg.contains("it returns `i64`"),
"{msg}"
);
}
#[test]
fn binding_local_field_conditional_handle() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_enc_get_id(e: &ZEnc) -> i32 { unimplemented!() }",
"pub fn z_enc_make() -> ZEnc { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZEnc {
_p: u8,
}
)),
loc.clone(),
)];
for src in fns {
items.push((
syn::Item::Fn(syn::parse_str(src).expect("parse fn")),
loc.clone(),
));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("enc")
.class(crate::ptr_class!(ZEnc).method(prebindgen_registry::fun!(z_enc_get_id)))
.fun(prebindgen_registry::fun!(z_enc_make)),
)
.expand(
prebindgen_registry::expand_return!(ZEnc)
.field(prebindgen_registry::fun!(z_enc_get_id))
.field(
prebindgen_registry::fun!(crate::enc_if_custom)
.sig(prebindgen_registry::sig!((e: &ZEnc) -> Option<&ZEnc>))
.name("handle"),
),
);
let dir = unique_test_dir("jnigen_local_field");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("crate::enc_if_custom("), "{rust}");
assert!(rc.contains("myflat::z_enc_get_id("), "{rust}");
assert!(rc.contains("box_jlong"), "{rust}");
assert!(
rc.contains("Option::None=>jni::objects::JObject::null()"),
"{rust}"
);
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
let raw = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n");
let all: String = raw.split_whitespace().collect();
assert!(all.contains("zEncGetId:Int,handle:ZEnc?"), "{raw}");
}
#[test]
#[should_panic(expected = "crate::")]
fn binding_local_field_bare_path_rejected() {
let _ = crate::FunctionDecl::new_local(syn::parse_quote!(enc_if_custom));
}
#[test]
fn binding_local_field_name_collision_rejected() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_enc_get_id(e: &ZEnc) -> i32 { unimplemented!() }",
"pub fn z_enc_make() -> ZEnc { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZEnc {
_p: u8,
}
)),
loc.clone(),
)];
for src in fns {
items.push((
syn::Item::Fn(syn::parse_str(src).expect("parse fn")),
loc.clone(),
));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("enc")
.class(crate::ptr_class!(ZEnc))
.fun(prebindgen_registry::fun!(z_enc_make)),
)
.expand(
prebindgen_registry::expand_return!(ZEnc).field(
prebindgen_registry::fun!(crate::z_enc_get_id)
.sig(prebindgen_registry::sig!((e: &ZEnc) -> i32))
.name("id"),
),
);
let err = jni
.build_with(registry)
.expect_err("collision must be rejected");
let msg = format!("{err}");
assert!(msg.contains("collides"), "{msg}");
}
#[test]
fn binding_local_field_splices_through_parent() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_enc_get_id(e: &ZEnc) -> i32 { unimplemented!() }",
"pub fn z_msg_enc(m: &ZMsg) -> &ZEnc { unimplemented!() }",
"pub fn z_msg_len(m: &ZMsg) -> i64 { unimplemented!() }",
"pub fn z_msg_make() -> ZMsg { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZEnc {
_p: u8,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZMsg {
_p: u8,
}
)),
loc.clone(),
),
];
for src in fns {
items.push((
syn::Item::Fn(syn::parse_str(src).expect("parse fn")),
loc.clone(),
));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("msg")
.class(crate::ptr_class!(ZEnc).method(prebindgen_registry::fun!(z_enc_get_id)))
.class(crate::ptr_class!(ZMsg))
.fun(prebindgen_registry::fun!(z_msg_make)),
)
.expand(
prebindgen_registry::expand_return!(ZEnc)
.field(prebindgen_registry::fun!(z_enc_get_id))
.field(
prebindgen_registry::fun!(crate::enc_if_custom)
.sig(prebindgen_registry::sig!((e: &ZEnc) -> Option<&ZEnc>))
.name("handle"),
),
)
.expand(
prebindgen_registry::expand_return!(ZMsg)
.field(prebindgen_registry::fun!(z_msg_len).name("len"))
.field(prebindgen_registry::fun!(z_msg_enc).name("enc")),
);
let dir = unique_test_dir("jnigen_local_field_splice");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(
rc.contains("crate::enc_if_custom(myflat::z_msg_enc("),
"{rust}"
);
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
let raw = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n");
let all: String = raw.split_whitespace().collect();
assert!(all.contains("enc__zEncGetId:Int"), "{raw}");
assert!(all.contains("enc__handle:ZEnc?"), "{raw}");
}
#[test]
fn binding_local_functions_all_positions() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_len(t: &ZThing) -> i64 { unimplemented!() }",
"pub fn z_use(primary: ZThing) -> bool { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
_p: u8,
}
)),
loc.clone(),
)];
for src in fns {
items.push((
syn::Item::Fn(syn::parse_str(src).expect("parse fn")),
loc.clone(),
));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni =
JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("t")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_len))
.method(
prebindgen_registry::fun!(crate::z_thing_ratio)
.sig(prebindgen_registry::sig!((t: &ZThing, scale: f64) -> f64)),
)
.constructor(
prebindgen_registry::fun!(crate::z_thing_from_len)
.sig(prebindgen_registry::sig!((len: i64) -> ZThing)),
),
)
.fun(prebindgen_registry::fun!(crate::z_thing_describe).sig(
prebindgen_registry::sig!((t: &ZThing, verbose: bool) -> Result<String, String>),
))
.fun(prebindgen_registry::fun!(z_use)),
)
.expand(
prebindgen_registry::expand_param!(ZThing)
.variant(prebindgen_registry::fun!(z_thing_from_len))
.variant_self(),
);
let dir = unique_test_dir("jnigen_local_funs");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("crate::z_thing_ratio("), "{rust}");
assert!(rc.contains("crate::z_thing_from_len("), "{rust}");
assert!(rc.contains("crate::z_thing_describe("), "{rust}");
assert!(rc.contains("myflat::z_thing_len("), "{rust}");
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
let raw = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n");
let all: String = raw.split_whitespace().collect();
assert!(all.contains("funzThingRatio(scale:Double,"), "{raw}");
assert!(all.contains("funzThingFromLen(len:Long,"), "{raw}");
assert!(all.contains("funzThingDescribe("), "{raw}");
assert!(
all.contains("tSel:Int,t0:Long?,t1:ZThing?,verbose:Boolean,"),
"{raw}"
);
assert!(all.contains("primarySel:Int"), "{raw}");
}
#[test]
fn binding_local_fn_names_flow_through_manglers() {
let loc = myflat_loc();
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
_p: u8,
}
)),
loc.clone(),
)];
for src in [
"pub fn z_thing_make() -> ZThing { unimplemented!() }",
"pub fn z_thing_query() -> ZThing { unimplemented!() }",
] {
items.push((syn::Item::Fn(syn::parse_str(src).unwrap()), loc.clone()));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.set_fun_name_mangle(|pkg, name| {
assert!(pkg.ends_with("t"), "fun hook package: {pkg}");
format!("pkg_{name}")
})
.set_method_name_mangle(|_pkg, class, name| {
if class == "ZThing" {
format!("cls_{name}")
} else {
name.to_string()
}
})
.package(
crate::package!("t")
.class(
crate::ptr_class!(ZThing)
.constructor(prebindgen_registry::fun!(z_thing_make))
.method(
prebindgen_registry::fun!(crate::sub::z_thing_ratio)
.sig(prebindgen_registry::sig!((t: &ZThing, scale: f64) -> f64)),
),
)
.fun(
prebindgen_registry::fun!(crate::sub::z_thing_tag)
.sig(prebindgen_registry::sig!((t: &ZThing) -> i64)),
)
.fun(prebindgen_registry::fun!(z_thing_query)),
)
.expand(
prebindgen_registry::expand_return!(ZThing)
.field(
prebindgen_registry::fun!(crate::sub::z_thing_len)
.sig(prebindgen_registry::sig!((t: &ZThing) -> i64)),
)
.field_self(),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_local_mangle",
);
let all: String = raw.split_whitespace().collect();
assert!(all.contains("funcls_zThingRatio(scale:Double,"), "{raw}");
assert!(all.contains("funpkg_zThingTag("), "{raw}");
assert!(all.contains("zThingLen:Long"), "{raw}");
}
#[test]
#[should_panic(expected = ".sig(sig!(")]
fn binding_local_fun_missing_sig_rejected() {
let _ = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!("t").fun(prebindgen_registry::fun!(crate::z_no_sig)));
}
#[test]
#[should_panic(expected = "read from the")]
fn sig_on_registry_fun_rejected() {
let _ =
prebindgen_registry::fun!(z_thing_len).sig(prebindgen_registry::sig!((t: &ZThing) -> i64));
}
#[test]
fn binding_local_fun_name_collision_rejected() {
let loc = myflat_loc();
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
_p: u8,
}
)),
loc.clone(),
)];
items.push((
syn::Item::Fn(
syn::parse_str("pub fn z_thing_len(t: &ZThing) -> i64 { unimplemented!() }").unwrap(),
),
loc.clone(),
));
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("t").class(crate::ptr_class!(ZThing)).fun(
prebindgen_registry::fun!(crate::z_thing_len)
.sig(prebindgen_registry::sig!((t: &ZThing) -> i64)),
),
);
let err = jni
.build_with(registry)
.expect_err("collision must be rejected");
assert!(format!("{err}").contains("collides"), "{err}");
}
#[test]
fn gc_managed_handle_lifecycle() {
let loc = myflat_loc();
let mut items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
_p: u8,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOther {
_p: u8,
}
)),
loc.clone(),
),
];
let fns: &[&str] = &[
"pub fn z_thing_new() -> ZThing { unimplemented!() }",
"pub fn z_thing_use(t: ZThing) -> bool { unimplemented!() }",
"pub fn z_other_new() -> ZOther { unimplemented!() }",
"pub fn z_other_use(t: ZOther) -> bool { unimplemented!() }",
];
for src in fns {
items.push((
syn::Item::Fn(syn::parse_str(src).expect("parse fn")),
loc.clone(),
));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("t")
.class(
crate::ptr_class!(ZThing)
.gc_managed()
.constructor(prebindgen_registry::fun!(z_thing_new)),
)
.class(
crate::ptr_class!(ZOther).constructor(prebindgen_registry::fun!(z_other_new)),
)
.fun(prebindgen_registry::fun!(z_thing_use))
.fun(prebindgen_registry::fun!(z_other_use)),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_gc_managed",
);
let all: String = raw.split_whitespace().collect();
assert!(all.contains("abstractclassGcNativeHandle"), "{raw}");
assert!(all.contains("internalfunreleaseCell"), "{raw}");
assert!(all.contains("internalobjectNativeCleaner"), "{raw}");
assert!(all.contains("internalfunregisterGcHandle"), "{raw}");
assert!(
all.contains("classZThing(initialPtr:Long):GcNativeHandle(initialPtr)"),
"{raw}"
);
assert!(
all.contains("privateval__cleanable=registerGcHandle(this){freePtr(it)}"),
"{raw}"
);
assert!(
all.contains("valp=releaseCell(cell)if(p!=0L)freePtr(p)__cleanable?.clean()"),
"{raw}"
);
assert!(
all.contains(
"valp=releaseCell(cell)__cleanable?.clean()returnZThing(if(p!=0L)pelsecell.get())"
),
"{raw}"
);
assert!(
all.contains("classZOther(initialPtr:Long):NativeHandle(initialPtr)"),
"{raw}"
);
assert!(all.contains("ptr=por1L"), "{raw}");
assert!(
!all.contains("classZOther(initialPtr:Long):GcNativeHandle"),
"{raw}"
);
assert!(all.contains("t.markConsumed()"), "{raw}");
assert!(!all.contains("t.ptr=t.ptror1L"), "{raw}");
}
fn split_fixture(extra: &[&str]) -> RegistryBuilder<KotlinMeta> {
let loc = myflat_loc();
let base: &[&str] = &[
"pub fn z_summary_new(count: i64, total: f64) -> ZSummary { unimplemented!() }",
"pub fn z_store_expect(expected: ZSummary) -> bool { unimplemented!() }",
"pub fn z_prefer(primary: ZSummary, fallback: ZSummary) -> i64 { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSummary {
_p: u8,
}
)),
loc.clone(),
)];
for src in base.iter().chain(extra) {
items.push((
syn::Item::Fn(syn::parse_str(src).expect("parse fn")),
loc.clone(),
));
}
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items")
}
pub(super) fn write_all(gen: JniGen, tag: &str) -> String {
let dir = unique_test_dir(tag);
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn split_on_param_emits_typed_overloads() {
let registry = split_fixture(&[]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(prebindgen_registry::fun!(z_store_expect).split_on_param("expected")),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant_self(),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_one",
);
let all: String = raw.split_whitespace().collect();
assert!(all.contains("expectedSel:Int"), "{raw}"); assert!(
all.contains("funzStoreExpect(count:Long,total:Double,"),
"{raw}"
);
assert!(all.contains("funzStoreExpect(expected:ZSummary,"), "{raw}");
assert!(all.contains("zStoreExpect(0,count,total,null,"), "{raw}");
assert!(all.contains("zStoreExpect(1,null,null,expected,"), "{raw}");
}
#[test]
fn split_on_param_cartesian_product() {
let registry = split_fixture(&[]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(
prebindgen_registry::fun!(z_prefer)
.split_on_param("primary")
.split_on_param("fallback"),
),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant_self(),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_prod",
);
let all: String = raw.split_whitespace().collect();
assert!(
all.contains(
"funzPrefer(primaryCount:Long,primaryTotal:Double,fallbackCount:Long,fallbackTotal:Double,"
),
"{raw}"
);
assert!(
all.contains("funzPrefer(primaryCount:Long,primaryTotal:Double,fallback:ZSummary,"),
"{raw}"
);
assert!(
all.contains("funzPrefer(primary:ZSummary,fallbackCount:Long,fallbackTotal:Double,"),
"{raw}"
);
assert!(
all.contains("funzPrefer(primary:ZSummary,fallback:ZSummary,"),
"{raw}"
);
assert!(
all.contains(
"zPrefer(0,primaryCount,primaryTotal,null,0,fallbackCount,fallbackTotal,null,"
),
"{raw}"
);
}
#[test]
fn split_on_param_preserves_wrapper_generics() {
let registry = split_fixture(&[
"pub fn z_summary_count(s: &ZSummary) -> i64 { unimplemented!() }",
"pub fn z_summary_total(s: &ZSummary) -> f64 { unimplemented!() }",
"pub fn z_summarize(primary: ZSummary, fallback: ZSummary) -> ZSummary { unimplemented!() }",
]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(
prebindgen_registry::fun!(z_summarize)
.split_on_param("primary")
.split_on_param("fallback"),
),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant_self(),
)
.expand(
prebindgen_registry::expand_return!(ZSummary)
.field(prebindgen_registry::fun!(z_summary_count))
.field(prebindgen_registry::fun!(z_summary_total)),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_generic",
);
let all: String = raw.split_whitespace().collect();
assert!(all.contains("fun<R>zSummarize(primarySel:Int"), "{raw}");
assert!(
all.contains(
"fun<R>zSummarize(primaryCount:Long,primaryTotal:Double,fallbackCount:Long,fallbackTotal:Double,"
),
"{raw}"
);
assert!(
all.contains("fun<R>zSummarize(primaryCount:Long,primaryTotal:Double,fallback:ZSummary,"),
"{raw}"
);
assert!(
all.contains("fun<R>zSummarize(primary:ZSummary,fallbackCount:Long,fallbackTotal:Double,"),
"{raw}"
);
assert!(
all.contains("fun<R>zSummarize(primary:ZSummary,fallback:ZSummary,"),
"{raw}"
);
assert!(!all.contains("publicfunzSummarize("), "{raw}");
}
#[test]
#[should_panic(expected = "ambiguous")]
fn split_on_param_product_ambiguous_rejected() {
let loc = myflat_loc();
let srcs: &[&str] = &[
"pub fn z_thing_one(a: i64) -> ZThing { unimplemented!() }",
"pub fn z_thing_two(a: i64, b: i64) -> ZThing { unimplemented!() }",
"pub fn z_combine(primary: ZThing, fallback: ZThing) -> bool { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
_p: u8,
}
)),
loc.clone(),
)];
for s in srcs {
items.push((syn::Item::Fn(syn::parse_str(s).unwrap()), loc.clone()));
}
let registry = crate::test_util::reg_from_items(declare_referenced(items)).expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops").class(crate::ptr_class!(ZThing)).fun(
prebindgen_registry::fun!(z_combine)
.split_on_param("primary")
.split_on_param("fallback"),
),
)
.expand(
prebindgen_registry::expand_param!(ZThing)
.variant(prebindgen_registry::fun!(z_thing_one))
.variant(prebindgen_registry::fun!(z_thing_two)),
);
let _ = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_ambig",
);
}
#[test]
#[should_panic(expected = "same JVM signature")]
fn split_declaration_colliding_variants_rejected() {
let loc = myflat_loc();
let srcs: &[&str] = &[
"pub fn z_name_from_text(text: String) -> ZName { unimplemented!() }",
"pub fn z_name_from_label(label: String) -> ZName { unimplemented!() }",
"pub fn z_use_name(name: ZName) -> bool { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZName {
_p: u8,
}
)),
loc.clone(),
)];
for s in srcs {
items.push((syn::Item::Fn(syn::parse_str(s).unwrap()), loc.clone()));
}
let registry = crate::test_util::reg_from_items(declare_referenced(items)).expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZName))
.fun(prebindgen_registry::fun!(z_use_name)), )
.expand(
prebindgen_registry::expand_param!(ZName)
.variant(prebindgen_registry::fun!(z_name_from_text))
.variant(prebindgen_registry::fun!(z_name_from_label)),
);
let _ = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_decl",
);
}
#[test]
fn split_declaration_collision_fails_resolve() {
let loc = myflat_loc();
let srcs: &[&str] = &[
"pub fn z_name_from_text(text: String) -> ZName { unimplemented!() }",
"pub fn z_name_from_label(label: String) -> ZName { unimplemented!() }",
"pub fn z_use_name(name: ZName) -> bool { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZName {
_p: u8,
}
)),
loc.clone(),
)];
for s in srcs {
items.push((syn::Item::Fn(syn::parse_str(s).unwrap()), loc.clone()));
}
let registry = crate::test_util::reg_from_items(declare_referenced(items)).expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZName))
.fun(prebindgen_registry::fun!(z_use_name)),
)
.expand(
prebindgen_registry::expand_param!(ZName)
.variant(prebindgen_registry::fun!(z_name_from_text))
.variant(prebindgen_registry::fun!(z_name_from_label)),
);
let err = jni
.build_with(registry)
.expect_err("colliding split declaration must fail resolve");
assert!(
err.to_string().contains("same JVM signature"),
"unexpected error: {err}"
);
}
#[test]
fn split_no_split_suppresses_check() {
let loc = myflat_loc();
let srcs: &[&str] = &[
"pub fn z_name_from_text(text: String) -> ZName { unimplemented!() }",
"pub fn z_name_from_label(label: String) -> ZName { unimplemented!() }",
"pub fn z_use_name(name: ZName) -> bool { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct ZName {
_p: u8,
}
)),
loc.clone(),
)];
for s in srcs {
items.push((syn::Item::Fn(syn::parse_str(s).unwrap()), loc.clone()));
}
let registry = crate::test_util::reg_from_items(declare_referenced(items)).expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZName))
.fun(prebindgen_registry::fun!(z_use_name)),
)
.expand(
prebindgen_registry::expand_param!(ZName)
.variant(prebindgen_registry::fun!(z_name_from_text))
.variant(prebindgen_registry::fun!(z_name_from_label))
.no_split(),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_no_split",
);
let all: String = raw.split_whitespace().collect();
assert!(all.contains("nameSel:Int"), "{raw}"); }
#[test]
#[should_panic(expected = "no parameter named")]
fn split_on_unknown_param_rejected() {
let registry = split_fixture(&[]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(prebindgen_registry::fun!(z_store_expect).split_on_param("nope")),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant_self(),
);
let _ = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_typo",
);
}
#[test]
fn split_on_option_param_emits_nullable_arm() {
let registry =
split_fixture(&["pub fn z_maybe(opt: Option<ZSummary>) -> bool { unimplemented!() }"]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(prebindgen_registry::fun!(z_maybe).split_on_param("opt")),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant_self(),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_opt",
);
let all: String = raw.split_whitespace().collect();
assert!(all.contains("optSel:Int"), "{raw}");
assert!(all.contains("funzMaybe(opt:ZSummary?,"), "{raw}");
assert!(
all.contains("zMaybe(if(opt!=null)1else-1,null,null,opt,"),
"{raw}"
);
assert!(!all.contains("funzMaybe(count:"), "{raw}");
}
#[test]
#[should_panic(expected = "none of its arms is a single leaf")]
fn split_on_option_param_without_single_leaf_arm_rejected() {
let registry = split_fixture(&[
"pub fn z_summary_scaled(units: String, factor: f64) -> ZSummary { unimplemented!() }",
"pub fn z_maybe(opt: Option<ZSummary>) -> bool { unimplemented!() }",
]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(prebindgen_registry::fun!(z_maybe).split_on_param("opt")),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant(prebindgen_registry::fun!(z_summary_scaled)),
);
let _ = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_opt_no_arm",
);
}
#[test]
fn split_on_param_optional_cartesian_with_plain() {
let registry = split_fixture(&[
"pub fn z_mixed(primary: ZSummary, fallback: Option<&ZSummary>) -> i64 { unimplemented!() }",
]);
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(crate::ptr_class!(ZSummary))
.fun(
prebindgen_registry::fun!(z_mixed)
.split_on_param("primary")
.split_on_param("fallback"),
),
)
.expand(
prebindgen_registry::expand_param!(ZSummary)
.variant(prebindgen_registry::fun!(z_summary_new))
.variant_self(),
);
let raw = write_all(
jni.build_with(registry).expect("resolve"),
"jnigen_split_opt_prod",
);
let all: String = raw.split_whitespace().collect();
assert!(
all.contains("funzMixed(primaryCount:Long,primaryTotal:Double,fallback:ZSummary?,"),
"{raw}"
);
assert!(
all.contains("funzMixed(primary:ZSummary,fallback:ZSummary?,"),
"{raw}"
);
assert!(
all.contains(
"zMixed(0,primaryCount,primaryTotal,null,if(fallback!=null)1else-1,null,null,fallback,"
),
"{raw}"
);
}
#[test]
fn optional_selector_dispatch_end_to_end() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZEnc {
_p: u8,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_enc_from_id(id: i32, schema: Option<String>) -> ZEnc {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_put(encoding: Option<&ZEnc>) -> bool {
unimplemented!()
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(
crate::ptr_class!(ZEnc).constructor(prebindgen_registry::fun!(z_enc_from_id)),
)
.fun(prebindgen_registry::fun!(z_put)),
)
.expand(
prebindgen_registry::expand_param!(ZEnc)
.variant(prebindgen_registry::fun!(z_enc_from_id))
.variant_self(),
);
let dir = unique_test_dir("jnigen_opt_selector");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("<0"), "{rust}");
assert!(rc.contains("Option::None"), "{rust}");
assert!(rc.contains("z_enc_from_id"), "{rust}");
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
let raw = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n");
let all: String = raw.split_whitespace().collect();
assert!(all.contains("encodingSel:Int"), "{raw}");
assert!(all.contains("encoding1:ZEnc?"), "{raw}");
assert!(all.contains("encoding01:String?"), "{raw}");
assert!(!all.contains("String??"), "{raw}");
}
#[test]
fn constructor_member_skips_default_output_expand() {
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_thing_make() -> ZThing { unimplemented!() }",
"pub fn z_thing_name(t: &ZThing) -> String { unimplemented!() }",
"pub fn z_thing_get(s: i64) -> ZThing { unimplemented!() }",
];
let items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("ops")
.class(
crate::ptr_class!(ZThing)
.constructor(prebindgen_registry::fun!(z_thing_make).name("make"))
.method(prebindgen_registry::fun!(z_thing_name).name("name")),
)
.fun(prebindgen_registry::fun!(z_thing_get)),
)
.expand(
prebindgen_registry::expand_return!(ZThing)
.field_self()
.field(prebindgen_registry::fun!(z_thing_name)),
);
let gen = jni.build_with(registry).expect("resolve");
let registry = gen.registry();
assert!(
registry.unfold_plans().contains_key(&syn::Ident::new(
"z_thing_get",
proc_macro2::Span::call_site()
)),
"free fn gets the default output expansion"
);
assert!(
!registry.unfold_plans().contains_key(&syn::Ident::new(
"z_thing_make",
proc_macro2::Span::call_site()
)),
"constructor member must skip the default output expansion"
);
}
#[test]
fn qualified_signature_spelling_matches_bare_ptr_class() {
let loc = myflat_loc();
let items: Vec<(syn::Item, prebindgen::SourceLocation)> = vec![
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_get() -> myflat::ZThing {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_name(this_: &crate::things::ZThing) -> std::string::String {
unimplemented!()
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("thing")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_name).name("name")),
)
.fun(prebindgen_registry::fun!(z_thing_get)),
);
let dir = unique_test_dir("jnigen_q95");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni
.build_with(registry)
.expect("qualified spellings resolve");
gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
let all: String = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect();
let ac: String = all.split_whitespace().collect();
assert!(ac.contains("classZThing(initialPtr:Long)"), "{all}");
assert!(ac.contains("funname(onError:"), "{all}");
assert!(
ac.contains("funzThingGet(onError:JniErrorHandler<ZThing>):ZThing"),
"{all}"
);
}