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| f.cfg.is_none() && field_references_opaque_type(&f.ty, cfg.opaque_type_names))
109 .map(|f| f.name.as_str())
110 .collect();
111 sb.add_derive("Default");
115 sb.add_derive("serde::Serialize");
116 sb.add_derive("serde::Deserialize");
117 let has_serde = true;
118 for field in &typ.fields {
119 if field.cfg.is_some() {
120 continue;
121 }
122 let force_optional = cfg.option_duration_on_defaults
123 && typ.has_default
124 && !field.optional
125 && matches!(field.ty, TypeRef::Duration);
126 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
127 mapper.optional(&mapper.map_type(&field.ty))
128 } else {
129 mapper.map_type(&field.ty)
131 };
132 let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
133 attrs.extend(extra_field_attrs(field));
134 let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
137 if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
138 attrs.push("serde(skip)".to_string());
139 }
140 sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
141 }
142 sb.build()
143}
144
145pub fn gen_struct_with_rename(
162 typ: &TypeDef,
163 mapper: &dyn TypeMapper,
164 cfg: &RustBindingConfig,
165 extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
166 field_name_override: impl Fn(&alef_core::ir::FieldDef) -> Option<String>,
167) -> String {
168 let mut sb = StructBuilder::new(&typ.name);
169 for attr in cfg.struct_attrs {
170 sb.add_attr(attr);
171 }
172
173 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
174 if has_similar_names(&field_names) {
175 sb.add_attr("allow(clippy::similar_names)");
176 }
177
178 for d in cfg.struct_derives {
179 sb.add_derive(d);
180 }
181 let opaque_fields: Vec<&str> = typ
182 .fields
183 .iter()
184 .filter(|f| f.cfg.is_none() && field_references_opaque_type(&f.ty, cfg.opaque_type_names))
185 .map(|f| f.name.as_str())
186 .collect();
187 sb.add_derive("Default");
188 sb.add_derive("serde::Serialize");
189 sb.add_derive("serde::Deserialize");
190 let has_serde = true;
191 for field in &typ.fields {
192 if field.cfg.is_some() {
193 continue;
194 }
195 let force_optional = cfg.option_duration_on_defaults
196 && typ.has_default
197 && !field.optional
198 && matches!(field.ty, TypeRef::Duration);
199 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
200 mapper.optional(&mapper.map_type(&field.ty))
201 } else {
202 mapper.map_type(&field.ty)
203 };
204 let name_override = field_name_override(field);
205 let extra_attrs = extra_field_attrs(field);
206 let mut attrs: Vec<String> = if name_override.is_some() && !extra_attrs.is_empty() {
210 extra_attrs
211 } else {
212 let mut a: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
213 a.extend(extra_attrs);
214 a
215 };
216 let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
221 if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
222 attrs.push("serde(skip)".to_string());
223 }
224 if has_serde
228 && !attrs.iter().any(|a| a.starts_with("serde(rename"))
229 && !attrs.iter().any(|a| a == "serde(skip)")
230 {
231 if let Some(rename) = &field.serde_rename {
232 attrs.push(format!("serde(rename = \"{rename}\")"));
233 }
234 }
235 let emit_name = name_override.unwrap_or_else(|| field.name.clone());
236 sb.add_field_with_doc(&emit_name, &ty, attrs, &field.doc);
237 }
238 sb.build()
239}
240
241pub fn gen_struct(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
243 let mut sb = StructBuilder::new(&typ.name);
244 for attr in cfg.struct_attrs {
245 sb.add_attr(attr);
246 }
247
248 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
250 if has_similar_names(&field_names) {
251 sb.add_attr("allow(clippy::similar_names)");
252 }
253
254 for d in cfg.struct_derives {
255 sb.add_derive(d);
256 }
257 let _opaque_fields: Vec<&str> = typ
258 .fields
259 .iter()
260 .filter(|f| f.cfg.is_none() && field_references_opaque_type(&f.ty, cfg.opaque_type_names))
261 .map(|f| f.name.as_str())
262 .collect();
263 sb.add_derive("Default");
264 sb.add_derive("serde::Serialize");
265 sb.add_derive("serde::Deserialize");
266 let _has_serde = true;
267 for field in &typ.fields {
268 if field.cfg.is_some() {
272 continue;
273 }
274 let force_optional = cfg.option_duration_on_defaults
278 && typ.has_default
279 && !field.optional
280 && matches!(field.ty, TypeRef::Duration);
281 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
282 mapper.optional(&mapper.map_type(&field.ty))
283 } else {
284 mapper.map_type(&field.ty)
286 };
287 let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
288 if let Some(rename) = &field.serde_rename {
291 if !attrs.iter().any(|a| a.starts_with("serde(rename")) {
292 attrs.push(format!("serde(rename = \"{rename}\")"));
293 }
294 }
295 sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
296 }
297 sb.build()
298}
299
300pub fn gen_struct_default_impl(typ: &TypeDef, name_prefix: &str) -> String {
309 let full_name = format!("{}{}", name_prefix, typ.name);
310 let fields: Vec<_> = typ
311 .fields
312 .iter()
313 .filter_map(|field| {
314 if field.cfg.is_some() {
315 return None;
316 }
317 let default_val = match &field.ty {
318 TypeRef::Optional(_) => "None".to_string(),
319 _ => "Default::default()".to_string(),
320 };
321 Some(minijinja::context! {
322 name => field.name.clone(),
323 default_val => default_val
324 })
325 })
326 .collect();
327
328 crate::template_env::render(
329 "structs/default_impl.jinja",
330 minijinja::context! {
331 full_name => full_name,
332 fields => fields
333 },
334 )
335}
336
337pub fn type_needs_mutex(typ: &TypeDef) -> bool {
340 typ.methods
341 .iter()
342 .any(|m| m.receiver == Some(alef_core::ir::ReceiverKind::RefMut))
343}
344
345pub fn type_needs_tokio_mutex(typ: &TypeDef) -> bool {
358 use alef_core::ir::ReceiverKind;
359 if !type_needs_mutex(typ) {
360 return false;
361 }
362 let refmut_methods = typ.methods.iter().filter(|m| m.receiver == Some(ReceiverKind::RefMut));
363 let mut any = false;
364 for m in refmut_methods {
365 any = true;
366 if !m.is_async {
367 return false;
368 }
369 }
370 any
371}
372
373pub fn gen_opaque_struct(typ: &TypeDef, cfg: &RustBindingConfig) -> String {
383 let needs_mutex = type_needs_mutex(typ);
384 let core_path = typ.rust_path.replace('-', "_");
392 let has_unresolvable_generics = core_path.contains('<');
393 let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
394 let omit_inner = all_methods_sanitized && has_unresolvable_generics;
395
396 let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
397 let has_derives = !cfg.struct_derives.is_empty();
398 let inner_type = if typ.is_trait {
399 format!("Arc<dyn {core_path} + Send + Sync>")
400 } else if needs_mutex {
401 format!("Arc<std::sync::Mutex<{core_path}>>")
402 } else {
403 format!("Arc<{core_path}>")
404 };
405
406 crate::template_env::render(
407 "structs/opaque_struct.jinja",
408 minijinja::context! {
409 struct_name => typ.name.clone(),
410 has_derives => has_derives,
411 struct_attrs => struct_attrs,
412 omit_inner => omit_inner,
413 inner_type => inner_type,
414 },
415 )
416}
417
418pub fn gen_opaque_struct_prefixed(typ: &TypeDef, cfg: &RustBindingConfig, prefix: &str) -> String {
424 let needs_mutex = type_needs_mutex(typ);
425 let core_path = typ.rust_path.replace('-', "_");
426 let has_unresolvable_generics = core_path.contains('<');
427 let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
428 let omit_inner = all_methods_sanitized && has_unresolvable_generics;
429
430 let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
431 let has_derives = !cfg.struct_derives.is_empty();
432 let struct_name = format!("{prefix}{}", typ.name);
433 let inner_type = if typ.is_trait {
434 format!("Arc<dyn {core_path} + Send + Sync>")
435 } else if needs_mutex {
436 format!("Arc<std::sync::Mutex<{core_path}>>")
437 } else {
438 format!("Arc<{core_path}>")
439 };
440
441 crate::template_env::render(
442 "structs/opaque_struct.jinja",
443 minijinja::context! {
444 struct_name => struct_name,
445 has_derives => has_derives,
446 struct_attrs => struct_attrs,
447 omit_inner => omit_inner,
448 inner_type => inner_type,
449 },
450 )
451}
452
453#[cfg(test)]
454mod tests {
455 use super::{type_needs_mutex, type_needs_tokio_mutex};
456 use alef_core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
457
458 fn method(name: &str, receiver: Option<ReceiverKind>, is_async: bool) -> MethodDef {
459 MethodDef {
460 name: name.into(),
461 params: vec![],
462 return_type: TypeRef::Unit,
463 is_async,
464 is_static: false,
465 error_type: None,
466 doc: String::new(),
467 receiver,
468 sanitized: false,
469 trait_source: None,
470 returns_ref: false,
471 returns_cow: false,
472 return_newtype_wrapper: None,
473 has_default_impl: false,
474 }
475 }
476
477 fn type_with_methods(name: &str, methods: Vec<MethodDef>) -> TypeDef {
478 TypeDef {
479 name: name.into(),
480 rust_path: format!("my_crate::{name}"),
481 original_rust_path: String::new(),
482 fields: vec![],
483 methods,
484 is_opaque: true,
485 is_clone: false,
486 is_copy: false,
487 is_trait: false,
488 has_default: false,
489 has_stripped_cfg_fields: false,
490 is_return_type: false,
491 serde_rename_all: None,
492 has_serde: false,
493 super_traits: vec![],
494 doc: String::new(),
495 cfg: None,
496 }
497 }
498
499 #[test]
500 fn tokio_mutex_when_all_refmut_methods_async() {
501 let typ = type_with_methods(
502 "WebSocketConnection",
503 vec![
504 method("send_text", Some(ReceiverKind::RefMut), true),
505 method("receive_text", Some(ReceiverKind::RefMut), true),
506 method("close", None, true),
507 ],
508 );
509 assert!(type_needs_mutex(&typ));
510 assert!(type_needs_tokio_mutex(&typ));
511 }
512
513 #[test]
514 fn no_tokio_mutex_when_any_refmut_is_sync() {
515 let typ = type_with_methods(
516 "Mixed",
517 vec![
518 method("async_op", Some(ReceiverKind::RefMut), true),
519 method("sync_op", Some(ReceiverKind::RefMut), false),
520 ],
521 );
522 assert!(type_needs_mutex(&typ));
523 assert!(!type_needs_tokio_mutex(&typ));
524 }
525
526 #[test]
527 fn no_tokio_mutex_when_no_refmut() {
528 let typ = type_with_methods("ReadOnly", vec![method("get", Some(ReceiverKind::Ref), true)]);
529 assert!(!type_needs_mutex(&typ));
530 assert!(!type_needs_tokio_mutex(&typ));
531 }
532
533 #[test]
534 fn no_tokio_mutex_when_empty_methods() {
535 let typ = type_with_methods("Empty", vec![]);
536 assert!(!type_needs_mutex(&typ));
537 assert!(!type_needs_tokio_mutex(&typ));
538 }
539}