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 if fields.is_empty() {
306 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
307 } else if is_tuple_variant(fields) {
308 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
310 let binding_pattern = field_names.join(", ");
311 let core_args: Vec<String> = fields
313 .iter()
314 .map(|f| {
315 let name = &f.name;
316 let expr = if matches!(&f.ty, TypeRef::Named(_)) {
317 format!("{name}.into()")
318 } else {
319 name.clone()
320 };
321 if f.is_boxed { format!("Box::new({expr})") } else { expr }
322 })
323 .collect();
324 format!(
325 "{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
326 core_args.join(", ")
327 )
328 } else {
329 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
331 let pattern = field_names.join(", ");
332 let core_fields: Vec<String> = fields
333 .iter()
334 .map(|f| {
335 if matches!(&f.ty, TypeRef::Named(_)) {
336 format!("{}: {}.into()", f.name, f.name)
337 } else {
338 format!("{0}: {0}", f.name)
339 }
340 })
341 .collect();
342 format!(
343 "{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
344 core_fields.join(", ")
345 )
346 }
347}
348
349pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
353 if fields.is_empty() {
354 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
355 } else if is_tuple_variant(fields) {
356 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
358 let core_pattern = field_names.join(", ");
359 let binding_fields: Vec<String> = fields
361 .iter()
362 .map(|f| {
363 let name = &f.name;
364 let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
365 format!("(*{name}).into()")
366 } else if f.is_boxed {
367 format!("*{name}")
368 } else if matches!(&f.ty, TypeRef::Named(_)) {
369 format!("{name}.into()")
370 } else {
371 name.clone()
372 };
373 format!("{name}: {expr}")
374 })
375 .collect();
376 format!(
377 "{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
378 binding_fields.join(", ")
379 )
380 } else {
381 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
382 let pattern = field_names.join(", ");
383 let binding_fields: Vec<String> = fields
384 .iter()
385 .map(|f| {
386 if matches!(&f.ty, TypeRef::Named(_)) {
387 format!("{}: {}.into()", f.name, f.name)
388 } else {
389 format!("{0}: {0}", f.name)
390 }
391 })
392 .collect();
393 format!(
394 "{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
395 binding_fields.join(", ")
396 )
397 }
398}