use super::*;
fn value_form_items() -> Vec<(syn::Item, prebindgen::SourceLocation)> {
let loc = myflat_loc();
vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZStamp {
pub secs: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOrigin {
pub node: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSampleStruct {
pub key_expr: ZKeyExpr,
pub payload: ZBytes,
pub express: bool,
pub stamp: Option<ZStamp>,
pub origin: ZOrigin,
pub attachment: Option<ZBytes>,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_to_struct(s: &ZSample) -> ZSampleStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_keyexpr_as_str(k: &ZKeyExpr) -> &str {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_sub(cb: impl Fn(ZSample) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc,
),
]
}
fn value_form_gen(tag: &str, decl: crate::ExpandReturnDecl) -> (String, String) {
let registry = crate::test_util::reg_from_items(declare_referenced(value_form_items()))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZSample))
.class(crate::ptr_class!(ZKeyExpr))
.class(crate::ptr_class!(ZBytes))
.class(crate::data_class!(ZStamp))
.class(crate::data_class!(ZOrigin))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZKeyExpr)
.field(prebindgen_registry::fun!(z_keyexpr_as_str)),
)
.expand(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 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");
(rust, kotlin)
}
#[test]
fn fields_expand_by_each_field_s_own_boundary() {
let (_, kotlin) = value_form_gen(
"jnigen_vf_basic",
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
assert!(
kotlin.contains("keyExpr__zKeyexprAsStr: String"),
"a field whose type has its own expand_return! is decomposed by it, \
not handed over as a handle:\n{kotlin}"
);
assert!(
kotlin.contains("express: Boolean"),
"a scalar field is one leaf:\n{kotlin}"
);
assert!(
kotlin.contains("stamp: ZStamp?"),
"an Option<data class> field stays ONE leaf (its converter builds it):\n{kotlin}"
);
assert!(
kotlin.contains("origin__node: Long"),
"a non-optional nested data class INLINES into its own fields:\n{kotlin}"
);
assert!(
kotlin.contains("payload: ZBytes") && kotlin.contains("attachment: ZBytes?"),
"a handle field with no boundary decl stays a handle, nullable under Option:\n{kotlin}"
);
}
#[test]
fn the_value_form_accessor_is_called_once() {
let (rust, _) = value_form_gen(
"jnigen_vf_hoist",
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
let calls = rust.matches("z_sample_to_struct").count();
assert_eq!(
calls, 1,
"the value form is bound to one local and every leaf reaches off it; \
found {calls} calls in:\n{rust}"
);
}
#[test]
fn an_optional_field_reaches_its_converter_whole() {
let (rust, _) = value_form_gen(
"jnigen_vf_opt",
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
for field in ["stamp", "attachment"] {
assert!(
rust.contains(&format!(".{field}.clone()")),
"`{field}` must be cloned whole, not matched open:\n{rust}"
);
assert!(
!rust.contains(&format!(".{field} {{")),
"`{field}` has nothing decomposed below it, so it is not a nesting \
step — no `match` on it:\n{rust}"
);
}
}
#[test]
fn deriving_matches_the_equivalent_hand_written_list() {
let items = value_form_items();
let accessors: Vec<(syn::Item, prebindgen::SourceLocation)> = {
let loc = myflat_loc();
vec![
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_key_expr(s: &ZSample) -> &ZKeyExpr {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_express(s: &ZSample) -> bool {
unimplemented!()
}
)),
loc,
),
]
};
let leaves_of = |decl: crate::ExpandReturnDecl,
extra: Vec<(syn::Item, prebindgen::SourceLocation)>|
-> Vec<(String, String)> {
let mut all = items.clone();
all.extend(extra);
let registry =
crate::test_util::reg_from_items(declare_referenced(all)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZSample))
.class(crate::ptr_class!(ZKeyExpr))
.class(crate::ptr_class!(ZBytes))
.class(crate::data_class!(ZStamp))
.class(crate::data_class!(ZOrigin))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZKeyExpr)
.field(prebindgen_registry::fun!(z_keyexpr_as_str)),
)
.expand(decl);
let gen = jni.build_with(registry).expect("resolve");
gen.registry()
.callback_arg_plans_for_test()
.flat_map(|p| p.leaves.iter())
.map(|l| (l.name.clone(), l.out_ty.to_string()))
.collect()
};
let derived = leaves_of(
prebindgen_registry::expand_return!(ZSample).fields(
prebindgen_registry::fields!(z_sample_to_struct)
.name("key_expr", "keyExpr")
.name("express", "express"),
),
vec![],
);
let by_hand = leaves_of(
prebindgen_registry::expand_return!(ZSample)
.field(prebindgen_registry::fun!(z_sample_key_expr).name("keyExpr"))
.field(prebindgen_registry::fun!(z_sample_express).name("express")),
accessors,
);
let take = |v: &[(String, String)], n: &str| -> Option<(String, String)> {
v.iter().find(|(name, _)| name.starts_with(n)).cloned()
};
for prefix in ["keyExpr", "express"] {
assert_eq!(
take(&derived, prefix).map(|(n, _)| n),
take(&by_hand, prefix).map(|(n, _)| n),
"derived and hand-written leaves must agree on `{prefix}`\n\
derived: {derived:?}\nby hand: {by_hand:?}"
);
}
}
#[test]
fn a_per_field_override_replaces_the_type_default() {
let (_, kotlin) = value_form_gen(
"jnigen_vf_override",
prebindgen_registry::expand_return!(ZSample).fields(
prebindgen_registry::fields!(z_sample_to_struct).field(
"key_expr",
prebindgen_registry::expand_return!(ZKeyExpr).field_self(),
),
),
);
assert!(
kotlin.contains("keyExpr: ZKeyExpr"),
"the override wins over ZKeyExpr's type-level decl:\n{kotlin}"
);
assert!(
!kotlin.contains("keyExpr__zKeyexprAsStr"),
"the overridden field must NOT also carry the type default:\n{kotlin}"
);
}
#[test]
fn an_empty_per_field_override_drops_the_field() {
let (_, kotlin) = value_form_gen(
"jnigen_vf_drop",
prebindgen_registry::expand_return!(ZSample).fields(
prebindgen_registry::fields!(z_sample_to_struct)
.field("key_expr", prebindgen_registry::expand_return!(ZKeyExpr)),
),
);
assert!(
!kotlin.contains("keyExpr"),
"a field whose override states no leaves contributes none:\n{kotlin}"
);
assert!(
kotlin.contains("express: Boolean"),
"and its siblings are untouched:\n{kotlin}"
);
}
#[test]
fn a_field_can_be_renamed_including_a_nested_one() {
let (_, kotlin) = value_form_gen(
"jnigen_vf_rename",
prebindgen_registry::expand_return!(ZSample).fields(
prebindgen_registry::fields!(z_sample_to_struct)
.name("express", "fast")
.name("origin.node", "nodeId"),
),
);
assert!(
kotlin.contains("fast: Boolean"),
"a renamed field uses the literal name:\n{kotlin}"
);
assert!(
kotlin.contains("origin__nodeId: Long"),
"a nested field is renamed through its dotted path, keeping the prefix:\n{kotlin}"
);
}
#[test]
fn fields_mixes_with_field_self() {
let (_, kotlin) = value_form_gen(
"jnigen_vf_mixed",
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct))
.field_self(),
);
assert!(
kotlin.contains("express: Boolean") && kotlin.contains("handle: ZSample"),
"the derived fields and the identity leaf are delivered together:\n{kotlin}"
);
}
fn sum_field_gen(tag: &str) -> (String, String) {
let loc = myflat_loc();
let mut items = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum ZOutcome {
Empty,
Ok(ZBytes),
Failed(String),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZReplyStruct {
pub result: ZOutcome,
pub seq: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_to_struct(r: &ZReply) -> ZReplyStruct {
unimplemented!()
}
)),
loc.clone(),
),
];
items.push((
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_sub(cb: impl Fn(ZReply) + Send + Sync + 'static) {
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!(ZReply))
.class(crate::ptr_class!(ZBytes))
.class(crate::sealed_class!(ZOutcome))
.fun(prebindgen_registry::fun!(z_reply_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZReply)
.fields(prebindgen_registry::fields!(z_reply_to_struct)),
);
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 = 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");
(rust, kotlin)
}
#[test]
fn a_sum_field_crosses_as_its_selector_and_groups() {
let (rust, kotlin) = sum_field_gen("jnigen_vf_sum");
assert!(
kotlin.contains("result__tag: Int"),
"the sum field contributes its selector, prefixed by the field:\n{kotlin}"
);
assert!(
kotlin.contains("result__ok_v0: Long") && kotlin.contains("result__failed_v0: String?"),
"one group slot per alternative payload, object slots nullable \
(an inert group arrives as null):\n{kotlin}"
);
assert!(
kotlin.contains("seq: Long"),
"a sibling field is unaffected:\n{kotlin}"
);
assert!(
kotlin.contains("ZOutcome.Ok(") && kotlin.contains("ZOutcome.Failed("),
"the receiver rebuilds the live alternative from the tag:\n{kotlin}"
);
assert!(
rust.contains("myflat::ZOutcome::Ok") && rust.contains("myflat::ZOutcome::Failed"),
"Rust matches the sum once, filling every group's slots:\n{rust}"
);
}
#[test]
fn a_vec_sum_field_is_rejected_by_name() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| {
let items = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum ZOutcome {
Empty,
Failed(String),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZReplyStruct {
pub result: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_to_struct(r: &ZReply) -> ZReplyStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_sub(cb: impl Fn(ZReply) + 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!(ZReply))
.class(crate::sealed_class!(ZOutcome))
.fun(prebindgen_registry::fun!(z_reply_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZReply)
.fields(prebindgen_registry::fields!(z_reply_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_sum_reject");
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")));
};
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
build(syn::parse_quote!(Vec<ZOutcome>))
}))
.expect_err("a Vec of sums 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"),
"expected the reason in: {msg}"
);
assert!(
msg.contains("ZReplyStruct.result"),
"the message names the offending field: {msg}"
);
}
#[test]
fn an_optional_sum_field_gates_its_whole_segment() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| {
let items = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum ZOutcome {
Empty,
Failed(String),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZReplyStruct {
pub seq: i64,
pub result: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_to_struct(r: &ZReply) -> ZReplyStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_sub(cb: impl Fn(ZReply) + 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!(ZReply))
.class(crate::sealed_class!(ZOutcome))
.fun(prebindgen_registry::fun!(z_reply_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZReply)
.fields(prebindgen_registry::fields!(z_reply_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_opt_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");
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)
};
let (rust, kotlin) = build(syn::parse_quote!(Option<ZOutcome>));
let rc: String = rust.split_whitespace().collect();
assert!(
rc.contains(":&::core::option::Option<_>=&(&__vf0).result;"),
"the optional step is a coercion site over the field:\n{rust}"
);
assert!(
rc.contains("::core::option::Option::None=>{(jni::objects::JObject::null(),jni::objects::JObject::null(),)}"),
"the absent arm yields the whole segment's defaults as one tuple:\n{rust}"
);
assert!(
rc.contains("jni::objects::JObject::null()"),
"an absent segment defaults its slots, the tag's to JVM null:\n{rust}"
);
assert!(
rust.contains("ZOutcome::Failed") && rust.contains("ZOutcome::Empty"),
"every alternative still gets its arm:\n{rust}"
);
assert!(
kotlin.contains("null -> null"),
"the reassembly answers `null` for an absent sum, before tag 0:\n{kotlin}"
);
assert!(
rust.contains("seq"),
"a non-sum field beside it still crosses normally:\n{rust}"
);
}
#[test]
fn a_bare_sum_field_takes_no_gate() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum ZOutcome {
Empty,
Failed(String),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZReplyStruct {
pub result: ZOutcome,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_to_struct(r: &ZReply) -> ZReplyStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_reply_sub(cb: impl Fn(ZReply) + 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!(ZReply))
.class(crate::sealed_class!(ZOutcome))
.fun(prebindgen_registry::fun!(z_reply_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZReply)
.fields(prebindgen_registry::fields!(z_reply_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_bare_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");
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!(
!rust
.split_whitespace()
.collect::<String>()
.contains("::core::option::Option::None=>{("),
"nothing to gate, so no tuple bind:\n{rust}"
);
assert!(
!kotlin.contains("null -> null"),
"the selector carries no absent case:\n{kotlin}"
);
assert!(
rust.contains("ZOutcome::Failed"),
"the segment still emits its arms:\n{rust}"
);
}
#[test]
fn a_vec_of_data_classes_crosses_as_a_return_and_as_a_field() {
let loc = myflat_loc();
let build = |field: Option<syn::Type>| {
let mut items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct Rec {
pub id: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn stack_records(n: i64) -> Vec<Rec> {
unimplemented!()
}
)),
loc.clone(),
),
];
let has_field = field.is_some();
if let Some(field) = field {
items.push((
syn::Item::Struct(syn::parse_quote!(
pub struct StackStruct {
pub records: #field,
}
)),
loc.clone(),
));
items.push((
syn::Item::Fn(syn::parse_quote!(
pub fn stack_struct_of(n: i64) -> StackStruct {
unimplemented!()
}
)),
loc.clone(),
));
}
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let mut jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::data_class!(Rec))
.fun(prebindgen_registry::fun!(stack_records)),
);
if has_field {
jni = jni.package(
crate::package!()
.class(crate::data_class!(StackStruct))
.fun(prebindgen_registry::fun!(stack_struct_of)),
);
}
let dir = unique_test_dir("jnigen_vec_dataclass_field");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
gen.write_rust(dir.join("g.rs")).expect("write_rust");
paths
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n")
};
let kotlin = build(None);
let kc: String = kotlin.split_whitespace().collect();
assert!(
kc.contains("stackRecords") && kc.contains("List<Rec>"),
"`Vec<Rec>` must cross as `List<Rec>` in the RETURN position — that is \
the half of the asymmetry that works:\n{kotlin}"
);
let kotlin = build(Some(syn::parse_quote!(Vec<Rec>)));
let kc: String = kotlin.split_whitespace().collect();
assert!(
kc.contains("publicdataclassStackStruct(valrecords:List<Rec>)"),
"the `Vec<Rec>` FIELD must surface as `List<Rec>`:\n{kotlin}"
);
assert!(
kc.contains("StackStructBuilder{records->StackStruct.fromParts(records)}"),
"the field is delivered as one builder slot:\n{kotlin}"
);
assert!(
kc.contains("RecFolderRaw{acc,id->acc.add(Rec.fromParts(id));acc}"),
"each element must be folded from its raw leaves, not crossed as an \
object — that is what the fixed bridge exists to avoid:\n{kotlin}"
);
}
#[test]
fn an_optional_field_crosses_the_same_however_rust_spells_it() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| -> String {
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSampleStruct {
pub kex: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_to_struct(s: &ZSample) -> ZSampleStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_keyexpr_as_str(k: &ZKeyExpr) -> &str {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_sub(cb: impl Fn(ZSample) + 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!(ZSample))
.class(crate::ptr_class!(ZKeyExpr))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZKeyExpr)
.field(prebindgen_registry::fun!(z_keyexpr_as_str)),
)
.expand(
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_boxed_opt");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
std::fs::read_to_string(gen.write_rust(dir.join("g.rs")).expect("write_rust"))
.expect("read rust")
};
let plain = build(syn::parse_quote!(Option<ZKeyExpr>));
let boxed = build(syn::parse_quote!(Box<Option<ZKeyExpr>>));
for (label, rust) in [("Option<T>", &plain), ("Box<Option<T>>", &boxed)] {
let rc: String = rust.split_whitespace().collect();
assert!(
rc.contains("let__o0:&::core::option::Option<_>=&"),
"{label}: the optional field is reached through a coercion site:\n{rust}"
);
assert!(
!rc.contains("match&(&__vf0).kex"),
"{label}: the raw place is never destructured directly:\n{rust}"
);
assert!(
rc.contains("myflat::z_keyexpr_as_str(__n0)"),
"{label}: the child's boundary still applies:\n{rust}"
);
}
assert_eq!(
plain.replace("Box<Option<ZKeyExpr>>", "Option<ZKeyExpr>"),
boxed.replace("Box<Option<ZKeyExpr>>", "Option<ZKeyExpr>"),
"a transparent wrapper must not change what crosses the boundary"
);
}
#[test]
fn an_adjustment_naming_an_unknown_field_is_an_error() {
let build = |decl: crate::FieldsDecl| {
let registry = crate::test_util::reg_from_items(declare_referenced(value_form_items()))
.expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZSample))
.class(crate::ptr_class!(ZKeyExpr))
.class(crate::ptr_class!(ZBytes))
.class(crate::data_class!(ZStamp))
.class(crate::data_class!(ZOrigin))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZKeyExpr)
.field(prebindgen_registry::fun!(z_keyexpr_as_str)),
)
.expand(prebindgen_registry::expand_return!(ZSample).fields(decl));
let dir = unique_test_dir("jnigen_vf_unknown");
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 decl in [
prebindgen_registry::fields!(z_sample_to_struct).name("kex", "kex"),
prebindgen_registry::fields!(z_sample_to_struct).field(
"kex",
prebindgen_registry::expand_return!(ZKeyExpr).field_self(),
),
] {
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| build(decl)))
.expect_err("an unknown field name 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("kex"), "the message names the field: {msg}");
assert!(
msg.contains("ZSampleStruct"),
"the message names the value form: {msg}"
);
}
}
#[test]
#[should_panic(expected = "already expands a value form")]
fn a_second_value_form_is_an_error() {
let _ = prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct))
.fields(prebindgen_registry::fields!(z_sample_to_struct));
}
#[test]
#[should_panic(expected = "already has an override")]
fn a_repeated_override_is_an_error() {
let _ = prebindgen_registry::fields!(z_sample_to_struct)
.field(
"key_expr",
prebindgen_registry::expand_return!(ZKeyExpr).field_self(),
)
.field(
"key_expr",
prebindgen_registry::expand_return!(ZKeyExpr).field_self(),
);
}
#[test]
#[should_panic(expected = "reserved")]
fn a_rename_may_not_contain_the_chain_separator() {
let _ = prebindgen_registry::fields!(z_sample_to_struct).name("express", "a__b");
}
#[test]
fn a_single_leaf_value_form_delivers_an_owned_field() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOneStruct {
pub label: String,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_one_to_struct(o: &ZOne) -> ZOneStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_one_make(n: i64) -> ZOne {
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!(ZOne))
.fun(prebindgen_registry::fun!(z_one_make)),
)
.expand(
prebindgen_registry::expand_return!(ZOne)
.fields(prebindgen_registry::fields!(z_one_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_single");
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");
assert!(
rust.contains(".label).clone()"),
"the single leaf is CLONED out of the value form, matching the owned \
`String` its converter takes — composing it as a borrow would feed \
`&String` to a `String` converter:\n{rust}"
);
}
#[test]
fn a_single_leaf_consuming_value_form_moves_its_field() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOneStruct {
pub label: String,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_one_into_struct(o: ZOne) -> ZOneStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_one_make(n: i64) -> ZOne {
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!(ZOne))
.fun(prebindgen_registry::fun!(z_one_make)),
)
.expand(
prebindgen_registry::expand_return!(ZOne)
.fields_self_into(prebindgen_registry::fields!(z_one_into_struct)),
);
let dir = unique_test_dir("jnigen_vf_single_consume");
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");
assert!(
rust.contains("z_one_into_struct(__cvsrc)") && !rust.contains("z_one_into_struct(&"),
"the by-value accessor is handed the value, not a borrow of it:\n{rust}"
);
assert!(
rust.contains(".label") && !rust.contains(".label).clone()"),
"and the field it owns is MOVED out, not cloned:\n{rust}"
);
}
#[test]
fn a_handle_field_of_a_consuming_value_form_moves() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZEnvelopeStruct {
pub child: ZChild,
pub tag: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_envelope_into_struct(e: ZEnvelope) -> ZEnvelopeStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_envelope_sub(cb: impl Fn(ZEnvelope) + Send + Sync + 'static) {
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!(ZEnvelope))
.class(crate::ptr_class!(ZChild))
.fun(prebindgen_registry::fun!(z_envelope_sub)),
)
.expand(prebindgen_registry::expand_return!(ZChild).field_self())
.expand(
prebindgen_registry::expand_return!(ZEnvelope)
.fields_self_into(prebindgen_registry::fields!(z_envelope_into_struct)),
);
let dir = unique_test_dir("jnigen_vf_handle_field_consume");
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");
assert!(
rust.contains("Box::new(__vf0.child)"),
"the handle field is MOVED into its Box:\n{rust}"
);
assert!(
!rust.contains("&__vf0.child"),
"and is not handed to the borrowed-opaque converter, which would clone \
it:\n{rust}"
);
}
#[test]
fn a_sole_handle_field_of_a_consuming_value_form_moves() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSingleEnvelopeStruct {
pub child: ZChild,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_single_envelope_into_struct(e: ZSingleEnvelope) -> ZSingleEnvelopeStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_single_envelope_make() -> ZSingleEnvelope {
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!(ZSingleEnvelope))
.class(crate::ptr_class!(ZChild))
.fun(prebindgen_registry::fun!(z_single_envelope_make)),
)
.expand(prebindgen_registry::expand_return!(ZChild).field_self())
.expand(
prebindgen_registry::expand_return!(ZSingleEnvelope)
.fields_self_into(prebindgen_registry::fields!(z_single_envelope_into_struct)),
);
let dir = unique_test_dir("jnigen_vf_sole_handle_consume");
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");
assert!(
rust.contains("z_single_envelope_into_struct(__cvsrc)"),
"the by-value accessor is handed the value:\n{rust}"
);
assert!(
rust.contains("__vf0.child") && !rust.contains("&__vf0.child"),
"and the sole handle field is MOVED out, not borrowed into the \
cloning converter:\n{rust}"
);
}
#[test]
fn an_optional_handle_field_of_a_consuming_value_form_moves() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOptionalEnvelopeStruct {
pub child: Option<ZChild>,
pub tag: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_optional_envelope_into_struct(
e: ZOptionalEnvelope,
) -> ZOptionalEnvelopeStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_optional_envelope_sub(
cb: impl Fn(ZOptionalEnvelope) + Send + Sync + 'static,
) {
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!(ZOptionalEnvelope))
.class(crate::ptr_class!(ZChild))
.fun(prebindgen_registry::fun!(z_optional_envelope_sub)),
)
.expand(prebindgen_registry::expand_return!(ZChild).field_self())
.expand(
prebindgen_registry::expand_return!(ZOptionalEnvelope).fields_self_into(
prebindgen_registry::fields!(z_optional_envelope_into_struct),
),
);
let dir = unique_test_dir("jnigen_vf_optional_handle_consume");
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");
assert!(
rust.contains("match __vf0.child") && !rust.contains("&__vf0.child"),
"the `Option` is matched BY VALUE, not borrowed:\n{rust}"
);
assert!(
rust.contains("Box::new(__n)"),
"and the present handle is MOVED into its Box rather than cloned \
through the borrowed converter:\n{rust}"
);
}
#[test]
fn a_sole_optional_handle_field_takes_callback_delivery() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOptionalSingleStruct {
pub child: Option<ZChild>,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_optional_single_into_struct(e: ZOptionalSingle) -> ZOptionalSingleStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_optional_single_make() -> ZOptionalSingle {
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!(ZOptionalSingle))
.class(crate::ptr_class!(ZChild))
.fun(prebindgen_registry::fun!(z_optional_single_make)),
)
.expand(prebindgen_registry::expand_return!(ZChild).field_self())
.expand(
prebindgen_registry::expand_return!(ZOptionalSingle)
.fields_self_into(prebindgen_registry::fields!(z_optional_single_into_struct)),
);
let dir = unique_test_dir("jnigen_vf_sole_optional_handle");
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");
assert!(
!rust.contains("&(&__vf0).child") && !rust.contains("&__vf0.child"),
"the optional field is never composed as a borrow — that is what the \
flat return path did, handing `&Option<ZChild>` to a `ZChild` \
converter:\n{rust}"
);
assert!(
rust.contains("match __vf0.child") && rust.contains("Box::new(__n)"),
"it takes callback delivery, whose `None` arm exists, and the present \
handle still moves:\n{rust}"
);
}
#[test]
fn an_owned_root_identity_moves_without_any_value_form() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_root_child_make() -> ZChild {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_root_child_maybe() -> Option<ZChild> {
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!(ZChild))
.fun(prebindgen_registry::fun!(z_root_child_make))
.fun(prebindgen_registry::fun!(z_root_child_maybe)),
)
.expand(prebindgen_registry::expand_return!(ZChild).field_self());
let dir = unique_test_dir("jnigen_vf_root_identity");
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");
assert!(
!rust.contains("&__cvsrc") && !rust.contains("&__inner"),
"an owned root is MOVED into its converter, not borrowed — the owning \
converter takes `ZChild`, not `&ZChild`:\n{rust}"
);
}
#[test]
fn a_per_field_override_must_name_the_field_s_own_type() {
let build = || {
let registry = crate::test_util::reg_from_items(declare_referenced(value_form_items()))
.expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZSample))
.class(crate::ptr_class!(ZKeyExpr))
.class(crate::ptr_class!(ZBytes))
.class(crate::data_class!(ZStamp))
.class(crate::data_class!(ZOrigin))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZKeyExpr)
.field(prebindgen_registry::fun!(z_keyexpr_as_str)),
)
.expand(prebindgen_registry::expand_return!(ZSample).fields(
prebindgen_registry::fields!(z_sample_to_struct).field(
"key_expr",
prebindgen_registry::expand_return!(ZBytes).field_self(),
),
));
let dir = unique_test_dir("jnigen_vf_ovr_ty");
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")));
};
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(build))
.expect_err("a mistyped override 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("key_expr"),
"the message names the field: {msg}"
);
assert!(
msg.contains("ZBytes") && msg.contains("ZKeyExpr"),
"the message names the declared type and the real one: {msg}"
);
}
#[test]
fn a_nested_value_form_is_hoisted_too() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZInnerStruct {
pub a: i64,
pub b: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOuterStruct {
pub inner: ZInner,
pub tag: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_inner_to_struct(i: &ZInner) -> ZInnerStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_outer_to_struct(o: &ZOuter) -> ZOuterStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_outer_sub(cb: impl Fn(ZOuter) + Send + Sync + 'static) {
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!(ZOuter))
.class(crate::ptr_class!(ZInner))
.fun(prebindgen_registry::fun!(z_outer_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZInner)
.fields(prebindgen_registry::fields!(z_inner_to_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZOuter)
.fields(prebindgen_registry::fields!(z_outer_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_nested");
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("inner__a: Long") && kotlin.contains("inner__b: Long"),
"the child value form's fields splice in, prefixed:\n{kotlin}"
);
for f in ["z_outer_to_struct", "z_inner_to_struct"] {
let calls = rust.matches(f).count();
assert_eq!(
calls, 1,
"`{f}` is bound to one local and every leaf below it reaches off \
that local; found {calls} calls in:\n{rust}"
);
}
}
#[test]
fn a_nested_consuming_value_form_moves_the_parent_s_field() {
let loc = myflat_loc();
let items = |outer_by_value: bool| -> Vec<(syn::Item, prebindgen::SourceLocation)> {
let outer: syn::Item = if outer_by_value {
syn::Item::Fn(syn::parse_quote!(
pub fn z_outer_into_struct(o: ZOuter) -> ZOuterStruct {
unimplemented!()
}
))
} else {
syn::Item::Fn(syn::parse_quote!(
pub fn z_outer_to_struct(o: &ZOuter) -> ZOuterStruct {
unimplemented!()
}
))
};
vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZInnerStruct {
pub a: i64,
pub b: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOuterStruct {
pub inner: ZInner,
pub tag: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_inner_into_struct(i: ZInner) -> ZInnerStruct {
unimplemented!()
}
)),
loc.clone(),
),
(outer, loc.clone()),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_outer_sub(cb: impl Fn(ZOuter) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
),
]
};
for (tag, outer_by_value, outer) in [
(
"borrow",
false,
prebindgen_registry::expand_return!(ZOuter)
.fields(prebindgen_registry::fields!(z_outer_to_struct)),
),
(
"consume",
true,
prebindgen_registry::expand_return!(ZOuter)
.fields_self_into(prebindgen_registry::fields!(z_outer_into_struct)),
),
] {
let registry = crate::test_util::reg_from_items(declare_referenced(items(outer_by_value)))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZOuter))
.class(crate::ptr_class!(ZInner))
.fun(prebindgen_registry::fun!(z_outer_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZInner)
.fields_self_into(prebindgen_registry::fields!(z_inner_into_struct)),
)
.expand(outer);
let dir = unique_test_dir(&format!("jnigen_vf_nested_consume_{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 = std::fs::read_to_string(gen.write_rust(dir.join("gen.rs")).expect("write_rust"))
.expect("read rust");
assert!(
rust.contains("z_inner_into_struct(__vf0.inner)"),
"[{tag}] the parent's field is MOVED into the nested form, not borrowed \
or cloned:\n{rust}"
);
assert!(
rust.contains("__vf0.tag") && !rust.contains("(&__vf0)"),
"[{tag}] and a sibling leaf still reads its own field off the parent local, \
projected directly — borrowing the partially-moved local as a whole would \
not compile:\n{rust}"
);
assert!(
!rust.contains("__vf1.a.clone()"),
"[{tag}] the nested form's own fields move out too:\n{rust}"
);
}
}
fn nested_review_items() -> Vec<(syn::Item, prebindgen::SourceLocation)> {
let loc = myflat_loc();
vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZReviewInnerStruct {
pub value: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZReviewOuterStruct {
pub optional: Option<ZReviewInner>,
pub items: Vec<ZReviewInner>,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_review_inner_to_struct(i: &ZReviewInner) -> ZReviewInnerStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_review_outer_to_struct(o: &ZReviewOuter) -> ZReviewOuterStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_review_outer_sub(cb: impl Fn(ZReviewOuter) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc,
),
]
}
fn nested_review_jni(outer: crate::ExpandReturnDecl) -> JniGenBuilder {
JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZReviewOuter))
.class(crate::ptr_class!(ZReviewInner))
.fun(prebindgen_registry::fun!(z_review_outer_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZReviewInner)
.fields(prebindgen_registry::fields!(z_review_inner_to_struct)),
)
.expand(outer)
}
#[test]
fn an_optional_nested_value_form_is_rejected_before_emission() {
let registry = crate::test_util::reg_from_items(declare_referenced(nested_review_items()))
.expect("index items");
let jni = nested_review_jni(
prebindgen_registry::expand_return!(ZReviewOuter)
.fields(prebindgen_registry::fields!(z_review_outer_to_struct)),
);
let err = match jni.build_with(registry) {
Ok(_) => panic!("an optional nested value form must be rejected"),
Err(e) => e,
};
let msg = err.to_string();
assert!(
msg.contains("z_review_inner_to_struct") && msg.contains("Option"),
"the error names the unsupported conditional hoist: {msg}"
);
}
#[test]
fn a_value_form_under_an_optional_accessor_is_hoisted_conditionally() {
let loc = myflat_loc();
let mut items = consuming_items();
items.extend([
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_get_carrier(h: &ZHolder) -> Option<&ZCarrier> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_sub(cb: impl Fn(ZHolder) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZHolder))
.fun(prebindgen_registry::fun!(zh_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZHolder)
.field(prebindgen_registry::fun!(zh_get_carrier)),
);
let dir = unique_test_dir("jnigen_vf_conditional");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni
.build_with(registry)
.expect("a conditional hoist resolves");
let rust = std::fs::read_to_string(gen.write_rust(dir.join("gen.rs")).expect("write_rust"))
.expect("read rust");
assert_eq!(
rust.matches("zc_into_struct").count(),
1,
"the value form runs ONCE, not once per leaf:\n{rust}"
);
assert!(
rust.contains("zh_get_carrier(&__cb_arg0)") && rust.contains(".map(|__hb0|"),
"the hoist is built only where the optional step has a value — as a \
`map`, since the equivalent `match` trips `clippy::manual_map` in the \
consumer:\n{rust}"
);
assert!(
rust.contains("zc_into_struct((__hb0).clone())"),
"what the arm binds is a borrow, so a consuming accessor gets a clone \
of it — the same trade a borrowed root makes:\n{rust}"
);
assert!(
rust.contains("match __vf0 {") && rust.contains("Some(__u0)"),
"and the leaves share ONE match on the local, taken by value so the \
struct's fields still move out:\n{rust}"
);
assert!(
rust.contains("__u0.label") && !rust.contains("__u0.label.clone()"),
"each leaf reads its own field off the arm binding, moved not cloned:\n{rust}"
);
assert!(
rust.contains("JObject::null()"),
"the absent arm fills every slot with the wire default:\n{rust}"
);
}
fn conditional_owned_gen(tag: &str, decl: crate::ExpandReturnDecl) -> String {
let loc = myflat_loc();
let mut items = consuming_items();
items.extend([
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_take_carrier(h: &ZHolder) -> Option<ZCarrier> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_sub(cb: impl Fn(ZHolder) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZHolder))
.fun(prebindgen_registry::fun!(zh_sub)),
)
.expand(decl)
.expand(
prebindgen_registry::expand_return!(ZHolder)
.field(prebindgen_registry::fun!(zh_take_carrier)),
);
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");
std::fs::read_to_string(gen.write_rust(dir.join("gen.rs")).expect("write_rust"))
.expect("read rust")
}
#[test]
fn an_owned_optional_payload_is_borrowed_for_the_steps_after_it() {
let loc = myflat_loc();
let mut items = consuming_items();
items.extend([
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_child(h: &ZHolder) -> Option<ZChild> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zchild_carrier(c: &ZChild) -> &ZCarrier {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_sub(cb: impl Fn(ZHolder) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZChild))
.class(crate::ptr_class!(ZHolder))
.fun(prebindgen_registry::fun!(zh_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields(prebindgen_registry::fields!(zc_to_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZChild)
.field(prebindgen_registry::fun!(zchild_carrier)),
)
.expand(
prebindgen_registry::expand_return!(ZHolder).field(prebindgen_registry::fun!(zh_child)),
);
let dir = unique_test_dir("jnigen_vf_cond_owned_chain");
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");
assert!(
rust.contains("zchild_carrier(&__hb0)"),
"the step after an owned payload BORROWS it — passing the bare value \
would hand `T` to an accessor typed for `&T`:\n{rust}"
);
}
#[test]
fn a_rebased_hoist_projects_its_leading_fields_past_a_sibling_move() {
let loc = myflat_loc();
let mut items = consuming_items();
items.extend([
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZOuterStruct {
pub direct: ZCarrier,
pub wrapper: ZWrapper,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zo_into_struct(o: ZOuter) -> ZOuterStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zw_carrier(w: &ZWrapper) -> ZCarrier {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zo_sub(cb: impl Fn(ZOuter) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZWrapper))
.class(crate::ptr_class!(ZOuter))
.fun(prebindgen_registry::fun!(zo_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZWrapper)
.field(prebindgen_registry::fun!(zw_carrier)),
)
.expand(
prebindgen_registry::expand_return!(ZOuter)
.fields_self_into(prebindgen_registry::fields!(zo_into_struct)),
);
let dir = unique_test_dir("jnigen_vf_sibling_move");
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");
assert!(
rust.contains("zc_into_struct(__vf0.direct)"),
"the first sibling still MOVES its field out of the parent:\n{rust}"
);
assert!(
rust.contains("zw_carrier(&__vf0.wrapper)"),
"and the second projects its own field directly — a disjoint borrow \
that survives that move:\n{rust}"
);
assert!(
!rust.contains("&(&__vf0)"),
"borrowing the partially moved parent as a whole is what E0382 rejects:\n{rust}"
);
}
#[test]
fn a_consuming_value_form_keeps_its_by_value_boundary_behind_accessors() {
let loc = myflat_loc();
let mut items = consuming_items();
items.extend([
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_carrier(h: &ZHolder) -> ZCarrier {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_sub(cb: impl Fn(ZHolder) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZHolder))
.fun(prebindgen_registry::fun!(zh_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZHolder)
.field(prebindgen_registry::fun!(zh_carrier)),
);
let dir = unique_test_dir("jnigen_vf_consume_behind_acc");
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");
assert!(
rust.contains("zc_into_struct(myflat::zh_carrier(&__cb_arg0))"),
"the accessor in front borrows its receiver, and its owned result MOVES \
into the by-value value form — neither boundary decides the other:\n{rust}"
);
}
#[test]
fn an_owned_intermediate_result_is_borrowed_for_the_next_step() {
let loc = myflat_loc();
let mut items = consuming_items();
items.extend([
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_child(h: &ZHolder) -> Option<ZChild> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zchild_middle(c: &ZChild) -> ZMiddle {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zmiddle_carrier(m: &ZMiddle) -> &ZCarrier {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_sub(cb: impl Fn(ZHolder) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZMiddle))
.class(crate::ptr_class!(ZChild))
.class(crate::ptr_class!(ZHolder))
.fun(prebindgen_registry::fun!(zh_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields(prebindgen_registry::fields!(zc_to_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZMiddle)
.field(prebindgen_registry::fun!(zmiddle_carrier)),
)
.expand(
prebindgen_registry::expand_return!(ZChild)
.field(prebindgen_registry::fun!(zchild_middle)),
)
.expand(
prebindgen_registry::expand_return!(ZHolder).field(prebindgen_registry::fun!(zh_child)),
);
let dir = unique_test_dir("jnigen_vf_cond_owned_middle");
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");
assert!(
rust.contains("zmiddle_carrier(&myflat::zchild_middle(&__hb0))"),
"an owned INTERMEDIATE result is borrowed for the call that follows \
it, exactly as the optional payload is:\n{rust}"
);
}
#[test]
fn an_owned_optional_payload_is_borrowed_for_a_borrowing_value_form() {
let rust = conditional_owned_gen(
"jnigen_vf_cond_owned_borrow",
prebindgen_registry::expand_return!(ZCarrier)
.fields(prebindgen_registry::fields!(zc_to_struct)),
);
assert!(
rust.contains("zc_to_struct(&__hb0)"),
"an owned `Option<T>` payload is BORROWED for a `&Self` accessor — \
passing it through would supply `T` where `&T` is required:\n{rust}"
);
}
#[test]
fn an_owned_optional_payload_is_moved_into_a_consuming_value_form() {
let rust = conditional_owned_gen(
"jnigen_vf_cond_owned_consume",
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
);
assert!(
rust.contains("zc_into_struct(__hb0)"),
"an owned payload MOVES into a by-value accessor:\n{rust}"
);
assert!(
!rust.contains("zc_into_struct((__hb0).clone())"),
"cloning it would demand a `Clone` the type need not have, on a value \
that was already ours:\n{rust}"
);
}
#[test]
fn a_sum_field_of_a_conditional_value_form_stays_inside_the_arm() {
let loc = myflat_loc();
let items = vec![
(
syn::Item::Enum(syn::parse_quote!(
pub enum ZOutcome {
Empty,
Failed(String),
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZCarrierStruct {
pub outcome: ZOutcome,
pub count: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zc_into_struct(c: ZCarrier) -> ZCarrierStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_get_carrier(h: &ZHolder) -> Option<&ZCarrier> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zh_sub(cb: impl Fn(ZHolder) + Send + Sync + 'static) {
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!(ZCarrier))
.class(crate::ptr_class!(ZHolder))
.class(crate::sealed_class!(ZOutcome))
.fun(prebindgen_registry::fun!(zh_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
)
.expand(
prebindgen_registry::expand_return!(ZHolder)
.field(prebindgen_registry::fun!(zh_get_carrier)),
);
let dir = unique_test_dir("jnigen_vf_conditional_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("gen.rs")).expect("write_rust"))
.expect("read rust");
let arm = rust
.split_once("Some(__u0)")
.expect("the conditional arm is emitted")
.1;
assert!(
arm.contains("__u0.outcome"),
"the sum's `match` is emitted INSIDE the arm that binds `__u0`, after \
it exists:\n{rust}"
);
assert!(
!rust
.split_once("Some(__u0)")
.expect("the conditional arm is emitted")
.0
.contains("__u0."),
"and nothing reaches the binding before the arm introduces it:\n{rust}"
);
assert!(
arm.contains("ZOutcome::Failed"),
"the variant arms are the sum's own, unchanged by being nested:\n{rust}"
);
assert!(
rust.contains("__u0.count"),
"the ordinary sibling leaf still rides the same arm:\n{rust}"
);
}
#[test]
fn a_vec_field_override_must_name_the_whole_vec_type() {
let build = || {
let registry = crate::test_util::reg_from_items(declare_referenced(nested_review_items()))
.expect("index items");
let jni = nested_review_jni(prebindgen_registry::expand_return!(ZReviewOuter).fields(
prebindgen_registry::fields!(z_review_outer_to_struct).field(
"items",
prebindgen_registry::expand_return!(ZReviewInner).field_self(),
),
));
let _ = jni.build_with(registry);
};
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(build))
.expect_err("an element-typed override on a Vec field 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("items") && msg.contains("Vec") && msg.contains("ZReviewInner"),
"the error names the field, its whole Vec type, and the declared element type: {msg}"
);
}
fn consuming_items() -> Vec<(syn::Item, prebindgen::SourceLocation)> {
let loc = myflat_loc();
vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZCarrierStruct {
pub label: String,
pub count: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zc_into_struct(c: ZCarrier) -> ZCarrierStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zc_to_struct(c: &ZCarrier) -> ZCarrierStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn zc_sub(cb: impl Fn(ZCarrier) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc,
),
]
}
fn consuming_gen(tag: &str, decl: crate::ExpandReturnDecl) -> String {
let registry = crate::test_util::reg_from_items(declare_referenced(consuming_items()))
.expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZCarrier))
.fun(prebindgen_registry::fun!(zc_sub)),
)
.expand(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");
std::fs::read_to_string(gen.write_rust(dir.join("gen.rs")).expect("write_rust"))
.expect("read rust")
}
#[test]
fn a_consuming_value_form_moves_its_fields() {
let rust = consuming_gen(
"jnigen_vf_consume",
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
);
assert!(
rust.contains("zc_into_struct(__cb_arg0)"),
"the value is passed BY MOVE, not borrowed:\n{rust}"
);
assert!(
rust.contains("__vf0.label") && rust.contains("__vf0.count"),
"each field is read off the one hoisted local:\n{rust}"
);
assert!(
!rust.contains("__vf0.label.clone()") && !rust.contains("__vf0.count.clone()"),
"and MOVED out of it — a consuming form exists precisely to drop these \
clones:\n{rust}"
);
}
#[test]
fn the_borrowing_value_form_still_clones() {
let rust = consuming_gen(
"jnigen_vf_borrow",
prebindgen_registry::expand_return!(ZCarrier)
.fields(prebindgen_registry::fields!(zc_to_struct)),
);
assert!(
rust.contains("zc_to_struct(&__cb_arg0)"),
"a `&T` accessor is still handed a borrow:\n{rust}"
);
assert!(
rust.contains("__vf0.label.clone()"),
"and its fields are still cloned out:\n{rust}"
);
}
#[test]
fn a_borrowed_plan_clones_before_consuming() {
let loc = myflat_loc();
let mut items = consuming_items();
items.push((
syn::Item::Fn(syn::parse_quote!(
pub fn zc_borrowed(v: &ZVault) -> Option<&ZCarrier> {
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!(ZCarrier))
.class(crate::ptr_class!(ZVault))
.fun(prebindgen_registry::fun!(zc_sub))
.fun(prebindgen_registry::fun!(zc_borrowed)),
)
.expand(
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct)),
);
let dir = unique_test_dir("jnigen_vf_consume_ref");
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");
assert!(
rust.contains("zc_into_struct((__inner).clone())"),
"a borrowed plan clones the value, then consumes the clone:\n{rust}"
);
}
#[test]
#[should_panic(expected = "only record")]
fn a_consuming_value_form_rejects_a_following_sibling() {
let _ = prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct))
.field_self();
}
#[test]
#[should_panic(expected = "only record")]
fn a_consuming_value_form_rejects_a_preceding_sibling() {
let _ = prebindgen_registry::expand_return!(ZCarrier)
.field_self()
.fields_self_into(prebindgen_registry::fields!(zc_into_struct));
}
#[test]
#[should_panic(expected = "only record")]
fn a_consuming_value_form_rejects_a_plain_field_sibling() {
let _ = prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_into_struct))
.field(prebindgen_registry::fun!(zc_to_struct));
}
#[test]
fn the_declarator_and_the_accessor_s_receiver_must_agree() {
let build = |decl: crate::ExpandReturnDecl| -> String {
let registry =
crate::test_util::reg_from_items(declare_referenced(consuming_items())).expect("index");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZCarrier))
.fun(prebindgen_registry::fun!(zc_sub)),
)
.expand(decl);
match jni.build_with(registry) {
Ok(_) => String::new(),
Err(e) => e.to_string(),
}
};
let msg = build(
prebindgen_registry::expand_return!(ZCarrier)
.fields_self_into(prebindgen_registry::fields!(zc_to_struct)),
);
assert!(
msg.contains("CONSUMING") && msg.contains("zc_to_struct"),
"`.fields_self_into` on a borrowing accessor must be refused, naming it: {msg:?}"
);
let msg = build(
prebindgen_registry::expand_return!(ZCarrier)
.fields(prebindgen_registry::fields!(zc_into_struct)),
);
assert!(
msg.contains("BORROWING") && msg.contains("zc_into_struct"),
"`.fields` on a by-value accessor must be refused, naming it: {msg:?}"
);
}
#[test]
fn a_whole_value_crossing_ignores_how_rust_spells_it() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| -> String {
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZStamp {
pub secs: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSampleStruct {
pub stamp: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_to_struct(s: &ZSample) -> ZSampleStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_sub(cb: impl Fn(ZSample) + 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!(ZSample))
.class(crate::data_class!(ZStamp))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_whole_boxed");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
std::fs::read_to_string(gen.write_rust(dir.join("g.rs")).expect("write_rust"))
.expect("read rust")
};
let plain = build(syn::parse_quote!(Option<ZStamp>));
let boxed = build(syn::parse_quote!(Box<Option<ZStamp>>));
let bc: String = boxed.split_whitespace().collect();
assert!(
bc.contains("v:Box<Option<myflat::ZStamp>>"),
"the converter takes the spelled type:\n{boxed}"
);
assert!(
bc.contains("letv:Option<myflat::ZStamp>="),
"the spelling is read as the canonical shape:\n{boxed}"
);
for (label, rust) in [("Option<T>", &plain), ("Box<Option<T>>", &boxed)] {
assert!(
rust.contains("ZStamp_to_JObject"),
"{label}: the field still crosses as its own converter:\n{rust}"
);
}
}
#[test]
fn an_owned_string_crosses_the_same_however_rust_spells_it() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| -> String {
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSampleStruct {
pub label: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_to_struct(s: &ZSample) -> ZSampleStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_sub(cb: impl Fn(ZSample) + 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!(ZSample))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_boxed_string");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let (rust, kotlin) = (
std::fs::read_to_string(gen.write_rust(dir.join("g.rs")).expect("write_rust"))
.expect("read rust"),
gen.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n"),
);
format!("{rust}\n// ---KOTLIN---\n{kotlin}")
};
for spelling in [
syn::parse_quote!(String),
syn::parse_quote!(Box<String>),
syn::parse_quote!(Cow<'static, str>),
] {
let out = build(spelling);
assert!(
out.contains("String"),
"every owned-string spelling crosses as a Kotlin String:\n{out}"
);
}
}
#[test]
fn a_transparent_wrapper_is_bridged_only_where_it_can_be() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| -> Result<String, String> {
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZSampleStruct {
pub f: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_to_struct(s: &ZSample) -> ZSampleStruct {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_sub(cb: impl Fn(ZSample) + 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!(ZSample))
.fun(prebindgen_registry::fun!(z_sample_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_struct)),
);
let dir = unique_test_dir("jnigen_vf_bridge");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
match jni.build_with(registry) {
Ok(g) => Ok(std::fs::read_to_string(
g.write_rust(dir.join("g.rs")).expect("write_rust"),
)
.expect("read rust")),
Err(e) => Err(format!("{e}")),
}
};
let one = build(syn::parse_quote!(Box<Option<String>>)).expect("a single box is bridgeable");
let oc: String = one.split_whitespace().collect();
assert!(
oc.contains("letv:Option<String>=*v;"),
"one layer, one dereference:\n{one}"
);
let two =
build(syn::parse_quote!(Box<Box<Option<String>>>)).expect("nested boxes are bridgeable");
let tc: String = two.split_whitespace().collect();
assert!(
tc.contains("letv:Option<String>=**v;"),
"two layers, two dereferences:\n{two}"
);
let cow = build(syn::parse_quote!(Cow<'static, Option<String>>))
.expect_err("a Cow payload cannot be moved out, so it must not resolve");
assert!(
cow.contains("could not be resolved") && cow.contains("Cow"),
"the refusal names the unsupported representation: {cow}"
);
}
#[test]
fn an_erased_wrapper_over_a_terminal_crosses_both_ways() {
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::Struct(syn::parse_quote!(
pub struct Leaf {
pub v: i64,
}
)),
loc.clone(),
),
(
syn::Item::Struct(syn::parse_quote!(
pub struct Wrapped {
pub boxed_enum: Box<Priority>,
pub plain_enum: Priority,
pub boxed_handle: Box<ZSample>,
pub boxed_data: Box<Leaf>,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sample_to_wrapped(s: &ZSample) -> Wrapped {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_wrapped_take(w: Wrapped) {
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!(ZSample))
.class(crate::enum_class!(Priority))
.class(crate::data_class!(Leaf))
.class(crate::data_class!(Wrapped))
.fun(prebindgen_registry::fun!(z_wrapped_take)),
)
.expand(
prebindgen_registry::expand_return!(ZSample)
.fields(prebindgen_registry::fields!(z_sample_to_wrapped)),
);
let dir = unique_test_dir("jnigen_terminal_bridge");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni
.build_with(registry)
.expect("an erased wrapper over a terminal resolves in both directions");
let rust =
std::fs::read_to_string(gen.write_rust(dir.join("g.rs")).expect("write_rust")).unwrap();
let rc: String = rust.split_whitespace().collect();
for kind in ["Priority", "ZSample", "Leaf"] {
assert!(
rc.contains(&format!("Box_{kind}_to_")),
"`Box<{kind}>` needs an OUTBOUND converter:\n{rust}"
);
assert!(
rc.contains(&format!("_to_Box_{kind}_")),
"`Box<{kind}>` needs an inbound converter:\n{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");
let kc: String = kotlin.split_whitespace().collect();
assert!(
kc.contains("valboxedEnum:Priority") && kc.contains("valplainEnum:Priority"),
"a wrapped enum presents as the enum class, exactly as the bare one does:\n{kotlin}"
);
}
#[test]
fn a_wrapped_borrow_has_nothing_to_bridge_and_refuses() {
let loc = myflat_loc();
let build = |param_ty: syn::Type| -> Result<String, String> {
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
pub v: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_take(t: #param_ty) -> i64 {
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))
.fun(prebindgen_registry::fun!(z_take)),
);
let dir = unique_test_dir("jnigen_wrapped_borrow");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
match jni.build_with(registry) {
Ok(g) => Ok(std::fs::read_to_string(
g.write_rust(dir.join("g.rs")).expect("write_rust"),
)
.expect("read rust")),
Err(e) => Err(format!("{e}")),
}
};
let borrowed = build(syn::parse_quote!(&ZThing)).expect("a plain borrow resolves");
assert!(
borrowed.contains("myflat::z_take(&t)"),
"the call site adds the borrow:\n{borrowed}"
);
let opt = build(syn::parse_quote!(Option<&ZThing>)).expect("an optional borrow resolves");
assert!(
opt.contains("myflat::z_take(t.as_deref())"),
"the call site derefs the OwnedObject:\n{opt}"
);
for spelling in [
syn::parse_quote!(Box<&ZThing>),
syn::parse_quote!(Box<Option<&ZThing>>),
] {
let err = build(spelling).expect_err("a wrapped borrow must not resolve");
assert!(
err.contains("could not be resolved"),
"the refusal names the type: {err}"
);
}
}
#[test]
fn nullability_ignores_how_rust_spells_the_optional() {
let loc = myflat_loc();
let build = |field_ty: syn::Type| -> String {
let items = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZRec {
pub note: #field_ty,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_rec_emit(cb: impl Fn(ZRec) + 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::data_class!(ZRec))
.fun(prebindgen_registry::fun!(z_rec_emit)),
);
let dir = unique_test_dir("jnigen_nullability");
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_rust(dir.join("g.rs")).expect("write_rust");
gen.write_kotlin(&dir.join("kotlin"))
.expect("write_kotlin")
.iter()
.map(|p| std::fs::read_to_string(p).unwrap())
.collect::<Vec<_>>()
.join("\n")
};
let plain = build(syn::parse_quote!(Option<String>));
let boxed = build(syn::parse_quote!(Box<Option<String>>));
for (label, kotlin) in [("Option<T>", &plain), ("Box<Option<T>>", &boxed)] {
assert!(
kotlin.contains("note: String?"),
"{label}: an optional field is nullable in Kotlin:\n{kotlin}"
);
}
assert_eq!(
plain, boxed,
"a transparent wrapper must not change the Kotlin surface"
);
}