use crate::codegen::naming::csharp_type_name;
use crate::core::config::HostCapsuleTypeConfig;
use crate::core::ir::{ApiSurface, PrimitiveType, TypeDef, TypeRef};
use heck::ToLowerCamelCase;
use std::collections::{HashMap, HashSet};
pub(super) fn is_handle_type(ty: &TypeRef) -> bool {
match ty {
TypeRef::Named(_) => true,
TypeRef::Optional(inner) => matches!(inner.as_ref(), TypeRef::Named(_)),
_ => false,
}
}
pub(super) fn enum_names_with_data_variants(api: &ApiSurface) -> HashSet<String> {
api.enums
.iter()
.map(|enum_def| csharp_type_name(&enum_def.name))
.collect()
}
pub(super) const CAPSULE_PINVOKE_RETURN_TYPE: &str = "IntPtr";
pub(super) const HANDLE_PINVOKE_TYPE: &str = "ulong";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum FfiEmitter {
FreeFunction,
Method,
}
pub(super) fn is_capsule_return(ty: &TypeRef, capsule_types: &HashMap<String, HostCapsuleTypeConfig>) -> bool {
matches!(ty, TypeRef::Named(name) if capsule_types.contains_key(name))
}
pub(super) fn zero_sentinel_for_pinvoke_type(pinvoke_ty: &str) -> &'static str {
if pinvoke_ty == CAPSULE_PINVOKE_RETURN_TYPE {
"IntPtr.Zero"
} else {
"0"
}
}
pub(super) fn zero_sentinel(ty: &TypeRef) -> &'static str {
zero_sentinel_for_pinvoke_type(pinvoke_return_type(ty))
}
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",
_ if is_handle_type(ty) => HANDLE_PINVOKE_TYPE,
TypeRef::Optional(inner) => optional_scalar_pinvoke_return_type(inner).unwrap_or("IntPtr"),
TypeRef::String
| TypeRef::Char
| TypeRef::Bytes
| TypeRef::Vec(_)
| TypeRef::Map(_, _)
| TypeRef::Named(_)
| TypeRef::Path
| TypeRef::Json => "IntPtr",
}
}
fn optional_scalar_pinvoke_return_type(inner: &TypeRef) -> Option<&'static str> {
if !crate::backends::ffi::type_map::optional_return_crosses_as_scalar(inner) {
return None;
}
Some(match inner {
TypeRef::Optional(nested) => pinvoke_return_type(nested),
_ => pinvoke_return_type(inner),
})
}
pub(super) fn pinvoke_return_type_with_capsules(
ty: &TypeRef,
capsule_types: &HashMap<String, HostCapsuleTypeConfig>,
emitter: FfiEmitter,
) -> &'static str {
if emitter == FfiEmitter::FreeFunction && is_capsule_return(ty, capsule_types) {
CAPSULE_PINVOKE_RETURN_TYPE
} else {
pinvoke_return_type(ty)
}
}
pub(super) fn pinvoke_param_type(ty: &TypeRef) -> &'static str {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "string",
_ if is_handle_type(ty) => HANDLE_PINVOKE_TYPE,
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) const SCALAR_NAMED_PINVOKE_TYPE: &str = "int";
pub(super) fn pinvoke_param_type_with_scalars(
ty: &TypeRef,
scalar_named_types: &ahash::AHashSet<String>,
) -> &'static str {
if crate::backends::ffi::type_map::crosses_c_abi_as_scalar(ty, scalar_named_types) {
SCALAR_NAMED_PINVOKE_TYPE
} else {
pinvoke_param_type(ty)
}
}
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))
|| matches!(ty, TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::Bool)))
}
pub(super) fn returns_json_object(ty: &TypeRef) -> bool {
matches!(
ty,
TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) | TypeRef::Bytes
) || matches!(ty, TypeRef::Optional(inner) if optional_scalar_pinvoke_return_type(inner).is_none())
}
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) ? 1 : 0)")
} else {
format!("({param_name} ? 1 : 0)")
}
}
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 bytes_len_arg(cast: &str, param_name: &str, optional: bool) -> String {
if optional {
format!("{cast}({param_name}?.Length ?? 0)")
} else {
format!("{cast}{param_name}.Length")
}
}
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 },
));
}
_ => {}
}
}
}