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