use crate::type_map::csharp_type;
use alef_codegen::naming::to_csharp_name;
use alef_core::config::{BridgeBinding, TraitBridgeConfig};
use alef_core::ir::{TypeDef, TypeRef};
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use std::fmt::Write;
fn csharp_unmanaged_type(ty: &TypeRef) -> String {
match ty {
TypeRef::Primitive(_) => csharp_type(ty).to_string(),
TypeRef::Unit => csharp_type(ty).to_string(),
_ => "IntPtr".to_string(),
}
}
pub fn gen_native_methods_trait_bridges(
_namespace: &str,
prefix: &str,
bridges: &[(String, &TraitBridgeConfig, &TypeDef)],
) -> String {
let mut out = String::with_capacity(1024);
if bridges.is_empty() {
return out;
}
writeln!(out).ok();
writeln!(out, " // Trait Bridge FFI").ok();
for (trait_name, _config, _trait_def) in bridges {
let trait_snake = trait_name.to_snake_case();
let register_fn = format!("{prefix}_register_{trait_snake}");
let unregister_fn = format!("{prefix}_unregister_{trait_snake}");
writeln!(out).ok();
writeln!(
out,
" [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = \"{register_fn}\")]"
)
.ok();
writeln!(
out,
" internal static extern int Register{}([MarshalAs(UnmanagedType.LPUTF8Str)] string name, IntPtr vtable, IntPtr userData, out IntPtr outError);",
trait_name
)
.ok();
writeln!(out).ok();
writeln!(
out,
" [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = \"{unregister_fn}\")]"
)
.ok();
writeln!(
out,
" internal static extern int Unregister{}([MarshalAs(UnmanagedType.LPUTF8Str)] string name, out IntPtr outError);",
trait_name
)
.ok();
}
out
}
pub fn gen_trait_bridges_file(
namespace: &str,
prefix: &str,
bridges: &[(String, &TraitBridgeConfig, &TypeDef)],
) -> (String, String) {
let mut out = String::with_capacity(16_384);
writeln!(out, "// DO NOT EDIT — generated by alef").ok();
writeln!(
out,
"// This file bridges managed C# trait implementations to the native FFI layer"
)
.ok();
writeln!(out, "#nullable enable").ok();
writeln!(out).ok();
writeln!(out, "using System;").ok();
writeln!(out, "using System.Collections.Concurrent;").ok();
writeln!(out, "using System.Collections.Generic;").ok();
writeln!(out, "using System.Runtime.InteropServices;").ok();
writeln!(out, "using System.Text.Json;").ok();
writeln!(out).ok();
writeln!(out, "namespace {};", namespace).ok();
writeln!(out).ok();
for (trait_name, bridge_cfg, trait_def) in bridges {
if bridge_cfg.exclude_languages.iter().any(|lang| lang == "csharp") {
continue;
}
gen_single_trait_bridge(&mut out, trait_name, bridge_cfg, trait_def, prefix);
writeln!(out).ok();
}
writeln!(out, "/// <summary>FFI JSON serialization extension methods</summary>").ok();
writeln!(out, "internal static class FfiJsonExtensions {{").ok();
writeln!(out).ok();
writeln!(
out,
" /// <summary>Serialize any object to JSON for FFI marshalling</summary>"
)
.ok();
writeln!(out, " internal static string ToFfiJson<T>(this T value) {{").ok();
writeln!(out, " return JsonSerializer.Serialize(value);").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, "}}").ok();
("TraitBridges.cs".to_string(), out)
}
fn gen_single_trait_bridge(
out: &mut String,
trait_name: &str,
bridge_cfg: &TraitBridgeConfig,
trait_def: &TypeDef,
_prefix: &str,
) {
let trait_pascal = trait_name.to_pascal_case();
let _trait_snake = trait_name.to_snake_case();
let has_super_trait = bridge_cfg.super_trait.is_some();
let has_bytes_param = trait_def
.methods
.iter()
.flat_map(|m| m.params.iter())
.any(|p| matches!(&p.ty, TypeRef::Bytes));
writeln!(out, "/// <summary>").ok();
writeln!(
out,
"/// Bridge interface for {} trait implementation via native FFI",
trait_pascal
)
.ok();
writeln!(out, "/// </summary>").ok();
writeln!(out, "public interface I{} {{", trait_pascal).ok();
writeln!(out).ok();
if has_super_trait {
writeln!(out, " /// <summary>Get the plugin name.</summary>").ok();
writeln!(out, " string Name {{ get; }}").ok();
writeln!(out).ok();
writeln!(out, " /// <summary>Get the plugin version.</summary>").ok();
writeln!(out, " string Version {{ get; }}").ok();
writeln!(out).ok();
writeln!(out, " /// <summary>Initialize the plugin.</summary>").ok();
writeln!(out, " void Initialize();").ok();
writeln!(out).ok();
writeln!(out, " /// <summary>Shut down the plugin.</summary>").ok();
writeln!(out, " void Shutdown();").ok();
writeln!(out).ok();
}
for method in &trait_def.methods {
let return_type = csharp_type(&method.return_type);
let params = method
.params
.iter()
.map(|p| format!("{} {}", csharp_type(&p.ty), to_csharp_name(&p.name)))
.collect::<Vec<_>>()
.join(", ");
writeln!(out, " /// <summary>{}</summary>", method.name).ok();
writeln!(out, " {} {}({});", return_type, to_csharp_name(&method.name), params).ok();
writeln!(out).ok();
}
writeln!(out, "}}").ok();
writeln!(out).ok();
writeln!(out, "/// <summary>").ok();
writeln!(
out,
"/// Manages the FFI vtable and delegates for a {} implementation",
trait_pascal
)
.ok();
writeln!(out, "/// </summary>").ok();
writeln!(out, "public sealed class {}Bridge : IDisposable {{", trait_pascal).ok();
writeln!(out).ok();
let num_methods = trait_def.methods.len();
let num_super_slots = if has_super_trait { 4usize } else { 0usize };
let num_vtable_fields = num_super_slots + num_methods + 1;
writeln!(out, " private readonly I{} _impl;", trait_pascal).ok();
writeln!(out, " private readonly GCHandle _implHandle;").ok();
writeln!(out, " internal IntPtr _vtable;").ok();
writeln!(out, " private bool _disposed;").ok();
writeln!(out, " private readonly object[] _delegates;").ok();
writeln!(out).ok();
writeln!(out, " // Vtable slot delegates ({})", num_vtable_fields).ok();
if has_super_trait {
writeln!(out, " [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
writeln!(
out,
" private delegate int NameFn(IntPtr userData, out IntPtr outName);"
)
.ok();
writeln!(out).ok();
writeln!(out, " [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
writeln!(
out,
" private delegate int VersionFn(IntPtr userData, out IntPtr outVersion);"
)
.ok();
writeln!(out).ok();
writeln!(out, " [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
writeln!(
out,
" private delegate int InitializeFn(IntPtr userData, out IntPtr outError);"
)
.ok();
writeln!(out).ok();
writeln!(out, " [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
writeln!(
out,
" private delegate int ShutdownFn(IntPtr userData, out IntPtr outError);"
)
.ok();
writeln!(out).ok();
}
for method in &trait_def.methods {
let params = method
.params
.iter()
.map(|p| format!("{} {}", csharp_unmanaged_type(&p.ty), to_csharp_name(&p.name)))
.collect::<Vec<_>>()
.join(", ");
writeln!(out, " [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
let method_pascal = to_csharp_name(&method.name);
let is_options_field = bridge_cfg.bind_via == BridgeBinding::OptionsField;
if params.is_empty() {
if is_options_field {
writeln!(
out,
" private delegate int {}Fn(IntPtr userData, out IntPtr outResult);",
method_pascal
)
.ok();
} else {
writeln!(
out,
" private delegate int {}Fn(IntPtr userData, out IntPtr outResult, out IntPtr outError);",
method_pascal
)
.ok();
}
} else {
if is_options_field {
writeln!(
out,
" private delegate int {}Fn(IntPtr userData, {}, out IntPtr outResult);",
method_pascal, params
)
.ok();
} else {
writeln!(
out,
" private delegate int {}Fn(IntPtr userData, {}, out IntPtr outResult, out IntPtr outError);",
method_pascal, params
)
.ok();
}
}
writeln!(out).ok();
}
writeln!(out, " [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
writeln!(out, " private delegate void FreeUserDataFn(IntPtr userData);").ok();
writeln!(out).ok();
writeln!(out, " public {}Bridge(I{} impl) {{", trait_pascal, trait_pascal).ok();
writeln!(
out,
" _impl = impl ?? throw new ArgumentNullException(nameof(impl));"
)
.ok();
writeln!(out, " _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);").ok();
writeln!(out, " _vtable = IntPtr.Zero;").ok();
writeln!(out, " _disposed = false;").ok();
writeln!(out, " _delegates = new object[{}];", num_vtable_fields).ok();
writeln!(out, " BuildVtable();").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " private void BuildVtable() {{").ok();
writeln!(
out,
" // Allocate unmanaged vtable struct (array of function pointers)"
)
.ok();
writeln!(
out,
" _vtable = Marshal.AllocHGlobal(IntPtr.Size * {});",
num_vtable_fields
)
.ok();
writeln!(out).ok();
let mut offset = 0usize;
let ptr_size = std::mem::size_of::<*const ()>();
if has_super_trait {
writeln!(out, " // Slot {}: name_fn", offset).ok();
writeln!(out, " var nameFn = new NameFn(NameFnCallback);",).ok();
writeln!(out, " _delegates[{}] = nameFn;", offset).ok();
writeln!(
out,
" Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(nameFn));",
offset * ptr_size
)
.ok();
writeln!(out).ok();
offset += 1;
writeln!(out, " // Slot {}: version_fn", offset).ok();
writeln!(out, " var versionFn = new VersionFn(VersionFnCallback);",).ok();
writeln!(out, " _delegates[{}] = versionFn;", offset).ok();
writeln!(
out,
" Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(versionFn));",
offset * ptr_size
)
.ok();
writeln!(out).ok();
offset += 1;
writeln!(out, " // Slot {}: initialize_fn", offset).ok();
writeln!(out, " var initFn = new InitializeFn(InitializeFnCallback);",).ok();
writeln!(out, " _delegates[{}] = initFn;", offset).ok();
writeln!(
out,
" Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(initFn));",
offset * ptr_size
)
.ok();
writeln!(out).ok();
offset += 1;
writeln!(out, " // Slot {}: shutdown_fn", offset).ok();
writeln!(out, " var shutdownFn = new ShutdownFn(ShutdownFnCallback);",).ok();
writeln!(out, " _delegates[{}] = shutdownFn;", offset).ok();
writeln!(
out,
" Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(shutdownFn));",
offset * ptr_size
)
.ok();
writeln!(out).ok();
offset += 1;
}
for method in &trait_def.methods {
let method_pascal = to_csharp_name(&method.name);
let method_camel = method.name.to_lower_camel_case();
writeln!(out, " // Slot {}: {}_fn", offset, method.name).ok();
writeln!(
out,
" var {}Fn = new {}Fn({}FnCallback);",
method_camel, method_pascal, method_pascal
)
.ok();
writeln!(out, " _delegates[{}] = {}Fn;", offset, method_camel).ok();
writeln!(
out,
" Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate({}Fn));",
offset * ptr_size,
method_camel
)
.ok();
writeln!(out).ok();
offset += 1;
}
writeln!(out, " // Slot {}: free_user_data", offset).ok();
writeln!(out, " var freeFn = new FreeUserDataFn(FreeUserDataCallback);",).ok();
writeln!(out, " _delegates[{}] = freeFn;", offset).ok();
writeln!(
out,
" Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(freeFn));",
offset * ptr_size
)
.ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " private static string ToJsonString<T>(T value) {{").ok();
writeln!(out, " return JsonSerializer.Serialize(value);").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
if has_bytes_param {
writeln!(out, " private static byte[] MarshalBytesFromIntPtr(IntPtr ptr) {{").ok();
writeln!(out, " if (ptr == IntPtr.Zero) return Array.Empty<byte>();").ok();
writeln!(out, " var json = Marshal.PtrToStringUTF8(ptr) ?? \"[]\";").ok();
writeln!(
out,
" return JsonSerializer.Deserialize<byte[]>(json) ?? Array.Empty<byte>();"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
}
if has_super_trait {
writeln!(
out,
" private int NameFnCallback(IntPtr userData, out IntPtr outName) {{"
)
.ok();
writeln!(out, " try {{").ok();
writeln!(out, " var name = _impl.Name;").ok();
writeln!(out, " outName = Marshal.StringToCoTaskMemUTF8(name);").ok();
writeln!(out, " return 0;").ok();
writeln!(out, " }} catch {{").ok();
writeln!(out, " outName = IntPtr.Zero;").ok();
writeln!(out, " return 1;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(
out,
" private int VersionFnCallback(IntPtr userData, out IntPtr outVersion) {{"
)
.ok();
writeln!(out, " try {{").ok();
writeln!(out, " var version = _impl.Version;").ok();
writeln!(out, " outVersion = Marshal.StringToCoTaskMemUTF8(version);").ok();
writeln!(out, " return 0;").ok();
writeln!(out, " }} catch {{").ok();
writeln!(out, " outVersion = IntPtr.Zero;").ok();
writeln!(out, " return 1;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(
out,
" private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {{"
)
.ok();
writeln!(out, " try {{").ok();
writeln!(out, " _impl.Initialize();").ok();
writeln!(out, " outError = IntPtr.Zero;").ok();
writeln!(out, " return 0;").ok();
writeln!(out, " }} catch (Exception ex) {{").ok();
writeln!(out, " outError = Marshal.StringToCoTaskMemUTF8(ex.Message);").ok();
writeln!(out, " return 1;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(
out,
" private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {{"
)
.ok();
writeln!(out, " try {{").ok();
writeln!(out, " _impl.Shutdown();").ok();
writeln!(out, " outError = IntPtr.Zero;").ok();
writeln!(out, " return 0;").ok();
writeln!(out, " }} catch (Exception ex) {{").ok();
writeln!(out, " outError = Marshal.StringToCoTaskMemUTF8(ex.Message);").ok();
writeln!(out, " return 1;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
}
let is_options_field = bridge_cfg.bind_via == BridgeBinding::OptionsField;
for method in &trait_def.methods {
let method_pascal = to_csharp_name(&method.name);
let unmanaged_param_sig = method
.params
.iter()
.map(|p| format!("{} {}", csharp_unmanaged_type(&p.ty), to_csharp_name(&p.name)))
.collect::<Vec<_>>()
.join(", ");
let params_decl = if unmanaged_param_sig.is_empty() {
String::new()
} else {
format!("{}, ", unmanaged_param_sig)
};
if is_options_field {
writeln!(
out,
" private int {}FnCallback(IntPtr userData, {}out IntPtr outResult) {{",
method_pascal, params_decl
)
.ok();
} else {
writeln!(
out,
" private int {}FnCallback(IntPtr userData, {}out IntPtr outResult, out IntPtr outError) {{",
method_pascal, params_decl
)
.ok();
}
writeln!(out, " try {{").ok();
let mut param_call_parts = Vec::new();
for param in &method.params {
let param_name = to_csharp_name(¶m.name);
let managed_type = csharp_type(¶m.ty);
match ¶m.ty {
TypeRef::Primitive(_) | TypeRef::Unit => {
param_call_parts.push(param_name);
}
TypeRef::String | TypeRef::Char => {
writeln!(
out,
" var managed_{} = Marshal.PtrToStringUTF8({}) ?? string.Empty;",
param_name, param_name
)
.ok();
param_call_parts.push(format!("managed_{}", param_name));
}
TypeRef::Bytes => {
writeln!(
out,
" var managed_{} = MarshalBytesFromIntPtr({});",
param_name, param_name
)
.ok();
param_call_parts.push(format!("managed_{}", param_name));
}
_ => {
writeln!(
out,
" var json_{} = Marshal.PtrToStringUTF8({}) ?? \"{{}}\";",
param_name, param_name
)
.ok();
writeln!(
out,
" var managed_{} = JsonSerializer.Deserialize<{}>(json_{})!;",
param_name, managed_type, param_name
)
.ok();
param_call_parts.push(format!("managed_{}", param_name));
}
}
}
let param_call = param_call_parts.join(", ");
if method.return_type == TypeRef::Unit {
writeln!(out, " _impl.{}({});", method_pascal, param_call).ok();
writeln!(out, " outResult = IntPtr.Zero;").ok();
} else {
writeln!(out, " var result = _impl.{}({});", method_pascal, param_call).ok();
let serialize_expr = if matches!(method.return_type, TypeRef::Named(_)) {
"result.ToFfiJson()".to_string()
} else {
"ToJsonString(result)".to_string()
};
writeln!(
out,
" outResult = Marshal.StringToCoTaskMemUTF8({});",
serialize_expr
)
.ok();
}
if !is_options_field {
writeln!(out, " outError = IntPtr.Zero;").ok();
}
writeln!(out, " return 0;").ok();
if is_options_field {
writeln!(out, " }} catch (Exception) {{").ok();
} else {
writeln!(out, " }} catch (Exception ex) {{").ok();
}
writeln!(out, " outResult = IntPtr.Zero;").ok();
if !is_options_field {
writeln!(out, " outError = Marshal.StringToCoTaskMemUTF8(ex.Message);").ok();
}
writeln!(out, " return 1;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
}
writeln!(out, " private void FreeUserDataCallback(IntPtr userData) {{").ok();
writeln!(out, " if (userData != IntPtr.Zero) {{").ok();
writeln!(out, " try {{").ok();
writeln!(out, " var handle = GCHandle.FromIntPtr(userData);").ok();
writeln!(out, " handle.Free();").ok();
writeln!(out, " }} catch (ObjectDisposedException) {{").ok();
writeln!(
out,
" // Handle already freed; safe to ignore during finalization"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " public void Dispose() {{").ok();
writeln!(out, " if (_disposed) return;").ok();
writeln!(out, " _disposed = true;").ok();
writeln!(out).ok();
writeln!(out, " if (_vtable != IntPtr.Zero) {{").ok();
writeln!(out, " Marshal.FreeHGlobal(_vtable);").ok();
writeln!(out, " _vtable = IntPtr.Zero;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " if (_implHandle.IsAllocated) {{").ok();
writeln!(out, " _implHandle.Free();").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
writeln!(out).ok();
writeln!(
out,
"/// <summary>Static helpers for registering trait implementations</summary>"
)
.ok();
writeln!(out, "public static class {}Registry {{", trait_pascal).ok();
writeln!(out).ok();
writeln!(
out,
" private static readonly ConcurrentDictionary<string, {}Bridge> _bridges =",
trait_pascal
)
.ok();
writeln!(
out,
" new ConcurrentDictionary<string, {}Bridge>();",
trait_pascal
)
.ok();
writeln!(out).ok();
writeln!(
out,
" /// <summary>Register a {} implementation</summary>",
trait_pascal
)
.ok();
if has_super_trait {
writeln!(out, " public static void Register(I{} impl) {{", trait_pascal).ok();
} else {
writeln!(
out,
" public static void Register(I{} impl, string name) {{",
trait_pascal
)
.ok();
}
writeln!(out, " if (impl == null)").ok();
writeln!(out, " throw new ArgumentNullException(nameof(impl));").ok();
writeln!(out).ok();
if has_super_trait {
writeln!(out, " var name = impl.Name;").ok();
}
writeln!(out, " var bridge = new {}Bridge(impl);", trait_pascal).ok();
writeln!(out).ok();
writeln!(out, " try {{").ok();
writeln!(
out,
" var userDataHandle = GCHandle.Alloc(bridge, GCHandleType.Normal);"
)
.ok();
writeln!(out, " var userData = GCHandle.ToIntPtr(userDataHandle);").ok();
writeln!(out, " var vtablePtr = bridge._vtable;").ok();
writeln!(out).ok();
writeln!(
out,
" var result = NativeMethods.Register{}(name, vtablePtr, userData, out var outError);",
trait_pascal
)
.ok();
writeln!(out, " if (result != 0) {{").ok();
writeln!(out, " userDataHandle.Free();").ok();
writeln!(out, " bridge.Dispose();").ok();
writeln!(
out,
" var errorMsg = Marshal.PtrToStringUTF8(outError) ?? \"Unknown error\";"
)
.ok();
writeln!(out, " Marshal.FreeCoTaskMem(outError);").ok();
writeln!(
out,
" throw new InvalidOperationException($\"Failed to register {{name}}: {{errorMsg}}\");"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " _bridges.TryAdd(name, bridge);").ok();
writeln!(out, " }} catch {{").ok();
writeln!(out, " bridge.Dispose();").ok();
writeln!(out, " throw;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(
out,
" /// <summary>Unregister a {} implementation</summary>",
trait_pascal
)
.ok();
writeln!(out, " public static void Unregister(string name) {{").ok();
writeln!(out, " if (string.IsNullOrEmpty(name))").ok();
writeln!(
out,
" throw new ArgumentException(\"Name cannot be empty\", nameof(name));"
)
.ok();
writeln!(out).ok();
writeln!(
out,
" var result = NativeMethods.Unregister{}(name, out var outError);",
trait_pascal
)
.ok();
writeln!(out, " if (result != 0) {{").ok();
writeln!(
out,
" var errorMsg = Marshal.PtrToStringUTF8(outError) ?? \"Unknown error\";"
)
.ok();
writeln!(out, " Marshal.FreeCoTaskMem(outError);").ok();
writeln!(
out,
" throw new InvalidOperationException($\"Failed to unregister {{name}}: {{errorMsg}}\");"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " if (_bridges.TryRemove(name, out var bridge)) {{").ok();
writeln!(out, " bridge.Dispose();").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
}
fn _to_json_string(_obj: &dyn std::any::Any) -> String {
"null".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_trait_def(name: &str) -> TypeDef {
TypeDef {
name: name.to_string(),
rust_path: format!("kreuzberg::{}", name),
original_rust_path: String::new(),
fields: vec![],
methods: vec![],
is_opaque: false,
is_clone: true,
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,
}
}
fn make_bridge_cfg(trait_name: &str, super_trait: Option<&str>) -> TraitBridgeConfig {
TraitBridgeConfig {
trait_name: trait_name.to_string(),
param_name: None,
type_alias: None,
exclude_languages: vec![],
super_trait: super_trait.map(|s| s.to_string()),
registry_getter: None,
register_fn: None,
unregister_fn: None,
clear_fn: None,
register_extra_args: None,
bind_via: alef_core::config::BridgeBinding::FunctionParam,
options_type: None,
options_field: None,
}
}
#[test]
fn test_interface_contains_lifecycle_when_super_trait_set() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);
assert!(content.contains("public interface IOcrBackend"));
assert!(content.contains("string Name { get; }"));
assert!(content.contains("string Version { get; }"));
assert!(content.contains("void Initialize();"));
assert!(content.contains("void Shutdown();"));
}
#[test]
fn test_interface_omits_lifecycle_when_super_trait_empty() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", None);
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);
assert!(content.contains("public interface IOcrBackend"));
assert!(!content.contains("string Name { get; }"));
}
#[test]
fn test_bridge_class_exists() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", None);
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);
assert!(content.contains("public sealed class OcrBackendBridge : IDisposable"));
}
#[test]
fn test_registry_no_super_trait_requires_explicit_name_param() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", None);
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);
assert!(content.contains("public static class OcrBackendRegistry"));
assert!(content.contains("public static void Register(IOcrBackend impl, string name)"));
assert!(content.contains("public static void Unregister(string name)"));
assert!(!content.contains("impl.Name"));
}
#[test]
fn test_registry_with_super_trait_reads_name_from_impl() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);
assert!(content.contains("public static class OcrBackendRegistry"));
assert!(content.contains("public static void Register(IOcrBackend impl)"));
assert!(!content.contains("Register(IOcrBackend impl, string name)"));
assert!(content.contains("impl.Name"));
}
#[test]
fn test_exclude_languages_skips_csharp() {
let trait_def = make_trait_def("OcrBackend");
let mut bridge_cfg = make_bridge_cfg("OcrBackend", None);
bridge_cfg.exclude_languages = vec!["csharp".to_string()];
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);
assert!(!content.contains("interface IOcrBackend"));
assert!(!content.contains("class OcrBackendBridge"));
}
#[test]
fn test_native_methods_declarations() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", None);
let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
let content = gen_native_methods_trait_bridges("Kreuzberg", "kreuzberg", &bridges);
assert!(content.contains("RegisterOcrBackend"));
assert!(content.contains("UnregisterOcrBackend"));
assert!(content.contains("[DllImport"));
assert!(content.contains("kreuzberg_register_ocr_backend"));
assert!(content.contains("kreuzberg_unregister_ocr_backend"));
}
}