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 _ => unreachable!(),
17 }
18}
19
20pub(crate) fn binding_prim_str(p: &PrimitiveType) -> &'static str {
22 match p {
23 PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize => "i64",
24 _ => unreachable!(),
25 }
26}
27
28pub fn core_to_binding_convertible_types(surface: &ApiSurface) -> AHashSet<String> {
32 let convertible_enums: AHashSet<&str> = surface
33 .enums
34 .iter()
35 .filter(|e| can_generate_enum_conversion_from_core(e))
36 .map(|e| e.name.as_str())
37 .collect();
38
39 let opaque_type_names: AHashSet<&str> = surface
40 .types
41 .iter()
42 .filter(|t| t.is_opaque)
43 .map(|t| t.name.as_str())
44 .collect();
45
46 let mut convertible: AHashSet<String> = surface
48 .types
49 .iter()
50 .filter(|t| !t.is_opaque)
51 .map(|t| t.name.clone())
52 .collect();
53
54 let mut changed = true;
55 while changed {
56 changed = false;
57 let snapshot: Vec<String> = convertible.iter().cloned().collect();
58 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
59 known.extend(&opaque_type_names);
60 let mut to_remove = Vec::new();
61 for type_name in &snapshot {
62 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
63 let ok = typ
64 .fields
65 .iter()
66 .all(|f| f.sanitized || is_field_convertible(&f.ty, &convertible_enums, &known));
67 if !ok {
68 to_remove.push(type_name.clone());
69 }
70 }
71 }
72 for name in to_remove {
73 if convertible.remove(&name) {
74 changed = true;
75 }
76 }
77 }
78 convertible
79}
80
81pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
86 let convertible_enums: AHashSet<&str> = surface
88 .enums
89 .iter()
90 .filter(|e| can_generate_enum_conversion(e))
91 .map(|e| e.name.as_str())
92 .collect();
93
94 let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
97
98 let mut convertible: AHashSet<String> = surface
102 .types
103 .iter()
104 .filter(|t| !t.is_opaque)
105 .map(|t| t.name.clone())
106 .collect();
107
108 let opaque_type_names: AHashSet<&str> = surface
111 .types
112 .iter()
113 .filter(|t| t.is_opaque)
114 .map(|t| t.name.as_str())
115 .collect();
116
117 let mut changed = true;
122 while changed {
123 changed = false;
124 let snapshot: Vec<String> = convertible.iter().cloned().collect();
125 let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
126 known.extend(&opaque_type_names);
127 let mut to_remove = Vec::new();
128 for type_name in &snapshot {
129 if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
130 let ok = typ
131 .fields
132 .iter()
133 .all(|f| is_field_convertible(&f.ty, &convertible_enums, &known));
134 if !ok {
135 to_remove.push(type_name.clone());
136 }
137 }
138 }
139 for name in to_remove {
140 if convertible.remove(&name) {
141 changed = true;
142 }
143 }
144 }
145 convertible
146}
147
148pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
150 convertible.contains(&typ.name)
151}
152
153pub(crate) fn is_field_convertible(
154 ty: &TypeRef,
155 convertible_enums: &AHashSet<&str>,
156 known_types: &AHashSet<&str>,
157) -> bool {
158 match ty {
159 TypeRef::Primitive(_)
160 | TypeRef::String
161 | TypeRef::Char
162 | TypeRef::Bytes
163 | TypeRef::Path
164 | TypeRef::Unit
165 | TypeRef::Duration => true,
166 TypeRef::Json => false,
167 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
168 TypeRef::Map(k, v) => {
169 is_field_convertible(k, convertible_enums, known_types)
170 && is_field_convertible(v, convertible_enums, known_types)
171 }
172 TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
174 }
175}
176
177pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
181 enum_def
182 .variants
183 .iter()
184 .all(|v| v.fields.iter().all(|f| is_simple_type(&f.ty)))
185}
186
187pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
190 !enum_def.variants.is_empty()
192}
193
194pub(crate) fn is_simple_type(ty: &TypeRef) -> bool {
197 match ty {
198 TypeRef::Primitive(_)
199 | TypeRef::String
200 | TypeRef::Char
201 | TypeRef::Bytes
202 | TypeRef::Path
203 | TypeRef::Unit
204 | TypeRef::Duration => true,
205 TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_simple_type(inner),
206 TypeRef::Map(k, v) => is_simple_type(k) && is_simple_type(v),
207 TypeRef::Named(_) | TypeRef::Json => false,
208 }
209}
210
211pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
213 !fields.is_empty()
214 && fields[0]
215 .name
216 .strip_prefix('_')
217 .is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
218}
219
220pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
222 let path = typ.rust_path.replace('-', "_");
225 if path.starts_with(core_import) {
227 path
228 } else {
229 format!("{core_import}::{}", typ.name)
231 }
232}
233
234pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
236 typ.fields.iter().any(|f| f.sanitized)
237}
238
239pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
241 let path = enum_def.rust_path.replace('-', "_");
242 if path.starts_with(core_import) {
243 path
244 } else {
245 format!("{core_import}::{}", enum_def.name)
246 }
247}
248
249pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
253 if fields.is_empty() {
254 format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
255 } else if is_tuple_variant(fields) {
256 let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
257 format!(
258 "{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
259 defaults.join(", ")
260 )
261 } else {
262 let defaults: Vec<String> = fields
263 .iter()
264 .map(|f| format!("{}: Default::default()", f.name))
265 .collect();
266 format!(
267 "{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
268 defaults.join(", ")
269 )
270 }
271}
272
273pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
277 if fields.is_empty() {
278 format!("{core_prefix}::{variant_name} => Self::{variant_name},")
279 } else if is_tuple_variant(fields) {
280 format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
281 } else {
282 format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
283 }
284}