use crate::codegen::naming::csharp_type_name;
use crate::core::ir::{PrimitiveType, TypeDef, TypeRef};
use heck::ToLowerCamelCase;
use std::collections::HashSet;
pub(super) fn pinvoke_return_type(ty: &TypeRef) -> &'static str {
match ty {
TypeRef::Unit => "void",
TypeRef::Primitive(PrimitiveType::Bool) => "int",
TypeRef::Primitive(PrimitiveType::U8) => "byte",
TypeRef::Primitive(PrimitiveType::U16) => "ushort",
TypeRef::Primitive(PrimitiveType::U32) => "uint",
TypeRef::Primitive(PrimitiveType::U64) => "ulong",
TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
TypeRef::Primitive(PrimitiveType::I16) => "short",
TypeRef::Primitive(PrimitiveType::I32) => "int",
TypeRef::Primitive(PrimitiveType::I64) => "long",
TypeRef::Primitive(PrimitiveType::F32) => "float",
TypeRef::Primitive(PrimitiveType::F64) => "double",
TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
TypeRef::Primitive(PrimitiveType::Isize) => "long",
TypeRef::Duration => "ulong",
TypeRef::String
| TypeRef::Char
| TypeRef::Bytes
| TypeRef::Optional(_)
| TypeRef::Vec(_)
| TypeRef::Map(_, _)
| TypeRef::Named(_)
| TypeRef::Path
| TypeRef::Json => "IntPtr",
}
}
pub(super) fn pinvoke_param_type(ty: &TypeRef) -> &'static str {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "string",
TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes | TypeRef::Optional(_) => "IntPtr",
TypeRef::Unit => "void",
TypeRef::Primitive(PrimitiveType::Bool) => "int",
TypeRef::Primitive(PrimitiveType::U8) => "byte",
TypeRef::Primitive(PrimitiveType::U16) => "ushort",
TypeRef::Primitive(PrimitiveType::U32) => "uint",
TypeRef::Primitive(PrimitiveType::U64) => "ulong",
TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
TypeRef::Primitive(PrimitiveType::I16) => "short",
TypeRef::Primitive(PrimitiveType::I32) => "int",
TypeRef::Primitive(PrimitiveType::I64) => "long",
TypeRef::Primitive(PrimitiveType::F32) => "float",
TypeRef::Primitive(PrimitiveType::F64) => "double",
TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
TypeRef::Primitive(PrimitiveType::Isize) => "long",
TypeRef::Duration => "ulong",
}
}
pub(super) fn is_bridge_param(
param: &crate::core::ir::ParamDef,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) -> bool {
bridge_param_names.contains(¶m.name)
|| matches!(¶m.ty, crate::core::ir::TypeRef::Named(n) if bridge_type_aliases.contains(n))
}
pub(super) fn returns_string(ty: &TypeRef) -> bool {
matches!(ty, TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json)
}
pub(super) fn returns_bool_via_int(ty: &TypeRef) -> bool {
matches!(ty, TypeRef::Primitive(PrimitiveType::Bool))
}
pub(super) fn returns_json_object(ty: &TypeRef) -> bool {
matches!(
ty,
TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) | TypeRef::Bytes | TypeRef::Optional(_)
)
}
pub(super) fn returns_ptr(ty: &TypeRef) -> bool {
matches!(
ty,
TypeRef::String
| TypeRef::Char
| TypeRef::Path
| TypeRef::Json
| TypeRef::Named(_)
| TypeRef::Vec(_)
| TypeRef::Map(_, _)
| TypeRef::Bytes
| TypeRef::Optional(_)
)
}
pub(super) fn native_call_arg(
ty: &TypeRef,
param_name: &str,
optional: bool,
true_opaque_types: &HashSet<String>,
) -> String {
match ty {
TypeRef::Named(type_name) if true_opaque_types.contains(type_name) => {
let bang = if optional { "!" } else { "" };
format!("{param_name}{bang}.Handle")
}
TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
format!("{param_name}Handle")
}
TypeRef::Bytes => {
format!("{param_name}Handle.AddrOfPinnedObject()")
}
TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool) => {
if optional {
format!("({param_name} ?? false)")
} else {
param_name.to_string()
}
}
ty => {
if optional {
if let TypeRef::Primitive(prim) = ty {
use crate::core::ir::PrimitiveType;
let sentinel = match prim {
PrimitiveType::U8 => "byte.MaxValue",
PrimitiveType::U16 => "ushort.MaxValue",
PrimitiveType::U32 => "uint.MaxValue",
PrimitiveType::U64 | PrimitiveType::Usize => "ulong.MaxValue",
PrimitiveType::I8 => "sbyte.MaxValue",
PrimitiveType::I16 => "short.MaxValue",
PrimitiveType::I32 => "int.MaxValue",
PrimitiveType::I64 | PrimitiveType::Isize => "long.MaxValue",
PrimitiveType::F32 => "float.NaN",
PrimitiveType::F64 => "double.NaN",
PrimitiveType::Bool => unreachable!("handled above"),
};
format!("{param_name} ?? {sentinel}")
} else if matches!(ty, TypeRef::Duration) {
format!("{param_name}.GetValueOrDefault()")
} else {
format!("{param_name}!")
}
} else {
param_name.to_string()
}
}
}
}
pub(super) fn needs_param_teardown(
params: &[crate::core::ir::ParamDef],
true_opaque_types: &HashSet<String>,
enum_names: &HashSet<String>,
) -> bool {
params.iter().any(|param| match ¶m.ty {
TypeRef::Named(type_name) => !true_opaque_types.contains(type_name) && !enum_names.contains(type_name),
TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes => true,
_ => false,
})
}
pub(super) fn emit_named_param_setup(
out: &mut String,
params: &[crate::core::ir::ParamDef],
indent: &str,
true_opaque_types: &HashSet<String>,
exception_name: &str,
_types: &[TypeDef],
enum_names: &HashSet<String>,
) {
use crate::backends::csharp::template_env::render;
for param in params {
let param_name = param.name.to_lower_camel_case();
let json_var = format!("{param_name}Json");
let handle_var = format!("{param_name}Handle");
match ¶m.ty {
TypeRef::Named(type_name) => {
if true_opaque_types.contains(type_name) {
continue;
}
if enum_names.contains(type_name) {
if param.optional {
out.push_str(&render(
"named_param_enum_optional.jinja",
minijinja::context! { indent, handle_var, param_name },
));
} else {
out.push_str(&render(
"named_param_enum_required.jinja",
minijinja::context! { indent, handle_var, param_name },
));
}
continue;
}
let from_json_method = format!("{}FromJson", csharp_type_name(type_name));
if param.optional {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_handle_from_json_optional.jinja",
minijinja::context! {
indent,
handle_var => &handle_var,
from_json_method => &from_json_method,
json_var => &json_var,
param_name => ¶m_name,
exception_name => exception_name,
},
));
} else {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_json_serialize.jinja",
minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
));
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_handle_from_json.jinja",
minijinja::context! {
indent,
handle_var => &handle_var,
from_json_method => &from_json_method,
json_var => &json_var,
exception_name => exception_name,
},
));
}
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_json_serialize.jinja",
minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
));
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_handle_string.jinja",
minijinja::context! { indent, handle_var => &handle_var, json_var => &json_var },
));
}
TypeRef::Bytes => {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_handle_pin.jinja",
minijinja::context! { indent, handle_var => &handle_var, param_name => ¶m_name },
));
}
_ => {}
}
}
}
pub(super) fn emit_named_param_teardown(
out: &mut String,
params: &[crate::core::ir::ParamDef],
true_opaque_types: &HashSet<String>,
enum_names: &HashSet<String>,
) {
for param in params {
let param_name = param.name.to_lower_camel_case();
let handle_var = format!("{param_name}Handle");
match ¶m.ty {
TypeRef::Named(type_name) => {
if true_opaque_types.contains(type_name) {
continue;
}
if enum_names.contains(type_name) {
continue;
}
let free_method = format!("{}Free", csharp_type_name(type_name));
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_teardown_free.jinja",
minijinja::context! { indent => " ", free_method => &free_method, handle_var => &handle_var },
));
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_teardown_hglobal.jinja",
minijinja::context! { indent => " ", handle_var => &handle_var },
));
}
TypeRef::Bytes => {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_teardown_gchandle.jinja",
minijinja::context! { indent => " ", handle_var => &handle_var },
));
}
_ => {}
}
}
}
pub(super) fn emit_named_param_teardown_indented(
out: &mut String,
params: &[crate::core::ir::ParamDef],
indent: &str,
true_opaque_types: &HashSet<String>,
enum_names: &HashSet<String>,
) {
for param in params {
let param_name = param.name.to_lower_camel_case();
let handle_var = format!("{param_name}Handle");
match ¶m.ty {
TypeRef::Named(type_name) => {
if true_opaque_types.contains(type_name) {
continue;
}
if enum_names.contains(type_name) {
continue;
}
let free_method = format!("{}Free", csharp_type_name(type_name));
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_teardown_free.jinja",
minijinja::context! { indent, free_method => &free_method, handle_var => &handle_var },
));
}
TypeRef::Vec(_) | TypeRef::Map(_, _) => {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_teardown_hglobal.jinja",
minijinja::context! { indent, handle_var => &handle_var },
));
}
TypeRef::Bytes => {
out.push_str(&crate::backends::csharp::template_env::render(
"named_param_teardown_gchandle.jinja",
minijinja::context! { indent, handle_var => &handle_var },
));
}
_ => {}
}
}
}