alef_codegen/conversions/
helpers.rs1use ahash::AHashSet;
2use alef_core::ir::{ApiSurface, EnumDef, FieldDef, PrimitiveType, TypeDef, TypeRef};
3
4pub(crate) fn needs_i64_cast(p: &PrimitiveType) -> bool {
6 matches!(p, PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize)
7}
8
9pub(crate) fn core_prim_str(p: &PrimitiveType) -> &'static str {
11 match p {
12 PrimitiveType::U64 => "u64",
13 PrimitiveType::Usize => "usize",
14 PrimitiveType::Isize => "isize",
15 PrimitiveType::F32 => "f32",
16 PrimitiveType::Bool => "bool",
17 PrimitiveType::U8 => "u8",
18 PrimitiveType::U16 => "u16",
19 PrimitiveType::U32 => "u32",
20 PrimitiveType::I8 => "i8",
21 PrimitiveType::I16 => "i16",
22 PrimitiveType::I32 => "i32",
23 PrimitiveType::I64 => "i64",
24 PrimitiveType::F64 => "f64",
25 }
26}
27
28pub(crate) fn binding_prim_str(p: &PrimitiveType) -> &'static str {
30 match p {
31 PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize => "i64",
32 PrimitiveType::F32 => "f64",
33 PrimitiveType::Bool => "bool",
34 PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 => "i32",
35 PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 => "i32",
36 PrimitiveType::I64 => "i64",
37 PrimitiveType::F64 => "f64",
38 }
39}
40
41pub fn core_to_binding_convertible_types(surface: &ApiSurface) -> AHashSet<String> {
45 let convertible_enums: AHashSet<&str> = surface
46 .enums
47 .iter()
48 .filter(|e| can_generate_enum_conversion_from_core(e))
49 .map(|e| e.name.as_str())
50 .collect();
51
52 let opaque_type_names: AHashSet<&str> = surface
53 .types
54 .iter()
55 .filter(|t| t.is_opaque)
56 .map(|t| t.name.as_str())
57 .collect();
58
59 let mut convertible: AHashSet<String> = surface
61 .types
62 .iter()
63 .filter(|t| !t.is_opaque)
64 .map(|t| t.name.clone())
65 .collect();
66
67 let mut changed = true;
68 while changed {
69 changed = false;
70 let snapshot: Vec<String> = convertible.iter().cloned().collect();
71 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
72 known.extend(&opaque_type_names);
73 let mut to_remove = Vec::new();
74 for type_name in &snapshot {
75 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
76 let ok = typ
77 .fields
78 .iter()
79 .all(|f| f.sanitized || is_field_convertible(&f.ty, &convertible_enums, &known));
80 if !ok {
81 to_remove.push(type_name.clone());
82 }
83 }
84 }
85 for name in to_remove {
86 if convertible.remove(&name) {
87 changed = true;
88 }
89 }
90 }
91 convertible
92}
93
94pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
99 let convertible_enums: AHashSet<&str> = surface
101 .enums
102 .iter()
103 .filter(|e| can_generate_enum_conversion(e))
104 .map(|e| e.name.as_str())
105 .collect();
106
107 let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
110
111 let default_type_names: AHashSet<&str> = surface
114 .types
115 .iter()
116 .filter(|t| t.has_default)
117 .map(|t| t.name.as_str())
118 .collect();
119
120 let mut convertible: AHashSet<String> = surface
124 .types
125 .iter()
126 .filter(|t| !t.is_opaque)
127 .map(|t| t.name.clone())
128 .collect();
129
130 let opaque_type_names: AHashSet<&str> = surface
133 .types
134 .iter()
135 .filter(|t| t.is_opaque)
136 .map(|t| t.name.as_str())
137 .collect();
138
139 let mut changed = true;
144 while changed {
145 changed = false;
146 let snapshot: Vec<String> = convertible.iter().cloned().collect();
147 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
148 known.extend(&opaque_type_names);
149 let mut to_remove = Vec::new();
150 for type_name in &snapshot {
151 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
152 let ok = typ.fields.iter().all(|f| {
153 if f.sanitized {
154 sanitized_field_has_default(&f.ty, &default_type_names)
157 } else {
158 is_field_convertible(&f.ty, &convertible_enums, &known)
159 }
160 });
161 if !ok {
162 to_remove.push(type_name.clone());
163 }
164 }
165 }
166 for name in to_remove {
167 if convertible.remove(&name) {
168 changed = true;
169 }
170 }
171 }
172 convertible
173}
174
175fn sanitized_field_has_default(ty: &TypeRef, default_types: &AHashSet<&str>) -> bool {
180 match ty {
181 TypeRef::Primitive(_)
182 | TypeRef::String
183 | TypeRef::Char
184 | TypeRef::Bytes
185 | TypeRef::Path
186 | TypeRef::Unit
187 | TypeRef::Duration
188 | TypeRef::Json => true,
189 TypeRef::Optional(_) => true,
191 TypeRef::Vec(_) => true,
193 TypeRef::Map(_, _) => true,
195 TypeRef::Named(name) => {
196 if is_tuple_type_name(name) {
197 true
199 } else {
200 default_types.contains(name.as_str())
202 }
203 }
204 }
205}
206
207pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
209 convertible.contains(&typ.name)
210}
211
212pub(crate) fn is_field_convertible(
213 ty: &TypeRef,
214 convertible_enums: &AHashSet<&str>,
215 known_types: &AHashSet<&str>,
216) -> bool {
217 match ty {
218 TypeRef::Primitive(_)
219 | TypeRef::String
220 | TypeRef::Char
221 | TypeRef::Bytes
222 | TypeRef::Path
223 | TypeRef::Unit
224 | TypeRef::Duration => true,
225 TypeRef::Json => true,
226 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
227 TypeRef::Map(k, v) => {
228 is_field_convertible(k, convertible_enums, known_types)
229 && is_field_convertible(v, convertible_enums, known_types)
230 }
231 TypeRef::Named(name) if is_tuple_type_name(name) => true,
233 TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
235 }
236}
237
238pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
242 !enum_def.variants.is_empty()
243}
244
245pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
248 !enum_def.variants.is_empty()
250}
251
252pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
254 !fields.is_empty()
255 && fields[0]
256 .name
257 .strip_prefix('_')
258 .is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
259}
260
261pub fn is_newtype(typ: &TypeDef) -> bool {
263 typ.fields.len() == 1 && typ.fields[0].name == "_0"
264}
265
266pub(crate) fn is_tuple_type_name(name: &str) -> bool {
269 name.starts_with('(')
270}
271
272pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
274 let path = typ.rust_path.replace('-', "_");
277 if path.starts_with(core_import) {
279 path
280 } else {
281 format!("{core_import}::{}", typ.name)
283 }
284}
285
286pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
288 typ.fields.iter().any(|f| f.sanitized)
289}
290
291pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
293 let path = enum_def.rust_path.replace('-', "_");
294 if path.starts_with(core_import) {
295 path
296 } else {
297 format!("{core_import}::{}", enum_def.name)
298 }
299}
300
301pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
305 binding_to_core_match_arm_ext(binding_prefix, variant_name, fields, false)
306}
307
308pub fn binding_to_core_match_arm_ext(
311 binding_prefix: &str,
312 variant_name: &str,
313 fields: &[FieldDef],
314 binding_has_data: bool,
315) -> String {
316 if fields.is_empty() {
317 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
318 } else if !binding_has_data {
319 if is_tuple_variant(fields) {
321 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
322 format!(
323 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
324 defaults.join(", ")
325 )
326 } else {
327 let defaults: Vec<String> = fields
328 .iter()
329 .map(|f| format!("{}: Default::default()", f.name))
330 .collect();
331 format!(
332 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
333 defaults.join(", ")
334 )
335 }
336 } else if is_tuple_variant(fields) {
337 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
339 let binding_pattern = field_names.join(", ");
340 let core_args: Vec<String> = fields
342 .iter()
343 .map(|f| {
344 let name = &f.name;
345 let expr = if matches!(&f.ty, TypeRef::Named(_)) {
346 format!("{name}.into()")
347 } else {
348 name.clone()
349 };
350 if f.is_boxed { format!("Box::new({expr})") } else { expr }
351 })
352 .collect();
353 format!(
354 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
355 core_args.join(", ")
356 )
357 } else {
358 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
360 let pattern = field_names.join(", ");
361 let core_fields: Vec<String> = fields
362 .iter()
363 .map(|f| {
364 if matches!(&f.ty, TypeRef::Named(_)) {
365 format!("{}: {}.into()", f.name, f.name)
366 } else {
367 format!("{0}: {0}", f.name)
368 }
369 })
370 .collect();
371 format!(
372 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
373 core_fields.join(", ")
374 )
375 }
376}
377
378pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
382 core_to_binding_match_arm_ext(core_prefix, variant_name, fields, false)
383}
384
385pub fn core_to_binding_match_arm_ext(
388 core_prefix: &str,
389 variant_name: &str,
390 fields: &[FieldDef],
391 binding_has_data: bool,
392) -> String {
393 if fields.is_empty() {
394 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
395 } else if !binding_has_data {
396 if is_tuple_variant(fields) {
398 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
399 } else {
400 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
401 }
402 } else if is_tuple_variant(fields) {
403 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
405 let core_pattern = field_names.join(", ");
406 let binding_fields: Vec<String> = fields
408 .iter()
409 .map(|f| {
410 let name = &f.name;
411 let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
412 format!("(*{name}).into()")
413 } else if f.is_boxed {
414 format!("*{name}")
415 } else if matches!(&f.ty, TypeRef::Named(_)) {
416 format!("{name}.into()")
417 } else {
418 name.clone()
419 };
420 format!("{name}: {expr}")
421 })
422 .collect();
423 format!(
424 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
425 binding_fields.join(", ")
426 )
427 } else {
428 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
429 let pattern = field_names.join(", ");
430 let binding_fields: Vec<String> = fields
431 .iter()
432 .map(|f| {
433 if matches!(&f.ty, TypeRef::Named(_)) {
434 format!("{}: {}.into()", f.name, f.name)
435 } else {
436 format!("{0}: {0}", f.name)
437 }
438 })
439 .collect();
440 format!(
441 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
442 binding_fields.join(", ")
443 )
444 }
445}