1use ahash::{AHashMap, AHashSet};
2use alef_core::ir::{ApiSurface, EnumDef, FieldDef, PrimitiveType, TypeDef, TypeRef};
3
4pub fn input_type_names(surface: &ApiSurface) -> AHashSet<String> {
13 let mut names = AHashSet::new();
14
15 for func in &surface.functions {
17 for param in &func.params {
18 collect_named_types(¶m.ty, &mut names);
19 }
20 }
21 for typ in &surface.types {
23 for method in &typ.methods {
24 for param in &method.params {
25 collect_named_types(¶m.ty, &mut names);
26 }
27 }
28 }
29
30 let mut changed = true;
32 while changed {
33 changed = false;
34 let snapshot: Vec<String> = names.iter().cloned().collect();
35 for name in &snapshot {
36 if let Some(typ) = surface.types.iter().find(|t| t.name == *name) {
37 for field in &typ.fields {
38 let mut field_names = AHashSet::new();
39 collect_named_types(&field.ty, &mut field_names);
40 for n in field_names {
41 if names.insert(n) {
42 changed = true;
43 }
44 }
45 }
46 }
47 }
48 }
49
50 names
51}
52
53fn collect_named_types(ty: &TypeRef, out: &mut AHashSet<String>) {
55 match ty {
56 TypeRef::Named(name) => {
57 out.insert(name.clone());
58 }
59 TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named_types(inner, out),
60 TypeRef::Map(k, v) => {
61 collect_named_types(k, out);
62 collect_named_types(v, out);
63 }
64 _ => {}
65 }
66}
67
68pub fn field_references_excluded_type(ty: &TypeRef, exclude_types: &[String]) -> bool {
72 match ty {
73 TypeRef::Named(name) => exclude_types.iter().any(|e| e == name),
74 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_excluded_type(inner, exclude_types),
75 TypeRef::Map(k, v) => {
76 field_references_excluded_type(k, exclude_types) || field_references_excluded_type(v, exclude_types)
77 }
78 _ => false,
79 }
80}
81
82pub(crate) fn needs_i64_cast(p: &PrimitiveType) -> bool {
84 matches!(p, PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize)
85}
86
87pub(crate) fn core_prim_str(p: &PrimitiveType) -> &'static str {
89 match p {
90 PrimitiveType::U64 => "u64",
91 PrimitiveType::Usize => "usize",
92 PrimitiveType::Isize => "isize",
93 PrimitiveType::F32 => "f32",
94 PrimitiveType::Bool => "bool",
95 PrimitiveType::U8 => "u8",
96 PrimitiveType::U16 => "u16",
97 PrimitiveType::U32 => "u32",
98 PrimitiveType::I8 => "i8",
99 PrimitiveType::I16 => "i16",
100 PrimitiveType::I32 => "i32",
101 PrimitiveType::I64 => "i64",
102 PrimitiveType::F64 => "f64",
103 }
104}
105
106pub(crate) fn binding_prim_str(p: &PrimitiveType) -> &'static str {
108 match p {
109 PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize => "i64",
110 PrimitiveType::F32 => "f64",
111 PrimitiveType::Bool => "bool",
112 PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 => "i32",
113 PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 => "i32",
114 PrimitiveType::I64 => "i64",
115 PrimitiveType::F64 => "f64",
116 }
117}
118
119pub fn core_to_binding_convertible_types(surface: &ApiSurface) -> AHashSet<String> {
123 let convertible_enums: AHashSet<&str> = surface
124 .enums
125 .iter()
126 .filter(|e| can_generate_enum_conversion_from_core(e))
127 .map(|e| e.name.as_str())
128 .collect();
129
130 let opaque_type_names: AHashSet<&str> = surface
131 .types
132 .iter()
133 .filter(|t| t.is_opaque)
134 .map(|t| t.name.as_str())
135 .collect();
136
137 let (enum_paths, type_paths) = build_rust_path_maps(surface);
139
140 let mut convertible: AHashSet<String> = surface
142 .types
143 .iter()
144 .filter(|t| !t.is_opaque)
145 .map(|t| t.name.clone())
146 .collect();
147
148 let mut changed = true;
149 while changed {
150 changed = false;
151 let snapshot: Vec<String> = convertible.iter().cloned().collect();
152 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
153 known.extend(&opaque_type_names);
154 let mut to_remove = Vec::new();
155 for type_name in &snapshot {
156 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
157 let ok = typ.fields.iter().all(|f| {
158 if f.sanitized {
159 true
160 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
161 false
162 } else {
163 is_field_convertible(&f.ty, &convertible_enums, &known)
164 }
165 });
166 if !ok {
167 to_remove.push(type_name.clone());
168 }
169 }
170 }
171 for name in to_remove {
172 if convertible.remove(&name) {
173 changed = true;
174 }
175 }
176 }
177 convertible
178}
179
180pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
185 let convertible_enums: AHashSet<&str> = surface
187 .enums
188 .iter()
189 .filter(|e| can_generate_enum_conversion(e))
190 .map(|e| e.name.as_str())
191 .collect();
192
193 let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
196
197 let default_type_names: AHashSet<&str> = surface
200 .types
201 .iter()
202 .filter(|t| t.has_default)
203 .map(|t| t.name.as_str())
204 .collect();
205
206 let mut convertible: AHashSet<String> = surface
210 .types
211 .iter()
212 .filter(|t| !t.is_opaque)
213 .map(|t| t.name.clone())
214 .collect();
215
216 let opaque_type_names: AHashSet<&str> = surface
219 .types
220 .iter()
221 .filter(|t| t.is_opaque)
222 .map(|t| t.name.as_str())
223 .collect();
224
225 let (enum_paths, type_paths) = build_rust_path_maps(surface);
227
228 let mut changed = true;
233 while changed {
234 changed = false;
235 let snapshot: Vec<String> = convertible.iter().cloned().collect();
236 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
237 known.extend(&opaque_type_names);
238 let mut to_remove = Vec::new();
239 for type_name in &snapshot {
240 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
241 let ok = typ.fields.iter().all(|f| {
242 if f.sanitized {
243 sanitized_field_has_default(&f.ty, &default_type_names)
244 } else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
245 false
246 } else {
247 is_field_convertible(&f.ty, &convertible_enums, &known)
248 }
249 });
250 if !ok {
251 to_remove.push(type_name.clone());
252 }
253 }
254 }
255 for name in to_remove {
256 if convertible.remove(&name) {
257 changed = true;
258 }
259 }
260 }
261 convertible
262}
263
264fn sanitized_field_has_default(ty: &TypeRef, default_types: &AHashSet<&str>) -> bool {
269 match ty {
270 TypeRef::Primitive(_)
271 | TypeRef::String
272 | TypeRef::Char
273 | TypeRef::Bytes
274 | TypeRef::Path
275 | TypeRef::Unit
276 | TypeRef::Duration
277 | TypeRef::Json => true,
278 TypeRef::Optional(_) => true,
280 TypeRef::Vec(_) => true,
282 TypeRef::Map(_, _) => true,
284 TypeRef::Named(name) => {
285 if is_tuple_type_name(name) {
286 true
288 } else {
289 default_types.contains(name.as_str())
291 }
292 }
293 }
294}
295
296pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
298 convertible.contains(&typ.name)
299}
300
301pub(crate) fn is_field_convertible(
302 ty: &TypeRef,
303 convertible_enums: &AHashSet<&str>,
304 known_types: &AHashSet<&str>,
305) -> bool {
306 match ty {
307 TypeRef::Primitive(_)
308 | TypeRef::String
309 | TypeRef::Char
310 | TypeRef::Bytes
311 | TypeRef::Path
312 | TypeRef::Unit
313 | TypeRef::Duration => true,
314 TypeRef::Json => true,
315 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
316 TypeRef::Map(k, v) => {
317 is_field_convertible(k, convertible_enums, known_types)
318 && is_field_convertible(v, convertible_enums, known_types)
319 }
320 TypeRef::Named(name) if is_tuple_type_name(name) => true,
322 TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
324 }
325}
326
327fn field_has_path_mismatch(
333 field: &FieldDef,
334 enum_rust_paths: &AHashMap<&str, &str>,
335 type_rust_paths: &AHashMap<&str, &str>,
336) -> bool {
337 let name = match &field.ty {
338 TypeRef::Named(n) => n.as_str(),
339 TypeRef::Optional(inner) | TypeRef::Vec(inner) => match inner.as_ref() {
340 TypeRef::Named(n) => n.as_str(),
341 _ => return false,
342 },
343 _ => return false,
344 };
345
346 if let Some(field_path) = &field.type_rust_path {
347 if let Some(enum_path) = enum_rust_paths.get(name) {
348 if !paths_compatible(field_path, enum_path) {
349 return true;
350 }
351 }
352 if let Some(type_path) = type_rust_paths.get(name) {
353 if !paths_compatible(field_path, type_path) {
354 return true;
355 }
356 }
357 }
358 false
359}
360
361fn paths_compatible(a: &str, b: &str) -> bool {
366 if a == b {
367 return true;
368 }
369 if a.ends_with(b) || b.ends_with(a) {
371 return true;
372 }
373 let a_root = a.split("::").next().unwrap_or("");
375 let b_root = b.split("::").next().unwrap_or("");
376 let a_name = a.rsplit("::").next().unwrap_or("");
377 let b_name = b.rsplit("::").next().unwrap_or("");
378 a_root == b_root && a_name == b_name
379}
380
381fn build_rust_path_maps(surface: &ApiSurface) -> (AHashMap<&str, &str>, AHashMap<&str, &str>) {
383 let enum_paths: AHashMap<&str, &str> = surface
384 .enums
385 .iter()
386 .map(|e| (e.name.as_str(), e.rust_path.as_str()))
387 .collect();
388 let type_paths: AHashMap<&str, &str> = surface
389 .types
390 .iter()
391 .map(|t| (t.name.as_str(), t.rust_path.as_str()))
392 .collect();
393 (enum_paths, type_paths)
394}
395
396pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
400 !enum_def.variants.is_empty()
401}
402
403pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
406 !enum_def.variants.is_empty()
408}
409
410pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
412 !fields.is_empty()
413 && fields[0]
414 .name
415 .strip_prefix('_')
416 .is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
417}
418
419pub fn is_newtype(typ: &TypeDef) -> bool {
421 typ.fields.len() == 1 && typ.fields[0].name == "_0"
422}
423
424pub(crate) fn is_tuple_type_name(name: &str) -> bool {
427 name.starts_with('(')
428}
429
430pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
432 let path = typ.rust_path.replace('-', "_");
435 if path.starts_with(core_import) {
437 path
438 } else {
439 format!("{core_import}::{}", typ.name)
441 }
442}
443
444pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
446 typ.fields.iter().any(|f| f.sanitized)
447}
448
449pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
451 let path = enum_def.rust_path.replace('-', "_");
452 if path.starts_with(core_import) {
453 path
454 } else {
455 format!("{core_import}::{}", enum_def.name)
456 }
457}
458
459pub fn build_type_path_map(surface: &ApiSurface, core_import: &str) -> AHashMap<String, String> {
464 let mut map = AHashMap::new();
465 for typ in &surface.types {
466 let path = typ.rust_path.replace('-', "_");
467 let resolved = if path.starts_with(core_import) {
468 path
469 } else {
470 format!("{core_import}::{}", typ.name)
471 };
472 map.insert(typ.name.clone(), resolved);
473 }
474 for en in &surface.enums {
475 let path = en.rust_path.replace('-', "_");
476 let resolved = if path.starts_with(core_import) {
477 path
478 } else {
479 format!("{core_import}::{}", en.name)
480 };
481 map.insert(en.name.clone(), resolved);
482 }
483 map
484}
485
486pub fn resolve_named_path(name: &str, core_import: &str, path_map: &AHashMap<String, String>) -> String {
491 if let Some(path) = path_map.get(name) {
492 path.clone()
493 } else {
494 format!("{core_import}::{name}")
495 }
496}
497
498pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
502 binding_to_core_match_arm_ext(binding_prefix, variant_name, fields, false)
503}
504
505pub fn binding_to_core_match_arm_ext(
508 binding_prefix: &str,
509 variant_name: &str,
510 fields: &[FieldDef],
511 binding_has_data: bool,
512) -> String {
513 if fields.is_empty() {
514 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
515 } else if !binding_has_data {
516 if is_tuple_variant(fields) {
518 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
519 format!(
520 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
521 defaults.join(", ")
522 )
523 } else {
524 let defaults: Vec<String> = fields
525 .iter()
526 .map(|f| format!("{}: Default::default()", f.name))
527 .collect();
528 format!(
529 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
530 defaults.join(", ")
531 )
532 }
533 } else if is_tuple_variant(fields) {
534 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
536 let binding_pattern = field_names.join(", ");
537 let core_args: Vec<String> = fields
539 .iter()
540 .map(|f| {
541 let name = &f.name;
542 let expr = if matches!(&f.ty, TypeRef::Named(_)) {
543 format!("{name}.into()")
544 } else {
545 name.clone()
546 };
547 if f.is_boxed { format!("Box::new({expr})") } else { expr }
548 })
549 .collect();
550 format!(
551 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
552 core_args.join(", ")
553 )
554 } else {
555 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
557 let pattern = field_names.join(", ");
558 let core_fields: Vec<String> = fields
559 .iter()
560 .map(|f| {
561 if matches!(&f.ty, TypeRef::Named(_)) {
562 format!("{}: {}.into()", f.name, f.name)
563 } else {
564 format!("{0}: {0}", f.name)
565 }
566 })
567 .collect();
568 format!(
569 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
570 core_fields.join(", ")
571 )
572 }
573}
574
575pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
579 core_to_binding_match_arm_ext(core_prefix, variant_name, fields, false)
580}
581
582pub fn core_to_binding_match_arm_ext(
585 core_prefix: &str,
586 variant_name: &str,
587 fields: &[FieldDef],
588 binding_has_data: bool,
589) -> String {
590 if fields.is_empty() {
591 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
592 } else if !binding_has_data {
593 if is_tuple_variant(fields) {
595 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
596 } else {
597 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
598 }
599 } else if is_tuple_variant(fields) {
600 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
602 let core_pattern = field_names.join(", ");
603 let binding_fields: Vec<String> = fields
605 .iter()
606 .map(|f| {
607 let name = &f.name;
608 let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
609 format!("(*{name}).into()")
610 } else if f.is_boxed {
611 format!("*{name}")
612 } else if matches!(&f.ty, TypeRef::Named(_)) {
613 format!("{name}.into()")
614 } else if f.sanitized {
615 format!("serde_json::to_string(&{name}).unwrap_or_default()")
616 } else {
617 name.clone()
618 };
619 format!("{name}: {expr}")
620 })
621 .collect();
622 format!(
623 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
624 binding_fields.join(", ")
625 )
626 } else {
627 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
628 let pattern = field_names.join(", ");
629 let binding_fields: Vec<String> = fields
630 .iter()
631 .map(|f| {
632 if matches!(&f.ty, TypeRef::Named(_)) {
633 format!("{}: {}.into()", f.name, f.name)
634 } else if f.sanitized {
635 format!("{}: serde_json::to_string(&{}).unwrap_or_default()", f.name, f.name)
639 } else {
640 format!("{0}: {0}", f.name)
641 }
642 })
643 .collect();
644 format!(
645 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
646 binding_fields.join(", ")
647 )
648 }
649}