1use alef_codegen::shared::binding_fields;
2use alef_core::backend::{Backend, BuildConfig, BuildDependency, Capabilities, GeneratedFile};
3use alef_core::config::{AdapterPattern, Language, ResolvedCrateConfig, resolve_output_dir};
4use alef_core::hash::{self, CommentStyle};
5use alef_core::ir::{ApiSurface, FieldDef, TypeRef};
6use heck::ToPascalCase;
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", api.crate_name.to_pascal_case());
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 )),
192 generated_header: true,
193 });
194
195 if !api.errors.is_empty() {
197 for error in &api.errors {
198 let error_files =
199 alef_codegen::error_gen::gen_csharp_error_types(error, &namespace, Some(&exception_class_name));
200 for (class_name, content) in error_files {
201 files.push(GeneratedFile {
202 path: base_path.join(format!("{}.cs", class_name)),
203 content: strip_trailing_whitespace(&content),
204 generated_header: false, });
206 }
207 }
208 }
209
210 if api.errors.is_empty()
212 || !api
213 .errors
214 .iter()
215 .any(|e| format!("{}Exception", e.name) == exception_class_name)
216 {
217 files.push(GeneratedFile {
218 path: base_path.join(format!("{}.cs", exception_class_name)),
219 content: strip_trailing_whitespace(&errors::gen_exception_class(&namespace, &exception_class_name)),
220 generated_header: true,
221 });
222 }
223
224 let base_class_name = api.crate_name.to_pascal_case();
226 let wrapper_class_name = if namespace == base_class_name {
227 format!("{}Lib", base_class_name)
228 } else {
229 base_class_name
230 };
231 files.push(GeneratedFile {
232 path: base_path.join(format!("{}.cs", wrapper_class_name)),
233 content: strip_trailing_whitespace(&methods::gen_wrapper_class(
234 api,
235 &namespace,
236 &wrapper_class_name,
237 &exception_class_name,
238 &prefix,
239 &bridge_param_names,
240 &bridge_type_aliases,
241 has_visitor_callbacks,
242 &streaming_methods,
243 &streaming_methods_meta,
244 &exclude_functions,
245 &config.trait_bridges,
246 )),
247 generated_header: true,
248 });
249
250 if has_visitor_callbacks {
252 let visitor_bridge_cfg = config
255 .trait_bridges
256 .iter()
257 .find(|b| b.bind_via == alef_core::config::BridgeBinding::OptionsField);
258 let trait_map: std::collections::HashMap<&str, &alef_core::ir::TypeDef> = api
259 .types
260 .iter()
261 .filter(|t| t.is_trait)
262 .map(|t| (t.name.as_str(), t))
263 .collect();
264 let visitor_trait = visitor_bridge_cfg.and_then(|b| trait_map.get(b.trait_name.as_str()).copied());
265
266 if let Some(trait_def) = visitor_trait {
267 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, trait_def) {
268 files.push(GeneratedFile {
269 path: base_path.join(filename),
270 content: strip_trailing_whitespace(&content),
271 generated_header: true,
272 });
273 }
274 } else {
275 let placeholder = alef_core::ir::TypeDef {
277 name: String::new(),
278 rust_path: String::new(),
279 original_rust_path: String::new(),
280 fields: vec![],
281 methods: vec![],
282 is_opaque: false,
283 is_clone: false,
284 is_copy: false,
285 is_trait: true,
286 has_default: false,
287 has_stripped_cfg_fields: false,
288 is_return_type: false,
289 serde_rename_all: None,
290 has_serde: false,
291 super_traits: vec![],
292 doc: String::new(),
293 cfg: None,
294 binding_excluded: false,
295 binding_exclusion_reason: None,
296 };
297 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, &placeholder) {
298 files.push(GeneratedFile {
299 path: base_path.join(filename),
300 content: strip_trailing_whitespace(&content),
301 generated_header: true,
302 });
303 }
304 }
305 delete_superseded_visitor_files(&base_path)?;
309 } else {
310 delete_stale_visitor_files(&base_path)?;
313 }
314
315 if !config.trait_bridges.is_empty() {
317 let trait_defs: Vec<_> = api.types.iter().filter(|t| t.is_trait).collect();
318 let bridges: Vec<_> = config
319 .trait_bridges
320 .iter()
321 .filter_map(|cfg| {
322 let trait_name = cfg.trait_name.clone();
323 trait_defs
324 .iter()
325 .find(|t| t.name == trait_name)
326 .map(|trait_def| (trait_name, cfg, *trait_def))
327 })
328 .collect();
329
330 if !bridges.is_empty() {
331 let visible_type_names: HashSet<&str> = api
335 .types
336 .iter()
337 .filter(|t| !t.is_trait)
338 .map(|t| t.name.as_str())
339 .chain(api.enums.iter().map(|e| e.name.as_str()))
340 .collect();
341 let (filename, content) =
342 crate::trait_bridge::gen_trait_bridges_file(&namespace, &prefix, &bridges, &visible_type_names);
343 files.push(GeneratedFile {
344 path: base_path.join(filename),
345 content: strip_trailing_whitespace(&content),
346 generated_header: true,
347 });
348 }
349 }
350
351 let enum_names: HashSet<String> = api.enums.iter().map(|e| e.name.to_pascal_case()).collect();
353
354 let all_opaque_type_names: HashSet<String> = api
357 .types
358 .iter()
359 .filter(|t| t.is_opaque)
360 .map(|t| t.name.to_pascal_case())
361 .collect();
362
363 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
365 if typ.is_opaque {
366 let type_filename = typ.name.to_pascal_case();
367 files.push(GeneratedFile {
368 path: base_path.join(format!("{}.cs", type_filename)),
369 content: strip_trailing_whitespace(&types::gen_opaque_handle(
370 typ,
371 &namespace,
372 &exception_class_name,
373 &enum_names,
374 &streaming_methods,
375 &streaming_methods_meta,
376 &all_opaque_type_names,
377 )),
378 generated_header: true,
379 });
380 }
381 }
382
383 let complex_enums: HashSet<String> = HashSet::new();
387
388 let custom_converter_enums: HashSet<String> = api
394 .enums
395 .iter()
396 .filter(|e| {
397 let is_tagged_union = e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty());
399 if is_tagged_union {
400 return false;
401 }
402 let rename_all_differs = matches!(
407 e.serde_rename_all.as_deref(),
408 Some("kebab-case") | Some("SCREAMING-KEBAB-CASE") | Some("camelCase") | Some("PascalCase")
409 );
410 if rename_all_differs {
411 return true;
412 }
413 e.variants.iter().any(|v| {
415 if let Some(ref rename) = v.serde_rename {
416 let snake = enums::apply_rename_all(&v.name, e.serde_rename_all.as_deref());
417 rename != &snake
418 } else {
419 false
420 }
421 })
422 })
423 .map(|e| e.name.to_pascal_case())
424 .collect();
425
426 let lang_rename_all = config.serde_rename_all_for_language(Language::Csharp);
428
429 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
431 if !typ.is_opaque {
432 let has_visible_fields = binding_fields(&typ.fields).next().is_some();
435 let has_named_fields = binding_fields(&typ.fields).any(|f| !is_tuple_field(f));
436 if has_visible_fields && !has_named_fields {
437 continue;
438 }
439 if has_visitor_callbacks && bridge_associated_types.contains(typ.name.as_str()) {
441 continue;
442 }
443
444 let type_filename = typ.name.to_pascal_case();
445 files.push(GeneratedFile {
446 path: base_path.join(format!("{}.cs", type_filename)),
447 content: strip_trailing_whitespace(&types::gen_record_type(
448 typ,
449 &namespace,
450 &enum_names,
451 &complex_enums,
452 &custom_converter_enums,
453 &lang_rename_all,
454 &bridge_type_aliases,
455 &exception_class_name,
456 )),
457 generated_header: true,
458 });
459 }
460 }
461
462 for enum_def in &api.enums {
464 if has_visitor_callbacks && bridge_associated_types.contains(enum_def.name.as_str()) {
466 continue;
467 }
468 let enum_filename = enum_def.name.to_pascal_case();
469 files.push(GeneratedFile {
470 path: base_path.join(format!("{}.cs", enum_filename)),
471 content: strip_trailing_whitespace(&enums::gen_enum(enum_def, &namespace)),
472 generated_header: true,
473 });
474 }
475
476 let needs_byte_array_converter = api
479 .types
480 .iter()
481 .any(|t| !t.is_opaque && t.fields.iter().any(|f| !f.optional && matches!(f.ty, TypeRef::Bytes)));
482 if needs_byte_array_converter {
483 files.push(GeneratedFile {
484 path: base_path.join("ByteArrayToIntArrayConverter.cs"),
485 content: types::gen_byte_array_to_int_array_converter(&namespace),
486 generated_header: true,
487 });
488 }
489
490 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Csharp)?;
492
493 files.push(GeneratedFile {
497 path: PathBuf::from("packages/csharp/Directory.Build.props"),
498 content: gen_directory_build_props(),
499 generated_header: true,
500 });
501
502 Ok(files)
503 }
504
505 fn generate_public_api(
510 &self,
511 _api: &ApiSurface,
512 _config: &ResolvedCrateConfig,
513 ) -> anyhow::Result<Vec<GeneratedFile>> {
514 Ok(vec![])
516 }
517
518 fn build_config(&self) -> Option<BuildConfig> {
519 Some(BuildConfig {
520 tool: "dotnet",
521 crate_suffix: "",
522 build_dep: BuildDependency::Ffi,
523 post_build: vec![],
524 })
525 }
526}
527
528pub(super) fn is_tuple_field(field: &FieldDef) -> bool {
530 (field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
531 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
532}
533
534pub(super) fn strip_trailing_whitespace(content: &str) -> String {
536 let mut result: String = content
537 .lines()
538 .map(|line| line.trim_end())
539 .collect::<Vec<_>>()
540 .join("\n");
541 if !result.ends_with('\n') {
542 result.push('\n');
543 }
544 result
545}
546
547pub(super) fn csharp_file_header() -> String {
549 let mut out = hash::header(CommentStyle::DoubleSlash);
550 out.push_str("#nullable enable\n\n");
551 out
552}
553
554fn gen_directory_build_props() -> String {
557 "<!-- auto-generated by alef (generate_bindings) -->\n\
558<Project>\n \
559<PropertyGroup>\n \
560<Nullable>enable</Nullable>\n \
561<LangVersion>latest</LangVersion>\n \
562<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n \
563</PropertyGroup>\n\
564</Project>\n"
565 .to_string()
566}
567
568fn delete_superseded_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
573 let superseded = ["IVisitor.cs", "VisitorCallbacks.cs"];
574 for filename in superseded {
575 let path = base_path.join(filename);
576 if path.exists() {
577 std::fs::remove_file(&path)
578 .map_err(|e| anyhow::anyhow!("Failed to delete superseded visitor file {}: {}", path.display(), e))?;
579 }
580 }
581 Ok(())
582}
583
584fn delete_stale_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
588 let stale_files = vec!["IVisitor.cs", "VisitorCallbacks.cs", "NodeContext.cs", "VisitResult.cs"];
589
590 for filename in stale_files {
591 let path = base_path.join(filename);
592 if path.exists() {
593 std::fs::remove_file(&path)
594 .map_err(|e| anyhow::anyhow!("Failed to delete stale visitor file {}: {}", path.display(), e))?;
595 }
596 }
597
598 Ok(())
599}
600
601use alef_core::ir::PrimitiveType;
606
607pub(super) fn pinvoke_return_type(ty: &TypeRef) -> &'static str {
614 match ty {
615 TypeRef::Unit => "void",
616 TypeRef::Primitive(PrimitiveType::Bool) => "int",
618 TypeRef::Primitive(PrimitiveType::U8) => "byte",
620 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
621 TypeRef::Primitive(PrimitiveType::U32) => "uint",
622 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
623 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
624 TypeRef::Primitive(PrimitiveType::I16) => "short",
625 TypeRef::Primitive(PrimitiveType::I32) => "int",
626 TypeRef::Primitive(PrimitiveType::I64) => "long",
627 TypeRef::Primitive(PrimitiveType::F32) => "float",
628 TypeRef::Primitive(PrimitiveType::F64) => "double",
629 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
630 TypeRef::Primitive(PrimitiveType::Isize) => "long",
631 TypeRef::Duration => "ulong",
633 TypeRef::String
635 | TypeRef::Char
636 | TypeRef::Bytes
637 | TypeRef::Optional(_)
638 | TypeRef::Vec(_)
639 | TypeRef::Map(_, _)
640 | TypeRef::Named(_)
641 | TypeRef::Path
642 | TypeRef::Json => "IntPtr",
643 }
644}
645
646pub(super) fn pinvoke_param_type(ty: &TypeRef) -> &'static str {
653 match ty {
654 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "string",
655 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes | TypeRef::Optional(_) => "IntPtr",
657 TypeRef::Unit => "void",
658 TypeRef::Primitive(PrimitiveType::Bool) => "int",
659 TypeRef::Primitive(PrimitiveType::U8) => "byte",
660 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
661 TypeRef::Primitive(PrimitiveType::U32) => "uint",
662 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
663 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
664 TypeRef::Primitive(PrimitiveType::I16) => "short",
665 TypeRef::Primitive(PrimitiveType::I32) => "int",
666 TypeRef::Primitive(PrimitiveType::I64) => "long",
667 TypeRef::Primitive(PrimitiveType::F32) => "float",
668 TypeRef::Primitive(PrimitiveType::F64) => "double",
669 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
670 TypeRef::Primitive(PrimitiveType::Isize) => "long",
671 TypeRef::Duration => "ulong",
672 }
673}
674
675pub(super) fn is_bridge_param(
678 param: &alef_core::ir::ParamDef,
679 bridge_param_names: &HashSet<String>,
680 bridge_type_aliases: &HashSet<String>,
681) -> bool {
682 bridge_param_names.contains(¶m.name)
683 || matches!(¶m.ty, alef_core::ir::TypeRef::Named(n) if bridge_type_aliases.contains(n))
684}
685
686pub(super) fn returns_string(ty: &TypeRef) -> bool {
688 matches!(ty, TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json)
689}
690
691pub(super) fn returns_bool_via_int(ty: &TypeRef) -> bool {
693 matches!(ty, TypeRef::Primitive(PrimitiveType::Bool))
694}
695
696pub(super) fn returns_json_object(ty: &TypeRef) -> bool {
698 matches!(
699 ty,
700 TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) | TypeRef::Bytes | TypeRef::Optional(_)
701 )
702}
703
704pub(super) fn returns_ptr(ty: &TypeRef) -> bool {
707 matches!(
708 ty,
709 TypeRef::String
710 | TypeRef::Char
711 | TypeRef::Path
712 | TypeRef::Json
713 | TypeRef::Named(_)
714 | TypeRef::Vec(_)
715 | TypeRef::Map(_, _)
716 | TypeRef::Bytes
717 | TypeRef::Optional(_)
718 )
719}
720
721pub(super) fn native_call_arg(
727 ty: &TypeRef,
728 param_name: &str,
729 optional: bool,
730 true_opaque_types: &HashSet<String>,
731) -> String {
732 match ty {
733 TypeRef::Named(type_name) if true_opaque_types.contains(type_name) => {
734 let bang = if optional { "!" } else { "" };
736 format!("{param_name}{bang}.Handle")
737 }
738 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
739 format!("{param_name}Handle")
740 }
741 TypeRef::Bytes => {
742 format!("{param_name}Handle.AddrOfPinnedObject()")
743 }
744 TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool) => {
745 if optional {
747 format!("({param_name}?.Value ? 1 : 0)")
748 } else {
749 format!("({param_name} ? 1 : 0)")
750 }
751 }
752 ty => {
753 if optional {
754 if let TypeRef::Primitive(prim) = ty {
762 use alef_core::ir::PrimitiveType;
763 let sentinel = match prim {
764 PrimitiveType::U8 => "byte.MaxValue",
765 PrimitiveType::U16 => "ushort.MaxValue",
766 PrimitiveType::U32 => "uint.MaxValue",
767 PrimitiveType::U64 | PrimitiveType::Usize => "ulong.MaxValue",
768 PrimitiveType::I8 => "sbyte.MaxValue",
769 PrimitiveType::I16 => "short.MaxValue",
770 PrimitiveType::I32 => "int.MaxValue",
771 PrimitiveType::I64 | PrimitiveType::Isize => "long.MaxValue",
772 PrimitiveType::F32 => "float.NaN",
773 PrimitiveType::F64 => "double.NaN",
774 PrimitiveType::Bool => unreachable!("handled above"),
775 };
776 format!("{param_name} ?? {sentinel}")
777 } else if matches!(ty, TypeRef::Duration) {
778 format!("{param_name}.GetValueOrDefault()")
779 } else {
780 format!("{param_name}!")
781 }
782 } else {
783 param_name.to_string()
784 }
785 }
786 }
787}
788
789pub(super) fn emit_named_param_setup(
794 out: &mut String,
795 params: &[alef_core::ir::ParamDef],
796 indent: &str,
797 true_opaque_types: &HashSet<String>,
798 exception_name: &str,
799) {
800 for param in params {
801 let param_name = param.name.to_lower_camel_case();
802 let json_var = format!("{param_name}Json");
803 let handle_var = format!("{param_name}Handle");
804
805 match ¶m.ty {
806 TypeRef::Named(type_name) => {
807 if true_opaque_types.contains(type_name) {
810 continue;
811 }
812 let from_json_method = format!("{}FromJson", type_name.to_pascal_case());
813
814 let is_config_param = param.name == "config";
816 let param_to_serialize = if is_config_param {
817 let type_pascal = type_name.to_pascal_case();
818 format!("({} ?? new {}())", param_name, type_pascal)
819 } else {
820 param_name.to_string()
821 };
822
823 if param.optional && !is_config_param {
824 out.push_str(&crate::template_env::render(
828 "named_param_handle_from_json_optional.jinja",
829 minijinja::context! {
830 indent,
831 handle_var => &handle_var,
832 from_json_method => &from_json_method,
833 json_var => &json_var,
834 param_name => ¶m_name,
835 exception_name => exception_name,
836 },
837 ));
838 } else {
839 out.push_str(&crate::template_env::render(
840 "named_param_json_serialize.jinja",
841 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_to_serialize },
842 ));
843 out.push_str(&crate::template_env::render(
844 "named_param_handle_from_json.jinja",
845 minijinja::context! {
846 indent,
847 handle_var => &handle_var,
848 from_json_method => &from_json_method,
849 json_var => &json_var,
850 exception_name => exception_name,
851 },
852 ));
853 }
854 }
855 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
856 out.push_str(&crate::template_env::render(
858 "named_param_json_serialize.jinja",
859 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
860 ));
861 out.push_str(&crate::template_env::render(
862 "named_param_handle_string.jinja",
863 minijinja::context! { indent, handle_var => &handle_var, json_var => &json_var },
864 ));
865 }
866 TypeRef::Bytes => {
867 out.push_str(&crate::template_env::render(
869 "named_param_handle_pin.jinja",
870 minijinja::context! { indent, handle_var => &handle_var, param_name => ¶m_name },
871 ));
872 }
873 _ => {}
874 }
875 }
876}
877
878pub(super) fn emit_named_param_teardown(
883 out: &mut String,
884 params: &[alef_core::ir::ParamDef],
885 true_opaque_types: &HashSet<String>,
886) {
887 for param in params {
888 let param_name = param.name.to_lower_camel_case();
889 let handle_var = format!("{param_name}Handle");
890 match ¶m.ty {
891 TypeRef::Named(type_name) => {
892 if true_opaque_types.contains(type_name) {
893 continue;
895 }
896 let free_method = format!("{}Free", type_name.to_pascal_case());
897 out.push_str(&crate::template_env::render(
898 "named_param_teardown_free.jinja",
899 minijinja::context! { indent => " ", free_method => &free_method, handle_var => &handle_var },
900 ));
901 }
902 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
903 out.push_str(&crate::template_env::render(
904 "named_param_teardown_hglobal.jinja",
905 minijinja::context! { indent => " ", handle_var => &handle_var },
906 ));
907 }
908 TypeRef::Bytes => {
909 out.push_str(&crate::template_env::render(
910 "named_param_teardown_gchandle.jinja",
911 minijinja::context! { indent => " ", handle_var => &handle_var },
912 ));
913 }
914 _ => {}
915 }
916 }
917}
918
919pub(super) fn emit_named_param_teardown_indented(
921 out: &mut String,
922 params: &[alef_core::ir::ParamDef],
923 indent: &str,
924 true_opaque_types: &HashSet<String>,
925) {
926 for param in params {
927 let param_name = param.name.to_lower_camel_case();
928 let handle_var = format!("{param_name}Handle");
929 match ¶m.ty {
930 TypeRef::Named(type_name) => {
931 if true_opaque_types.contains(type_name) {
932 continue;
934 }
935 let free_method = format!("{}Free", type_name.to_pascal_case());
936 out.push_str(&crate::template_env::render(
937 "named_param_teardown_free.jinja",
938 minijinja::context! { indent, free_method => &free_method, handle_var => &handle_var },
939 ));
940 }
941 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
942 out.push_str(&crate::template_env::render(
943 "named_param_teardown_hglobal.jinja",
944 minijinja::context! { indent, handle_var => &handle_var },
945 ));
946 }
947 TypeRef::Bytes => {
948 out.push_str(&crate::template_env::render(
949 "named_param_teardown_gchandle.jinja",
950 minijinja::context! { indent, handle_var => &handle_var },
951 ));
952 }
953 _ => {}
954 }
955 }
956}
957
958use heck::ToLowerCamelCase;