1use ahash::{AHashMap, AHashSet};
2use alef_core::ir::{ApiSurface, EnumDef, FieldDef, PrimitiveType, TypeDef, TypeRef};
3
4pub fn input_type_names(surface: &ApiSurface) -> AHashSet<String> {
17 let mut names = AHashSet::new();
18
19 for func in &surface.functions {
21 for param in &func.params {
22 collect_named_types(¶m.ty, &mut names);
23 }
24 }
25 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
27 for method in &typ.methods {
28 for param in &method.params {
29 collect_named_types(¶m.ty, &mut names);
30 }
31 }
32 }
33 for func in &surface.functions {
37 collect_named_types(&func.return_type, &mut names);
38 }
39 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
41 for method in &typ.methods {
42 collect_named_types(&method.return_type, &mut names);
43 }
44 }
45 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
50 if !typ.is_opaque && !typ.methods.is_empty() {
51 for field in &typ.fields {
52 if !field.sanitized {
53 collect_named_types(&field.ty, &mut names);
54 }
55 }
56 }
57 }
58
59 let mut changed = true;
61 while changed {
62 changed = false;
63 let snapshot: Vec<String> = names.iter().cloned().collect();
64 for name in &snapshot {
65 if let Some(typ) = surface.types.iter().find(|t| t.name == *name) {
66 for field in &typ.fields {
67 let mut field_names = AHashSet::new();
68 collect_named_types(&field.ty, &mut field_names);
69 for n in field_names {
70 if names.insert(n) {
71 changed = true;
72 }
73 }
74 }
75 }
76 }
77 }
78
79 names
80}
81
82fn collect_named_types(ty: &TypeRef, out: &mut AHashSet<String>) {
84 match ty {
85 TypeRef::Named(name) => {
86 out.insert(name.clone());
87 }
88 TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named_types(inner, out),
89 TypeRef::Map(k, v) => {
90 collect_named_types(k, out);
91 collect_named_types(v, out);
92 }
93 _ => {}
94 }
95}
96
97pub fn field_references_excluded_type(ty: &TypeRef, exclude_types: &[String]) -> bool {
101 match ty {
102 TypeRef::Named(name) => exclude_types.iter().any(|e| e == name),
103 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_excluded_type(inner, exclude_types),
104 TypeRef::Map(k, v) => {
105 field_references_excluded_type(k, exclude_types) || field_references_excluded_type(v, exclude_types)
106 }
107 _ => false,
108 }
109}
110
111pub(crate) fn needs_i64_cast(p: &PrimitiveType) -> bool {
113 matches!(p, PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize)
114}
115
116pub(crate) fn core_prim_str(p: &PrimitiveType) -> &'static str {
118 match p {
119 PrimitiveType::U64 => "u64",
120 PrimitiveType::Usize => "usize",
121 PrimitiveType::Isize => "isize",
122 PrimitiveType::F32 => "f32",
123 PrimitiveType::Bool => "bool",
124 PrimitiveType::U8 => "u8",
125 PrimitiveType::U16 => "u16",
126 PrimitiveType::U32 => "u32",
127 PrimitiveType::I8 => "i8",
128 PrimitiveType::I16 => "i16",
129 PrimitiveType::I32 => "i32",
130 PrimitiveType::I64 => "i64",
131 PrimitiveType::F64 => "f64",
132 }
133}
134
135pub(crate) fn binding_prim_str(p: &PrimitiveType) -> &'static str {
137 match p {
138 PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize => "i64",
139 PrimitiveType::F32 => "f64",
140 PrimitiveType::Bool => "bool",
141 PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 => "i32",
142 PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 => "i32",
143 PrimitiveType::I64 => "i64",
144 PrimitiveType::F64 => "f64",
145 }
146}
147
148pub fn core_to_binding_convertible_types(surface: &ApiSurface) -> AHashSet<String> {
152 let convertible_enums: AHashSet<&str> = surface
153 .enums
154 .iter()
155 .filter(|e| can_generate_enum_conversion_from_core(e))
156 .map(|e| e.name.as_str())
157 .collect();
158
159 let opaque_type_names: AHashSet<&str> = surface
160 .types
161 .iter()
162 .filter(|t| t.is_opaque)
163 .map(|t| t.name.as_str())
164 .collect();
165
166 let (enum_paths, type_paths) = build_rust_path_maps(surface);
168
169 let mut convertible: AHashSet<String> = surface
171 .types
172 .iter()
173 .filter(|t| !t.is_opaque)
174 .map(|t| t.name.clone())
175 .collect();
176
177 let mut changed = true;
178 while changed {
179 changed = false;
180 let snapshot: Vec<String> = convertible.iter().cloned().collect();
181 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
182 known.extend(&opaque_type_names);
183 let mut to_remove = Vec::new();
184 for type_name in &snapshot {
185 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
186 let ok = typ.fields.iter().all(|f| {
187 if f.sanitized {
188 true
189 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
190 false
191 } else {
192 is_field_convertible(&f.ty, &convertible_enums, &known)
193 }
194 });
195 if !ok {
196 to_remove.push(type_name.clone());
197 }
198 }
199 }
200 for name in to_remove {
201 if convertible.remove(&name) {
202 changed = true;
203 }
204 }
205 }
206 convertible
207}
208
209pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
214 let convertible_enums: AHashSet<&str> = surface
216 .enums
217 .iter()
218 .filter(|e| can_generate_enum_conversion(e))
219 .map(|e| e.name.as_str())
220 .collect();
221
222 let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
225
226 let default_type_names: AHashSet<&str> = surface
229 .types
230 .iter()
231 .filter(|t| t.has_default)
232 .map(|t| t.name.as_str())
233 .collect();
234
235 let mut convertible: AHashSet<String> = surface
239 .types
240 .iter()
241 .filter(|t| !t.is_opaque)
242 .map(|t| t.name.clone())
243 .collect();
244
245 let opaque_type_names: AHashSet<&str> = surface
248 .types
249 .iter()
250 .filter(|t| t.is_opaque)
251 .map(|t| t.name.as_str())
252 .collect();
253
254 let (enum_paths, type_paths) = build_rust_path_maps(surface);
256
257 let mut changed = true;
262 while changed {
263 changed = false;
264 let snapshot: Vec<String> = convertible.iter().cloned().collect();
265 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
266 known.extend(&opaque_type_names);
267 let mut to_remove = Vec::new();
268 for type_name in &snapshot {
269 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
270 let ok = typ.fields.iter().all(|f| {
271 if f.sanitized {
272 sanitized_field_has_default(&f.ty, &default_type_names)
273 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
274 false
275 } else {
276 is_field_convertible(&f.ty, &convertible_enums, &known)
277 }
278 });
279 if !ok {
280 to_remove.push(type_name.clone());
281 }
282 }
283 }
284 for name in to_remove {
285 if convertible.remove(&name) {
286 changed = true;
287 }
288 }
289 }
290 convertible
291}
292
293fn sanitized_field_has_default(ty: &TypeRef, default_types: &AHashSet<&str>) -> bool {
298 match ty {
299 TypeRef::Primitive(_)
300 | TypeRef::String
301 | TypeRef::Char
302 | TypeRef::Bytes
303 | TypeRef::Path
304 | TypeRef::Unit
305 | TypeRef::Duration
306 | TypeRef::Json => true,
307 TypeRef::Optional(_) => true,
309 TypeRef::Vec(_) => true,
311 TypeRef::Map(_, _) => true,
313 TypeRef::Named(name) => {
314 if is_tuple_type_name(name) {
315 true
317 } else {
318 default_types.contains(name.as_str())
320 }
321 }
322 }
323}
324
325pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
327 convertible.contains(&typ.name)
328}
329
330pub(crate) fn is_field_convertible(
331 ty: &TypeRef,
332 convertible_enums: &AHashSet<&str>,
333 known_types: &AHashSet<&str>,
334) -> bool {
335 match ty {
336 TypeRef::Primitive(_)
337 | TypeRef::String
338 | TypeRef::Char
339 | TypeRef::Bytes
340 | TypeRef::Path
341 | TypeRef::Unit
342 | TypeRef::Duration => true,
343 TypeRef::Json => true,
344 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
345 TypeRef::Map(k, v) => {
346 is_field_convertible(k, convertible_enums, known_types)
347 && is_field_convertible(v, convertible_enums, known_types)
348 }
349 TypeRef::Named(name) if is_tuple_type_name(name) => true,
351 TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
353 }
354}
355
356fn field_has_path_mismatch(
362 field: &FieldDef,
363 enum_rust_paths: &AHashMap<&str, &str>,
364 type_rust_paths: &AHashMap<&str, &str>,
365) -> bool {
366 let name = match &field.ty {
367 TypeRef::Named(n) => n.as_str(),
368 TypeRef::Optional(inner) | TypeRef::Vec(inner) => match inner.as_ref() {
369 TypeRef::Named(n) => n.as_str(),
370 _ => return false,
371 },
372 _ => return false,
373 };
374
375 if let Some(field_path) = &field.type_rust_path {
376 if let Some(enum_path) = enum_rust_paths.get(name) {
377 if !paths_compatible(field_path, enum_path) {
378 return true;
379 }
380 }
381 if let Some(type_path) = type_rust_paths.get(name) {
382 if !paths_compatible(field_path, type_path) {
383 return true;
384 }
385 }
386 }
387 false
388}
389
390fn paths_compatible(a: &str, b: &str) -> bool {
395 if a == b {
396 return true;
397 }
398 let a_norm = a.replace('-', "_");
401 let b_norm = b.replace('-', "_");
402 if a_norm == b_norm {
403 return true;
404 }
405 if a_norm.ends_with(&b_norm) || b_norm.ends_with(&a_norm) {
407 return true;
408 }
409 let a_root = a_norm.split("::").next().unwrap_or("");
411 let b_root = b_norm.split("::").next().unwrap_or("");
412 let a_name = a_norm.rsplit("::").next().unwrap_or("");
413 let b_name = b_norm.rsplit("::").next().unwrap_or("");
414 a_root == b_root && a_name == b_name
415}
416
417fn build_rust_path_maps(surface: &ApiSurface) -> (AHashMap<&str, &str>, AHashMap<&str, &str>) {
419 let enum_paths: AHashMap<&str, &str> = surface
420 .enums
421 .iter()
422 .map(|e| (e.name.as_str(), e.rust_path.as_str()))
423 .collect();
424 let type_paths: AHashMap<&str, &str> = surface
425 .types
426 .iter()
427 .map(|t| (t.name.as_str(), t.rust_path.as_str()))
428 .collect();
429 (enum_paths, type_paths)
430}
431
432pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
436 !enum_def.variants.is_empty()
437}
438
439pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
442 !enum_def.variants.is_empty()
444}
445
446pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
448 !fields.is_empty()
449 && fields[0]
450 .name
451 .strip_prefix('_')
452 .is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
453}
454
455pub fn is_newtype(typ: &TypeDef) -> bool {
457 typ.fields.len() == 1 && typ.fields[0].name == "_0"
458}
459
460pub(crate) fn is_tuple_type_name(name: &str) -> bool {
463 name.starts_with('(')
464}
465
466pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
468 let path = typ.rust_path.replace('-', "_");
471 if path.starts_with(core_import) {
473 path
474 } else {
475 format!("{core_import}::{}", typ.name)
477 }
478}
479
480pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
482 typ.fields.iter().any(|f| f.sanitized)
483}
484
485pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
487 let path = enum_def.rust_path.replace('-', "_");
488 if path.starts_with(core_import) {
489 path
490 } else {
491 format!("{core_import}::{}", enum_def.name)
492 }
493}
494
495pub fn build_type_path_map(surface: &ApiSurface, core_import: &str) -> AHashMap<String, String> {
500 let mut map = AHashMap::new();
501 for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
502 let path = typ.rust_path.replace('-', "_");
503 let resolved = if path.starts_with(core_import) {
504 path
505 } else {
506 format!("{core_import}::{}", typ.name)
507 };
508 map.insert(typ.name.clone(), resolved);
509 }
510 for en in &surface.enums {
511 let path = en.rust_path.replace('-', "_");
512 let resolved = if path.starts_with(core_import) {
513 path
514 } else {
515 format!("{core_import}::{}", en.name)
516 };
517 map.insert(en.name.clone(), resolved);
518 }
519 map
520}
521
522pub fn resolve_named_path(name: &str, core_import: &str, path_map: &AHashMap<String, String>) -> String {
527 if let Some(path) = path_map.get(name) {
528 path.clone()
529 } else {
530 format!("{core_import}::{name}")
531 }
532}
533
534pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
538 binding_to_core_match_arm_ext(binding_prefix, variant_name, fields, false)
539}
540
541pub fn binding_to_core_match_arm_ext(
544 binding_prefix: &str,
545 variant_name: &str,
546 fields: &[FieldDef],
547 binding_has_data: bool,
548) -> String {
549 if fields.is_empty() {
550 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
551 } else if !binding_has_data {
552 if is_tuple_variant(fields) {
554 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
555 format!(
556 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
557 defaults.join(", ")
558 )
559 } else {
560 let defaults: Vec<String> = fields
561 .iter()
562 .map(|f| format!("{}: Default::default()", f.name))
563 .collect();
564 format!(
565 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
566 defaults.join(", ")
567 )
568 }
569 } else if is_tuple_variant(fields) {
570 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
572 let binding_pattern = field_names.join(", ");
573 let core_args: Vec<String> = fields
575 .iter()
576 .map(|f| {
577 let name = &f.name;
578 let expr = if matches!(&f.ty, TypeRef::Named(_)) {
579 format!("{name}.into()")
580 } else if f.sanitized {
581 format!("serde_json::from_str(&{name}).unwrap_or_default()")
582 } else {
583 name.clone()
584 };
585 if f.is_boxed { format!("Box::new({expr})") } else { expr }
586 })
587 .collect();
588 format!(
589 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
590 core_args.join(", ")
591 )
592 } else {
593 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
595 let pattern = field_names.join(", ");
596 let core_fields: Vec<String> = fields
597 .iter()
598 .map(|f| {
599 if matches!(&f.ty, TypeRef::Named(_)) {
600 format!("{}: {}.into()", f.name, f.name)
601 } else if f.sanitized {
602 format!("{}: serde_json::from_str(&{}).unwrap_or_default()", f.name, f.name)
606 } else {
607 format!("{0}: {0}", f.name)
608 }
609 })
610 .collect();
611 format!(
612 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
613 core_fields.join(", ")
614 )
615 }
616}
617
618pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
622 core_to_binding_match_arm_ext(core_prefix, variant_name, fields, false)
623}
624
625pub fn core_to_binding_match_arm_ext(
628 core_prefix: &str,
629 variant_name: &str,
630 fields: &[FieldDef],
631 binding_has_data: bool,
632) -> String {
633 if fields.is_empty() {
634 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
635 } else if !binding_has_data {
636 if is_tuple_variant(fields) {
638 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
639 } else {
640 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
641 }
642 } else if is_tuple_variant(fields) {
643 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
645 let core_pattern = field_names.join(", ");
646 let binding_fields: Vec<String> = fields
648 .iter()
649 .map(|f| {
650 let name = &f.name;
651 let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
652 format!("(*{name}).into()")
653 } else if f.is_boxed {
654 format!("*{name}")
655 } else if matches!(&f.ty, TypeRef::Named(_)) {
656 format!("{name}.into()")
657 } else if f.sanitized {
658 format!("serde_json::to_string(&{name}).unwrap_or_default()")
659 } else {
660 name.clone()
661 };
662 format!("{name}: {expr}")
663 })
664 .collect();
665 format!(
666 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
667 binding_fields.join(", ")
668 )
669 } else {
670 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
671 let pattern = field_names.join(", ");
672 let binding_fields: Vec<String> = fields
673 .iter()
674 .map(|f| {
675 if matches!(&f.ty, TypeRef::Named(_)) {
676 format!("{}: {}.into()", f.name, f.name)
677 } else if f.sanitized {
678 format!("{}: serde_json::to_string(&{}).unwrap_or_default()", f.name, f.name)
682 } else {
683 format!("{0}: {0}", f.name)
684 }
685 })
686 .collect();
687 format!(
688 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
689 binding_fields.join(", ")
690 )
691 }
692}