1use alef_codegen::naming::{csharp_type_name, to_csharp_name};
2use alef_codegen::shared::binding_fields;
3use alef_core::backend::{Backend, BuildConfig, BuildDependency, Capabilities, GeneratedFile};
4use alef_core::config::{AdapterPattern, Language, ResolvedCrateConfig, resolve_output_dir};
5use alef_core::hash::{self, CommentStyle};
6use alef_core::ir::{ApiSurface, FieldDef, TypeRef};
7use std::collections::{HashMap, HashSet};
8use std::path::PathBuf;
9
10#[derive(Debug, Clone)]
14pub(super) struct StreamingMethodMeta {
15 #[allow(dead_code)]
18 pub owner_type: String,
19 pub item_type: String,
20}
21
22pub(super) mod enums;
23pub(super) mod errors;
24pub(super) mod functions;
25pub(super) mod methods;
26pub(super) mod types;
27
28pub struct CsharpBackend;
29
30impl CsharpBackend {
31 }
33
34fn effective_exclude_types(config: &ResolvedCrateConfig) -> HashSet<String> {
35 let mut exclude_types: HashSet<String> = config
36 .ffi
37 .as_ref()
38 .map(|ffi| ffi.exclude_types.iter().cloned().collect())
39 .unwrap_or_default();
40 if let Some(csharp) = &config.csharp {
41 exclude_types.extend(csharp.exclude_types.iter().cloned());
42 }
43 exclude_types
44}
45
46fn references_excluded_type(ty: &TypeRef, exclude_types: &HashSet<String>) -> bool {
47 exclude_types.iter().any(|name| ty.references_named(name))
48}
49
50fn signature_references_excluded_type(
51 params: &[alef_core::ir::ParamDef],
52 return_type: &TypeRef,
53 exclude_types: &HashSet<String>,
54) -> bool {
55 references_excluded_type(return_type, exclude_types)
56 || params
57 .iter()
58 .any(|param| references_excluded_type(¶m.ty, exclude_types))
59}
60
61fn api_without_excluded_types(api: &ApiSurface, exclude_types: &HashSet<String>) -> ApiSurface {
62 let mut filtered = api.clone();
63 filtered.types.retain(|typ| !exclude_types.contains(&typ.name));
64 for typ in &mut filtered.types {
65 typ.fields
66 .retain(|field| !references_excluded_type(&field.ty, exclude_types));
67 typ.methods
68 .retain(|method| !signature_references_excluded_type(&method.params, &method.return_type, exclude_types));
69 }
70 filtered
71 .enums
72 .retain(|enum_def| !exclude_types.contains(&enum_def.name));
73 for enum_def in &mut filtered.enums {
74 for variant in &mut enum_def.variants {
75 variant
76 .fields
77 .retain(|field| !references_excluded_type(&field.ty, exclude_types));
78 }
79 }
80 filtered
81 .functions
82 .retain(|func| !signature_references_excluded_type(&func.params, &func.return_type, exclude_types));
83 filtered.errors.retain(|error| !exclude_types.contains(&error.name));
84 filtered
85}
86
87impl Backend for CsharpBackend {
88 fn name(&self) -> &str {
89 "csharp"
90 }
91
92 fn language(&self) -> Language {
93 Language::Csharp
94 }
95
96 fn capabilities(&self) -> Capabilities {
97 Capabilities {
98 supports_async: true,
99 supports_classes: true,
100 supports_enums: true,
101 supports_option: true,
102 supports_result: true,
103 ..Capabilities::default()
104 }
105 }
106
107 fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
108 let exclude_types = effective_exclude_types(config);
109 let filtered_api;
110 let api = if exclude_types.is_empty() {
111 api
112 } else {
113 filtered_api = api_without_excluded_types(api, &exclude_types);
114 &filtered_api
115 };
116 let namespace = config.csharp_namespace();
117 let prefix = config.ffi_prefix();
118 let lib_name = config.ffi_lib_name();
119
120 let bridge_param_names: HashSet<String> = config
123 .trait_bridges
124 .iter()
125 .filter_map(|b| b.param_name.clone())
126 .collect();
127 let bridge_type_aliases: HashSet<String> = config
128 .trait_bridges
129 .iter()
130 .filter_map(|b| b.type_alias.clone())
131 .collect();
132 let has_visitor_callbacks = config.ffi.as_ref().map(|f| f.visitor_callbacks).unwrap_or(false);
134 let bridge_associated_types = config.bridge_associated_types();
135
136 let streaming_methods: HashSet<String> = config
141 .adapters
142 .iter()
143 .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
144 .map(|a| a.name.clone())
145 .collect();
146 let streaming_methods_meta: HashMap<String, StreamingMethodMeta> = config
147 .adapters
148 .iter()
149 .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
150 .filter_map(|a| {
151 let owner_type = a.owner_type.clone()?;
152 let item_type = a.item_type.clone()?;
153 Some((a.name.clone(), StreamingMethodMeta { owner_type, item_type }))
154 })
155 .collect();
156
157 let mut exclude_functions: HashSet<String> = config
159 .csharp
160 .as_ref()
161 .map(|c| c.exclude_functions.iter().cloned().collect())
162 .unwrap_or_default();
163 if let Some(ffi) = &config.ffi {
164 exclude_functions.extend(ffi.exclude_functions.iter().cloned());
165 }
166
167 let output_dir = resolve_output_dir(config.output_paths.get("csharp"), &config.name, "packages/csharp/");
168
169 let base_path = PathBuf::from(&output_dir).join(namespace.replace('.', "/"));
170
171 let mut files = Vec::new();
172
173 let exception_class_name = format!("{}Exception", to_csharp_name(&api.crate_name));
175
176 files.push(GeneratedFile {
178 path: base_path.join("NativeMethods.cs"),
179 content: strip_trailing_whitespace(&functions::gen_native_methods(
180 api,
181 &namespace,
182 &lib_name,
183 &prefix,
184 &bridge_param_names,
185 &bridge_type_aliases,
186 has_visitor_callbacks,
187 &config.trait_bridges,
188 &streaming_methods,
189 &streaming_methods_meta,
190 &exclude_functions,
191 &config.client_constructors,
192 )),
193 generated_header: true,
194 });
195
196 if !api.errors.is_empty() {
198 for error in &api.errors {
199 let error_files =
200 alef_codegen::error_gen::gen_csharp_error_types(error, &namespace, Some(&exception_class_name));
201 for (class_name, content) in error_files {
202 files.push(GeneratedFile {
203 path: base_path.join(format!("{}.cs", class_name)),
204 content: strip_trailing_whitespace(&content),
205 generated_header: false, });
207 }
208 }
209 }
210
211 if api.errors.is_empty()
213 || !api
214 .errors
215 .iter()
216 .any(|e| format!("{}Exception", e.name) == exception_class_name)
217 {
218 files.push(GeneratedFile {
219 path: base_path.join(format!("{}.cs", exception_class_name)),
220 content: strip_trailing_whitespace(&errors::gen_exception_class(&namespace, &exception_class_name)),
221 generated_header: true,
222 });
223 }
224
225 let base_class_name = to_csharp_name(&api.crate_name);
227 let wrapper_class_name = if namespace == base_class_name {
228 format!("{}Lib", base_class_name)
229 } else {
230 base_class_name
231 };
232 files.push(GeneratedFile {
233 path: base_path.join(format!("{}.cs", wrapper_class_name)),
234 content: strip_trailing_whitespace(&methods::gen_wrapper_class(
235 api,
236 &namespace,
237 &wrapper_class_name,
238 &exception_class_name,
239 &prefix,
240 &bridge_param_names,
241 &bridge_type_aliases,
242 has_visitor_callbacks,
243 &streaming_methods,
244 &streaming_methods_meta,
245 &exclude_functions,
246 &config.trait_bridges,
247 )),
248 generated_header: true,
249 });
250
251 if has_visitor_callbacks {
253 let visitor_bridge_cfg = config
256 .trait_bridges
257 .iter()
258 .find(|b| b.bind_via == alef_core::config::BridgeBinding::OptionsField);
259 let trait_map: std::collections::HashMap<&str, &alef_core::ir::TypeDef> = api
260 .types
261 .iter()
262 .filter(|t| t.is_trait)
263 .map(|t| (t.name.as_str(), t))
264 .collect();
265 let visitor_trait = visitor_bridge_cfg.and_then(|b| trait_map.get(b.trait_name.as_str()).copied());
266
267 if let Some(trait_def) = visitor_trait {
268 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, trait_def) {
269 files.push(GeneratedFile {
270 path: base_path.join(filename),
271 content: strip_trailing_whitespace(&content),
272 generated_header: true,
273 });
274 }
275 } else {
276 let placeholder = alef_core::ir::TypeDef {
278 name: String::new(),
279 rust_path: String::new(),
280 original_rust_path: String::new(),
281 fields: vec![],
282 methods: vec![],
283 is_opaque: false,
284 is_clone: false,
285 is_copy: false,
286 is_trait: true,
287 has_default: false,
288 has_stripped_cfg_fields: false,
289 is_return_type: false,
290 serde_rename_all: None,
291 has_serde: false,
292 super_traits: vec![],
293 doc: String::new(),
294 cfg: None,
295 binding_excluded: false,
296 binding_exclusion_reason: None,
297 };
298 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, &placeholder) {
299 files.push(GeneratedFile {
300 path: base_path.join(filename),
301 content: strip_trailing_whitespace(&content),
302 generated_header: true,
303 });
304 }
305 }
306 delete_superseded_visitor_files(&base_path)?;
310 } else {
311 delete_stale_visitor_files(&base_path)?;
314 }
315
316 if !config.trait_bridges.is_empty() {
318 let trait_defs: Vec<_> = api.types.iter().filter(|t| t.is_trait).collect();
319 let bridges: Vec<_> = config
320 .trait_bridges
321 .iter()
322 .filter_map(|cfg| {
323 let trait_name = cfg.trait_name.clone();
324 trait_defs
325 .iter()
326 .find(|t| t.name == trait_name)
327 .map(|trait_def| (trait_name, cfg, *trait_def))
328 })
329 .collect();
330
331 if !bridges.is_empty() {
332 let visible_type_names: HashSet<&str> = api
336 .types
337 .iter()
338 .filter(|t| !t.is_trait)
339 .map(|t| t.name.as_str())
340 .chain(api.enums.iter().map(|e| e.name.as_str()))
341 .collect();
342 let (filename, content) =
343 crate::trait_bridge::gen_trait_bridges_file(&namespace, &prefix, &bridges, &visible_type_names);
344 files.push(GeneratedFile {
345 path: base_path.join(filename),
346 content: strip_trailing_whitespace(&content),
347 generated_header: true,
348 });
349 }
350 }
351
352 let enum_names: HashSet<String> = api.enums.iter().map(|e| csharp_type_name(&e.name)).collect();
354
355 let all_opaque_type_names: HashSet<String> = api
358 .types
359 .iter()
360 .filter(|t| t.is_opaque)
361 .map(|t| csharp_type_name(&t.name))
362 .collect();
363
364 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
366 if typ.is_opaque {
367 let type_filename = csharp_type_name(&typ.name);
368 let client_ctor = config.client_constructors.get(&typ.name);
369 files.push(GeneratedFile {
370 path: base_path.join(format!("{}.cs", type_filename)),
371 content: strip_trailing_whitespace(&types::gen_opaque_handle(
372 typ,
373 &namespace,
374 &exception_class_name,
375 &enum_names,
376 &streaming_methods,
377 &streaming_methods_meta,
378 &all_opaque_type_names,
379 client_ctor,
380 )),
381 generated_header: true,
382 });
383 }
384 }
385
386 let complex_enums: HashSet<String> = HashSet::new();
390
391 let tagged_union_enums: HashSet<String> = api
397 .enums
398 .iter()
399 .filter(|e| e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty()))
400 .map(|e| csharp_type_name(&e.name))
401 .collect();
402
403 let custom_converter_enums: HashSet<String> = api
409 .enums
410 .iter()
411 .filter(|e| {
412 let is_tagged_union = e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty());
414 if is_tagged_union {
415 return false;
416 }
417 let rename_all_differs = matches!(
422 e.serde_rename_all.as_deref(),
423 Some("kebab-case") | Some("SCREAMING-KEBAB-CASE") | Some("camelCase") | Some("PascalCase")
424 );
425 if rename_all_differs {
426 return true;
427 }
428 e.variants.iter().any(|v| {
430 if let Some(ref rename) = v.serde_rename {
431 let snake = enums::apply_rename_all(&v.name, e.serde_rename_all.as_deref());
432 rename != &snake
433 } else {
434 false
435 }
436 })
437 })
438 .map(|e| csharp_type_name(&e.name))
439 .collect();
440
441 let lang_rename_all = config.serde_rename_all_for_language(Language::Csharp);
443
444 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
446 if !typ.is_opaque {
447 let has_visible_fields = binding_fields(&typ.fields).next().is_some();
450 let has_named_fields = binding_fields(&typ.fields).any(|f| !is_tuple_field(f));
451 if has_visible_fields && !has_named_fields {
452 continue;
453 }
454 if has_visitor_callbacks && bridge_associated_types.contains(typ.name.as_str()) {
456 continue;
457 }
458
459 let type_filename = csharp_type_name(&typ.name);
460 let excluded_types: HashSet<String> =
461 api.excluded_type_paths.keys().map(|n| csharp_type_name(n)).collect();
462 files.push(GeneratedFile {
463 path: base_path.join(format!("{}.cs", type_filename)),
464 content: strip_trailing_whitespace(&types::gen_record_type(
465 typ,
466 &namespace,
467 &prefix,
468 &enum_names,
469 &complex_enums,
470 &custom_converter_enums,
471 &lang_rename_all,
472 &bridge_type_aliases,
473 &exception_class_name,
474 &excluded_types,
475 &tagged_union_enums,
476 )),
477 generated_header: true,
478 });
479 }
480 }
481
482 for enum_def in &api.enums {
484 if has_visitor_callbacks && bridge_associated_types.contains(enum_def.name.as_str()) {
486 continue;
487 }
488 let enum_filename = csharp_type_name(&enum_def.name);
489 files.push(GeneratedFile {
490 path: base_path.join(format!("{}.cs", enum_filename)),
491 content: strip_trailing_whitespace(&enums::gen_enum(enum_def, &namespace)),
492 generated_header: true,
493 });
494 }
495
496 let needs_byte_array_converter = api
499 .types
500 .iter()
501 .any(|t| !t.is_opaque && t.fields.iter().any(|f| !f.optional && matches!(f.ty, TypeRef::Bytes)));
502 if needs_byte_array_converter {
503 files.push(GeneratedFile {
504 path: base_path.join("ByteArrayToIntArrayConverter.cs"),
505 content: types::gen_byte_array_to_int_array_converter(&namespace),
506 generated_header: true,
507 });
508 }
509
510 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Csharp)?;
512
513 files.push(GeneratedFile {
517 path: PathBuf::from("packages/csharp/Directory.Build.props"),
518 content: gen_directory_build_props(),
519 generated_header: true,
520 });
521
522 Ok(files)
523 }
524
525 fn generate_public_api(
530 &self,
531 _api: &ApiSurface,
532 _config: &ResolvedCrateConfig,
533 ) -> anyhow::Result<Vec<GeneratedFile>> {
534 Ok(vec![])
536 }
537
538 fn build_config(&self) -> Option<BuildConfig> {
539 Some(BuildConfig {
540 tool: "dotnet",
541 crate_suffix: "",
542 build_dep: BuildDependency::Ffi,
543 post_build: vec![],
544 })
545 }
546}
547
548pub(super) fn is_tuple_field(field: &FieldDef) -> bool {
550 (field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
551 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
552}
553
554pub(super) fn strip_trailing_whitespace(content: &str) -> String {
556 let mut result: String = content
557 .lines()
558 .map(|line| line.trim_end())
559 .collect::<Vec<_>>()
560 .join("\n");
561 if !result.ends_with('\n') {
562 result.push('\n');
563 }
564 result
565}
566
567pub(super) fn csharp_file_header() -> String {
569 let mut out = hash::header(CommentStyle::DoubleSlash);
570 out.push_str("#nullable enable\n\n");
571 out
572}
573
574fn gen_directory_build_props() -> String {
577 "<!-- auto-generated by alef (generate_bindings) -->\n\
578<Project>\n \
579<PropertyGroup>\n \
580<Nullable>enable</Nullable>\n \
581<LangVersion>latest</LangVersion>\n \
582<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n \
583</PropertyGroup>\n\
584</Project>\n"
585 .to_string()
586}
587
588fn delete_superseded_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
593 let superseded = ["IVisitor.cs", "VisitorCallbacks.cs"];
594 for filename in superseded {
595 let path = base_path.join(filename);
596 if path.exists() {
597 std::fs::remove_file(&path)
598 .map_err(|e| anyhow::anyhow!("Failed to delete superseded visitor file {}: {}", path.display(), e))?;
599 }
600 }
601 Ok(())
602}
603
604fn delete_stale_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
608 let stale_files = vec!["IVisitor.cs", "VisitorCallbacks.cs", "NodeContext.cs", "VisitResult.cs"];
609
610 for filename in stale_files {
611 let path = base_path.join(filename);
612 if path.exists() {
613 std::fs::remove_file(&path)
614 .map_err(|e| anyhow::anyhow!("Failed to delete stale visitor file {}: {}", path.display(), e))?;
615 }
616 }
617
618 Ok(())
619}
620
621use alef_core::ir::PrimitiveType;
626
627pub(super) fn pinvoke_return_type(ty: &TypeRef) -> &'static str {
634 match ty {
635 TypeRef::Unit => "void",
636 TypeRef::Primitive(PrimitiveType::Bool) => "int",
638 TypeRef::Primitive(PrimitiveType::U8) => "byte",
640 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
641 TypeRef::Primitive(PrimitiveType::U32) => "uint",
642 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
643 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
644 TypeRef::Primitive(PrimitiveType::I16) => "short",
645 TypeRef::Primitive(PrimitiveType::I32) => "int",
646 TypeRef::Primitive(PrimitiveType::I64) => "long",
647 TypeRef::Primitive(PrimitiveType::F32) => "float",
648 TypeRef::Primitive(PrimitiveType::F64) => "double",
649 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
650 TypeRef::Primitive(PrimitiveType::Isize) => "long",
651 TypeRef::Duration => "ulong",
653 TypeRef::String
655 | TypeRef::Char
656 | TypeRef::Bytes
657 | TypeRef::Optional(_)
658 | TypeRef::Vec(_)
659 | TypeRef::Map(_, _)
660 | TypeRef::Named(_)
661 | TypeRef::Path
662 | TypeRef::Json => "IntPtr",
663 }
664}
665
666pub(super) fn pinvoke_param_type(ty: &TypeRef) -> &'static str {
673 match ty {
674 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "string",
675 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes | TypeRef::Optional(_) => "IntPtr",
677 TypeRef::Unit => "void",
678 TypeRef::Primitive(PrimitiveType::Bool) => "int",
679 TypeRef::Primitive(PrimitiveType::U8) => "byte",
680 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
681 TypeRef::Primitive(PrimitiveType::U32) => "uint",
682 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
683 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
684 TypeRef::Primitive(PrimitiveType::I16) => "short",
685 TypeRef::Primitive(PrimitiveType::I32) => "int",
686 TypeRef::Primitive(PrimitiveType::I64) => "long",
687 TypeRef::Primitive(PrimitiveType::F32) => "float",
688 TypeRef::Primitive(PrimitiveType::F64) => "double",
689 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
690 TypeRef::Primitive(PrimitiveType::Isize) => "long",
691 TypeRef::Duration => "ulong",
692 }
693}
694
695pub(super) fn is_bridge_param(
698 param: &alef_core::ir::ParamDef,
699 bridge_param_names: &HashSet<String>,
700 bridge_type_aliases: &HashSet<String>,
701) -> bool {
702 bridge_param_names.contains(¶m.name)
703 || matches!(¶m.ty, alef_core::ir::TypeRef::Named(n) if bridge_type_aliases.contains(n))
704}
705
706pub(super) fn returns_string(ty: &TypeRef) -> bool {
708 matches!(ty, TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json)
709}
710
711pub(super) fn returns_bool_via_int(ty: &TypeRef) -> bool {
713 matches!(ty, TypeRef::Primitive(PrimitiveType::Bool))
714}
715
716pub(super) fn returns_json_object(ty: &TypeRef) -> bool {
718 matches!(
719 ty,
720 TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) | TypeRef::Bytes | TypeRef::Optional(_)
721 )
722}
723
724pub(super) fn returns_ptr(ty: &TypeRef) -> bool {
727 matches!(
728 ty,
729 TypeRef::String
730 | TypeRef::Char
731 | TypeRef::Path
732 | TypeRef::Json
733 | TypeRef::Named(_)
734 | TypeRef::Vec(_)
735 | TypeRef::Map(_, _)
736 | TypeRef::Bytes
737 | TypeRef::Optional(_)
738 )
739}
740
741pub(super) fn native_call_arg(
747 ty: &TypeRef,
748 param_name: &str,
749 optional: bool,
750 true_opaque_types: &HashSet<String>,
751) -> String {
752 match ty {
753 TypeRef::Named(type_name) if true_opaque_types.contains(type_name) => {
754 let bang = if optional { "!" } else { "" };
756 format!("{param_name}{bang}.Handle")
757 }
758 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
759 format!("{param_name}Handle")
760 }
761 TypeRef::Bytes => {
762 format!("{param_name}Handle.AddrOfPinnedObject()")
763 }
764 TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool) => {
765 if optional {
767 format!("({param_name}?.Value ? 1 : 0)")
768 } else {
769 format!("({param_name} ? 1 : 0)")
770 }
771 }
772 ty => {
773 if optional {
774 if let TypeRef::Primitive(prim) = ty {
782 use alef_core::ir::PrimitiveType;
783 let sentinel = match prim {
784 PrimitiveType::U8 => "byte.MaxValue",
785 PrimitiveType::U16 => "ushort.MaxValue",
786 PrimitiveType::U32 => "uint.MaxValue",
787 PrimitiveType::U64 | PrimitiveType::Usize => "ulong.MaxValue",
788 PrimitiveType::I8 => "sbyte.MaxValue",
789 PrimitiveType::I16 => "short.MaxValue",
790 PrimitiveType::I32 => "int.MaxValue",
791 PrimitiveType::I64 | PrimitiveType::Isize => "long.MaxValue",
792 PrimitiveType::F32 => "float.NaN",
793 PrimitiveType::F64 => "double.NaN",
794 PrimitiveType::Bool => unreachable!("handled above"),
795 };
796 format!("{param_name} ?? {sentinel}")
797 } else if matches!(ty, TypeRef::Duration) {
798 format!("{param_name}.GetValueOrDefault()")
799 } else {
800 format!("{param_name}!")
801 }
802 } else {
803 param_name.to_string()
804 }
805 }
806 }
807}
808
809pub(super) fn emit_named_param_setup(
814 out: &mut String,
815 params: &[alef_core::ir::ParamDef],
816 indent: &str,
817 true_opaque_types: &HashSet<String>,
818 exception_name: &str,
819) {
820 for param in params {
821 let param_name = param.name.to_lower_camel_case();
822 let json_var = format!("{param_name}Json");
823 let handle_var = format!("{param_name}Handle");
824
825 match ¶m.ty {
826 TypeRef::Named(type_name) => {
827 if true_opaque_types.contains(type_name) {
830 continue;
831 }
832 let from_json_method = format!("{}FromJson", csharp_type_name(type_name));
833
834 let is_config_param = param.name == "config";
836 let param_to_serialize = if is_config_param {
837 let type_pascal = csharp_type_name(type_name);
838 format!("({} ?? new {}())", param_name, type_pascal)
839 } else {
840 param_name.to_string()
841 };
842
843 if param.optional && !is_config_param {
844 out.push_str(&crate::template_env::render(
848 "named_param_handle_from_json_optional.jinja",
849 minijinja::context! {
850 indent,
851 handle_var => &handle_var,
852 from_json_method => &from_json_method,
853 json_var => &json_var,
854 param_name => ¶m_name,
855 exception_name => exception_name,
856 },
857 ));
858 } else {
859 out.push_str(&crate::template_env::render(
860 "named_param_json_serialize.jinja",
861 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_to_serialize },
862 ));
863 out.push_str(&crate::template_env::render(
864 "named_param_handle_from_json.jinja",
865 minijinja::context! {
866 indent,
867 handle_var => &handle_var,
868 from_json_method => &from_json_method,
869 json_var => &json_var,
870 exception_name => exception_name,
871 },
872 ));
873 }
874 }
875 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
876 out.push_str(&crate::template_env::render(
878 "named_param_json_serialize.jinja",
879 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
880 ));
881 out.push_str(&crate::template_env::render(
882 "named_param_handle_string.jinja",
883 minijinja::context! { indent, handle_var => &handle_var, json_var => &json_var },
884 ));
885 }
886 TypeRef::Bytes => {
887 out.push_str(&crate::template_env::render(
889 "named_param_handle_pin.jinja",
890 minijinja::context! { indent, handle_var => &handle_var, param_name => ¶m_name },
891 ));
892 }
893 _ => {}
894 }
895 }
896}
897
898pub(super) fn emit_named_param_teardown(
903 out: &mut String,
904 params: &[alef_core::ir::ParamDef],
905 true_opaque_types: &HashSet<String>,
906) {
907 for param in params {
908 let param_name = param.name.to_lower_camel_case();
909 let handle_var = format!("{param_name}Handle");
910 match ¶m.ty {
911 TypeRef::Named(type_name) => {
912 if true_opaque_types.contains(type_name) {
913 continue;
915 }
916 let free_method = format!("{}Free", csharp_type_name(type_name));
917 out.push_str(&crate::template_env::render(
918 "named_param_teardown_free.jinja",
919 minijinja::context! { indent => " ", free_method => &free_method, handle_var => &handle_var },
920 ));
921 }
922 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
923 out.push_str(&crate::template_env::render(
924 "named_param_teardown_hglobal.jinja",
925 minijinja::context! { indent => " ", handle_var => &handle_var },
926 ));
927 }
928 TypeRef::Bytes => {
929 out.push_str(&crate::template_env::render(
930 "named_param_teardown_gchandle.jinja",
931 minijinja::context! { indent => " ", handle_var => &handle_var },
932 ));
933 }
934 _ => {}
935 }
936 }
937}
938
939pub(super) fn emit_named_param_teardown_indented(
941 out: &mut String,
942 params: &[alef_core::ir::ParamDef],
943 indent: &str,
944 true_opaque_types: &HashSet<String>,
945) {
946 for param in params {
947 let param_name = param.name.to_lower_camel_case();
948 let handle_var = format!("{param_name}Handle");
949 match ¶m.ty {
950 TypeRef::Named(type_name) => {
951 if true_opaque_types.contains(type_name) {
952 continue;
954 }
955 let free_method = format!("{}Free", csharp_type_name(type_name));
956 out.push_str(&crate::template_env::render(
957 "named_param_teardown_free.jinja",
958 minijinja::context! { indent, free_method => &free_method, handle_var => &handle_var },
959 ));
960 }
961 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
962 out.push_str(&crate::template_env::render(
963 "named_param_teardown_hglobal.jinja",
964 minijinja::context! { indent, handle_var => &handle_var },
965 ));
966 }
967 TypeRef::Bytes => {
968 out.push_str(&crate::template_env::render(
969 "named_param_teardown_gchandle.jinja",
970 minijinja::context! { indent, handle_var => &handle_var },
971 ));
972 }
973 _ => {}
974 }
975 }
976}
977
978use heck::ToLowerCamelCase;