use prebindgen_registry::RegistryBuilder;
use super::*;
fn sealed_kotlin(rename_labeled: Option<&str>) -> String {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Priority {
Low = 0,
High = 1,
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
Range {
low: i64,
high: i64,
},
Labeled(String, Priority),
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let mut sealed = crate::sealed_class!(Reading);
if let Some(n) = rename_labeled {
sealed = sealed.variant(crate::variant!(Labeled).name(n));
}
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::enum_class!(Priority))
.class(sealed),
);
let dir = unique_test_dir("jnigen_sealed");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
paths
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn sealed_class_kotlin_surface() {
let kt = sealed_kotlin(None);
let c: String = kt.split_whitespace().collect();
assert!(c.contains("publicsealedinterfaceReading{"), "{kt}");
assert!(c.contains("publicdataobjectMissing:Reading"), "{kt}");
assert!(
c.contains("publicdataclassExact(publicvalv0:Long):Reading"),
"{kt}"
);
assert!(
c.contains("publicdataclassRange(publicvallow:Long,publicvalhigh:Long):Reading"),
"{kt}"
);
assert!(
c.contains("publicdataclassLabeled(publicvalv0:String,publicvalv1:Priority):Reading"),
"{kt}"
);
assert!(c.contains("funfromParts("), "{kt}");
assert!(c.contains("tag:Int,"), "{kt}");
assert!(c.contains("exact_v0:Long,"), "{kt}");
assert!(c.contains("range_low:Long,"), "{kt}");
assert!(c.contains("range_high:Long,"), "{kt}");
assert!(c.contains("labeled_v0:String,"), "{kt}");
assert!(c.contains("labeled_v1:Priority,"), "{kt}");
assert!(c.contains("0->Missing"), "{kt}");
assert!(c.contains("1->Exact(exact_v0)"), "{kt}");
assert!(c.contains("2->Range(range_low,range_high)"), "{kt}");
assert!(c.contains("3->Labeled(labeled_v0,labeled_v1)"), "{kt}");
assert!(
c.contains("else->throwIllegalArgumentException(\"Reading:invalidtag$tag\")"),
"{kt}"
);
assert!(c.contains("publicenumclassPriority"), "{kt}");
}
#[test]
fn variant_rename_carries_to_slots() {
let kt = sealed_kotlin(Some("Tagged"));
let c: String = kt.split_whitespace().collect();
assert!(c.contains("publicdataclassTagged("), "{kt}");
assert!(!c.contains("dataclassLabeled("), "{kt}");
assert!(c.contains("tagged_v0:String,"), "{kt}");
assert!(c.contains("tagged_v1:Priority,"), "{kt}");
assert!(c.contains("3->Tagged(tagged_v0,tagged_v1)"), "{kt}");
assert!(c.contains("0->Missing"), "{kt}");
}
#[test]
fn sealed_class_carries_docs() {
let kt = sealed_kotlin(None);
assert!(kt.contains("A sensor reading."), "{kt}");
assert!(kt.contains("Nothing read."), "{kt}");
assert!(
kt.contains("exactly one alternative is live"),
"the framework kdoc line is missing:\n{kt}"
);
}
#[test]
fn declarators_do_not_accept_each_others_shape() {
let loc = myflat_loc();
let unit_enum: syn::Item = syn::Item::Enum(syn::parse_quote!(
pub enum Priority {
Low = 0,
High = 1,
}
));
let payload_enum: syn::Item = syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
));
let emit = |item: syn::Item, decl: crate::ClassDecl, tag: &str| {
let registry =
crate::test_util::reg_from_items(declare_referenced(vec![(item, loc.clone())]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!().class(decl));
let dir = unique_test_dir(tag);
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let _ = gen.write_kotlin(&dir.join("kotlin"));
};
let unit = unit_enum.clone();
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
emit(unit, crate::sealed_class!(Priority).into(), "sealed_unit");
}))
.is_err());
let payload = payload_enum.clone();
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
emit(payload, crate::enum_class!(Reading).into(), "enum_payload");
}))
.is_err());
}
#[test]
fn unknown_variant_is_an_error() {
let loc = myflat_loc();
let boom = || {
let registry = crate::test_util::reg_from_items(declare_referenced(vec![(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::sealed_class!(Reading).variant(crate::variant!(Nope).name("X"))),
);
let dir = unique_test_dir("sealed_unknown_variant");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let _ = gen.write_kotlin(&dir.join("kotlin"));
};
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(boom)).is_err());
}
#[test]
fn reopened_sealed_class_merges_variant_names() {
let loc = myflat_loc();
let registry = crate::test_util::reg_from_items(declare_referenced(vec![(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
Range { low: i64, high: i64 },
}
)),
loc.clone(),
)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!().class(
crate::sealed_class!(Reading).variant(crate::variant!(Missing).name("None_")),
),
)
.package(
crate::package!()
.class(crate::sealed_class!(Reading).variant(crate::variant!(Exact).name("One"))),
);
let dir = unique_test_dir("sealed_reopen");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let paths = gen.write_kotlin(&dir.join("kotlin")).expect("write_kotlin");
let kt: String = paths
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n");
let c: String = kt.split_whitespace().collect();
assert!(c.contains("publicdataobjectNone_:Reading"), "{kt}");
assert!(
c.contains("publicdataclassOne(publicvalv0:Long):Reading"),
"{kt}"
);
assert!(c.contains("publicdataclassRange("), "{kt}");
assert!(c.contains("0->None_"), "{kt}");
assert!(c.contains("1->One(one_v0)"), "{kt}");
}
#[test]
fn reopened_ptr_class_keeps_gc_managed() {
let loc = myflat_loc();
let items = || {
vec![(
syn::Item::Struct(syn::parse_quote!(
pub struct Session {
pub id: i64,
}
)),
loc.clone(),
)]
};
let gc_managed_of = |first: crate::PtrClassDecl, second: crate::PtrClassDecl| {
let registry: RegistryBuilder<KotlinMeta> =
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!().class(first).class(second));
drop(registry);
let key = TypeKey::from_type(&syn::parse_quote!(Session));
jni.decls
.types
.get(&key)
.expect("declared")
.opaque()
.expect("ptr_class")
.gc_managed
};
assert!(gc_managed_of(
crate::ptr_class!(Session).gc_managed(),
crate::ptr_class!(Session),
));
assert!(gc_managed_of(
crate::ptr_class!(Session),
crate::ptr_class!(Session).gc_managed(),
));
assert!(!gc_managed_of(
crate::ptr_class!(Session),
crate::ptr_class!(Session),
));
}
#[test]
fn a_type_gets_one_class_declarator() {
let loc = myflat_loc();
let items = || {
vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Sample {
pub id: i64,
}
)),
loc.clone(),
),
]
};
let declare = |first: crate::ClassDecl, second: crate::ClassDecl| {
let registry: RegistryBuilder<KotlinMeta> =
crate::test_util::reg_from_items(declare_referenced(items())).expect("index items");
let _ = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!().class(first).class(second));
drop(registry);
};
type MakeDecl = fn() -> crate::ClassDecl;
let pairs: Vec<(MakeDecl, MakeDecl)> = vec![
(
|| crate::sealed_class!(Reading).into(),
|| crate::data_class!(Reading).into(),
),
(
|| crate::data_class!(Reading).into(),
|| crate::sealed_class!(Reading).into(),
),
(
|| crate::sealed_class!(Reading).into(),
|| crate::enum_class!(Reading).into(),
),
(
|| crate::enum_class!(Reading).into(),
|| crate::sealed_class!(Reading).into(),
),
(
|| crate::sealed_class!(Reading).into(),
|| crate::ptr_class!(Reading).into(),
),
(
|| crate::ptr_class!(Reading).into(),
|| crate::sealed_class!(Reading).into(),
),
(
|| crate::data_class!(Sample).into(),
|| crate::enum_class!(Sample).into(),
),
(
|| crate::enum_class!(Sample).into(),
|| crate::data_class!(Sample).into(),
),
(
|| crate::ptr_class!(Sample).into(),
|| crate::data_class!(Sample).into(),
),
(
|| crate::data_class!(Sample).into(),
|| crate::ptr_class!(Sample).into(),
),
(
|| crate::ptr_class!(Sample).into(),
|| crate::enum_class!(Sample).into(),
),
(
|| crate::enum_class!(Sample).into(),
|| crate::ptr_class!(Sample).into(),
),
];
for (a, b) in pairs {
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| declare(a(), b()))).is_err(),
"a conflicting declarator pair was accepted"
);
}
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
declare(
crate::sealed_class!(Reading).into(),
crate::sealed_class!(Reading).into(),
);
}))
.is_ok());
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
declare(
crate::data_class!(Sample).into(),
crate::data_class!(Sample).into(),
);
}))
.is_ok());
}
#[test]
fn variant_cannot_take_a_name_the_interface_body_already_uses() {
let loc = myflat_loc();
let resolve_err = |decl: crate::SealedClassDecl, item: syn::ItemEnum| -> String {
let registry = crate::test_util::reg_from_items(declare_referenced(vec![(
syn::Item::Enum(item),
loc.clone(),
)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!().class(decl));
match jni.build_with(registry) {
Ok(_) => String::new(),
Err(e) => e.to_string(),
}
};
let e: syn::ItemEnum = syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
);
let e2: syn::ItemEnum = syn::parse_quote!(
pub enum Reading {
Reading(i64),
Exact(i64),
}
);
let msg = resolve_err(crate::sealed_class!(Reading), e2);
assert!(msg.contains("Reading"), "{msg}");
assert!(msg.contains("supertype"), "{msg}");
let msg = resolve_err(
crate::sealed_class!(Reading)
.name("Measure")
.variant(crate::variant!(Exact).name("Measure")),
e.clone(),
);
assert!(msg.contains("Measure"), "{msg}");
assert!(msg.contains("supertype"), "{msg}");
let e3: syn::ItemEnum = syn::parse_quote!(
pub enum Reading {
Reading(i64),
Exact(i64),
}
);
let ok = resolve_err(crate::sealed_class!(Reading).name("Measure"), e3);
assert!(ok.is_empty(), "expected no error, got: {ok}");
}
#[test]
fn variant_named_companion_moves_the_companion_not_the_variant() {
let loc = myflat_loc();
let emit = |decl: crate::SealedClassDecl, item: syn::ItemEnum, tag: &str| -> String {
let registry = crate::test_util::reg_from_items(declare_referenced(vec![(
syn::Item::Enum(item),
loc.clone(),
)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!().class(decl));
let dir = unique_test_dir(tag);
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
gen.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n")
};
let e: syn::ItemEnum = syn::parse_quote!(
pub enum Reading {
Companion(i64),
Exact(i64),
}
);
let kt = emit(crate::sealed_class!(Reading), e, "sealed_companion_variant");
let c: String = kt.split_whitespace().collect();
assert!(
c.contains("publicdataclassCompanion(publicvalv0:Long):Reading"),
"{kt}"
);
assert!(c.contains("publiccompanionobjectCompanion_{"), "{kt}");
assert!(c.contains("funfromParts("), "{kt}");
assert!(c.contains("0->Companion(companion_v0)"), "{kt}");
let e2: syn::ItemEnum = syn::parse_quote!(
pub enum Reading {
Companion(i64),
Other(i64),
}
);
let kt = emit(
crate::sealed_class!(Reading).variant(crate::variant!(Other).name("Companion_")),
e2,
"sealed_companion_twice",
);
let c: String = kt.split_whitespace().collect();
assert!(c.contains("publiccompanionobjectCompanion__{"), "{kt}");
let e3: syn::ItemEnum = syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
);
let kt = emit(crate::sealed_class!(Reading), e3, "sealed_companion_plain");
let c: String = kt.split_whitespace().collect();
assert!(c.contains("publiccompanionobject{"), "{kt}");
}
#[test]
fn payload_without_output_converter_is_an_error() {
let loc = myflat_loc();
let boom = || {
let registry = crate::test_util::reg_from_items(declare_referenced(vec![(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(Unmapped),
}
)),
loc.clone(),
)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!().class(crate::sealed_class!(Reading)));
let dir = unique_test_dir("sealed_unmapped_payload");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let _ = gen.write_kotlin(&dir.join("kotlin"));
};
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(boom)).expect_err("must fail");
let msg = err
.downcast_ref::<String>()
.cloned()
.or_else(|| err.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_default();
assert!(msg.contains("Reading"), "{msg}");
assert!(msg.contains("Exact.v0"), "{msg}");
assert!(msg.contains("OUTPUT converter"), "{msg}");
}
#[test]
fn sum_is_its_own_type_kind() {
let loc = myflat_loc();
let registry = crate::test_util::reg_from_items(declare_referenced(vec![(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
)]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!().class(crate::sealed_class!(Reading)));
let ty: syn::Type = syn::parse_quote!(Reading);
assert!(matches!(
jni.decls.type_kind(®istry, &TypeKey::from_type(&ty)),
crate::jni::classify::TypeKind::Sum
));
let cfg = jni
.decls
.types
.get(&TypeKey::from_type(&ty))
.expect("declared");
assert!(cfg.special_decl());
}
#[test]
fn a_sums_registry_cells_are_registered_but_not_required() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_one() -> Reading {
unimplemented!()
}
)),
loc,
),
];
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!()
.class(crate::sealed_class!(Reading))
.fun(prebindgen_registry::fun!(read_one)),
);
let gen = jni.build_with(registry).expect("resolve");
let reg = gen.registry();
let key = TypeKey::from_type(&syn::parse_quote!(Reading));
let input_root = reg
.is_root_for_test(Direction::Input, &key)
.expect("input cell");
let output_root = reg
.is_root_for_test(Direction::Output, &key)
.expect("output cell");
assert!(!input_root, "a declared sum crosses decomposed, not whole");
assert!(!output_root, "a declared sum crosses decomposed, not whole");
assert!(
reg.has_entry_for_test(Direction::Input, &key) == Some(true),
"the input direction has a whole-object decoder"
);
assert!(
reg.has_entry_for_test(Direction::Output, &key) == Some(false),
"the output direction has none — a sum crosses flattened, always"
);
}
#[test]
fn vec_of_sum_is_rejected_as_a_struct_field() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| {
let st: syn::ItemStruct = syn::parse_quote!(
pub struct Holder {
pub readings: #field_ty,
}
);
let f: syn::ItemFn = syn::parse_quote!(
pub fn holder_new() -> Holder {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced(vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(f), loc.clone()),
]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::sealed_class!(Reading))
.class(crate::data_class!(Holder))
.fun(prebindgen_registry::fun!(holder_new)),
);
let dir = unique_test_dir("sealed_vec_field");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let _ = jni
.build_with(registry)
.map(|g| g.write_rust(dir.join("g.rs")));
};
for ty in [
syn::parse_quote!(Vec<Reading>),
syn::parse_quote!(Option<Vec<Reading>>),
] {
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| build(ty)))
.expect_err("Vec<sum> must be rejected");
let msg = err
.downcast_ref::<String>()
.cloned()
.or_else(|| err.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_default();
assert!(msg.contains("variable arity"), "{msg}");
assert!(msg.contains("Reading"), "{msg}");
}
}
#[test]
fn recursive_sum_shapes_fail_deterministically() {
let loc = myflat_loc();
let attempt = |variant: proc_macro2::TokenStream, tag: &str| -> Result<(), String> {
let e: syn::ItemEnum = syn::parse_quote!(
pub enum Node {
Leaf(i64),
#variant
}
);
let st: syn::ItemStruct = syn::parse_quote!(
pub struct Holder {
pub node: Node,
}
);
let f: syn::ItemFn = syn::parse_quote!(
pub fn holder_new() -> Holder {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced(vec![
(syn::Item::Enum(e), loc.clone()),
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(f), loc.clone()),
]))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::sealed_class!(Node))
.class(crate::data_class!(Holder))
.fun(prebindgen_registry::fun!(holder_new)),
);
let dir = unique_test_dir(tag);
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
jni.build_with(registry)
.map(|g| g.write_rust(dir.join("g.rs")))
.map(|_| ())
.map_err(|e| e.to_string())
}));
match outcome {
Ok(r) => r,
Err(p) => Err(p
.downcast_ref::<String>()
.cloned()
.or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_else(|| "panic".to_string())),
}
};
let msg = attempt(quote::quote!(Branch(Vec<Node>)), "rec_vec").expect_err("must fail");
assert!(msg.contains("variable arity"), "{msg}");
let msg = attempt(quote::quote!(Branch(Box<Node>)), "rec_box").expect_err("must fail");
assert!(
!msg.contains("too deep"),
"expected a resolution failure, not the depth guard: {msg}"
);
}
fn sum_returns(tag: &str) -> (String, String) {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Priority {
Low = 0,
High = 1,
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
Range { low: i64, high: i64 },
Labeled(String, Priority),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Probe {
value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Lookup {
Absent,
Found(Probe),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_one(which: i32) -> Reading {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_maybe(which: i32) -> Option<Reading> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_all(n: i32) -> Vec<Reading> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn look_up(n: i64) -> Lookup {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_each(n: i32, sink: impl Fn(Reading) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_borrowed(p: &Probe) -> &Reading {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_borrowed_maybe(p: &Probe) -> Option<&Reading> {
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!()
.class(crate::enum_class!(Priority))
.class(crate::sealed_class!(Reading))
.class(crate::sealed_class!(Lookup))
.class(crate::ptr_class!(Probe))
.fun(prebindgen_registry::fun!(read_one))
.fun(prebindgen_registry::fun!(read_maybe))
.fun(prebindgen_registry::fun!(read_all))
.fun(prebindgen_registry::fun!(look_up))
.fun(prebindgen_registry::fun!(read_each))
.fun(prebindgen_registry::fun!(read_borrowed))
.fun(prebindgen_registry::fun!(read_borrowed_maybe)),
);
let dir = unique_test_dir(tag);
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 kotlin = gen
.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n");
(rust, kotlin)
}
#[test]
fn sum_return_builds_through_a_wire_shaped_singleton() {
let (_, kotlin) = sum_returns("jnigen_sum_return");
assert!(
kotlin.contains(
"public fun run(\n tag: Int,\n exact_v0: Long,\n \
range_low: Long,\n range_high: Long,\n labeled_v0: String?,\n \
labeled_v1: Int,\n ): R"
),
"builder must take the tag plus every group's WIRE slots, inert object \
slots nullable:\n{kotlin}"
);
assert!(
kotlin.contains(
"when (tag) { 0 -> Reading.Missing; 1 -> Reading.Exact(exact_v0); \
2 -> Reading.Range(range_low, range_high); \
3 -> Reading.Labeled(labeled_v0!!, Priority.fromInt(labeled_v1)); \
else -> throw IllegalArgumentException(\"Reading: invalid tag $tag\") }"
),
"the singleton picks the live group by tag, re-asserts the inert-nullable \
slot in its own arm, and rebuilds the enum payload from its \
discriminant:\n{kotlin}"
);
assert!(kotlin.contains("Reading: invalid tag $tag"), "{kotlin}");
}
#[test]
fn a_fixed_builder_emits_no_dead_typed_twin() {
let (_, kotlin) = sum_returns("jnigen_sum_no_dead_twin");
assert!(
kotlin.contains("public fun interface LookupBuilderRaw<out R>"),
"the raw twin is what exists:\n{kotlin}"
);
assert!(
kotlin.contains("internal val __LookupBuilderRaw: LookupBuilderRaw<Lookup>"),
"the singleton implements it:\n{kotlin}"
);
assert!(
!kotlin.contains("public fun interface LookupBuilder<out R>"),
"no typed twin for a fixed builder:\n{kotlin}"
);
assert!(
!kotlin.contains("LookupBuilder<R>.asRaw()"),
"and so no proxy that would wrap an inert group's sentinel:\n{kotlin}"
);
assert!(
kotlin.contains("public fun interface ReadingCallback"),
"a callback keeps its typed interface — the user implements it:\n{kotlin}"
);
assert!(
kotlin.contains("ReadingCallback.asRaw()"),
"and keeps the proxy that feeds it:\n{kotlin}"
);
}
#[test]
fn sum_from_parts_stays_the_property_typed_convenience() {
let (_, kotlin) = sum_returns("jnigen_sum_fromparts");
assert!(
kotlin.contains("labeled_v0: String,\n labeled_v1: Priority,"),
"`fromParts` keeps property types and non-null object slots:\n{kotlin}"
);
}
#[test]
fn sum_return_emits_one_match_with_wire_defaults() {
let (rust, _) = sum_returns("jnigen_sum_match");
let at = rust
.find("fn Java_io_test_jni_JNINative_readOne")
.expect("extern");
let body = &rust[at..at + 4000];
assert!(
body.contains("match &__out"),
"one match over the value:\n{body}"
);
assert!(
body.contains("myflat::Reading::Missing =>")
&& body.contains("myflat::Reading::Range { low"),
"arms bind each variant's payload by pattern:\n{body}"
);
assert!(
body.contains("jni :: objects :: JObject :: null ()") || body.contains("JObject::null()"),
"an inert object slot is wire-defaulted to null:\n{body}"
);
}
#[test]
fn borrowed_sum_return_matches_through_the_reference() {
let (rust, kotlin) = sum_returns("jnigen_sum_borrowed");
for extern_fn in ["readBorrowed", "readBorrowedMaybe"] {
let at = rust
.find(&format!("fn Java_io_test_jni_JNINative_{extern_fn}"))
.unwrap_or_else(|| panic!("{extern_fn} extern missing:\n{rust}"));
let body = &rust[at..at + 4000];
assert!(
body.contains("match __out {"),
"{extern_fn}: one match over the borrowed value:\n{body}"
);
assert!(
!body.contains("match &__out"),
"{extern_fn}: a borrowed return must not take a second reference:\n{body}"
);
assert!(
body.contains("myflat::Reading::Missing =>"),
"{extern_fn}: arms bind each variant through the reference:\n{body}"
);
assert!(
body.contains(".clone()"),
"{extern_fn}: a borrowed group's payload is cloned, not moved:\n{body}"
);
}
assert!(
kotlin.contains(
"public fun readBorrowed(p: Probe, onError: JniErrorHandler<Reading>): Reading"
),
"a borrowed sum arrives as an ordinary value:\n{kotlin}"
);
assert!(
kotlin.contains(
"public fun readBorrowedMaybe(p: Probe, onError: JniErrorHandler<Reading?>): Reading?"
),
"and the optional layer only nulls the whole result:\n{kotlin}"
);
}
#[test]
fn sum_return_composes_with_option_and_vec() {
let (_, kotlin) = sum_returns("jnigen_sum_layers");
assert!(
kotlin.contains(
"public fun readMaybe(which: Int, onError: JniErrorHandler<Reading?>): Reading?"
),
"Option<sum> return is a nullable sum:\n{kotlin}"
);
assert!(
kotlin.contains(
"public fun readAll(n: Int, onError: JniErrorHandler<List<Reading>>): List<Reading>"
) && kotlin.contains("__ReadingFolderRawHolder.instance"),
"Vec<sum> folds through a hoisted appender singleton:\n{kotlin}"
);
assert!(
kotlin.contains(
"public fun interface ReadingCallback {\n public fun run(reading: Reading)\n}"
),
"the user callback sees the whole sum:\n{kotlin}"
);
}
#[test]
fn sum_return_group_can_own_a_handle() {
let (_, kotlin) = sum_returns("jnigen_sum_handle");
assert!(
kotlin.contains("public fun run(tag: Int, found_v0: Long): R"),
"a handle payload's group slot is the raw pointer:\n{kotlin}"
);
assert!(
kotlin.contains("1 -> Lookup.Found(Probe(found_v0))"),
"the live arm wraps the pointer into its typed handle class:\n{kotlin}"
);
}
#[test]
fn a_data_class_field_may_be_a_sum_carrying_a_handle() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct Probe {
value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Lookup {
Absent,
Found(Probe),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Holder {
pub id: i64,
pub outcome: Lookup,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn holder_new(id: i64) -> Holder {
unimplemented!()
}
)),
loc,
),
];
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!()
.class(crate::ptr_class!(Probe))
.class(crate::sealed_class!(Lookup))
.class(crate::data_class!(Holder))
.fun(prebindgen_registry::fun!(holder_new)),
);
let dir = unique_test_dir("jnigen_sum_handle_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 = std::fs::read_to_string(gen.write_rust(dir.join("gen.rs")).expect("write_rust"))
.expect("read rust");
let kotlin = gen
.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n");
assert!(
kotlin.contains("outcome__tag: Int") && kotlin.contains("outcome_found_v0: Long"),
"the selector plus a raw-pointer group slot, both prefixed by the field:\n{kotlin}"
);
assert!(
kotlin.contains("Lookup.Found(Probe(outcome_found_v0))"),
"the parent's fromParts inlines the `when` and wraps the pointer:\n{kotlin}"
);
assert!(
kotlin.contains("public data class Holder(val id: Long, val outcome: Lookup)"),
"the field surfaces as the typed sum:\n{kotlin}"
);
let kc: String = kotlin.split_whitespace().collect();
assert!(
kotlin.contains(
"public data class Holder(val id: Long, val outcome: Lookup) : AutoCloseable"
),
"a sum-carried handle is the container's to close, like a plain handle \
field:\n{kotlin}"
);
assert!(
kc.contains("overridefunclose(){outcome.close()}"),
"the container closes the field as a whole; the walk into the \
alternatives lives in `Lookup`:\n{kotlin}"
);
assert!(
kc.contains("publicsealedinterfaceLookup:AutoCloseable"),
"the sum owns the cascade its containers delegate to:\n{kotlin}"
);
assert!(
kc.contains("dataclassFound(publicvalv0:Probe):Lookup{overridefunclose(){v0.close()}}"),
"the live alternative closes its payload:\n{kotlin}"
);
assert!(
kc.contains("dataobjectAbsent:Lookup{overridefunclose(){}}"),
"an alternative holding nothing native overrides with an empty body — \
Kotlin requires the member, and the emptiness is the statement:\n{kotlin}"
);
assert!(
rust.contains("Lookup::Found") && rust.contains("Lookup::Absent"),
"Rust matches the field's sum, filling every group's slots:\n{rust}"
);
}
#[test]
fn a_data_class_field_may_be_a_nested_data_class_carrying_a_handle() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct Probe {
value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Inner {
pub probe: Probe,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Outer {
pub id: i64,
pub inner: Inner,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn outer_new(id: i64) -> Outer {
unimplemented!()
}
)),
loc,
),
];
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!()
.class(crate::ptr_class!(Probe))
.class(crate::data_class!(Inner))
.class(crate::data_class!(Outer))
.fun(prebindgen_registry::fun!(outer_new)),
);
let dir = unique_test_dir("jnigen_nested_handle_field");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let kotlin = gen
.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n");
let kc: String = kotlin.split_whitespace().collect();
assert!(
kc.contains(
"dataclassInner(valprobe:Probe):AutoCloseable{overridefunclose(){probe.close()}"
),
"the direct handle field cascades, as it always did:\n{kotlin}"
);
assert!(
kc.contains("dataclassOuter(valid:Long,valinner:Inner):AutoCloseable"),
"…and so does the container that only REACHES the handle:\n{kotlin}"
);
assert!(
kc.contains("overridefunclose(){inner.close()}"),
"the outer closes the field as a whole — the walk is the inner \
class's:\n{kotlin}"
);
}
#[test]
fn a_sum_owning_nothing_native_is_not_closeable() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Gauge {
pub id: i64,
pub reading: Reading,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn gauge_new(id: i64) -> Gauge {
unimplemented!()
}
)),
loc,
),
];
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!()
.class(crate::sealed_class!(Reading))
.class(crate::data_class!(Gauge))
.fun(prebindgen_registry::fun!(gauge_new)),
);
let dir = unique_test_dir("jnigen_sum_no_handle");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let kotlin = gen
.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n");
let kc: String = kotlin.split_whitespace().collect();
assert!(
kc.contains("publicsealedinterfaceReading{"),
"an `i64` payload owns nothing, so the sum takes no lifecycle:\n{kotlin}"
);
assert!(
!kc.contains("interfaceReading:AutoCloseable"),
"…and does not implement `AutoCloseable`:\n{kotlin}"
);
assert!(
!kc.contains("Exact(publicvalv0:Long):Reading{overridefunclose"),
"…so its variant classes carry no `close()` either:\n{kotlin}"
);
assert!(
!kc.contains("Gauge(valid:Long,valreading:Reading):AutoCloseable"),
"and the container holding it stays non-closeable:\n{kotlin}"
);
}
#[test]
fn two_sum_callback_args_keep_their_own_selectors() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Probe {
value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Lookup {
Absent,
Found(Probe),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_pair(f: impl Fn(Reading, Lookup) + Send + Sync + 'static) {
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!()
.class(crate::sealed_class!(Reading))
.class(crate::sealed_class!(Lookup))
.class(crate::ptr_class!(Probe))
.fun(prebindgen_registry::fun!(read_pair)),
);
let dir = unique_test_dir("jnigen_two_sum_cb");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let kotlin = gen
.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n");
assert!(
kotlin.contains("public fun run(reading: Reading, lookup: Lookup)"),
"{kotlin}"
);
assert!(
kotlin.contains("tag: Int,") && kotlin.contains("tag2: Int,"),
"each sum contributes its own selector:\n{kotlin}"
);
let kc: String = kotlin.split_whitespace().collect();
assert!(
kc.contains("when(tag){0->Reading.Missing;")
&& kotlin.contains(r#"IllegalArgumentException("Reading: invalid tag $tag")"#),
"{kotlin}"
);
assert!(
kc.contains("when(tag2){0->Lookup.Absent;")
&& kotlin.contains(r#"IllegalArgumentException("Lookup: invalid tag $tag2")"#),
"the second sum's reassembly must follow its renamed selector, template \
included:\n{kotlin}"
);
assert!(
kc.contains("val__own0=when(tag2)") && kc.contains("finally{__own0.close()}"),
"a handle reached through a sum arg is closed after `run`, exactly as a \
handle arg is:\n{kotlin}"
);
assert!(
!kc.contains("__own1"),
"a sum whose payload owns nothing native is not bound and not closed:\n{kotlin}"
);
}
#[test]
fn sum_in_result_ok_position_is_rejected_with_its_reason() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Probe {
value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_try(n: i64) -> Result<Reading, Probe> {
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!()
.class(crate::sealed_class!(Reading))
.class(crate::ptr_class!(Probe))
.fun(prebindgen_registry::fun!(read_try)),
);
let err = jni
.build_with(registry)
.expect_err("must be rejected")
.to_string();
assert!(
err.contains("read_try") && err.contains("success position of a fallible return"),
"the error must name the function and the unsupported position: {err}"
);
assert!(
err.contains("Return `Reading` directly"),
"…and say what to write instead: {err}"
);
}
#[test]
fn undeclared_sum_in_result_error_position_is_rejected() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_try(n: i64) -> Result<i64, Reading> {
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!()
.class(crate::sealed_class!(Reading))
.fun(prebindgen_registry::fun!(read_try)),
);
let err = jni
.build_with(registry)
.expect_err("must be rejected")
.to_string();
assert!(
err.contains("read_try") && err.contains("Result<_, Reading>"),
"the error must name the function and the position: {err}"
);
assert!(
err.contains("e.to_string()") && err.contains("Display"),
"…and both consequences of the silent path: {err}"
);
assert!(
err.contains("expand_return!(Reading)"),
"…and what to write instead: {err}"
);
}
#[test]
fn the_diagnostic_names_the_whole_error_type_where_it_must() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_try(n: i64) -> Result<i64, Option<Reading>> {
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!()
.class(crate::sealed_class!(Reading))
.fun(prebindgen_registry::fun!(read_try)),
);
let err = jni
.build_with(registry)
.expect_err("must be rejected")
.to_string();
let compact: String = err.split_whitespace().collect();
assert!(compact.contains("`Result<_,Option<Reading>>`"), "{err}");
assert!(compact.contains("`Option<Reading>:Display`"), "{err}");
assert!(compact.contains("expand_return!(Option<Reading>)"), "{err}");
assert!(
compact.contains("`Reading`isdeclared`sealed_class!`"),
"{err}"
);
}
#[test]
fn declared_sum_in_result_error_position_resolves() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn reading_code(v: &Reading) -> i64 {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_try(n: i64) -> Result<i64, Reading> {
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")
.expand(
prebindgen_registry::expand_return!(Reading)
.field(prebindgen_registry::fun!(reading_code)),
)
.package(
crate::package!()
.class(crate::sealed_class!(Reading))
.fun(prebindgen_registry::fun!(read_try)),
);
jni.build_with(registry)
.expect("a declared error deconstructor is the supported shape");
}
#[test]
fn slice_of_sum_callback_arg_is_rejected_with_its_reason() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Reading {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn read_batch(f: impl Fn(&[Reading]) + Send + Sync + 'static) {
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!()
.class(crate::sealed_class!(Reading))
.fun(prebindgen_registry::fun!(read_batch)),
);
let err = jni
.build_with(registry)
.expect_err("must be rejected")
.to_string();
assert!(
err.contains("read_batch") && err.contains("slice of a sealed_class"),
"the error must name the function and the unsupported position: {err}"
);
assert!(
err.contains("impl Fn(Reading)") && err.contains("Vec<Reading>"),
"…and point at the two shapes that do work: {err}"
);
}
#[test]
fn a_raw_named_sum_generates() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum r#type {
Missing,
Exact(i64),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThingStruct {
pub reading: r#type,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_to_struct(t: &ZThing) -> ZThingStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_emit(cb: impl Fn(ZThing) + Send + Sync + 'static) {
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!()
.class(crate::ptr_class!(ZThing))
.class(crate::sealed_class!(r#type))
.fun(prebindgen_registry::fun!(z_emit)),
)
.expand(
prebindgen_registry::expand_return!(ZThing)
.fields(prebindgen_registry::fields!(z_thing_to_struct)),
);
let dir = unique_test_dir("jnigen_raw_sum");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust = std::fs::read_to_string(gen.write_rust(dir.join("g.rs")).expect("write_rust"))
.expect("read rust");
assert!(
rust.contains("myflat::r#type::Missing") && rust.contains("myflat::r#type::Exact"),
"the sum encoder matches the raw-named enum by its real path:\n{rust}"
);
}
#[test]
fn empty_sum_alternatives_keep_their_own_pattern_delimiters() {
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum Shape {
Bare,
Parens(),
Braces {},
Full(i64),
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn make_shape() -> Shape {
unimplemented!()
}
)),
loc,
),
];
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!()
.class(crate::sealed_class!(Shape))
.fun(prebindgen_registry::fun!(make_shape)),
);
let dir = unique_test_dir("jnigen_empty_sum_alternatives");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust = std::fs::read_to_string(gen.write_rust(dir.join("g.rs")).expect("write_rust"))
.expect("read rust");
let rc: String = rust.split_whitespace().collect();
assert!(
rc.contains("myflat::Shape::Bare=>"),
"a unit alternative is matched bare:\n{rust}"
);
assert!(
rc.contains("myflat::Shape::Parens()=>"),
"an empty TUPLE alternative keeps its parens:\n{rust}"
);
assert!(
rc.contains("myflat::Shape::Braces{}=>"),
"an empty STRUCT alternative keeps its braces:\n{rust}"
);
assert!(
!rc.contains("myflat::Shape::Parens=>") && !rc.contains("myflat::Shape::Braces=>"),
"neither empty alternative may be matched bare:\n{rust}"
);
}
#[test]
fn a_types_close_answer_matches_its_plans() {
use crate::jni::struct_plan::{classify_field, type_close_strategy};
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct Probe {
value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Lookup {
Absent,
Found(Probe),
Failed(String),
}
)),
loc.clone(),
),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Tally {
None,
Count(i64),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Inner {
pub probe: Probe,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Everything {
pub id: i64,
pub probe: Probe,
pub maybe_probe: Option<Probe>,
pub outcome: Lookup,
pub tally: Tally,
pub inner: Inner,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn everything_new(id: i64) -> Everything {
unimplemented!()
}
)),
loc,
),
];
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!()
.class(crate::ptr_class!(Probe))
.class(crate::sealed_class!(Lookup))
.class(crate::sealed_class!(Tally))
.class(crate::data_class!(Inner))
.class(crate::data_class!(Everything))
.fun(prebindgen_registry::fun!(everything_new)),
);
let gen = jni.build_with(registry).expect("resolve");
let (ext, registry) = (&gen.decls, &gen.registry);
let mut checked = 0usize;
for ty in registry.flat().types() {
let (owner, fields): (&syn::Ident, Vec<&prebindgen_registry::flat::Field>) = match ty {
prebindgen_registry::flat::Type::Struct(st) => (&st.name, st.fields.iter().collect()),
prebindgen_registry::flat::Type::Variant(sum) => (
&sum.name,
sum.alternatives
.iter()
.flat_map(|a| a.fields.iter())
.collect(),
),
_ => continue,
};
for field in fields {
let path = format!("{owner}.{}", field.member().to_token_stream());
let Some(kind) = classify_field(ext, registry, &field.ty, &path, 0) else {
continue;
};
assert_eq!(
kind.destructible().is_some(),
type_close_strategy(ext, registry, &field.ty, 0).is_some(),
"the two forms disagree about `{path}`: a plan field and its \
bare type must reach the same handles",
);
checked += 1;
}
}
assert!(
checked >= 10,
"expected every declared field to be compared, saw {checked}"
);
let everything = match registry
.flat()
.declared_type("Everything")
.expect("Everything is declared")
{
prebindgen_registry::flat::Type::Struct(st) => st,
_ => panic!("Everything is a struct"),
};
let closes: Vec<String> = everything
.fields
.iter()
.filter(|f| type_close_strategy(ext, registry, &f.ty, 0).is_some())
.map(|f| f.name.as_ref().unwrap().to_string())
.collect();
assert_eq!(
closes,
vec!["probe", "maybe_probe", "outcome", "inner"],
"the fixture must cover reaching a handle four ways AND not reaching one"
);
}