use alef_codegen::naming::{csharp_type_name, to_csharp_name};
use alef_codegen::shared::binding_fields;
use alef_core::backend::{Backend, BuildConfig, BuildDependency, Capabilities, GeneratedFile};
use alef_core::config::{AdapterPattern, Language, ResolvedCrateConfig, resolve_output_dir};
use alef_core::hash::{self, CommentStyle};
use alef_core::ir::{ApiSurface, FieldDef, TypeRef};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub(super) struct StreamingMethodMeta {
#[allow(dead_code)]
pub owner_type: String,
pub item_type: String,
}
pub(super) mod enums;
pub(super) mod errors;
pub(super) mod functions;
pub(super) mod methods;
pub(super) mod types;
pub struct CsharpBackend;
impl CsharpBackend {
}
fn effective_exclude_types(config: &ResolvedCrateConfig) -> HashSet<String> {
let mut exclude_types: HashSet<String> = config
.ffi
.as_ref()
.map(|ffi| ffi.exclude_types.iter().cloned().collect())
.unwrap_or_default();
if let Some(csharp) = &config.csharp {
exclude_types.extend(csharp.exclude_types.iter().cloned());
}
exclude_types
}
fn references_excluded_type(ty: &TypeRef, exclude_types: &HashSet<String>) -> bool {
exclude_types.iter().any(|name| ty.references_named(name))
}
fn signature_references_excluded_type(
params: &[alef_core::ir::ParamDef],
return_type: &TypeRef,
exclude_types: &HashSet<String>,
) -> bool {
references_excluded_type(return_type, exclude_types)
|| params
.iter()
.any(|param| references_excluded_type(¶m.ty, exclude_types))
}
fn api_without_excluded_types(api: &ApiSurface, exclude_types: &HashSet<String>) -> ApiSurface {
let mut filtered = api.clone();
filtered.types.retain(|typ| !exclude_types.contains(&typ.name));
for typ in &mut filtered.types {
typ.fields
.retain(|field| !references_excluded_type(&field.ty, exclude_types));
typ.methods
.retain(|method| !signature_references_excluded_type(&method.params, &method.return_type, exclude_types));
}
filtered
.enums
.retain(|enum_def| !exclude_types.contains(&enum_def.name));
for enum_def in &mut filtered.enums {
for variant in &mut enum_def.variants {
variant
.fields
.retain(|field| !references_excluded_type(&field.ty, exclude_types));
}
}
filtered
.functions
.retain(|func| !signature_references_excluded_type(&func.params, &func.return_type, exclude_types));
filtered.errors.retain(|error| !exclude_types.contains(&error.name));
filtered
}
impl Backend for CsharpBackend {
fn name(&self) -> &str {
"csharp"
}
fn language(&self) -> Language {
Language::Csharp
}
fn capabilities(&self) -> Capabilities {
Capabilities {
supports_async: true,
supports_classes: true,
supports_enums: true,
supports_option: true,
supports_result: true,
..Capabilities::default()
}
}
fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let exclude_types = effective_exclude_types(config);
let filtered_api;
let api = if exclude_types.is_empty() {
api
} else {
filtered_api = api_without_excluded_types(api, &exclude_types);
&filtered_api
};
let namespace = config.csharp_namespace();
let prefix = config.ffi_prefix();
let lib_name = config.ffi_lib_name();
let bridge_param_names: HashSet<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.param_name.clone())
.collect();
let bridge_type_aliases: HashSet<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.type_alias.clone())
.collect();
let has_visitor_callbacks = config.ffi.as_ref().map(|f| f.visitor_callbacks).unwrap_or(false);
let bridge_associated_types = config.bridge_associated_types();
let streaming_methods: HashSet<String> = config
.adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
.map(|a| a.name.clone())
.collect();
let streaming_methods_meta: HashMap<String, StreamingMethodMeta> = config
.adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
.filter_map(|a| {
let owner_type = a.owner_type.clone()?;
let item_type = a.item_type.clone()?;
Some((a.name.clone(), StreamingMethodMeta { owner_type, item_type }))
})
.collect();
let mut exclude_functions: HashSet<String> = config
.csharp
.as_ref()
.map(|c| c.exclude_functions.iter().cloned().collect())
.unwrap_or_default();
if let Some(ffi) = &config.ffi {
exclude_functions.extend(ffi.exclude_functions.iter().cloned());
}
let output_dir = resolve_output_dir(config.output_paths.get("csharp"), &config.name, "packages/csharp/");
let base_path = PathBuf::from(&output_dir).join(namespace.replace('.', "/"));
let mut files = Vec::new();
let exception_class_name = format!("{}Exception", to_csharp_name(&api.crate_name));
files.push(GeneratedFile {
path: base_path.join("NativeMethods.cs"),
content: strip_trailing_whitespace(&functions::gen_native_methods(
api,
&namespace,
&lib_name,
&prefix,
&bridge_param_names,
&bridge_type_aliases,
has_visitor_callbacks,
&config.trait_bridges,
&streaming_methods,
&streaming_methods_meta,
&exclude_functions,
&config.client_constructors,
)),
generated_header: true,
});
if !api.errors.is_empty() {
for error in &api.errors {
let error_files =
alef_codegen::error_gen::gen_csharp_error_types(error, &namespace, Some(&exception_class_name));
for (class_name, content) in error_files {
files.push(GeneratedFile {
path: base_path.join(format!("{}.cs", class_name)),
content: strip_trailing_whitespace(&content),
generated_header: false, });
}
}
}
if api.errors.is_empty()
|| !api
.errors
.iter()
.any(|e| format!("{}Exception", e.name) == exception_class_name)
{
files.push(GeneratedFile {
path: base_path.join(format!("{}.cs", exception_class_name)),
content: strip_trailing_whitespace(&errors::gen_exception_class(&namespace, &exception_class_name)),
generated_header: true,
});
}
let base_class_name = to_csharp_name(&api.crate_name);
let wrapper_class_name = if namespace == base_class_name {
format!("{}Lib", base_class_name)
} else {
base_class_name
};
files.push(GeneratedFile {
path: base_path.join(format!("{}.cs", wrapper_class_name)),
content: strip_trailing_whitespace(&methods::gen_wrapper_class(
api,
&namespace,
&wrapper_class_name,
&exception_class_name,
&prefix,
&bridge_param_names,
&bridge_type_aliases,
has_visitor_callbacks,
&streaming_methods,
&streaming_methods_meta,
&exclude_functions,
&config.trait_bridges,
)),
generated_header: true,
});
if has_visitor_callbacks {
let visitor_bridge_cfg = config
.trait_bridges
.iter()
.find(|b| b.bind_via == alef_core::config::BridgeBinding::OptionsField);
let trait_map: std::collections::HashMap<&str, &alef_core::ir::TypeDef> = api
.types
.iter()
.filter(|t| t.is_trait)
.map(|t| (t.name.as_str(), t))
.collect();
let visitor_trait = visitor_bridge_cfg.and_then(|b| trait_map.get(b.trait_name.as_str()).copied());
if let Some(trait_def) = visitor_trait {
for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, trait_def) {
files.push(GeneratedFile {
path: base_path.join(filename),
content: strip_trailing_whitespace(&content),
generated_header: true,
});
}
} else {
let placeholder = alef_core::ir::TypeDef {
name: String::new(),
rust_path: String::new(),
original_rust_path: String::new(),
fields: vec![],
methods: vec![],
is_opaque: false,
is_clone: false,
is_copy: false,
is_trait: true,
has_default: false,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
doc: String::new(),
cfg: None,
binding_excluded: false,
binding_exclusion_reason: None,
};
for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, &placeholder) {
files.push(GeneratedFile {
path: base_path.join(filename),
content: strip_trailing_whitespace(&content),
generated_header: true,
});
}
}
delete_superseded_visitor_files(&base_path)?;
} else {
delete_stale_visitor_files(&base_path)?;
}
if !config.trait_bridges.is_empty() {
let trait_defs: Vec<_> = api.types.iter().filter(|t| t.is_trait).collect();
let bridges: Vec<_> = config
.trait_bridges
.iter()
.filter_map(|cfg| {
let trait_name = cfg.trait_name.clone();
trait_defs
.iter()
.find(|t| t.name == trait_name)
.map(|trait_def| (trait_name, cfg, *trait_def))
})
.collect();
if !bridges.is_empty() {
let visible_type_names: HashSet<&str> = api
.types
.iter()
.filter(|t| !t.is_trait)
.map(|t| t.name.as_str())
.chain(api.enums.iter().map(|e| e.name.as_str()))
.collect();
let (filename, content) =
crate::trait_bridge::gen_trait_bridges_file(&namespace, &prefix, &bridges, &visible_type_names);
files.push(GeneratedFile {
path: base_path.join(filename),
content: strip_trailing_whitespace(&content),
generated_header: true,
});
}
}
let enum_names: HashSet<String> = api.enums.iter().map(|e| csharp_type_name(&e.name)).collect();
let all_opaque_type_names: HashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| csharp_type_name(&t.name))
.collect();
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if typ.is_opaque {
let type_filename = csharp_type_name(&typ.name);
let client_ctor = config.client_constructors.get(&typ.name);
files.push(GeneratedFile {
path: base_path.join(format!("{}.cs", type_filename)),
content: strip_trailing_whitespace(&types::gen_opaque_handle(
typ,
&namespace,
&exception_class_name,
&enum_names,
&streaming_methods,
&streaming_methods_meta,
&all_opaque_type_names,
client_ctor,
)),
generated_header: true,
});
}
}
let complex_enums: HashSet<String> = HashSet::new();
let tagged_union_enums: HashSet<String> = api
.enums
.iter()
.filter(|e| e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty()))
.map(|e| csharp_type_name(&e.name))
.collect();
let custom_converter_enums: HashSet<String> = api
.enums
.iter()
.filter(|e| {
let is_tagged_union = e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty());
if is_tagged_union {
return false;
}
let rename_all_differs = matches!(
e.serde_rename_all.as_deref(),
Some("kebab-case") | Some("SCREAMING-KEBAB-CASE") | Some("camelCase") | Some("PascalCase")
);
if rename_all_differs {
return true;
}
e.variants.iter().any(|v| {
if let Some(ref rename) = v.serde_rename {
let snake = enums::apply_rename_all(&v.name, e.serde_rename_all.as_deref());
rename != &snake
} else {
false
}
})
})
.map(|e| csharp_type_name(&e.name))
.collect();
let lang_rename_all = config.serde_rename_all_for_language(Language::Csharp);
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if !typ.is_opaque {
let has_visible_fields = binding_fields(&typ.fields).next().is_some();
let has_named_fields = binding_fields(&typ.fields).any(|f| !is_tuple_field(f));
if has_visible_fields && !has_named_fields {
continue;
}
if has_visitor_callbacks && bridge_associated_types.contains(typ.name.as_str()) {
continue;
}
let type_filename = csharp_type_name(&typ.name);
let excluded_types: HashSet<String> =
api.excluded_type_paths.keys().map(|n| csharp_type_name(n)).collect();
files.push(GeneratedFile {
path: base_path.join(format!("{}.cs", type_filename)),
content: strip_trailing_whitespace(&types::gen_record_type(
typ,
&namespace,
&prefix,
&enum_names,
&complex_enums,
&custom_converter_enums,
&lang_rename_all,
&bridge_type_aliases,
&exception_class_name,
&excluded_types,
&tagged_union_enums,
)),
generated_header: true,
});
}
}
for enum_def in &api.enums {
if has_visitor_callbacks && bridge_associated_types.contains(enum_def.name.as_str()) {
continue;
}
let enum_filename = csharp_type_name(&enum_def.name);
files.push(GeneratedFile {
path: base_path.join(format!("{}.cs", enum_filename)),
content: strip_trailing_whitespace(&enums::gen_enum(enum_def, &namespace)),
generated_header: true,
});
}
let needs_byte_array_converter = api
.types
.iter()
.any(|t| !t.is_opaque && t.fields.iter().any(|f| !f.optional && matches!(f.ty, TypeRef::Bytes)));
if needs_byte_array_converter {
files.push(GeneratedFile {
path: base_path.join("ByteArrayToIntArrayConverter.cs"),
content: types::gen_byte_array_to_int_array_converter(&namespace),
generated_header: true,
});
}
let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Csharp)?;
files.push(GeneratedFile {
path: PathBuf::from("packages/csharp/Directory.Build.props"),
content: gen_directory_build_props(),
generated_header: true,
});
Ok(files)
}
fn generate_public_api(
&self,
_api: &ApiSurface,
_config: &ResolvedCrateConfig,
) -> anyhow::Result<Vec<GeneratedFile>> {
Ok(vec![])
}
fn build_config(&self) -> Option<BuildConfig> {
Some(BuildConfig {
tool: "dotnet",
crate_suffix: "",
build_dep: BuildDependency::Ffi,
post_build: vec![],
})
}
}
pub(super) fn is_tuple_field(field: &FieldDef) -> bool {
(field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
}
pub(super) fn strip_trailing_whitespace(content: &str) -> String {
let mut result: String = content
.lines()
.map(|line| line.trim_end())
.collect::<Vec<_>>()
.join("\n");
if !result.ends_with('\n') {
result.push('\n');
}
result
}
pub(super) fn csharp_file_header() -> String {
let mut out = hash::header(CommentStyle::DoubleSlash);
out.push_str("#nullable enable\n\n");
out
}
fn gen_directory_build_props() -> String {
"<!-- auto-generated by alef (generate_bindings) -->\n\
<Project>\n \
<PropertyGroup>\n \
<Nullable>enable</Nullable>\n \
<LangVersion>latest</LangVersion>\n \
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n \
</PropertyGroup>\n\
</Project>\n"
.to_string()
}
fn delete_superseded_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
let superseded = ["IVisitor.cs", "VisitorCallbacks.cs"];
for filename in superseded {
let path = base_path.join(filename);
if path.exists() {
std::fs::remove_file(&path)
.map_err(|e| anyhow::anyhow!("Failed to delete superseded visitor file {}: {}", path.display(), e))?;
}
}
Ok(())
}
fn delete_stale_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
let stale_files = vec!["IVisitor.cs", "VisitorCallbacks.cs", "NodeContext.cs", "VisitResult.cs"];
for filename in stale_files {
let path = base_path.join(filename);
if path.exists() {
std::fs::remove_file(&path)
.map_err(|e| anyhow::anyhow!("Failed to delete stale visitor file {}: {}", path.display(), e))?;
}
}
Ok(())
}
use alef_core::ir::PrimitiveType;
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: &alef_core::ir::ParamDef,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) -> bool {
bridge_param_names.contains(¶m.name)
|| matches!(¶m.ty, alef_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(alef_core::ir::PrimitiveType::Bool) => {
if optional {
format!("({param_name}?.Value ? 1 : 0)")
} else {
format!("({param_name} ? 1 : 0)")
}
}
ty => {
if optional {
if let TypeRef::Primitive(prim) = ty {
use alef_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 emit_named_param_setup(
out: &mut String,
params: &[alef_core::ir::ParamDef],
indent: &str,
true_opaque_types: &HashSet<String>,
exception_name: &str,
) {
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;
}
let from_json_method = format!("{}FromJson", csharp_type_name(type_name));
let is_config_param = param.name == "config";
let param_to_serialize = if is_config_param {
let type_pascal = csharp_type_name(type_name);
format!("({} ?? new {}())", param_name, type_pascal)
} else {
param_name.to_string()
};
if param.optional && !is_config_param {
out.push_str(&crate::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::template_env::render(
"named_param_json_serialize.jinja",
minijinja::context! { indent, json_var => &json_var, param_name => ¶m_to_serialize },
));
out.push_str(&crate::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::template_env::render(
"named_param_json_serialize.jinja",
minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
));
out.push_str(&crate::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::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: &[alef_core::ir::ParamDef],
true_opaque_types: &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;
}
let free_method = format!("{}Free", csharp_type_name(type_name));
out.push_str(&crate::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::template_env::render(
"named_param_teardown_hglobal.jinja",
minijinja::context! { indent => " ", handle_var => &handle_var },
));
}
TypeRef::Bytes => {
out.push_str(&crate::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: &[alef_core::ir::ParamDef],
indent: &str,
true_opaque_types: &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;
}
let free_method = format!("{}Free", csharp_type_name(type_name));
out.push_str(&crate::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::template_env::render(
"named_param_teardown_hglobal.jinja",
minijinja::context! { indent, handle_var => &handle_var },
));
}
TypeRef::Bytes => {
out.push_str(&crate::template_env::render(
"named_param_teardown_gchandle.jinja",
minijinja::context! { indent, handle_var => &handle_var },
));
}
_ => {}
}
}
}
use heck::ToLowerCamelCase;