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 binding_excluded: false,
294 binding_exclusion_reason: None,
295 };
296 for (filename, content) in crate::gen_visitor::gen_visitor_files(&namespace, &placeholder) {
297 files.push(GeneratedFile {
298 path: base_path.join(filename),
299 content: strip_trailing_whitespace(&content),
300 generated_header: true,
301 });
302 }
303 }
304 delete_superseded_visitor_files(&base_path)?;
308 } else {
309 delete_stale_visitor_files(&base_path)?;
312 }
313
314 if !config.trait_bridges.is_empty() {
316 let trait_defs: Vec<_> = api.types.iter().filter(|t| t.is_trait).collect();
317 let bridges: Vec<_> = config
318 .trait_bridges
319 .iter()
320 .filter_map(|cfg| {
321 let trait_name = cfg.trait_name.clone();
322 trait_defs
323 .iter()
324 .find(|t| t.name == trait_name)
325 .map(|trait_def| (trait_name, cfg, *trait_def))
326 })
327 .collect();
328
329 if !bridges.is_empty() {
330 let visible_type_names: HashSet<&str> = api
334 .types
335 .iter()
336 .filter(|t| !t.is_trait)
337 .map(|t| t.name.as_str())
338 .chain(api.enums.iter().map(|e| e.name.as_str()))
339 .collect();
340 let (filename, content) =
341 crate::trait_bridge::gen_trait_bridges_file(&namespace, &prefix, &bridges, &visible_type_names);
342 files.push(GeneratedFile {
343 path: base_path.join(filename),
344 content: strip_trailing_whitespace(&content),
345 generated_header: true,
346 });
347 }
348 }
349
350 let enum_names: HashSet<String> = api.enums.iter().map(|e| e.name.to_pascal_case()).collect();
352
353 let all_opaque_type_names: HashSet<String> = api
356 .types
357 .iter()
358 .filter(|t| t.is_opaque)
359 .map(|t| t.name.to_pascal_case())
360 .collect();
361
362 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
364 if typ.is_opaque {
365 let type_filename = typ.name.to_pascal_case();
366 files.push(GeneratedFile {
367 path: base_path.join(format!("{}.cs", type_filename)),
368 content: strip_trailing_whitespace(&types::gen_opaque_handle(
369 typ,
370 &namespace,
371 &exception_class_name,
372 &enum_names,
373 &streaming_methods,
374 &streaming_methods_meta,
375 &all_opaque_type_names,
376 )),
377 generated_header: true,
378 });
379 }
380 }
381
382 let complex_enums: HashSet<String> = HashSet::new();
386
387 let custom_converter_enums: HashSet<String> = api
393 .enums
394 .iter()
395 .filter(|e| {
396 let is_tagged_union = e.serde_tag.is_some() && e.variants.iter().any(|v| !v.fields.is_empty());
398 if is_tagged_union {
399 return false;
400 }
401 let rename_all_differs = matches!(
406 e.serde_rename_all.as_deref(),
407 Some("kebab-case") | Some("SCREAMING-KEBAB-CASE") | Some("camelCase") | Some("PascalCase")
408 );
409 if rename_all_differs {
410 return true;
411 }
412 e.variants.iter().any(|v| {
414 if let Some(ref rename) = v.serde_rename {
415 let snake = enums::apply_rename_all(&v.name, e.serde_rename_all.as_deref());
416 rename != &snake
417 } else {
418 false
419 }
420 })
421 })
422 .map(|e| e.name.to_pascal_case())
423 .collect();
424
425 let lang_rename_all = config.serde_rename_all_for_language(Language::Csharp);
427
428 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
430 if !typ.is_opaque {
431 let has_named_fields = typ.fields.iter().any(|f| !is_tuple_field(f));
434 if !typ.fields.is_empty() && !has_named_fields {
435 continue;
436 }
437 if has_visitor_callbacks && bridge_associated_types.contains(typ.name.as_str()) {
439 continue;
440 }
441
442 let type_filename = typ.name.to_pascal_case();
443 files.push(GeneratedFile {
444 path: base_path.join(format!("{}.cs", type_filename)),
445 content: strip_trailing_whitespace(&types::gen_record_type(
446 typ,
447 &namespace,
448 &enum_names,
449 &complex_enums,
450 &custom_converter_enums,
451 &lang_rename_all,
452 &bridge_type_aliases,
453 &exception_class_name,
454 )),
455 generated_header: true,
456 });
457 }
458 }
459
460 for enum_def in &api.enums {
462 if has_visitor_callbacks && bridge_associated_types.contains(enum_def.name.as_str()) {
464 continue;
465 }
466 let enum_filename = enum_def.name.to_pascal_case();
467 files.push(GeneratedFile {
468 path: base_path.join(format!("{}.cs", enum_filename)),
469 content: strip_trailing_whitespace(&enums::gen_enum(enum_def, &namespace)),
470 generated_header: true,
471 });
472 }
473
474 let needs_byte_array_converter = api
477 .types
478 .iter()
479 .any(|t| !t.is_opaque && t.fields.iter().any(|f| !f.optional && matches!(f.ty, TypeRef::Bytes)));
480 if needs_byte_array_converter {
481 files.push(GeneratedFile {
482 path: base_path.join("ByteArrayToIntArrayConverter.cs"),
483 content: types::gen_byte_array_to_int_array_converter(&namespace),
484 generated_header: true,
485 });
486 }
487
488 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Csharp)?;
490
491 files.push(GeneratedFile {
495 path: PathBuf::from("packages/csharp/Directory.Build.props"),
496 content: gen_directory_build_props(),
497 generated_header: true,
498 });
499
500 Ok(files)
501 }
502
503 fn generate_public_api(
508 &self,
509 _api: &ApiSurface,
510 _config: &ResolvedCrateConfig,
511 ) -> anyhow::Result<Vec<GeneratedFile>> {
512 Ok(vec![])
514 }
515
516 fn build_config(&self) -> Option<BuildConfig> {
517 Some(BuildConfig {
518 tool: "dotnet",
519 crate_suffix: "",
520 build_dep: BuildDependency::Ffi,
521 post_build: vec![],
522 })
523 }
524}
525
526pub(super) fn is_tuple_field(field: &FieldDef) -> bool {
528 (field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit()))
529 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
530}
531
532pub(super) fn strip_trailing_whitespace(content: &str) -> String {
534 let mut result: String = content
535 .lines()
536 .map(|line| line.trim_end())
537 .collect::<Vec<_>>()
538 .join("\n");
539 if !result.ends_with('\n') {
540 result.push('\n');
541 }
542 result
543}
544
545pub(super) fn csharp_file_header() -> String {
547 let mut out = hash::header(CommentStyle::DoubleSlash);
548 out.push_str("#nullable enable\n\n");
549 out
550}
551
552fn gen_directory_build_props() -> String {
555 "<!-- auto-generated by alef (generate_bindings) -->\n\
556<Project>\n \
557<PropertyGroup>\n \
558<Nullable>enable</Nullable>\n \
559<LangVersion>latest</LangVersion>\n \
560<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n \
561</PropertyGroup>\n\
562</Project>\n"
563 .to_string()
564}
565
566fn delete_superseded_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
571 let superseded = ["IVisitor.cs", "VisitorCallbacks.cs"];
572 for filename in superseded {
573 let path = base_path.join(filename);
574 if path.exists() {
575 std::fs::remove_file(&path)
576 .map_err(|e| anyhow::anyhow!("Failed to delete superseded visitor file {}: {}", path.display(), e))?;
577 }
578 }
579 Ok(())
580}
581
582fn delete_stale_visitor_files(base_path: &std::path::Path) -> anyhow::Result<()> {
586 let stale_files = vec!["IVisitor.cs", "VisitorCallbacks.cs", "NodeContext.cs", "VisitResult.cs"];
587
588 for filename in stale_files {
589 let path = base_path.join(filename);
590 if path.exists() {
591 std::fs::remove_file(&path)
592 .map_err(|e| anyhow::anyhow!("Failed to delete stale visitor file {}: {}", path.display(), e))?;
593 }
594 }
595
596 Ok(())
597}
598
599use alef_core::ir::PrimitiveType;
604
605pub(super) fn pinvoke_return_type(ty: &TypeRef) -> &'static str {
612 match ty {
613 TypeRef::Unit => "void",
614 TypeRef::Primitive(PrimitiveType::Bool) => "int",
616 TypeRef::Primitive(PrimitiveType::U8) => "byte",
618 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
619 TypeRef::Primitive(PrimitiveType::U32) => "uint",
620 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
621 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
622 TypeRef::Primitive(PrimitiveType::I16) => "short",
623 TypeRef::Primitive(PrimitiveType::I32) => "int",
624 TypeRef::Primitive(PrimitiveType::I64) => "long",
625 TypeRef::Primitive(PrimitiveType::F32) => "float",
626 TypeRef::Primitive(PrimitiveType::F64) => "double",
627 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
628 TypeRef::Primitive(PrimitiveType::Isize) => "long",
629 TypeRef::Duration => "ulong",
631 TypeRef::String
633 | TypeRef::Char
634 | TypeRef::Bytes
635 | TypeRef::Optional(_)
636 | TypeRef::Vec(_)
637 | TypeRef::Map(_, _)
638 | TypeRef::Named(_)
639 | TypeRef::Path
640 | TypeRef::Json => "IntPtr",
641 }
642}
643
644pub(super) fn pinvoke_param_type(ty: &TypeRef) -> &'static str {
651 match ty {
652 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "string",
653 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes | TypeRef::Optional(_) => "IntPtr",
655 TypeRef::Unit => "void",
656 TypeRef::Primitive(PrimitiveType::Bool) => "int",
657 TypeRef::Primitive(PrimitiveType::U8) => "byte",
658 TypeRef::Primitive(PrimitiveType::U16) => "ushort",
659 TypeRef::Primitive(PrimitiveType::U32) => "uint",
660 TypeRef::Primitive(PrimitiveType::U64) => "ulong",
661 TypeRef::Primitive(PrimitiveType::I8) => "sbyte",
662 TypeRef::Primitive(PrimitiveType::I16) => "short",
663 TypeRef::Primitive(PrimitiveType::I32) => "int",
664 TypeRef::Primitive(PrimitiveType::I64) => "long",
665 TypeRef::Primitive(PrimitiveType::F32) => "float",
666 TypeRef::Primitive(PrimitiveType::F64) => "double",
667 TypeRef::Primitive(PrimitiveType::Usize) => "ulong",
668 TypeRef::Primitive(PrimitiveType::Isize) => "long",
669 TypeRef::Duration => "ulong",
670 }
671}
672
673pub(super) fn is_bridge_param(
676 param: &alef_core::ir::ParamDef,
677 bridge_param_names: &HashSet<String>,
678 bridge_type_aliases: &HashSet<String>,
679) -> bool {
680 bridge_param_names.contains(¶m.name)
681 || matches!(¶m.ty, alef_core::ir::TypeRef::Named(n) if bridge_type_aliases.contains(n))
682}
683
684pub(super) fn returns_string(ty: &TypeRef) -> bool {
686 matches!(ty, TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json)
687}
688
689pub(super) fn returns_bool_via_int(ty: &TypeRef) -> bool {
691 matches!(ty, TypeRef::Primitive(PrimitiveType::Bool))
692}
693
694pub(super) fn returns_json_object(ty: &TypeRef) -> bool {
696 matches!(
697 ty,
698 TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) | TypeRef::Bytes | TypeRef::Optional(_)
699 )
700}
701
702pub(super) fn returns_ptr(ty: &TypeRef) -> bool {
705 matches!(
706 ty,
707 TypeRef::String
708 | TypeRef::Char
709 | TypeRef::Path
710 | TypeRef::Json
711 | TypeRef::Named(_)
712 | TypeRef::Vec(_)
713 | TypeRef::Map(_, _)
714 | TypeRef::Bytes
715 | TypeRef::Optional(_)
716 )
717}
718
719pub(super) fn native_call_arg(
725 ty: &TypeRef,
726 param_name: &str,
727 optional: bool,
728 true_opaque_types: &HashSet<String>,
729) -> String {
730 match ty {
731 TypeRef::Named(type_name) if true_opaque_types.contains(type_name) => {
732 let bang = if optional { "!" } else { "" };
734 format!("{param_name}{bang}.Handle")
735 }
736 TypeRef::Named(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) => {
737 format!("{param_name}Handle")
738 }
739 TypeRef::Bytes => {
740 format!("{param_name}Handle.AddrOfPinnedObject()")
741 }
742 TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool) => {
743 if optional {
745 format!("({param_name}?.Value ? 1 : 0)")
746 } else {
747 format!("({param_name} ? 1 : 0)")
748 }
749 }
750 ty => {
751 if optional {
752 if let TypeRef::Primitive(prim) = ty {
760 use alef_core::ir::PrimitiveType;
761 let sentinel = match prim {
762 PrimitiveType::U8 => "byte.MaxValue",
763 PrimitiveType::U16 => "ushort.MaxValue",
764 PrimitiveType::U32 => "uint.MaxValue",
765 PrimitiveType::U64 | PrimitiveType::Usize => "ulong.MaxValue",
766 PrimitiveType::I8 => "sbyte.MaxValue",
767 PrimitiveType::I16 => "short.MaxValue",
768 PrimitiveType::I32 => "int.MaxValue",
769 PrimitiveType::I64 | PrimitiveType::Isize => "long.MaxValue",
770 PrimitiveType::F32 => "float.NaN",
771 PrimitiveType::F64 => "double.NaN",
772 PrimitiveType::Bool => unreachable!("handled above"),
773 };
774 format!("{param_name} ?? {sentinel}")
775 } else if matches!(ty, TypeRef::Duration) {
776 format!("{param_name}.GetValueOrDefault()")
777 } else {
778 format!("{param_name}!")
779 }
780 } else {
781 param_name.to_string()
782 }
783 }
784 }
785}
786
787pub(super) fn emit_named_param_setup(
792 out: &mut String,
793 params: &[alef_core::ir::ParamDef],
794 indent: &str,
795 true_opaque_types: &HashSet<String>,
796 exception_name: &str,
797) {
798 for param in params {
799 let param_name = param.name.to_lower_camel_case();
800 let json_var = format!("{param_name}Json");
801 let handle_var = format!("{param_name}Handle");
802
803 match ¶m.ty {
804 TypeRef::Named(type_name) => {
805 if true_opaque_types.contains(type_name) {
808 continue;
809 }
810 let from_json_method = format!("{}FromJson", type_name.to_pascal_case());
811
812 let is_config_param = param.name == "config";
814 let param_to_serialize = if is_config_param {
815 let type_pascal = type_name.to_pascal_case();
816 format!("({} ?? new {}())", param_name, type_pascal)
817 } else {
818 param_name.to_string()
819 };
820
821 if param.optional && !is_config_param {
822 out.push_str(&crate::template_env::render(
826 "named_param_handle_from_json_optional.jinja",
827 minijinja::context! {
828 indent,
829 handle_var => &handle_var,
830 from_json_method => &from_json_method,
831 json_var => &json_var,
832 param_name => ¶m_name,
833 exception_name => exception_name,
834 },
835 ));
836 } else {
837 out.push_str(&crate::template_env::render(
838 "named_param_json_serialize.jinja",
839 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_to_serialize },
840 ));
841 out.push_str(&crate::template_env::render(
842 "named_param_handle_from_json.jinja",
843 minijinja::context! {
844 indent,
845 handle_var => &handle_var,
846 from_json_method => &from_json_method,
847 json_var => &json_var,
848 exception_name => exception_name,
849 },
850 ));
851 }
852 }
853 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
854 out.push_str(&crate::template_env::render(
856 "named_param_json_serialize.jinja",
857 minijinja::context! { indent, json_var => &json_var, param_name => ¶m_name },
858 ));
859 out.push_str(&crate::template_env::render(
860 "named_param_handle_string.jinja",
861 minijinja::context! { indent, handle_var => &handle_var, json_var => &json_var },
862 ));
863 }
864 TypeRef::Bytes => {
865 out.push_str(&crate::template_env::render(
867 "named_param_handle_pin.jinja",
868 minijinja::context! { indent, handle_var => &handle_var, param_name => ¶m_name },
869 ));
870 }
871 _ => {}
872 }
873 }
874}
875
876pub(super) fn emit_named_param_teardown(
881 out: &mut String,
882 params: &[alef_core::ir::ParamDef],
883 true_opaque_types: &HashSet<String>,
884) {
885 for param in params {
886 let param_name = param.name.to_lower_camel_case();
887 let handle_var = format!("{param_name}Handle");
888 match ¶m.ty {
889 TypeRef::Named(type_name) => {
890 if true_opaque_types.contains(type_name) {
891 continue;
893 }
894 let free_method = format!("{}Free", type_name.to_pascal_case());
895 out.push_str(&crate::template_env::render(
896 "named_param_teardown_free.jinja",
897 minijinja::context! { indent => " ", free_method => &free_method, handle_var => &handle_var },
898 ));
899 }
900 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
901 out.push_str(&crate::template_env::render(
902 "named_param_teardown_hglobal.jinja",
903 minijinja::context! { indent => " ", handle_var => &handle_var },
904 ));
905 }
906 TypeRef::Bytes => {
907 out.push_str(&crate::template_env::render(
908 "named_param_teardown_gchandle.jinja",
909 minijinja::context! { indent => " ", handle_var => &handle_var },
910 ));
911 }
912 _ => {}
913 }
914 }
915}
916
917pub(super) fn emit_named_param_teardown_indented(
919 out: &mut String,
920 params: &[alef_core::ir::ParamDef],
921 indent: &str,
922 true_opaque_types: &HashSet<String>,
923) {
924 for param in params {
925 let param_name = param.name.to_lower_camel_case();
926 let handle_var = format!("{param_name}Handle");
927 match ¶m.ty {
928 TypeRef::Named(type_name) => {
929 if true_opaque_types.contains(type_name) {
930 continue;
932 }
933 let free_method = format!("{}Free", type_name.to_pascal_case());
934 out.push_str(&crate::template_env::render(
935 "named_param_teardown_free.jinja",
936 minijinja::context! { indent, free_method => &free_method, handle_var => &handle_var },
937 ));
938 }
939 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
940 out.push_str(&crate::template_env::render(
941 "named_param_teardown_hglobal.jinja",
942 minijinja::context! { indent, handle_var => &handle_var },
943 ));
944 }
945 TypeRef::Bytes => {
946 out.push_str(&crate::template_env::render(
947 "named_param_teardown_gchandle.jinja",
948 minijinja::context! { indent, handle_var => &handle_var },
949 ));
950 }
951 _ => {}
952 }
953 }
954}
955
956use heck::ToLowerCamelCase;