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() && !cfg.never_skip_cfg_field_names.contains(&field.name) {
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;
144 let skip_cfg_gated_field = field.cfg.is_some();
145 if has_serde
146 && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field || skip_cfg_gated_field)
147 {
148 attrs.push("serde(skip)".to_string());
149 }
150 sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
151 }
152 sb.build()
153}
154
155pub fn gen_struct_with_rename(
172 typ: &TypeDef,
173 mapper: &dyn TypeMapper,
174 cfg: &RustBindingConfig,
175 extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
176 field_name_override: impl Fn(&alef_core::ir::FieldDef) -> Option<String>,
177) -> String {
178 let mut sb = StructBuilder::new(&typ.name);
179 for attr in cfg.struct_attrs {
180 sb.add_attr(attr);
181 }
182
183 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
184 if has_similar_names(&field_names) {
185 sb.add_attr("allow(clippy::similar_names)");
186 }
187
188 for d in cfg.struct_derives {
189 sb.add_derive(d);
190 }
191 let opaque_fields: Vec<&str> = typ
192 .fields
193 .iter()
194 .filter(|f| {
195 f.cfg.is_none()
196 && field_references_opaque_type(&f.ty, cfg.opaque_type_names)
197 && !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
198 })
199 .map(|f| f.name.as_str())
200 .collect();
201 sb.add_derive("Default");
202 sb.add_derive("serde::Serialize");
203 sb.add_derive("serde::Deserialize");
204 let has_serde = true;
205 for field in &typ.fields {
206 if field.cfg.is_some() && !cfg.never_skip_cfg_field_names.contains(&field.name) {
207 continue;
208 }
209 let force_optional = cfg.option_duration_on_defaults
210 && typ.has_default
211 && !field.optional
212 && matches!(field.ty, TypeRef::Duration);
213 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
214 mapper.optional(&mapper.map_type(&field.ty))
215 } else {
216 mapper.map_type(&field.ty)
217 };
218 let name_override = field_name_override(field);
219 let extra_attrs = extra_field_attrs(field);
220 let mut attrs: Vec<String> = if name_override.is_some() && !extra_attrs.is_empty() {
224 extra_attrs
225 } else {
226 let mut a: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
227 a.extend(extra_attrs);
228 a
229 };
230 let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
238 let skip_cfg_gated_field = field.cfg.is_some();
239 if has_serde
240 && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field || skip_cfg_gated_field)
241 {
242 attrs.push("serde(skip)".to_string());
243 }
244 if has_serde
248 && !attrs.iter().any(|a| a.starts_with("serde(rename"))
249 && !attrs.iter().any(|a| a == "serde(skip)")
250 {
251 if let Some(rename) = &field.serde_rename {
252 attrs.push(format!("serde(rename = \"{rename}\")"));
253 }
254 }
255 let emit_name = name_override.unwrap_or_else(|| field.name.clone());
256 sb.add_field_with_doc(&emit_name, &ty, attrs, &field.doc);
257 }
258 sb.build()
259}
260
261pub fn gen_struct(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
263 let mut sb = StructBuilder::new(&typ.name);
264 for attr in cfg.struct_attrs {
265 sb.add_attr(attr);
266 }
267
268 let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
270 if has_similar_names(&field_names) {
271 sb.add_attr("allow(clippy::similar_names)");
272 }
273
274 for d in cfg.struct_derives {
275 sb.add_derive(d);
276 }
277 let _opaque_fields: Vec<&str> = typ
278 .fields
279 .iter()
280 .filter(|f| {
281 f.cfg.is_none()
282 && field_references_opaque_type(&f.ty, cfg.opaque_type_names)
283 && !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
284 })
285 .map(|f| f.name.as_str())
286 .collect();
287 sb.add_derive("Default");
288 sb.add_derive("serde::Serialize");
289 sb.add_derive("serde::Deserialize");
290 let _has_serde = true;
291 for field in &typ.fields {
292 if field.cfg.is_some() && !cfg.never_skip_cfg_field_names.contains(&field.name) {
296 continue;
297 }
298 let force_optional = cfg.option_duration_on_defaults
302 && typ.has_default
303 && !field.optional
304 && matches!(field.ty, TypeRef::Duration);
305 let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
306 mapper.optional(&mapper.map_type(&field.ty))
307 } else {
308 mapper.map_type(&field.ty)
310 };
311 let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
312 if let Some(rename) = &field.serde_rename {
315 if !attrs.iter().any(|a| a.starts_with("serde(rename")) {
316 attrs.push(format!("serde(rename = \"{rename}\")"));
317 }
318 }
319 sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
320 }
321 sb.build()
322}
323
324pub fn gen_struct_default_impl(typ: &TypeDef, name_prefix: &str) -> String {
333 let full_name = format!("{}{}", name_prefix, typ.name);
334 let fields: Vec<_> = typ
335 .fields
336 .iter()
337 .filter_map(|field| {
338 if field.cfg.is_some() {
339 return None;
340 }
341 let default_val = match &field.ty {
342 TypeRef::Optional(_) => "None".to_string(),
343 _ => "Default::default()".to_string(),
344 };
345 Some(minijinja::context! {
346 name => field.name.clone(),
347 default_val => default_val
348 })
349 })
350 .collect();
351
352 crate::template_env::render(
353 "structs/default_impl.jinja",
354 minijinja::context! {
355 full_name => full_name,
356 fields => fields
357 },
358 )
359}
360
361pub fn type_needs_mutex(typ: &TypeDef) -> bool {
364 typ.methods
365 .iter()
366 .any(|m| m.receiver == Some(alef_core::ir::ReceiverKind::RefMut))
367}
368
369pub fn type_needs_tokio_mutex(typ: &TypeDef) -> bool {
382 use alef_core::ir::ReceiverKind;
383 if !type_needs_mutex(typ) {
384 return false;
385 }
386 let refmut_methods = typ.methods.iter().filter(|m| m.receiver == Some(ReceiverKind::RefMut));
387 let mut any = false;
388 for m in refmut_methods {
389 any = true;
390 if !m.is_async {
391 return false;
392 }
393 }
394 any
395}
396
397pub fn gen_opaque_struct(typ: &TypeDef, cfg: &RustBindingConfig) -> String {
407 let needs_mutex = type_needs_mutex(typ);
408 let core_path = typ.rust_path.replace('-', "_");
416 let has_unresolvable_generics = core_path.contains('<');
417 let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
418 let omit_inner = all_methods_sanitized && has_unresolvable_generics;
419
420 let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
421 let has_derives = !cfg.struct_derives.is_empty();
422 let inner_type = if typ.is_trait {
423 format!("Arc<dyn {core_path} + Send + Sync>")
424 } else if needs_mutex {
425 format!("Arc<std::sync::Mutex<{core_path}>>")
426 } else {
427 format!("Arc<{core_path}>")
428 };
429
430 crate::template_env::render(
431 "structs/opaque_struct.jinja",
432 minijinja::context! {
433 struct_name => typ.name.clone(),
434 has_derives => has_derives,
435 struct_attrs => struct_attrs,
436 omit_inner => omit_inner,
437 inner_type => inner_type,
438 },
439 )
440}
441
442pub fn gen_opaque_struct_prefixed(typ: &TypeDef, cfg: &RustBindingConfig, prefix: &str) -> String {
448 let needs_mutex = type_needs_mutex(typ);
449 let core_path = typ.rust_path.replace('-', "_");
450 let has_unresolvable_generics = core_path.contains('<');
451 let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
452 let omit_inner = all_methods_sanitized && has_unresolvable_generics;
453
454 let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
455 let has_derives = !cfg.struct_derives.is_empty();
456 let struct_name = format!("{prefix}{}", typ.name);
457 let inner_type = if typ.is_trait {
458 format!("Arc<dyn {core_path} + Send + Sync>")
459 } else if needs_mutex {
460 format!("Arc<std::sync::Mutex<{core_path}>>")
461 } else {
462 format!("Arc<{core_path}>")
463 };
464
465 crate::template_env::render(
466 "structs/opaque_struct.jinja",
467 minijinja::context! {
468 struct_name => struct_name,
469 has_derives => has_derives,
470 struct_attrs => struct_attrs,
471 omit_inner => omit_inner,
472 inner_type => inner_type,
473 },
474 )
475}
476
477#[cfg(test)]
478mod tests {
479 use super::{type_needs_mutex, type_needs_tokio_mutex};
480 use alef_core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
481
482 fn method(name: &str, receiver: Option<ReceiverKind>, is_async: bool) -> MethodDef {
483 MethodDef {
484 name: name.into(),
485 params: vec![],
486 return_type: TypeRef::Unit,
487 is_async,
488 is_static: false,
489 error_type: None,
490 doc: String::new(),
491 receiver,
492 sanitized: false,
493 trait_source: None,
494 returns_ref: false,
495 returns_cow: false,
496 return_newtype_wrapper: None,
497 has_default_impl: false,
498 }
499 }
500
501 fn type_with_methods(name: &str, methods: Vec<MethodDef>) -> TypeDef {
502 TypeDef {
503 name: name.into(),
504 rust_path: format!("my_crate::{name}"),
505 original_rust_path: String::new(),
506 fields: vec![],
507 methods,
508 is_opaque: true,
509 is_clone: false,
510 is_copy: false,
511 is_trait: false,
512 has_default: false,
513 has_stripped_cfg_fields: false,
514 is_return_type: false,
515 serde_rename_all: None,
516 has_serde: false,
517 super_traits: vec![],
518 doc: String::new(),
519 cfg: None,
520 }
521 }
522
523 #[test]
524 fn tokio_mutex_when_all_refmut_methods_async() {
525 let typ = type_with_methods(
526 "WebSocketConnection",
527 vec![
528 method("send_text", Some(ReceiverKind::RefMut), true),
529 method("receive_text", Some(ReceiverKind::RefMut), true),
530 method("close", None, true),
531 ],
532 );
533 assert!(type_needs_mutex(&typ));
534 assert!(type_needs_tokio_mutex(&typ));
535 }
536
537 #[test]
538 fn no_tokio_mutex_when_any_refmut_is_sync() {
539 let typ = type_with_methods(
540 "Mixed",
541 vec![
542 method("async_op", Some(ReceiverKind::RefMut), true),
543 method("sync_op", Some(ReceiverKind::RefMut), false),
544 ],
545 );
546 assert!(type_needs_mutex(&typ));
547 assert!(!type_needs_tokio_mutex(&typ));
548 }
549
550 #[test]
551 fn no_tokio_mutex_when_no_refmut() {
552 let typ = type_with_methods("ReadOnly", vec![method("get", Some(ReceiverKind::Ref), true)]);
553 assert!(!type_needs_mutex(&typ));
554 assert!(!type_needs_tokio_mutex(&typ));
555 }
556
557 #[test]
558 fn no_tokio_mutex_when_empty_methods() {
559 let typ = type_with_methods("Empty", vec![]);
560 assert!(!type_needs_mutex(&typ));
561 assert!(!type_needs_tokio_mutex(&typ));
562 }
563}