alef_codegen/conversions/
core_to_binding.rs1use ahash::AHashSet;
2use alef_core::ir::{PrimitiveType, TypeDef, TypeRef};
3use std::fmt::Write;
4
5use super::ConversionConfig;
6use super::binding_to_core::field_conversion_to_core;
7use super::helpers::{binding_prim_str, core_type_path, needs_i64_cast};
8
9pub fn gen_from_core_to_binding(typ: &TypeDef, core_import: &str, opaque_types: &AHashSet<String>) -> String {
11 gen_from_core_to_binding_cfg(typ, core_import, opaque_types, &ConversionConfig::default())
12}
13
14pub fn gen_from_core_to_binding_cfg(
16 typ: &TypeDef,
17 core_import: &str,
18 opaque_types: &AHashSet<String>,
19 config: &ConversionConfig,
20) -> String {
21 let core_path = core_type_path(typ, core_import);
22 let binding_name = format!("{}{}", config.type_name_prefix, typ.name);
23 let mut out = String::with_capacity(256);
24 writeln!(out, "impl From<{core_path}> for {binding_name} {{").ok();
25 writeln!(out, " fn from(val: {core_path}) -> Self {{").ok();
26 let optionalized = config.optionalize_defaults && typ.has_default;
27 writeln!(out, " Self {{").ok();
28 for field in &typ.fields {
29 let base_conversion = field_conversion_from_core_cfg(
30 &field.name,
31 &field.ty,
32 field.optional,
33 field.sanitized,
34 opaque_types,
35 config,
36 );
37 let base_conversion = if field.is_boxed && matches!(&field.ty, TypeRef::Named(_)) {
39 if field.optional {
40 let src = format!("{}: val.{}.map(Into::into)", field.name, field.name);
42 let dst = format!("{}: val.{}.map(|v| (*v).into())", field.name, field.name);
43 if base_conversion == src { dst } else { base_conversion }
44 } else {
45 base_conversion.replace(&format!("val.{}", field.name), &format!("(*val.{})", field.name))
47 }
48 } else {
49 base_conversion
50 };
51 let conversion = if optionalized && !field.optional {
53 if let Some(expr) = base_conversion.strip_prefix(&format!("{}: ", field.name)) {
55 format!("{}: Some({})", field.name, expr)
56 } else {
57 base_conversion
58 }
59 } else {
60 base_conversion
61 };
62 if field.cfg.is_some() {
64 continue;
65 }
66 writeln!(out, " {conversion},").ok();
67 }
68
69 if config.include_cfg_metadata && typ.has_stripped_cfg_fields && typ.name == "ConversionResult" {
71 writeln!(out, " metadata: Some(val.metadata.into()),").ok();
72 }
73
74 writeln!(out, " }}").ok();
75 writeln!(out, " }}").ok();
76 write!(out, "}}").ok();
77 out
78}
79
80pub fn field_conversion_from_core(
83 name: &str,
84 ty: &TypeRef,
85 optional: bool,
86 sanitized: bool,
87 opaque_types: &AHashSet<String>,
88) -> String {
89 if sanitized {
92 if let TypeRef::Vec(inner) = ty {
94 if matches!(inner.as_ref(), TypeRef::String) {
95 if optional {
96 return format!(
97 "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
98 );
99 }
100 return format!("{name}: val.{name}.iter().map(|v| format!(\"{{:?}}\", v)).collect()");
101 }
102 }
103 if let TypeRef::Optional(opt_inner) = ty {
105 if let TypeRef::Vec(vec_inner) = opt_inner.as_ref() {
106 if matches!(vec_inner.as_ref(), TypeRef::String) {
107 return format!(
108 "{name}: val.{name}.as_ref().map(|v| v.iter().map(|i| format!(\"{{:?}}\", i)).collect())"
109 );
110 }
111 }
112 }
113 if optional {
114 return format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{:?}}\", v))");
115 }
116 return format!("{name}: format!(\"{{:?}}\", val.{name})");
117 }
118 match ty {
119 TypeRef::Duration => {
121 if optional {
122 return format!("{name}: val.{name}.map(|d| d.as_secs())");
123 }
124 format!("{name}: val.{name}.as_secs()")
125 }
126 TypeRef::Path => {
128 if optional {
129 format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
130 } else {
131 format!("{name}: val.{name}.to_string_lossy().to_string()")
132 }
133 }
134 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Path) => {
135 format!("{name}: val.{name}.map(|p| p.to_string_lossy().to_string())")
136 }
137 TypeRef::Char => {
139 if optional {
140 format!("{name}: val.{name}.map(|c| c.to_string())")
141 } else {
142 format!("{name}: val.{name}.to_string()")
143 }
144 }
145 TypeRef::Bytes => {
147 if optional {
148 format!("{name}: val.{name}.map(|v| v.to_vec())")
149 } else {
150 format!("{name}: val.{name}.to_vec()")
151 }
152 }
153 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
155 if optional {
156 format!("{name}: val.{name}.map(|v| {n} {{ inner: Arc::new(v) }})")
157 } else {
158 format!("{name}: {n} {{ inner: Arc::new(val.{name}) }}")
159 }
160 }
161 _ => field_conversion_to_core(name, ty, optional),
163 }
164}
165
166pub fn field_conversion_from_core_cfg(
168 name: &str,
169 ty: &TypeRef,
170 optional: bool,
171 sanitized: bool,
172 opaque_types: &AHashSet<String>,
173 config: &ConversionConfig,
174) -> String {
175 if sanitized {
177 return field_conversion_from_core(name, ty, optional, sanitized, opaque_types);
178 }
179
180 if config.map_uses_jsvalue {
182 let is_nested_vec = matches!(ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Vec(_)));
183 let is_map = matches!(ty, TypeRef::Map(_, _));
184 if is_nested_vec || is_map {
185 if optional {
186 return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
187 }
188 return format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)");
189 }
190 if let TypeRef::Optional(inner) = ty {
191 let is_inner_nested = matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Vec(_)));
192 let is_inner_map = matches!(inner.as_ref(), TypeRef::Map(_, _));
193 if is_inner_nested || is_inner_map {
194 return format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())");
195 }
196 }
197 }
198
199 let prefix = config.type_name_prefix;
200 let is_enum_string = |n: &str| -> bool { config.enum_string_names.as_ref().is_some_and(|names| names.contains(n)) };
201
202 match ty {
203 TypeRef::Primitive(p) if config.cast_large_ints_to_i64 && needs_i64_cast(p) => {
205 let cast_to = binding_prim_str(p);
206 if optional {
207 format!("{name}: val.{name}.map(|v| v as {cast_to})")
208 } else {
209 format!("{name}: val.{name} as {cast_to}")
210 }
211 }
212 TypeRef::Primitive(PrimitiveType::F32) if config.cast_f32_to_f64 => {
214 if optional {
215 format!("{name}: val.{name}.map(|v| v as f64)")
216 } else {
217 format!("{name}: val.{name} as f64")
218 }
219 }
220 TypeRef::Duration if config.cast_large_ints_to_i64 => {
222 if optional {
223 format!("{name}: val.{name}.map(|d| d.as_secs() as i64)")
224 } else {
225 format!("{name}: val.{name}.as_secs() as i64")
226 }
227 }
228 TypeRef::Named(n) if opaque_types.contains(n.as_str()) && !prefix.is_empty() => {
230 let prefixed = format!("{prefix}{n}");
231 if optional {
232 format!("{name}: val.{name}.map(|v| {prefixed} {{ inner: Arc::new(v) }})")
233 } else {
234 format!("{name}: {prefixed} {{ inner: Arc::new(val.{name}) }}")
235 }
236 }
237 TypeRef::Named(n) if is_enum_string(n) => {
239 if optional {
240 format!("{name}: val.{name}.as_ref().map(|v| format!(\"{{:?}}\", v))")
241 } else {
242 format!("{name}: format!(\"{{:?}}\", val.{name})")
243 }
244 }
245 TypeRef::Vec(inner)
247 if config.cast_f32_to_f64 && matches!(inner.as_ref(), TypeRef::Primitive(PrimitiveType::F32)) =>
248 {
249 if optional {
250 format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
251 } else {
252 format!("{name}: val.{name}.iter().map(|&v| v as f64).collect()")
253 }
254 }
255 TypeRef::Optional(inner)
257 if config.cast_f32_to_f64
258 && matches!(inner.as_ref(), TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(PrimitiveType::F32))) =>
259 {
260 format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as f64).collect())")
261 }
262 TypeRef::Optional(inner)
264 if config.cast_large_ints_to_i64
265 && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
266 {
267 if let TypeRef::Primitive(p) = inner.as_ref() {
268 let cast_to = binding_prim_str(p);
269 format!("{name}: val.{name}.map(|v| v as {cast_to})")
270 } else {
271 field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
272 }
273 }
274 TypeRef::Vec(inner)
276 if config.cast_large_ints_to_i64
277 && matches!(inner.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) =>
278 {
279 if let TypeRef::Primitive(p) = inner.as_ref() {
280 let cast_to = binding_prim_str(p);
281 if optional {
282 format!("{name}: val.{name}.as_ref().map(|v| v.iter().map(|&x| x as {cast_to}).collect())")
283 } else {
284 format!("{name}: val.{name}.iter().map(|&v| v as {cast_to}).collect()")
285 }
286 } else {
287 field_conversion_from_core(name, ty, optional, sanitized, opaque_types)
288 }
289 }
290 TypeRef::Json if config.json_to_string => {
292 if optional {
293 format!("{name}: val.{name}.as_ref().map(|v| v.to_string())")
294 } else {
295 format!("{name}: val.{name}.to_string()")
296 }
297 }
298 TypeRef::Json if config.map_uses_jsvalue => {
300 if optional {
301 format!("{name}: val.{name}.as_ref().and_then(|v| serde_wasm_bindgen::to_value(v).ok())")
302 } else {
303 format!("{name}: serde_wasm_bindgen::to_value(&val.{name}).unwrap_or(JsValue::NULL)")
304 }
305 }
306 _ => field_conversion_from_core(name, ty, optional, sanitized, opaque_types),
308 }
309}