use crate::backends::java::type_map::{java_boxed_type, java_type};
use crate::codegen::mut_writeback;
use crate::codegen::naming::to_java_name;
use crate::core::ir::{FunctionDef, TypeRef};
use ahash::AHashSet;
use std::collections::HashSet;
use super::super::helpers::is_bridge_param_java;
pub(super) fn gen_async_wrapper_method(
out: &mut String,
func: &FunctionDef,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
opaque_types: &AHashSet<String>,
) {
let params: Vec<String> = func
.params
.iter()
.filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
.map(|p| {
let ptype = java_type(&p.ty);
format!("final {} {}", ptype, to_java_name(&p.name))
})
.collect();
let writeback = mut_writeback::writeback_param(&func.params, &func.return_type, opaque_types);
let return_type = match writeback {
Some(wb) => java_boxed_type(&wb.ty).to_string(),
None => match &func.return_type {
TypeRef::Unit => "Void".to_string(),
other => java_boxed_type(other).to_string(),
},
};
let sync_method_name = to_java_name(&func.name);
let async_method_name = format!("{}Async", sync_method_name);
let param_names: Vec<String> = func
.params
.iter()
.filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
.map(|p| to_java_name(&p.name))
.collect();
out.push_str(&crate::backends::java::template_env::render(
"ffi_async_method_signature.jinja",
minijinja::context! {
return_type => &return_type,
async_method_name => &async_method_name,
params => params.join(", "),
},
));
out.push_str(" return CompletableFuture.supplyAsync(() -> {\n");
out.push_str(" try {\n");
if writeback.is_none() && matches!(func.return_type, TypeRef::Unit) {
out.push_str(" ");
out.push_str(&sync_method_name);
out.push('(');
out.push_str(¶m_names.join(", "));
out.push_str(");\n");
out.push_str(" return null;\n");
} else {
out.push_str(" return ");
out.push_str(&sync_method_name);
out.push('(');
out.push_str(¶m_names.join(", "));
out.push_str(");\n");
}
out.push_str(" } catch (Throwable e) {\n");
out.push_str(" throw new CompletionException(e);\n");
out.push_str(" }\n");
out.push_str(" });\n");
out.push_str(" }\n");
}
#[cfg(test)]
mod tests {
use super::gen_async_wrapper_method;
use crate::core::ir::{FunctionDef, ParamDef, TypeRef};
use ahash::AHashSet;
use std::collections::HashSet;
fn record_param(is_ref: bool, is_mut: bool) -> ParamDef {
ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref,
is_mut,
..ParamDef::default()
}
}
fn render(func: &FunctionDef, opaque_types: &AHashSet<String>) -> String {
let mut out = String::new();
let bridge_param_names = HashSet::new();
let bridge_type_aliases = HashSet::new();
gen_async_wrapper_method(&mut out, func, &bridge_param_names, &bridge_type_aliases, opaque_types);
out
}
#[test]
fn java_async_overload_mut_dto_param_returns_the_updated_value() {
let func = FunctionDef {
name: "tag_record_async".to_owned(),
rust_path: "sample_core::tag_record_async".to_owned(),
params: vec![record_param(true, true)],
return_type: TypeRef::Unit,
is_async: true,
error_type: None,
..FunctionDef::default()
};
let output = render(&func, &AHashSet::new());
assert!(
output.contains("CompletableFuture<Record>"),
"expected the async overload to resolve with the mutated DTO type instead of Void:\n{output}"
);
assert!(
!output.contains("CompletableFuture<Void>"),
"must not still advertise a Void future:\n{output}"
);
assert!(
output.contains("return tagRecordAsync(record);"),
"expected the body to return the sync method's mutated result:\n{output}"
);
assert!(
!output.contains("return null;"),
"must not discard the mutated value behind `return null;`:\n{output}"
);
}
#[test]
fn java_async_overload_immutable_dto_param_keeps_void_future() {
let func = FunctionDef {
name: "read_record_async".to_owned(),
rust_path: "sample_core::read_record_async".to_owned(),
params: vec![record_param(true, false)],
return_type: TypeRef::Unit,
is_async: true,
error_type: None,
..FunctionDef::default()
};
let output = render(&func, &AHashSet::new());
assert!(
output.contains("CompletableFuture<Void>"),
"immutable borrow must keep a Void future:\n{output}"
);
assert!(
output.contains("return null;"),
"immutable borrow must not gain a write-back return:\n{output}"
);
}
#[test]
fn java_async_overload_owned_dto_param_unaffected_by_writeback() {
let func = FunctionDef {
name: "consume_record_async".to_owned(),
rust_path: "sample_core::consume_record_async".to_owned(),
params: vec![record_param(false, false)],
return_type: TypeRef::Unit,
is_async: true,
error_type: None,
..FunctionDef::default()
};
let output = render(&func, &AHashSet::new());
assert!(
output.contains("CompletableFuture<Void>"),
"owned param must keep a Void future:\n{output}"
);
assert!(
output.contains("return null;"),
"owned param must not gain a write-back return:\n{output}"
);
}
#[test]
fn java_async_overload_non_unit_return_unaffected_by_writeback() {
let func = FunctionDef {
name: "make_record_async".to_owned(),
rust_path: "sample_core::make_record_async".to_owned(),
params: vec![],
return_type: TypeRef::Named("Record".to_owned()),
is_async: true,
error_type: None,
..FunctionDef::default()
};
let output = render(&func, &AHashSet::new());
assert!(
output.contains("CompletableFuture<Record>"),
"expected the pre-existing non-unit return type to keep flowing through:\n{output}"
);
assert!(
output.contains("return makeRecordAsync();"),
"expected the body to return the sync method's result unmodified:\n{output}"
);
}
}