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(crate) fn sanitize_rust_syntax_for_csharp(doc: &str) -> String {
35 alef_codegen::doc_emission::sanitize_rust_idioms(doc, alef_codegen::doc_emission::DocTarget::CSharpDoc)
36}
37
38pub(crate) fn sanitize_doc_lines_for_csharp(doc: &str) -> Vec<String> {
45 if doc.is_empty() {
46 return Vec::new();
47 }
48 let sanitized = sanitize_rust_syntax_for_csharp(doc);
49 if sanitized.trim().is_empty() {
50 return Vec::new();
51 }
52 sanitized.lines().map(ToString::to_string).collect()
53}
54
55pub struct CsharpBackend;
56
57impl CsharpBackend {
58 }
60
61fn effective_exclude_types(config: &ResolvedCrateConfig) -> HashSet<String> {
62 let mut exclude_types: HashSet<String> = config
63 .ffi
64 .as_ref()
65 .map(|ffi| ffi.exclude_types.iter().cloned().collect())
66 .unwrap_or_default();
67 if let Some(csharp) = &config.csharp {
68 exclude_types.extend(csharp.exclude_types.iter().cloned());
69 }
70 exclude_types
71}
72
73fn references_excluded_type(ty: &TypeRef, exclude_types: &HashSet<String>) -> bool {
74 exclude_types.iter().any(|name| ty.references_named(name))
75}
76
77fn signature_references_excluded_type(
78 params: &[alef_core::ir::ParamDef],
79 return_type: &TypeRef,
80 exclude_types: &HashSet<String>,
81) -> bool {
82 references_excluded_type(return_type, exclude_types)
83 || params
84 .iter()
85 .any(|param| references_excluded_type(¶m.ty, exclude_types))
86}
87
88fn api_without_excluded_types(api: &ApiSurface, exclude_types: &HashSet<String>) -> ApiSurface {
89 let mut filtered = api.clone();
90 filtered.types.retain(|typ| !exclude_types.contains(&typ.name));
91 for typ in &mut filtered.types {
92 typ.fields
93 .retain(|field| !references_excluded_type(&field.ty, exclude_types));
94 typ.methods
95 .retain(|method| !signature_references_excluded_type(&method.params, &method.return_type, exclude_types));
96 }
97 filtered
98 .enums
99 .retain(|enum_def| !exclude_types.contains(&enum_def.name));
100 for enum_def in &mut filtered.enums {
101 for variant in &mut enum_def.variants {
102 variant
103 .fields
104 .retain(|field| !references_excluded_type(&field.ty, exclude_types));
105 }
106 }
107 filtered
108 .functions
109 .retain(|func| !signature_references_excluded_type(&func.params, &func.return_type, exclude_types));
110 filtered.errors.retain(|error| !exclude_types.contains(&error.name));
111 filtered
112}
113
114impl Backend for CsharpBackend {
115 fn name(&self) -> &str {
116 "csharp"
117 }
118
119 fn language(&self) -> Language {
120 Language::Csharp
121 }
122
123 fn capabilities(&self) -> Capabilities {
124 Capabilities {
125 supports_async: true,
126 supports_classes: true,
127 supports_enums: true,
128 supports_option: true,
129 supports_result: true,
130 ..Capabilities::default()
131 }
132 }
133
134 fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
135 let exclude_types = effective_exclude_types(config);
136 let filtered_api;
137 let api = if exclude_types.is_empty() {
138 api
139 } else {
140 filtered_api = api_without_excluded_types(api, &exclude_types);
141 &filtered_api
142 };
143 let namespace = config.csharp_namespace();
144 let prefix = config.ffi_prefix();
145 let lib_name = config.ffi_lib_name();
146
147 let bridge_param_names: HashSet<String> = config
150 .trait_bridges
151 .iter()
152 .filter_map(|b| b.param_name.clone())
153 .collect();
154 let bridge_type_aliases: HashSet<String> = config
155 .trait_bridges
156 .iter()
157 .filter_map(|b| b.type_alias.clone())
158 .collect();
159 let has_visitor_callbacks = config.ffi.as_ref().map(|f| f.visitor_callbacks).unwrap_or(false);
161 let bridge_associated_types = config.bridge_associated_types();
162
163 let streaming_methods: HashSet<String> = config
168 .adapters
169 .iter()
170 .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
171 .map(|a| a.name.clone())
172 .collect();
173 let streaming_methods_meta: HashMap<String, StreamingMethodMeta> = config
174 .adapters
175 .iter()
176 .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
177 .filter_map(|a| {
178 let owner_type = a.owner_type.clone()?;
179 let item_type = a.item_type.clone()?;
180 Some((a.name.clone(), StreamingMethodMeta { owner_type, item_type }))
181 })
182 .collect();
183
184 let mut exclude_functions: HashSet<String> = config
186 .csharp
187 .as_ref()
188 .map(|c| c.exclude_functions.iter().cloned().collect())
189 .unwrap_or_default();
190 if let Some(ffi) = &config.ffi {
191 exclude_functions.extend(ffi.exclude_functions.iter().cloned());
192 }
193
194 let output_dir = resolve_output_dir(config.output_paths.get("csharp"), &config.name, "packages/csharp/");
195
196 let base_path = PathBuf::from(&output_dir).join(namespace.replace('.', "/"));
197
198 let mut files = Vec::new();
199
200 let exception_class_name = format!("{}Exception", to_csharp_name(&api.crate_name));
202
203 files.push(GeneratedFile {
205 path: base_path.join("NativeMethods.cs"),
206 content: strip_trailing_whitespace(&functions::gen_native_methods(
207 api,
208 &namespace,
209 &lib_name,
210 &prefix,
211 &bridge_param_names,
212 &bridge_type_aliases,
213 has_visitor_callbacks,
214 &config.trait_bridges,
215 &streaming_methods,
216 &streaming_methods_meta,
217 &exclude_functions,
218 &config.client_constructors,
219 )),
220 generated_header: true,
221 });
222
223 if !api.errors.is_empty() {
225 for error in &api.errors {
226 let error_files =
227 alef_codegen::error_gen::gen_csharp_error_types(error, &namespace, Some(&exception_class_name));
228 for (class_name, content) in error_files {
229 files.push(GeneratedFile {
230 path: base_path.join(format!("{}.cs", class_name)),
231 content: strip_trailing_whitespace(&content),
232 generated_header: false, });
234 }
235 }
236 }
237
238 if api.errors.is_empty()
240 || !api
241 .errors
242 .iter()
243 .any(|e| format!("{}Exception", e.name) == exception_class_name)
244 {
245 files.push(GeneratedFile {
246 path: base_path.join(format!("{}.cs", exception_class_name)),
247 content: strip_trailing_whitespace(&errors::gen_exception_class(&namespace, &exception_class_name)),
248 generated_header: true,
249 });
250 }
251
252 let all_opaque_type_names: HashSet<String> = api
254 .types
255 .iter()
256 .filter(|t| t.is_opaque)
257 .map(|t| csharp_type_name(&t.name))
258 .collect();
259
260 let base_class_name = to_csharp_name(&api.crate_name);
262 let wrapper_class_name = if namespace == base_class_name {
263 format!("{}Lib", base_class_name)
264 } else {
265 base_class_name
266 };
267 files.push(GeneratedFile {
268 path: base_path.join(format!("{}.cs", wrapper_class_name)),
269 content: strip_trailing_whitespace(&methods::gen_wrapper_class(
270 api,
271 &namespace,
272 &wrapper_class_name,
273 &exception_class_name,
274 &prefix,
275 &bridge_param_names,
276 &bridge_type_aliases,
277 has_visitor_callbacks,
278 &streaming_methods,
279 &streaming_methods_meta,
280 &exclude_functions,
281 &config.trait_bridges,
282 &all_opaque_type_names,
283 )),
284 generated_header: true,
285 });
286
287 if has_visitor_callbacks {
289 let visitor_bridge_cfg = config
292 .trait_bridges
293 .iter()
294 .find(|b| b.bind_via == alef_core::config::BridgeBinding::OptionsField);
295 let trait_map: std::collections::HashMap<&str, &alef_core::ir::TypeDef> = api
296 .types
297 .iter()
298 .filter(|t| t.is_trait)
299 .map(|t| (t.name.as_str(), t))
300 .collect();
301 let visitor_trait = visitor_bridge_cfg.and_then(|b| trait_map.get(b.trait_name.as_str()).copied());
302
303 if let Some(trait_def) = visitor_trait {
304 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, trait_def) {
305 files.push(GeneratedFile {
306 path: base_path.join(filename),
307 content: strip_trailing_whitespace(&content),
308 generated_header: true,
309 });
310 }
311 } else {
312 let placeholder = alef_core::ir::TypeDef {
314 name: String::new(),
315 rust_path: String::new(),
316 original_rust_path: String::new(),
317 fields: vec![],
318 methods: vec![],
319 is_opaque: false,
320 is_clone: false,
321 is_copy: false,
322 is_trait: true,
323 has_default: false,
324 has_stripped_cfg_fields: false,
325 is_return_type: false,
326 serde_rename_all: None,
327 has_serde: false,
328 super_traits: vec![],
329 doc: String::new(),
330 cfg: None,
331 binding_excluded: false,
332 binding_exclusion_reason: None,
333 };
334 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, &placeholder) {
335 files.push(GeneratedFile {
336 path: base_path.join(filename),
337 content: strip_trailing_whitespace(&content),
338 generated_header: true,
339 });
340 }
341 }
342 delete_superseded_visitor_files(&base_path)?;
346 } else {
347 delete_stale_visitor_files(&base_path)?;
350 }
351
352 if !config.trait_bridges.is_empty() {
354 let trait_defs: Vec<_> = api.types.iter().filter(|t| t.is_trait).collect();
355 let bridges: Vec<_> = config
356 .trait_bridges
357 .iter()
358 .filter_map(|cfg| {
359 let trait_name = cfg.trait_name.clone();
360 trait_defs
361 .iter()
362 .find(|t| t.name == trait_name)
363 .map(|trait_def| (trait_name, cfg, *trait_def))
364 })
365 .collect();
366
367 if !bridges.is_empty() {
368 let visible_type_names: HashSet<&str> = api
372 .types
373 .iter()
374 .filter(|t| !t.is_trait)
375 .map(|t| t.name.as_str())
376 .chain(api.enums.iter().map(|e| e.name.as_str()))
377 .collect();
378 let (filename, content) =
379 crate::trait_bridge::gen_trait_bridges_file(&namespace, &prefix, &bridges, &visible_type_names);
380 files.push(GeneratedFile {
381 path: base_path.join(filename),
382 content: strip_trailing_whitespace(&content),
383 generated_header: true,
384 });
385 }
386 }
387
388 let enum_names: HashSet<String> = api.enums.iter().map(|e| csharp_type_name(&e.name)).collect();
390
391 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
393 if typ.is_opaque {
394 let type_filename = csharp_type_name(&typ.name);
395 let client_ctor = config.client_constructors.get(&typ.name);
396 files.push(GeneratedFile {
397 path: base_path.join(format!("{}.cs", type_filename)),
398 content: strip_trailing_whitespace(&types::gen_opaque_handle(
399 typ,
400 &namespace,
401 &exception_class_name,
402 &enum_names,
403 &streaming_methods,
404 &streaming_methods_meta,
405 &all_opaque_type_names,
406 client_ctor,
407 )),
408 generated_header: true,
409 });
410 }
411 }
412
413 let complex_enums: HashSet<String> = HashSet::new();
417
418 let tagged_union_enums: HashSet<String> = api
424 .enums
425 .iter()
426 .filter(|e| e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty()))
427 .map(|e| csharp_type_name(&e.name))
428 .collect();
429
430 let custom_converter_enums: HashSet<String> = api
436 .enums
437 .iter()
438 .filter(|e| {
439 let is_tagged_union = e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty());
441 if is_tagged_union {
442 return false;
443 }
444 let rename_all_differs = matches!(
449 e.serde_rename_all.as_deref(),
450 Some("kebab-case") | Some("SCREAMING-KEBAB-CASE") | Some("camelCase") | Some("PascalCase")
451 );
452 if rename_all_differs {
453 return true;
454 }
455 e.variants.iter().any(|v| {
457 if let Some(ref rename) = v.serde_rename {
458 let snake = enums::apply_rename_all(&v.name, e.serde_rename_all.as_deref());
459 rename != &snake
460 } else {
461 false
462 }
463 })
464 })
465 .map(|e| csharp_type_name(&e.name))
466 .collect();
467
468 let lang_rename_all = config.serde_rename_all_for_language(Language::Csharp);
470
471 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
473 if !typ.is_opaque {
474 let has_visible_fields = binding_fields(&typ.fields).next().is_some();
477 let has_named_fields = binding_fields(&typ.fields).any(|f| !is_tuple_field(f));
478 if has_visible_fields && !has_named_fields {
479 continue;
480 }
481 if has_visitor_callbacks && bridge_associated_types.contains(typ.name.as_str()) {
483 continue;
484 }
485
486 let type_filename = csharp_type_name(&typ.name);
487 let excluded_types: HashSet<String> =
488 api.excluded_type_paths.keys().map(|n| csharp_type_name(n)).collect();
489 files.push(GeneratedFile {
490 path: base_path.join(format!("{}.cs", type_filename)),
491 content: strip_trailing_whitespace(&types::gen_record_type(
492 typ,
493 &namespace,
494 &prefix,
495 &enum_names,
496 &complex_enums,
497 &custom_converter_enums,
498 &lang_rename_all,
499 &bridge_type_aliases,
500 &exception_class_name,
501 &excluded_types,
502 &tagged_union_enums,
503 )),
504 generated_header: true,
505 });
506 }
507 }
508
509 for enum_def in &api.enums {
511 if has_visitor_callbacks && bridge_associated_types.contains(enum_def.name.as_str()) {
513 continue;
514 }
515 let enum_filename = csharp_type_name(&enum_def.name);
516 files.push(GeneratedFile {
517 path: base_path.join(format!("{}.cs", enum_filename)),
518 content: strip_trailing_whitespace(&enums::gen_enum(enum_def, &namespace)),
519 generated_header: true,
520 });
521 }
522
523 let needs_byte_array_converter = api
526 .types
527 .iter()
528 .any(|t| !t.is_opaque && t.fields.iter().any(|f| !f.optional && matches!(f.ty, TypeRef::Bytes)));
529 if needs_byte_array_converter {
530 files.push(GeneratedFile {
531 path: base_path.join("ByteArrayToIntArrayConverter.cs"),
532 content: types::gen_byte_array_to_int_array_converter(&namespace),
533 generated_header: true,
534 });
535 }
536
537 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Csharp)?;
539
540 files.push(GeneratedFile {
544 path: PathBuf::from("packages/csharp/Directory.Build.props"),
545 content: gen_directory_build_props(),
546 generated_header: true,
547 });
548
549 Ok(files)
550 }
551
552 fn generate_public_api(
557 &self,
558 _api: &ApiSurface,
559 _config: &ResolvedCrateConfig,
560 ) -> anyhow::Result<Vec<GeneratedFile>> {
561 Ok(vec![])
563 }
564
565 fn build_config(&self) -> Option<BuildConfig> {
566 Some(BuildConfig {
567 tool: "dotnet",
568 crate_suffix: "",
569 build_dep: BuildDependency::Ffi,
570 post_build: vec![],
571 })
572 }
573}
574
575pub(super) fn is_tuple_field(field: &FieldDef) -> bool {
577 (field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
578 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
579}
580
581pub(super) fn strip_trailing_whitespace(content: &str) -> String {
583 let mut result: String = content
584 .lines()
585 .map(|line| line.trim_end())
586 .collect::<Vec<_>>()
587 .join("\n");
588 if !result.ends_with('\n') {
589 result.push('\n');
590 }
591 result
592}
593
594pub(super) fn csharp_file_header() -> String {
596 let mut out = hash::header(CommentStyle::DoubleSlash);
597 out.push_str("#nullable enable\n\n");
598 out
599}
600
601fn gen_directory_build_props() -> String {
604 "<!-- auto-generated by alef (generate_bindings) -->\n\
605<Project>\n \
606<PropertyGroup>\n \
607<Nullable>enable</Nullable>\n \
608<LangVersion>latest</LangVersion>\n \
609<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n \
610</PropertyGroup>\n\
611</Project>\n"
612 .to_string()
613}
614
615fn delete_superseded_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
620 let superseded = ["IVisitor.cs", "VisitorCallbacks.cs"];
621 for filename in superseded {
622 let path = base_path.join(filename);
623 if path.exists() {
624 std::fs::remove_file(&path)
625 .map_err(|e| anyhow::anyhow!("Failed to delete superseded visitor file {}: {}", path.display(), e))?;
626 }
627 }
628 Ok(())
629}
630
631fn delete_stale_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
635 let stale_files = vec!["IVisitor.cs", "VisitorCallbacks.cs", "NodeContext.cs", "VisitResult.cs"];
636
637 for filename in stale_files {
638 let path = base_path.join(filename);
639 if path.exists() {
640 std::fs::remove_file(&path)
641 .map_err(|e| anyhow::anyhow!("Failed to delete stale visitor file {}: {}", path.display(), e))?;
642 }
643 }
644
645 Ok(())
646}
647
648use alef_core::ir::PrimitiveType;
653
654pub(super) fn pinvoke_return_type(ty: &TypeRef) -> &'static str {
661 match ty {
662 TypeRef::Unit => "void",
663 TypeRef::Primitive(PrimitiveType::Bool) => "int",
665 TypeRef::Primitive(PrimitiveType::U8) => "byte",
667 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
668 TypeRef::Primitive(PrimitiveType::U32) => "uint",
669 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
670 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
671 TypeRef::Primitive(PrimitiveType::I16) => "short",
672 TypeRef::Primitive(PrimitiveType::I32) => "int",
673 TypeRef::Primitive(PrimitiveType::I64) => "long",
674 TypeRef::Primitive(PrimitiveType::F32) => "float",
675 TypeRef::Primitive(PrimitiveType::F64) => "double",
676 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
677 TypeRef::Primitive(PrimitiveType::Isize) => "long",
678 TypeRef::Duration => "ulong",
680 TypeRef::String
682 | TypeRef::Char
683 | TypeRef::Bytes
684 | TypeRef::Optional(_)
685 | TypeRef::Vec(_)
686 | TypeRef::Map(_, _)
687 | TypeRef::Named(_)
688 | TypeRef::Path
689 | TypeRef::Json => "IntPtr",
690 }
691}
692
693pub(super) fn pinvoke_param_type(ty: &TypeRef) -> &'static str {
700 match ty {
701 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "string",
702 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes | TypeRef::Optional(_) => "IntPtr",
704 TypeRef::Unit => "void",
705 TypeRef::Primitive(PrimitiveType::Bool) => "int",
706 TypeRef::Primitive(PrimitiveType::U8) => "byte",
707 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
708 TypeRef::Primitive(PrimitiveType::U32) => "uint",
709 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
710 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
711 TypeRef::Primitive(PrimitiveType::I16) => "short",
712 TypeRef::Primitive(PrimitiveType::I32) => "int",
713 TypeRef::Primitive(PrimitiveType::I64) => "long",
714 TypeRef::Primitive(PrimitiveType::F32) => "float",
715 TypeRef::Primitive(PrimitiveType::F64) => "double",
716 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
717 TypeRef::Primitive(PrimitiveType::Isize) => "long",
718 TypeRef::Duration => "ulong",
719 }
720}
721
722pub(super) fn is_bridge_param(
725 param: &alef_core::ir::ParamDef,
726 bridge_param_names: &HashSet<String>,
727 bridge_type_aliases: &HashSet<String>,
728) -> bool {
729 bridge_param_names.contains(¶m.name)
730 || matches!(¶m.ty, alef_core::ir::TypeRef::Named(n) if bridge_type_aliases.contains(n))
731}
732
733pub(super) fn returns_string(ty: &TypeRef) -> bool {
735 matches!(ty, TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json)
736}
737
738pub(super) fn returns_bool_via_int(ty: &TypeRef) -> bool {
740 matches!(ty, TypeRef::Primitive(PrimitiveType::Bool))
741}
742
743pub(super) fn returns_json_object(ty: &TypeRef) -> bool {
745 matches!(
746 ty,
747 TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) | TypeRef::Bytes | TypeRef::Optional(_)
748 )
749}
750
751pub(super) fn returns_ptr(ty: &TypeRef) -> bool {
754 matches!(
755 ty,
756 TypeRef::String
757 | TypeRef::Char
758 | TypeRef::Path
759 | TypeRef::Json
760 | TypeRef::Named(_)
761 | TypeRef::Vec(_)
762 | TypeRef::Map(_, _)
763 | TypeRef::Bytes
764 | TypeRef::Optional(_)
765 )
766}
767
768pub(super) fn native_call_arg(
774 ty: &TypeRef,
775 param_name: &str,
776 optional: bool,
777 true_opaque_types: &HashSet<String>,
778) -> String {
779 match ty {
780 TypeRef::Named(type_name) if true_opaque_types.contains(type_name) => {
781 let bang = if optional { "!" } else { "" };
783 format!("{param_name}{bang}.Handle")
784 }
785 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
786 format!("{param_name}Handle")
787 }
788 TypeRef::Bytes => {
789 format!("{param_name}Handle.AddrOfPinnedObject()")
790 }
791 TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool) => {
792 if optional {
794 format!("({param_name}?.Value ? 1 : 0)")
795 } else {
796 format!("({param_name} ? 1 : 0)")
797 }
798 }
799 ty => {
800 if optional {
801 if let TypeRef::Primitive(prim) = ty {
809 use alef_core::ir::PrimitiveType;
810 let sentinel = match prim {
811 PrimitiveType::U8 => "byte.MaxValue",
812 PrimitiveType::U16 => "ushort.MaxValue",
813 PrimitiveType::U32 => "uint.MaxValue",
814 PrimitiveType::U64 | PrimitiveType::Usize => "ulong.MaxValue",
815 PrimitiveType::I8 => "sbyte.MaxValue",
816 PrimitiveType::I16 => "short.MaxValue",
817 PrimitiveType::I32 => "int.MaxValue",
818 PrimitiveType::I64 | PrimitiveType::Isize => "long.MaxValue",
819 PrimitiveType::F32 => "float.NaN",
820 PrimitiveType::F64 => "double.NaN",
821 PrimitiveType::Bool => unreachable!("handled above"),
822 };
823 format!("{param_name} ?? {sentinel}")
824 } else if matches!(ty, TypeRef::Duration) {
825 format!("{param_name}.GetValueOrDefault()")
826 } else {
827 format!("{param_name}!")
828 }
829 } else {
830 param_name.to_string()
831 }
832 }
833 }
834}
835
836pub(super) fn emit_named_param_setup(
841 out: &mut String,
842 params: &[alef_core::ir::ParamDef],
843 indent: &str,
844 true_opaque_types: &HashSet<String>,
845 exception_name: &str,
846) {
847 for param in params {
848 let param_name = param.name.to_lower_camel_case();
849 let json_var = format!("{param_name}Json");
850 let handle_var = format!("{param_name}Handle");
851
852 match ¶m.ty {
853 TypeRef::Named(type_name) => {
854 if true_opaque_types.contains(type_name) {
857 continue;
858 }
859 let from_json_method = format!("{}FromJson", csharp_type_name(type_name));
860
861 let is_config_param = param.name == "config";
863 let param_to_serialize = if is_config_param {
864 let type_pascal = csharp_type_name(type_name);
865 format!("({} ?? new {}())", param_name, type_pascal)
866 } else {
867 param_name.to_string()
868 };
869
870 if param.optional && !is_config_param {
871 out.push_str(&crate::template_env::render(
875 "named_param_handle_from_json_optional.jinja",
876 minijinja::context! {
877 indent,
878 handle_var => &handle_var,
879 from_json_method => &from_json_method,
880 json_var => &json_var,
881 param_name => ¶m_name,
882 exception_name => exception_name,
883 },
884 ));
885 } else {
886 out.push_str(&crate::template_env::render(
887 "named_param_json_serialize.jinja",
888 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_to_serialize },
889 ));
890 out.push_str(&crate::template_env::render(
891 "named_param_handle_from_json.jinja",
892 minijinja::context! {
893 indent,
894 handle_var => &handle_var,
895 from_json_method => &from_json_method,
896 json_var => &json_var,
897 exception_name => exception_name,
898 },
899 ));
900 }
901 }
902 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
903 out.push_str(&crate::template_env::render(
905 "named_param_json_serialize.jinja",
906 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
907 ));
908 out.push_str(&crate::template_env::render(
909 "named_param_handle_string.jinja",
910 minijinja::context! { indent, handle_var => &handle_var, json_var => &json_var },
911 ));
912 }
913 TypeRef::Bytes => {
914 out.push_str(&crate::template_env::render(
916 "named_param_handle_pin.jinja",
917 minijinja::context! { indent, handle_var => &handle_var, param_name => ¶m_name },
918 ));
919 }
920 _ => {}
921 }
922 }
923}
924
925pub(super) fn emit_named_param_teardown(
930 out: &mut String,
931 params: &[alef_core::ir::ParamDef],
932 true_opaque_types: &HashSet<String>,
933) {
934 for param in params {
935 let param_name = param.name.to_lower_camel_case();
936 let handle_var = format!("{param_name}Handle");
937 match ¶m.ty {
938 TypeRef::Named(type_name) => {
939 if true_opaque_types.contains(type_name) {
940 continue;
942 }
943 let free_method = format!("{}Free", csharp_type_name(type_name));
944 out.push_str(&crate::template_env::render(
945 "named_param_teardown_free.jinja",
946 minijinja::context! { indent => " ", free_method => &free_method, handle_var => &handle_var },
947 ));
948 }
949 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
950 out.push_str(&crate::template_env::render(
951 "named_param_teardown_hglobal.jinja",
952 minijinja::context! { indent => " ", handle_var => &handle_var },
953 ));
954 }
955 TypeRef::Bytes => {
956 out.push_str(&crate::template_env::render(
957 "named_param_teardown_gchandle.jinja",
958 minijinja::context! { indent => " ", handle_var => &handle_var },
959 ));
960 }
961 _ => {}
962 }
963 }
964}
965
966pub(super) fn emit_named_param_teardown_indented(
968 out: &mut String,
969 params: &[alef_core::ir::ParamDef],
970 indent: &str,
971 true_opaque_types: &HashSet<String>,
972) {
973 for param in params {
974 let param_name = param.name.to_lower_camel_case();
975 let handle_var = format!("{param_name}Handle");
976 match ¶m.ty {
977 TypeRef::Named(type_name) => {
978 if true_opaque_types.contains(type_name) {
979 continue;
981 }
982 let free_method = format!("{}Free", csharp_type_name(type_name));
983 out.push_str(&crate::template_env::render(
984 "named_param_teardown_free.jinja",
985 minijinja::context! { indent, free_method => &free_method, handle_var => &handle_var },
986 ));
987 }
988 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
989 out.push_str(&crate::template_env::render(
990 "named_param_teardown_hglobal.jinja",
991 minijinja::context! { indent, handle_var => &handle_var },
992 ));
993 }
994 TypeRef::Bytes => {
995 out.push_str(&crate::template_env::render(
996 "named_param_teardown_gchandle.jinja",
997 minijinja::context! { indent, handle_var => &handle_var },
998 ));
999 }
1000 _ => {}
1001 }
1002 }
1003}
1004
1005use heck::ToLowerCamelCase;