1use crate::builder::StructBuilder;
2use crate::generators::RustBindingConfig;
3use crate::type_mapper::TypeMapper;
4use alef_core::ir::{CoreWrapper, TypeDef, TypeRef};
5
6pub fn can_generate_default_impl(typ: &TypeDef, known_default_types: &std::collections::HashSet<&str>) -> bool {
12 for field in &typ.fields {
13 if field.cfg.is_some() {
14 continue; }
16 if !field_type_has_default(&field.ty, known_default_types) {
17 return false;
18 }
19 }
20 true
21}
22
23fn field_type_has_default(ty: &TypeRef, known_default_types: &std::collections::HashSet<&str>) -> bool {
25 match ty {
26 TypeRef::Primitive(_)
27 | TypeRef::String
28 | TypeRef::Char
29 | TypeRef::Bytes
30 | TypeRef::Path
31 | TypeRef::Unit
32 | TypeRef::Duration
33 | TypeRef::Json => true,
34 TypeRef::Optional(inner) => field_type_has_default(inner, known_default_types),
36 TypeRef::Vec(inner) => field_type_has_default(inner, known_default_types),
38 TypeRef::Map(k, v) => {
40 field_type_has_default(k, known_default_types) && field_type_has_default(v, known_default_types)
41 }
42 TypeRef::Named(name) => known_default_types.contains(name.as_str()),
44 }
45}
46
47fn has_similar_names(names: &[&String]) -> bool {
50 for (i, &name1) in names.iter().enumerate() {
51 for &name2 in &names[i + 1..] {
52 if name1.len() == name2.len() && diff_count(name1, name2) <= 2 {
54 return true;
55 }
56 }
57 }
58 false
59}
60
61fn diff_count(s1: &str, s2: &str) -> usize {
63 s1.chars().zip(s2.chars()).filter(|(c1, c2)| c1 != c2).count()
64}
65
66pub fn field_references_opaque_type(ty: &TypeRef, opaque_names: &[String]) -> bool {
70 match ty {
71 TypeRef::Named(name) => opaque_names.contains(name),
72 TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_opaque_type(inner, opaque_names),
73 TypeRef::Map(k, v) => {
74 field_references_opaque_type(k, opaque_names) || field_references_opaque_type(v, opaque_names)
75 }
76 _ => false,
77 }
78}
79
80pub fn gen_struct_with_per_field_attrs(
85 typ: &TypeDef,
86 mapper: &dyn TypeMapper,
87 cfg: &RustBindingConfig,
88 extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
89) -> String {
90 let mut sb = StructBuilder::new(&typ.name);
91 for attr in cfg.struct_attrs {
92 sb.add_attr(attr);
93 }
94
95 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
97 if has_similar_names(&field_names) {
98 sb.add_attr("allow(clippy::similar_names)");
99 }
100
101 for d in cfg.struct_derives {
102 sb.add_derive(d);
103 }
104 let opaque_fields: Vec<&str> = typ
106 .fields
107 .iter()
108 .filter(|f| {
109 f.cfg.is_none()
110 && field_references_opaque_type(&f.ty, cfg.opaque_type_names)
111 && !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
112 })
113 .map(|f| f.name.as_str())
114 .collect();
115 sb.add_derive("Default");
119 sb.add_derive("serde::Serialize");
120 sb.add_derive("serde::Deserialize");
121 let has_serde = true;
122 for field in &typ.fields {
123 if field.cfg.is_some() {
124 continue;
125 }
126 let force_optional = cfg.option_duration_on_defaults
127 && typ.has_default
128 && !field.optional
129 && matches!(field.ty, TypeRef::Duration);
130 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
131 mapper.optional(&mapper.map_type(&field.ty))
132 } else {
133 mapper.map_type(&field.ty)
135 };
136 let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
137 attrs.extend(extra_field_attrs(field));
138 let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
141 if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
142 attrs.push("serde(skip)".to_string());
143 }
144 sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
145 }
146 sb.build()
147}
148
149pub fn gen_struct_with_rename(
166 typ: &TypeDef,
167 mapper: &dyn TypeMapper,
168 cfg: &RustBindingConfig,
169 extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
170 field_name_override: impl Fn(&alef_core::ir::FieldDef) -> Option<String>,
171) -> String {
172 let mut sb = StructBuilder::new(&typ.name);
173 for attr in cfg.struct_attrs {
174 sb.add_attr(attr);
175 }
176
177 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
178 if has_similar_names(&field_names) {
179 sb.add_attr("allow(clippy::similar_names)");
180 }
181
182 for d in cfg.struct_derives {
183 sb.add_derive(d);
184 }
185 let opaque_fields: Vec<&str> = typ
186 .fields
187 .iter()
188 .filter(|f| {
189 f.cfg.is_none()
190 && field_references_opaque_type(&f.ty, cfg.opaque_type_names)
191 && !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
192 })
193 .map(|f| f.name.as_str())
194 .collect();
195 sb.add_derive("Default");
196 sb.add_derive("serde::Serialize");
197 sb.add_derive("serde::Deserialize");
198 let has_serde = true;
199 for field in &typ.fields {
200 if field.cfg.is_some() {
201 continue;
202 }
203 let force_optional = cfg.option_duration_on_defaults
204 && typ.has_default
205 && !field.optional
206 && matches!(field.ty, TypeRef::Duration);
207 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
208 mapper.optional(&mapper.map_type(&field.ty))
209 } else {
210 mapper.map_type(&field.ty)
211 };
212 let name_override = field_name_override(field);
213 let extra_attrs = extra_field_attrs(field);
214 let mut attrs: Vec<String> = if name_override.is_some() && !extra_attrs.is_empty() {
218 extra_attrs
219 } else {
220 let mut a: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
221 a.extend(extra_attrs);
222 a
223 };
224 let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
229 if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
230 attrs.push("serde(skip)".to_string());
231 }
232 if has_serde
236 && !attrs.iter().any(|a| a.starts_with("serde(rename"))
237 && !attrs.iter().any(|a| a == "serde(skip)")
238 {
239 if let Some(rename) = &field.serde_rename {
240 attrs.push(format!("serde(rename = \"{rename}\")"));
241 }
242 }
243 let emit_name = name_override.unwrap_or_else(|| field.name.clone());
244 sb.add_field_with_doc(&emit_name, &ty, attrs, &field.doc);
245 }
246 sb.build()
247}
248
249pub fn gen_struct(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
251 let mut sb = StructBuilder::new(&typ.name);
252 for attr in cfg.struct_attrs {
253 sb.add_attr(attr);
254 }
255
256 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
258 if has_similar_names(&field_names) {
259 sb.add_attr("allow(clippy::similar_names)");
260 }
261
262 for d in cfg.struct_derives {
263 sb.add_derive(d);
264 }
265 let _opaque_fields: Vec<&str> = typ
266 .fields
267 .iter()
268 .filter(|f| {
269 f.cfg.is_none()
270 && field_references_opaque_type(&f.ty, cfg.opaque_type_names)
271 && !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
272 })
273 .map(|f| f.name.as_str())
274 .collect();
275 sb.add_derive("Default");
276 sb.add_derive("serde::Serialize");
277 sb.add_derive("serde::Deserialize");
278 let _has_serde = true;
279 for field in &typ.fields {
280 if field.cfg.is_some() {
284 continue;
285 }
286 let force_optional = cfg.option_duration_on_defaults
290 && typ.has_default
291 && !field.optional
292 && matches!(field.ty, TypeRef::Duration);
293 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
294 mapper.optional(&mapper.map_type(&field.ty))
295 } else {
296 mapper.map_type(&field.ty)
298 };
299 let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
300 if let Some(rename) = &field.serde_rename {
303 if !attrs.iter().any(|a| a.starts_with("serde(rename")) {
304 attrs.push(format!("serde(rename = \"{rename}\")"));
305 }
306 }
307 sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
308 }
309 sb.build()
310}
311
312pub fn gen_struct_default_impl(typ: &TypeDef, name_prefix: &str) -> String {
321 let full_name = format!("{}{}", name_prefix, typ.name);
322 let fields: Vec<_> = typ
323 .fields
324 .iter()
325 .filter_map(|field| {
326 if field.cfg.is_some() {
327 return None;
328 }
329 let default_val = match &field.ty {
330 TypeRef::Optional(_) => "None".to_string(),
331 _ => "Default::default()".to_string(),
332 };
333 Some(minijinja::context! {
334 name => field.name.clone(),
335 default_val => default_val
336 })
337 })
338 .collect();
339
340 crate::template_env::render(
341 "structs/default_impl.jinja",
342 minijinja::context! {
343 full_name => full_name,
344 fields => fields
345 },
346 )
347}
348
349pub fn type_needs_mutex(typ: &TypeDef) -> bool {
352 typ.methods
353 .iter()
354 .any(|m| m.receiver == Some(alef_core::ir::ReceiverKind::RefMut))
355}
356
357pub fn type_needs_tokio_mutex(typ: &TypeDef) -> bool {
370 use alef_core::ir::ReceiverKind;
371 if !type_needs_mutex(typ) {
372 return false;
373 }
374 let refmut_methods = typ.methods.iter().filter(|m| m.receiver == Some(ReceiverKind::RefMut));
375 let mut any = false;
376 for m in refmut_methods {
377 any = true;
378 if !m.is_async {
379 return false;
380 }
381 }
382 any
383}
384
385pub fn gen_opaque_struct(typ: &TypeDef, cfg: &RustBindingConfig) -> String {
395 let needs_mutex = type_needs_mutex(typ);
396 let core_path = typ.rust_path.replace('-', "_");
404 let has_unresolvable_generics = core_path.contains('<');
405 let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
406 let omit_inner = all_methods_sanitized && has_unresolvable_generics;
407
408 let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
409 let has_derives = !cfg.struct_derives.is_empty();
410 let inner_type = if typ.is_trait {
411 format!("Arc<dyn {core_path} + Send + Sync>")
412 } else if needs_mutex {
413 format!("Arc<std::sync::Mutex<{core_path}>>")
414 } else {
415 format!("Arc<{core_path}>")
416 };
417
418 crate::template_env::render(
419 "structs/opaque_struct.jinja",
420 minijinja::context! {
421 struct_name => typ.name.clone(),
422 has_derives => has_derives,
423 struct_attrs => struct_attrs,
424 omit_inner => omit_inner,
425 inner_type => inner_type,
426 },
427 )
428}
429
430pub fn gen_opaque_struct_prefixed(typ: &TypeDef, cfg: &RustBindingConfig, prefix: &str) -> String {
436 let needs_mutex = type_needs_mutex(typ);
437 let core_path = typ.rust_path.replace('-', "_");
438 let has_unresolvable_generics = core_path.contains('<');
439 let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
440 let omit_inner = all_methods_sanitized && has_unresolvable_generics;
441
442 let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
443 let has_derives = !cfg.struct_derives.is_empty();
444 let struct_name = format!("{prefix}{}", typ.name);
445 let inner_type = if typ.is_trait {
446 format!("Arc<dyn {core_path} + Send + Sync>")
447 } else if needs_mutex {
448 format!("Arc<std::sync::Mutex<{core_path}>>")
449 } else {
450 format!("Arc<{core_path}>")
451 };
452
453 crate::template_env::render(
454 "structs/opaque_struct.jinja",
455 minijinja::context! {
456 struct_name => struct_name,
457 has_derives => has_derives,
458 struct_attrs => struct_attrs,
459 omit_inner => omit_inner,
460 inner_type => inner_type,
461 },
462 )
463}
464
465#[cfg(test)]
466mod tests {
467 use super::{type_needs_mutex, type_needs_tokio_mutex};
468 use alef_core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
469
470 fn method(name: &str, receiver: Option<ReceiverKind>, is_async: bool) -> MethodDef {
471 MethodDef {
472 name: name.into(),
473 params: vec![],
474 return_type: TypeRef::Unit,
475 is_async,
476 is_static: false,
477 error_type: None,
478 doc: String::new(),
479 receiver,
480 sanitized: false,
481 trait_source: None,
482 returns_ref: false,
483 returns_cow: false,
484 return_newtype_wrapper: None,
485 has_default_impl: false,
486 }
487 }
488
489 fn type_with_methods(name: &str, methods: Vec<MethodDef>) -> TypeDef {
490 TypeDef {
491 name: name.into(),
492 rust_path: format!("my_crate::{name}"),
493 original_rust_path: String::new(),
494 fields: vec![],
495 methods,
496 is_opaque: true,
497 is_clone: false,
498 is_copy: false,
499 is_trait: false,
500 has_default: false,
501 has_stripped_cfg_fields: false,
502 is_return_type: false,
503 serde_rename_all: None,
504 has_serde: false,
505 super_traits: vec![],
506 doc: String::new(),
507 cfg: None,
508 }
509 }
510
511 #[test]
512 fn tokio_mutex_when_all_refmut_methods_async() {
513 let typ = type_with_methods(
514 "WebSocketConnection",
515 vec![
516 method("send_text", Some(ReceiverKind::RefMut), true),
517 method("receive_text", Some(ReceiverKind::RefMut), true),
518 method("close", None, true),
519 ],
520 );
521 assert!(type_needs_mutex(&typ));
522 assert!(type_needs_tokio_mutex(&typ));
523 }
524
525 #[test]
526 fn no_tokio_mutex_when_any_refmut_is_sync() {
527 let typ = type_with_methods(
528 "Mixed",
529 vec![
530 method("async_op", Some(ReceiverKind::RefMut), true),
531 method("sync_op", Some(ReceiverKind::RefMut), false),
532 ],
533 );
534 assert!(type_needs_mutex(&typ));
535 assert!(!type_needs_tokio_mutex(&typ));
536 }
537
538 #[test]
539 fn no_tokio_mutex_when_no_refmut() {
540 let typ = type_with_methods("ReadOnly", vec![method("get", Some(ReceiverKind::Ref), true)]);
541 assert!(!type_needs_mutex(&typ));
542 assert!(!type_needs_tokio_mutex(&typ));
543 }
544
545 #[test]
546 fn no_tokio_mutex_when_empty_methods() {
547 let typ = type_with_methods("Empty", vec![]);
548 assert!(!type_needs_mutex(&typ));
549 assert!(!type_needs_tokio_mutex(&typ));
550 }
551}