use super::errors::{emit_return_marshalling_indented, emit_return_statement, emit_return_statement_indented};
use super::functions::{is_bytes_result_func, is_bytes_result_method};
use super::{
StreamingMethodMeta, emit_named_param_setup, emit_named_param_teardown, emit_named_param_teardown_indented,
is_bridge_param, native_call_arg, needs_param_teardown, returns_ptr,
};
use crate::backends::csharp::type_map::csharp_type;
use crate::codegen::doc_emission;
use crate::codegen::generators::trait_bridge::find_bridge_field;
use crate::codegen::naming::{csharp_type_name, to_csharp_name};
use crate::core::config::AdapterConfig;
use crate::core::ir::{ApiSurface, FunctionDef, MethodDef, TypeRef};
use heck::ToLowerCamelCase;
use std::collections::{HashMap, HashSet};
fn should_skip_ffi_method(func: &FunctionDef) -> bool {
let name = &func.name;
if name.ends_with("_is_valid") || name == "is_valid" {
return true;
}
if name.ends_with("_default") || name == "default" {
return true;
}
false
}
fn sanitize_doc_for_csharp(doc: &str) -> String {
doc.lines()
.filter_map(|line| {
if line.trim().starts_with("use ") && line.contains("::") {
return None;
}
Some(line.to_string())
})
.collect::<Vec<_>>()
.join("\n")
}
fn gen_opaque_streaming_static_wrapper(
method: &MethodDef,
opaque_type_name: &str,
meta: &StreamingMethodMeta,
_exception_name: &str,
) -> String {
use crate::backends::csharp::template_env::render;
let mut out = String::new();
let class_name = csharp_type_name(opaque_type_name);
let method_name = to_csharp_name(&method.name);
let item_type = csharp_type_name(&meta.item_type);
let method_name = if method.is_async {
format!("{method_name}Async")
} else {
method_name
};
let params_decl = method
.params
.iter()
.map(|param| {
let param_name = param.name.to_lower_camel_case();
let param_type = csharp_type(¶m.ty);
format!("{param_type} {param_name}")
})
.collect::<Vec<_>>()
.join(", ");
let args = method
.params
.iter()
.map(|param| param.name.to_lower_camel_case())
.collect::<Vec<_>>()
.join(", ");
let doc_lines: Vec<String> = method.doc.lines().map(str::to_owned).collect();
let param_docs: Vec<String> = method
.params
.iter()
.map(|param| param.name.to_lower_camel_case())
.collect();
out.push_str(&render(
"opaque_streaming_static_wrapper.jinja",
minijinja::context! {
doc_lines,
opaque_type_name,
param_docs,
is_async => method.is_async,
item_type,
method_name,
class_name,
params_decl,
args,
},
));
out
}
fn gen_adapter_wrapper(adapter: &AdapterConfig, _prefix: &str, _exception_name: &str, _api: &ApiSurface) -> String {
use crate::backends::csharp::template_env::render;
let adapter_name = &adapter.name;
let method_name = to_csharp_name(adapter_name);
let Some(owner_type) = adapter.owner_type.as_deref() else {
return String::new();
};
let Some(item_type) = adapter.item_type.as_deref() else {
return String::new();
};
let cs_item_type = csharp_type_name(item_type);
let owner_cs_name = csharp_type_name(owner_type);
let mut param_parts = vec!["IntPtr engine".to_string()];
for param in &adapter.params {
let param_name = param.name.to_lower_camel_case();
let param_type = if param.ty.contains("::") {
let parts: Vec<&str> = param.ty.split("::").collect();
csharp_type_name(parts.last().unwrap_or(&"object"))
} else {
csharp_type_name(¶m.ty)
};
param_parts.push(format!("{param_type} {param_name}"));
}
let params_decl = param_parts.join(", ");
let mut setup_lines = Vec::new();
for param in &adapter.params {
let param_name = param.name.to_lower_camel_case();
let param_type_pascal = to_csharp_name(param.ty.split("::").last().unwrap_or(""));
setup_lines.push(format!(
" var {param_name}Json = JsonSerializer.Serialize({param_name}, JsonSerializationOptions);"
));
setup_lines.push(format!(
" var {param_name}Handle = NativeMethods.{param_type_pascal}FromJson({param_name}Json);"
));
}
let setup_code = if setup_lines.is_empty() {
String::new()
} else {
format!("{}\n", setup_lines.join("\n"))
};
let mut native_args = vec!["engine".to_string()];
for param in &adapter.params {
let param_name = param.name.to_lower_camel_case();
native_args.push(format!("{param_name}Handle"));
}
let native_args_str = native_args.join(", ");
let mut cleanup_lines = Vec::new();
for param in &adapter.params {
let param_name = param.name.to_lower_camel_case();
let param_type_pascal = to_csharp_name(param.ty.split("::").last().unwrap_or(""));
cleanup_lines.push(format!(
" NativeMethods.{param_type_pascal}Free({param_name}Handle);"
));
}
let cleanup_code = cleanup_lines.join("\n");
let adapter_cs_name = to_csharp_name(adapter_name);
let start_method = format!("{owner_cs_name}{adapter_cs_name}Start");
let next_method = format!("{owner_cs_name}{adapter_cs_name}Next");
let free_method = format!("{owner_cs_name}{adapter_cs_name}Free");
render(
"streaming_adapter_wrapper.jinja",
minijinja::context! {
item_type => cs_item_type,
method_name,
params_decl,
setup_code,
start_method,
native_args => native_args_str,
next_method,
free_method,
cleanup_code,
},
)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn gen_wrapper_class(
api: &ApiSurface,
namespace: &str,
class_name: &str,
exception_name: &str,
prefix: &str,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
has_visitor_callbacks: bool,
streaming_methods: &HashSet<String>,
_streaming_methods_meta: &HashMap<String, StreamingMethodMeta>,
exclude_functions: &HashSet<String>,
trait_bridges: &[crate::core::config::TraitBridgeConfig],
_all_opaque_type_names: &HashSet<String>,
adapters: &[AdapterConfig],
) -> String {
use crate::backends::csharp::template_env::render;
use minijinja::Value;
let has_async =
api.functions.iter().any(|f| f.is_async) || api.types.iter().flat_map(|t| t.methods.iter()).any(|m| m.is_async);
let mut out = render(
"wrapper_class_header.jinja",
Value::from_serialize(serde_json::json!({
"namespace": namespace,
"class_name": class_name,
"has_async": has_async,
})),
);
out.push('\n');
let enum_names: HashSet<String> = api.enums.iter().map(|e| csharp_type_name(&e.name)).collect();
let true_opaque_types: HashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.clone())
.collect();
let handle_returned_types = super::errors::compute_handle_returned_types(api);
for func in api.functions.iter().filter(|f| {
!exclude_functions.contains(&f.name)
&& !should_skip_ffi_method(f)
&& !crate::codegen::generators::trait_bridge::is_trait_bridge_managed_fn(&f.name, trait_bridges)
}) {
let bridge_field = find_bridge_field(func, &api.types, trait_bridges);
if let Some(bm) = bridge_field {
out.push_str(&gen_bridge_field_wrapper_function(
func,
&bm,
exception_name,
&enum_names,
&true_opaque_types,
&handle_returned_types,
));
} else {
out.push_str(&gen_wrapper_function(
func,
exception_name,
prefix,
&enum_names,
&true_opaque_types,
&handle_returned_types,
bridge_param_names,
bridge_type_aliases,
has_visitor_callbacks,
&api.types,
));
}
}
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if typ.is_opaque {
continue;
}
for method in &typ.methods {
if streaming_methods.contains(&method.name) {
continue;
}
if method.name == "is_valid" || method.name == "default" {
continue;
}
out.push_str(&gen_wrapper_method(
method,
exception_name,
prefix,
&typ.name,
&enum_names,
&true_opaque_types,
&handle_returned_types,
bridge_param_names,
bridge_type_aliases,
&api.types,
));
}
}
for typ in api.types.iter().filter(|typ| typ.is_opaque) {
for method in &typ.methods {
if !streaming_methods.contains(&method.name) {
continue;
}
if let Some(meta) = _streaming_methods_meta.get(&method.name) {
out.push_str(&gen_opaque_streaming_static_wrapper(
method,
&typ.name,
meta,
exception_name,
));
}
}
}
for adapter in adapters {
if matches!(adapter.pattern, crate::core::config::AdapterPattern::Streaming) {
out.push_str(&gen_adapter_wrapper(adapter, prefix, exception_name, api));
}
}
for bridge_cfg in trait_bridges {
let trait_pascal = csharp_type_name(&bridge_cfg.trait_name);
let has_super = bridge_cfg.super_trait.is_some();
let register_method_name = format!("Register{trait_pascal}");
out.push_str(&render(
"trait_register_facade.jinja",
minijinja::context! {
trait_name => trait_pascal,
method_name => register_method_name,
has_super,
exception_name,
},
));
if bridge_cfg.unregister_fn.is_some() {
let unregister_method_name = format!("Unregister{trait_pascal}");
out.push_str(&render(
"trait_unregister_facade.jinja",
minijinja::context! {
trait_name => trait_pascal,
method_name => unregister_method_name,
exception_name,
},
));
}
}
for bridge_cfg in trait_bridges {
if let Some(clear_fn) = &bridge_cfg.clear_fn {
let trait_pascal = csharp_type_name(&bridge_cfg.trait_name);
let clear_method_name = to_csharp_name(clear_fn);
out.push_str(&render(
"trait_clear_facade.jinja",
minijinja::context! {
trait_name => trait_pascal,
method_name => clear_method_name,
},
));
}
}
let has_base_error = !api.errors.is_empty();
let (base_exception_class, has_invalid_input_variant, variant_dispatch_lines) = if has_base_error {
let base_error = &api.errors[0];
let base_ex = format!("{}Exception", base_error.name);
let has_invalid = base_error.variants.iter().any(|v| v.name == "InvalidInput");
let mut variants_with_prefix: Vec<(String, String)> = base_error
.variants
.iter()
.filter(|v| v.name != "InvalidInput")
.filter_map(|v| {
let template = v.message_template.as_deref()?;
let prefix_end = template.find('{').unwrap_or(template.len());
let prefix = template[..prefix_end].trim_end().to_string();
if prefix.is_empty() {
return None;
}
Some((format!("{}Exception", v.name), prefix))
})
.collect();
variants_with_prefix.sort_by_key(|item| std::cmp::Reverse(item.1.len()));
let dispatch_lines: Vec<String> = variants_with_prefix
.into_iter()
.map(|(class, prefix)| {
let escaped_prefix = prefix.replace('\\', "\\\\").replace('"', "\\\"");
format!(" if (message.StartsWith(\"{escaped_prefix}\")) return new {class}(message);")
})
.collect();
(base_ex, has_invalid, dispatch_lines)
} else {
(String::new(), false, Vec::new())
};
out.push_str(&render(
"error_helper_method.jinja",
Value::from_serialize(serde_json::json!({
"exception_name": exception_name,
"has_base_error": has_base_error,
"base_exception_class": base_exception_class,
"has_invalid_input_variant": has_invalid_input_variant,
"variant_dispatch_lines": variant_dispatch_lines,
})),
));
out.push_str("}\n");
out
}
#[allow(clippy::too_many_arguments)]
fn gen_wrapper_function(
func: &FunctionDef,
exception_name: &str,
_prefix: &str,
enum_names: &HashSet<String>,
true_opaque_types: &HashSet<String>,
handle_returned_types: &HashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
_has_visitor_callbacks: bool,
types: &[crate::core::ir::TypeDef],
) -> String {
use crate::backends::csharp::template_env::render;
let mut out = String::with_capacity(1024);
let visible_params: Vec<crate::core::ir::ParamDef> = func
.params
.iter()
.filter(|p| !is_bridge_param(p, bridge_param_names, bridge_type_aliases))
.cloned()
.collect();
doc_emission::emit_csharp_doc(&mut out, &func.doc, " ", exception_name);
for param in &visible_params {
if !func.doc.is_empty() {
let param_name = param.name.to_lower_camel_case();
let optional_text = if param.optional { "Optional." } else { "" };
out.push_str(&render(
"param_doc.jinja",
minijinja::context! { param_name, optional_text },
));
}
}
out.push_str(" public static ");
if func.is_async {
if func.return_type == TypeRef::Unit {
out.push_str("async Task");
} else {
let return_type = csharp_type(&func.return_type);
out.push_str(
render("async_task_return_type.jinja", minijinja::context! { return_type }).trim_end_matches('\n'),
);
}
} else if func.return_type == TypeRef::Unit {
out.push_str("void");
} else {
out.push_str(&csharp_type(&func.return_type));
}
out.push(' ');
let func_name = to_csharp_name(&func.name);
if func.is_async && !func_name.ends_with("Async") {
out.push_str(&func_name);
out.push_str("Async");
} else {
out.push_str(&func_name);
}
out.push('(');
for (i, param) in visible_params.iter().enumerate() {
let param_name = param.name.to_lower_camel_case();
let param_type = csharp_type(¶m.ty);
if param.optional && !param_type.ends_with('?') {
out.push_str(
render(
"param_decl_optional.jinja",
minijinja::context! { param_type, param_name },
)
.trim_end_matches('\n'),
);
} else {
out.push_str(
render(
"param_decl_required.jinja",
minijinja::context! { param_type, param_name },
)
.trim_end_matches('\n'),
);
}
if i < visible_params.len() - 1 {
out.push_str(", ");
}
}
out.push_str(")\n {\n");
for param in &visible_params {
let is_enum = matches!(¶m.ty, TypeRef::Named(n) if enum_names.contains(n.as_str()));
if !param.optional && !is_enum && matches!(param.ty, TypeRef::String | TypeRef::Named(_) | TypeRef::Bytes) {
let param_name = param.name.to_lower_camel_case();
out.push_str(&render("null_check.jinja", minijinja::context! { param_name }));
}
}
if is_bytes_result_func(func) {
let cs_native_name = to_csharp_name(&func.name);
emit_named_param_setup(
&mut out,
&visible_params,
" ",
true_opaque_types,
exception_name,
types,
enum_names,
);
let mut args_block = String::new();
for param in visible_params.iter() {
let param_name = param.name.to_lower_camel_case();
let arg = native_call_arg(¶m.ty, ¶m_name, param.optional, true_opaque_types);
args_block.push_str(&render(
"native_arg_line.jinja",
minijinja::context! { indent => " ", arg },
));
if matches!(param.ty, TypeRef::Bytes) {
args_block.push_str(&render(
"native_bytes_len_arg_line.jinja",
minijinja::context! { indent => " ", param_name },
));
}
}
let mut cleanup_block = String::new();
emit_named_param_teardown_indented(
&mut cleanup_block,
&visible_params,
" ",
true_opaque_types,
enum_names,
);
out.push_str(&render(
"bytes_result_call.jinja",
minijinja::context! {
native_method_name => &cs_native_name,
args_block => &args_block,
cleanup_block => &cleanup_block,
},
));
out.push_str(" }\n\n");
return out;
}
emit_named_param_setup(
&mut out,
&visible_params,
" ",
true_opaque_types,
exception_name,
types,
enum_names,
);
let cs_native_name = to_csharp_name(&func.name);
let needs_outer_try = needs_param_teardown(&visible_params, true_opaque_types, enum_names);
if func.is_async {
if needs_outer_try {
out.push_str(" try\n {\n");
}
if func.return_type == TypeRef::Unit {
out.push_str(" await Task.Run(() =>\n {\n");
} else {
out.push_str(" return await Task.Run(() =>\n {\n");
}
if func.return_type != TypeRef::Unit {
out.push_str(" var nativeResult = ");
} else {
out.push_str(" ");
}
out.push_str(
render(
"native_call_start.jinja",
minijinja::context! { method_name => &cs_native_name },
)
.trim_end_matches('\n'),
);
if visible_params.is_empty() {
out.push_str(");\n");
} else {
out.push('\n');
let mut arg_parts: Vec<String> = Vec::new();
for param in visible_params.iter() {
let param_name = param.name.to_lower_camel_case();
let arg = native_call_arg(¶m.ty, ¶m_name, param.optional, true_opaque_types);
arg_parts.push(arg.clone());
if matches!(param.ty, TypeRef::Bytes) {
arg_parts.push(format!("(UIntPtr){param_name}.Length"));
}
}
for (i, arg) in arg_parts.iter().enumerate() {
out.push_str(render("indented_arg_async.jinja", minijinja::context! { arg }).trim_end_matches('\n'));
if i < arg_parts.len() - 1 {
out.push(',');
}
out.push('\n');
}
out.push_str(" );\n");
}
if func.return_type != TypeRef::Unit && returns_ptr(&func.return_type) {
if matches!(func.return_type, TypeRef::Optional(_)) {
out.push_str(
" if (nativeResult == IntPtr.Zero)\n {\n return null;\n }\n",
);
} else {
out.push_str(
" if (nativeResult == IntPtr.Zero)\n {\n throw GetLastError();\n }\n",
);
}
} else if func.error_type.is_some() {
out.push_str(
" if (NativeMethods.LastErrorCode() != 0)\n {\n throw GetLastError();\n }\n",
);
}
emit_return_marshalling_indented(
&mut out,
&func.return_type,
" ",
enum_names,
true_opaque_types,
handle_returned_types,
);
emit_return_statement_indented(&mut out, &func.return_type, " ");
out.push_str(" });\n");
if needs_outer_try {
out.push_str(" }\n finally\n {\n");
emit_named_param_teardown_indented(
&mut out,
&visible_params,
" ",
true_opaque_types,
enum_names,
);
out.push_str(" }\n");
}
} else {
if needs_outer_try {
out.push_str(" try\n {\n");
}
if func.return_type != TypeRef::Unit {
out.push_str(" var nativeResult = ");
} else {
out.push_str(" ");
}
out.push_str(
render(
"native_call_start.jinja",
minijinja::context! { method_name => &cs_native_name },
)
.trim_end_matches('\n'),
);
if visible_params.is_empty() {
out.push_str(");\n");
} else {
out.push('\n');
let mut arg_parts: Vec<String> = Vec::new();
for param in visible_params.iter() {
let param_name = param.name.to_lower_camel_case();
let arg = native_call_arg(¶m.ty, ¶m_name, param.optional, true_opaque_types);
arg_parts.push(arg.clone());
if matches!(param.ty, TypeRef::Bytes) {
arg_parts.push(format!("(UIntPtr){param_name}.Length"));
}
}
for (i, arg) in arg_parts.iter().enumerate() {
out.push_str(render("indented_arg_sync.jinja", minijinja::context! { arg }).trim_end_matches('\n'));
if i < arg_parts.len() - 1 {
out.push(',');
}
out.push('\n');
}
out.push_str(" );\n");
}
let body_indent = if needs_outer_try { " " } else { " " };
if func.return_type != TypeRef::Unit && returns_ptr(&func.return_type) {
if matches!(func.return_type, TypeRef::Optional(_)) {
out.push_str(&render(
"null_result_return.jinja",
minijinja::context! { indent => body_indent },
));
} else {
out.push_str(&render(
"last_error_throw.jinja",
minijinja::context! { indent => body_indent },
));
}
} else if func.error_type.is_some() {
out.push_str(&render(
"last_error_throw.jinja",
minijinja::context! { indent => body_indent },
));
}
emit_return_marshalling_indented(
&mut out,
&func.return_type,
body_indent,
enum_names,
true_opaque_types,
handle_returned_types,
);
if needs_outer_try {
emit_return_statement_indented(&mut out, &func.return_type, body_indent);
out.push_str(" }\n finally\n {\n");
emit_named_param_teardown_indented(
&mut out,
&visible_params,
" ",
true_opaque_types,
enum_names,
);
out.push_str(" }\n");
} else {
emit_named_param_teardown(&mut out, &visible_params, true_opaque_types, enum_names);
emit_return_statement(&mut out, &func.return_type);
}
}
out.push_str(" }\n\n");
out
}
fn gen_bridge_field_wrapper_function(
func: &FunctionDef,
bridge_match: &crate::codegen::generators::trait_bridge::BridgeFieldMatch<'_>,
exception_name: &str,
_enum_names: &HashSet<String>,
_true_opaque_types: &HashSet<String>,
_handle_returned_types: &HashSet<String>,
) -> String {
use crate::backends::csharp::template_env::render;
let mut out = String::with_capacity(2048);
let visible_params: Vec<crate::core::ir::ParamDef> = func.params.to_vec();
doc_emission::emit_csharp_doc(&mut out, &func.doc, " ", exception_name);
for param in &visible_params {
if !func.doc.is_empty() {
let param_name = param.name.to_lower_camel_case();
let optional_text = if param.optional { "Optional." } else { "" };
out.push_str(&render(
"bridge_field_param_doc.jinja",
minijinja::context! { param_name, optional_text },
));
}
}
out.push_str(" public static ");
if func.is_async {
if func.return_type == TypeRef::Unit {
out.push_str("async Task");
} else {
let return_type = csharp_type(&func.return_type);
out.push_str(
render("async_task_generic.jinja", minijinja::context! { return_type }).trim_end_matches('\n'),
);
}
} else if func.return_type == TypeRef::Unit {
out.push_str("void");
} else {
out.push_str(&csharp_type(&func.return_type));
}
out.push(' ');
let func_name = to_csharp_name(&func.name);
if func.is_async && !func_name.ends_with("Async") {
out.push_str(&func_name);
out.push_str("Async");
} else {
out.push_str(&func_name);
}
out.push('(');
for (i, param) in visible_params.iter().enumerate() {
let param_name = param.name.to_lower_camel_case();
let param_type = csharp_type(¶m.ty);
if param.optional && !param_type.ends_with('?') {
out.push_str(
render(
"param_decl_inline_optional.jinja",
minijinja::context! { param_type, param_name },
)
.trim_end_matches('\n'),
);
} else {
out.push_str(
render(
"param_decl_inline_required.jinja",
minijinja::context! { param_type, param_name },
)
.trim_end_matches('\n'),
);
}
if i < visible_params.len() - 1 {
out.push_str(", ");
}
}
out.push_str(")\n {\n");
let options_param = &bridge_match.param_name;
let options_param_camel = options_param.to_lower_camel_case();
let field_name = &bridge_match.field_name;
let field_name_pascal = to_csharp_name(field_name);
let trait_pascal = csharp_type_name(&bridge_match.bridge.trait_name);
let options_pascal = csharp_type_name(&bridge_match.options_type);
let result_pascal = match &func.return_type {
TypeRef::Named(name) => csharp_type_name(name),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(name) => csharp_type_name(name),
_ => csharp_type(&func.return_type).into_owned(),
},
_ => csharp_type(&func.return_type).into_owned(),
};
out.push_str(&render(
"bridge_field_setup.jinja",
minijinja::context! {
field_name,
options_param_camel,
field_name_pascal,
options_pascal,
trait_pascal,
},
));
out.push_str(&render(
"bridge_field_register.jinja",
minijinja::context! { trait_pascal },
));
out.push_str(" if (bridgeHandle == IntPtr.Zero) throw GetLastError();\n");
out.push_str(" try\n {\n");
let cs_native_name = to_csharp_name(&func.name);
out.push_str(&render(
"bridge_field_inject.jinja",
minijinja::context! { options_pascal, field_name_pascal, options_param_camel },
));
if func.return_type != TypeRef::Unit {
out.push_str(" var nativeResult = ");
} else {
out.push_str(" ");
}
out.push_str(
render(
"native_call_start.jinja",
minijinja::context! { method_name => &cs_native_name },
)
.trim_end_matches('\n'),
);
let call_args: Vec<String> = func
.params
.iter()
.map(|p| {
if p.name == *options_param {
format!("{options_param_camel}Handle")
} else {
p.name.to_lower_camel_case().to_string()
}
})
.collect();
for (i, arg) in call_args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(arg);
}
out.push_str(");\n");
if func.return_type != TypeRef::Unit {
out.push_str(" if (nativeResult == IntPtr.Zero) throw GetLastError();\n");
}
if func.return_type != TypeRef::Unit {
out.push_str(&render(
"bridge_field_json_return.jinja",
minijinja::context! { indent => " ", result_pascal },
));
}
out.push_str(" }\n");
out.push_str(" finally\n");
out.push_str(" {\n");
out.push_str(&render(
"bridge_field_unregister.jinja",
minijinja::context! { trait_pascal },
));
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str(" else\n");
out.push_str(" {\n");
if func.return_type != TypeRef::Unit {
out.push_str(" var nativeResult = ");
} else {
out.push_str(" ");
}
out.push_str(
render(
"native_call_start.jinja",
minijinja::context! { method_name => &cs_native_name },
)
.trim_end_matches('\n'),
);
for (i, arg) in call_args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(arg);
}
out.push_str(");\n");
if func.return_type != TypeRef::Unit {
out.push_str(" if (nativeResult == IntPtr.Zero) throw GetLastError();\n");
out.push_str(&render(
"bridge_field_json_return.jinja",
minijinja::context! { indent => " ", result_pascal },
));
}
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str(" finally\n");
out.push_str(" {\n");
out.push_str(&render(
"bridge_field_free_options.jinja",
minijinja::context! { options_pascal, options_param_camel },
));
out.push_str(" }\n");
out.push_str(" }\n\n");
out
}
#[allow(clippy::too_many_arguments)]
fn gen_wrapper_method(
method: &MethodDef,
exception_name: &str,
_prefix: &str,
type_name: &str,
enum_names: &HashSet<String>,
true_opaque_types: &HashSet<String>,
handle_returned_types: &HashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
types: &[crate::core::ir::TypeDef],
) -> String {
use crate::backends::csharp::template_env::render;
let mut out = String::with_capacity(1024);
let visible_params: Vec<crate::core::ir::ParamDef> = method
.params
.iter()
.filter(|p| !is_bridge_param(p, bridge_param_names, bridge_type_aliases))
.cloned()
.collect();
let sanitized_doc = sanitize_doc_for_csharp(&method.doc);
doc_emission::emit_csharp_doc(&mut out, &sanitized_doc, " ", exception_name);
for param in &visible_params {
if !method.doc.is_empty() {
let param_name = param.name.to_lower_camel_case();
let optional_text = if param.optional { "Optional." } else { "" };
out.push_str(&render(
"param_doc.jinja",
minijinja::context! { param_name, optional_text },
));
}
}
out.push_str(" public static ");
if method.is_async {
if method.return_type == TypeRef::Unit {
out.push_str("async Task");
} else {
let return_type = csharp_type(&method.return_type);
out.push_str(
render("async_task_return_type.jinja", minijinja::context! { return_type }).trim_end_matches('\n'),
);
}
} else if method.return_type == TypeRef::Unit {
out.push_str("void");
} else {
out.push_str(&csharp_type(&method.return_type));
}
let method_name = to_csharp_name(&method.name);
let method_cs_name = if method.is_async && !method_name.ends_with("Async") {
format!("{}{}Async", type_name, method_name)
} else {
format!("{}{}", type_name, method_name)
};
out.push(' ');
out.push_str(&method_cs_name);
out.push('(');
let has_receiver = !method.is_static && method.receiver.is_some();
if has_receiver {
out.push_str("IntPtr handle");
if !visible_params.is_empty() {
out.push_str(", ");
}
}
for (i, param) in visible_params.iter().enumerate() {
let param_name = param.name.to_lower_camel_case();
let param_type = csharp_type(¶m.ty);
if param.optional && !param_type.ends_with('?') {
out.push_str(
render(
"param_decl_optional.jinja",
minijinja::context! { param_type, param_name },
)
.trim_end_matches('\n'),
);
} else {
out.push_str(
render(
"param_decl_required.jinja",
minijinja::context! { param_type, param_name },
)
.trim_end_matches('\n'),
);
}
if i < visible_params.len() - 1 {
out.push_str(", ");
}
}
out.push_str(")\n {\n");
for param in &visible_params {
let is_enum = matches!(¶m.ty, TypeRef::Named(n) if enum_names.contains(n.as_str()));
if !param.optional && !is_enum && matches!(param.ty, TypeRef::String | TypeRef::Named(_) | TypeRef::Bytes) {
let param_name = param.name.to_lower_camel_case();
out.push_str(&render("null_check.jinja", minijinja::context! { param_name }));
}
}
let cs_native_name = format!("{}{}", csharp_type_name(type_name), to_csharp_name(&method.name));
if is_bytes_result_method(method) {
emit_named_param_setup(
&mut out,
&visible_params,
" ",
true_opaque_types,
exception_name,
types,
enum_names,
);
let mut args_block = String::new();
if has_receiver {
args_block.push_str(&render(
"native_arg_line.jinja",
minijinja::context! { indent => " ", arg => "handle" },
));
}
for param in visible_params.iter() {
let param_name = param.name.to_lower_camel_case();
let arg = native_call_arg(¶m.ty, ¶m_name, param.optional, true_opaque_types);
args_block.push_str(&render(
"native_arg_line.jinja",
minijinja::context! { indent => " ", arg },
));
if matches!(param.ty, TypeRef::Bytes) {
args_block.push_str(&render(
"native_bytes_len_arg_line.jinja",
minijinja::context! { indent => " ", param_name },
));
}
}
let mut cleanup_block = String::new();
emit_named_param_teardown_indented(
&mut cleanup_block,
&visible_params,
" ",
true_opaque_types,
enum_names,
);
out.push_str(&render(
"bytes_result_call.jinja",
minijinja::context! {
native_method_name => &cs_native_name,
args_block => &args_block,
cleanup_block => &cleanup_block,
},
));
out.push_str(" }\n\n");
return out;
}
emit_named_param_setup(
&mut out,
&visible_params,
" ",
true_opaque_types,
exception_name,
types,
enum_names,
);
if method.is_async {
if method.return_type == TypeRef::Unit {
out.push_str(" await Task.Run(() =>\n {\n");
} else {
out.push_str(" return await Task.Run(() =>\n {\n");
}
if method.return_type != TypeRef::Unit {
out.push_str(" var nativeResult = ");
} else {
out.push_str(" ");
}
out.push_str(
render(
"native_call_start.jinja",
minijinja::context! { method_name => &cs_native_name },
)
.trim_end_matches('\n'),
);
if !has_receiver && visible_params.is_empty() {
out.push_str(");\n");
} else {
out.push('\n');
let mut arg_parts: Vec<String> = Vec::new();
if has_receiver {
arg_parts.push("handle".to_string());
}
for param in visible_params.iter() {
let param_name = param.name.to_lower_camel_case();
let arg = native_call_arg(¶m.ty, ¶m_name, param.optional, true_opaque_types);
arg_parts.push(arg.clone());
if matches!(param.ty, TypeRef::Bytes) {
arg_parts.push(format!("(UIntPtr){param_name}.Length"));
}
}
for (i, arg) in arg_parts.iter().enumerate() {
out.push_str(render("indented_arg_async.jinja", minijinja::context! { arg }).trim_end_matches('\n'));
if i < arg_parts.len() - 1 {
out.push(',');
}
out.push('\n');
}
out.push_str(" );\n");
}
if method.return_type != TypeRef::Unit && returns_ptr(&method.return_type) {
if matches!(method.return_type, TypeRef::Optional(_)) {
out.push_str(
" if (nativeResult == IntPtr.Zero)\n {\n return null;\n }\n",
);
} else {
out.push_str(
" if (nativeResult == IntPtr.Zero)\n {\n throw GetLastError();\n }\n",
);
}
} else if method.error_type.is_some() {
out.push_str(
" if (NativeMethods.LastErrorCode() != 0)\n {\n throw GetLastError();\n }\n",
);
}
emit_return_marshalling_indented(
&mut out,
&method.return_type,
" ",
enum_names,
true_opaque_types,
&HashSet::new(),
);
emit_named_param_teardown_indented(&mut out, &visible_params, " ", true_opaque_types, enum_names);
emit_return_statement_indented(&mut out, &method.return_type, " ");
out.push_str(" });\n");
} else {
if method.return_type != TypeRef::Unit {
out.push_str(" var nativeResult = ");
} else {
out.push_str(" ");
}
out.push_str(
render(
"native_call_start.jinja",
minijinja::context! { method_name => &cs_native_name },
)
.trim_end_matches('\n'),
);
if !has_receiver && visible_params.is_empty() {
out.push_str(");\n");
} else {
out.push('\n');
let mut arg_parts: Vec<String> = Vec::new();
if has_receiver {
arg_parts.push("handle".to_string());
}
for param in visible_params.iter() {
let param_name = param.name.to_lower_camel_case();
let arg = native_call_arg(¶m.ty, ¶m_name, param.optional, true_opaque_types);
arg_parts.push(arg.clone());
if matches!(param.ty, TypeRef::Bytes) {
arg_parts.push(format!("(UIntPtr){param_name}.Length"));
}
}
for (i, arg) in arg_parts.iter().enumerate() {
out.push_str(render("indented_arg_sync.jinja", minijinja::context! { arg }).trim_end_matches('\n'));
if i < arg_parts.len() - 1 {
out.push(',');
}
out.push('\n');
}
out.push_str(" );\n");
}
if method.return_type != TypeRef::Unit && returns_ptr(&method.return_type) {
if matches!(method.return_type, TypeRef::Optional(_)) {
out.push_str(
" if (nativeResult == IntPtr.Zero)\n {\n return null;\n }\n",
);
} else {
out.push_str(
" if (nativeResult == IntPtr.Zero)\n {\n throw GetLastError();\n }\n",
);
}
} else if method.error_type.is_some() {
out.push_str(
" if (NativeMethods.LastErrorCode() != 0)\n {\n throw GetLastError();\n }\n",
);
}
emit_return_marshalling_indented(
&mut out,
&method.return_type,
" ",
enum_names,
true_opaque_types,
handle_returned_types,
);
emit_named_param_teardown(&mut out, &visible_params, true_opaque_types, enum_names);
emit_return_statement(&mut out, &method.return_type);
}
out.push_str(" }\n\n");
out
}