Skip to main content

alef_codegen/generators/
structs.rs

1use crate::builder::StructBuilder;
2use crate::generators::RustBindingConfig;
3use crate::type_mapper::TypeMapper;
4use alef_core::ir::{CoreWrapper, DefaultValue, TypeDef, TypeRef};
5use std::fmt::Write;
6
7/// Check if a type's fields can all be safely defaulted.
8/// Primitives, strings, collections, Options, and Duration all have Default impls.
9/// Named types (custom structs) only have Default if explicitly marked with `has_default=true`.
10/// If any field is a Named type without `has_default`, returning true would generate
11/// code that calls `Default::default()` on a type that doesn't implement it.
12pub fn can_generate_default_impl(typ: &TypeDef, known_default_types: &std::collections::HashSet<&str>) -> bool {
13    for field in &typ.fields {
14        if field.cfg.is_some() {
15            continue; // Skip cfg-gated fields
16        }
17        if !field_type_has_default(&field.ty, known_default_types) {
18            return false;
19        }
20    }
21    true
22}
23
24/// Check if a specific TypeRef can be safely defaulted.
25fn field_type_has_default(ty: &TypeRef, known_default_types: &std::collections::HashSet<&str>) -> bool {
26    match ty {
27        TypeRef::Primitive(_)
28        | TypeRef::String
29        | TypeRef::Char
30        | TypeRef::Bytes
31        | TypeRef::Path
32        | TypeRef::Unit
33        | TypeRef::Duration
34        | TypeRef::Json => true,
35        // Optional<T> defaults to None regardless of T
36        TypeRef::Optional(inner) => field_type_has_default(inner, known_default_types),
37        // Vec<T> defaults to empty vec regardless of T
38        TypeRef::Vec(inner) => field_type_has_default(inner, known_default_types),
39        // Map<K, V> defaults to empty map regardless of K/V
40        TypeRef::Map(k, v) => {
41            field_type_has_default(k, known_default_types) && field_type_has_default(v, known_default_types)
42        }
43        // Named types only have Default if marked with has_default=true
44        TypeRef::Named(name) => known_default_types.contains(name.as_str()),
45    }
46}
47
48/// Check if any two field names are similar enough to trigger clippy::similar_names.
49/// This detects patterns like "sub_symbol" and "sup_symbol" (differ by 1-2 chars).
50fn has_similar_names(names: &[&String]) -> bool {
51    for (i, &name1) in names.iter().enumerate() {
52        for &name2 in &names[i + 1..] {
53            // Simple heuristic: if names differ by <= 2 characters and have same length, flag it
54            if name1.len() == name2.len() && diff_count(name1, name2) <= 2 {
55                return true;
56            }
57        }
58    }
59    false
60}
61
62/// Count how many characters differ between two strings of equal length.
63fn diff_count(s1: &str, s2: &str) -> usize {
64    s1.chars().zip(s2.chars()).filter(|(c1, c2)| c1 != c2).count()
65}
66
67/// Check if a TypeRef references an opaque type, including through Optional and Vec wrappers.
68/// Opaque types use Arc<T> which doesn't implement Serialize/Deserialize, so any struct with
69/// such a field cannot derive those traits.
70pub fn field_references_opaque_type(ty: &TypeRef, opaque_names: &[String]) -> bool {
71    match ty {
72        TypeRef::Named(name) => opaque_names.contains(name),
73        TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_opaque_type(inner, opaque_names),
74        TypeRef::Map(k, v) => {
75            field_references_opaque_type(k, opaque_names) || field_references_opaque_type(v, opaque_names)
76        }
77        _ => false,
78    }
79}
80
81/// Generate a struct definition using the builder, with a per-field attribute callback.
82///
83/// `extra_field_attrs` is called for each field and returns additional `#[...]` attributes to
84/// prepend (beyond `cfg.field_attrs`). Pass `|_| vec![]` to use the default behaviour.
85pub fn gen_struct_with_per_field_attrs(
86    typ: &TypeDef,
87    mapper: &dyn TypeMapper,
88    cfg: &RustBindingConfig,
89    extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
90) -> String {
91    let mut sb = StructBuilder::new(&typ.name);
92    for attr in cfg.struct_attrs {
93        sb.add_attr(attr);
94    }
95
96    // Check if struct has similar field names (e.g., sub_symbol and sup_symbol)
97    let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
98    if has_similar_names(&field_names) {
99        sb.add_attr("allow(clippy::similar_names)");
100    }
101
102    for d in cfg.struct_derives {
103        sb.add_derive(d);
104    }
105    // Track which fields are opaque so we can conditionally skip derives and add #[serde(skip)].
106    let opaque_fields: Vec<&str> = typ
107        .fields
108        .iter()
109        .filter(|f| f.cfg.is_none() && field_references_opaque_type(&f.ty, cfg.opaque_type_names))
110        .map(|f| f.name.as_str())
111        .collect();
112    // Always derive Default/Serialize/Deserialize. Opaque fields get #[serde(skip)]
113    // so they use Default::default() during deserialization. This is needed for the
114    // serde recovery path where binding types round-trip through JSON.
115    sb.add_derive("Default");
116    sb.add_derive("serde::Serialize");
117    sb.add_derive("serde::Deserialize");
118    let has_serde = true;
119    for field in &typ.fields {
120        if field.cfg.is_some() {
121            continue;
122        }
123        let force_optional = cfg.option_duration_on_defaults
124            && typ.has_default
125            && !field.optional
126            && matches!(field.ty, TypeRef::Duration);
127        let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
128            mapper.optional(&mapper.map_type(&field.ty))
129        } else {
130            // field.ty is already Optional(T) — mapped type is already Option<T>, don't double-wrap
131            mapper.map_type(&field.ty)
132        };
133        let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
134        attrs.extend(extra_field_attrs(field));
135        // Add #[serde(skip)] for opaque fields or sanitized fields when the struct derives serde.
136        // Cow-backed strings are lossless String bindings, so they must remain serializable.
137        let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
138        if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
139            attrs.push("serde(skip)".to_string());
140        }
141        sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
142    }
143    sb.build()
144}
145
146/// Generate a struct definition using the builder, with per-field attribute and name override callbacks.
147///
148/// This is the most flexible variant.  Use it when the target language may need to escape
149/// reserved keywords in field names (e.g. Python's `class` → `class_`).
150///
151/// * `extra_field_attrs` — called per field, returns additional `#[…]` attribute strings to
152///   append **after** `cfg.field_attrs`.  Return an empty vec for the default behaviour.
153/// * `field_name_override` — called per field, returns `Some(escaped_name)` when the Rust
154///   binding struct field name should differ from `field.name` (e.g. for keyword escaping),
155///   or `None` to keep the original name.
156///
157/// When a field name is overridden the caller is responsible for adding the appropriate
158/// language attribute (e.g. `pyo3(get, name = "original")`) via `extra_field_attrs`.
159/// `cfg.field_attrs` is **still** applied for non-renamed fields; for renamed fields the
160/// caller should replace the default field attrs entirely by returning them from
161/// `extra_field_attrs` and passing a modified `cfg` with empty `field_attrs`.
162pub fn gen_struct_with_rename(
163    typ: &TypeDef,
164    mapper: &dyn TypeMapper,
165    cfg: &RustBindingConfig,
166    extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
167    field_name_override: impl Fn(&alef_core::ir::FieldDef) -> Option<String>,
168) -> String {
169    let mut sb = StructBuilder::new(&typ.name);
170    for attr in cfg.struct_attrs {
171        sb.add_attr(attr);
172    }
173
174    let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
175    if has_similar_names(&field_names) {
176        sb.add_attr("allow(clippy::similar_names)");
177    }
178
179    for d in cfg.struct_derives {
180        sb.add_derive(d);
181    }
182    let opaque_fields: Vec<&str> = typ
183        .fields
184        .iter()
185        .filter(|f| f.cfg.is_none() && field_references_opaque_type(&f.ty, cfg.opaque_type_names))
186        .map(|f| f.name.as_str())
187        .collect();
188    sb.add_derive("Default");
189    sb.add_derive("serde::Serialize");
190    sb.add_derive("serde::Deserialize");
191    let has_serde = true;
192    for field in &typ.fields {
193        if field.cfg.is_some() {
194            continue;
195        }
196        let force_optional = cfg.option_duration_on_defaults
197            && typ.has_default
198            && !field.optional
199            && matches!(field.ty, TypeRef::Duration);
200        let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
201            mapper.optional(&mapper.map_type(&field.ty))
202        } else {
203            mapper.map_type(&field.ty)
204        };
205        let name_override = field_name_override(field);
206        let extra_attrs = extra_field_attrs(field);
207        // When the field name is overridden (keyword-escaped), skip cfg.field_attrs so the
208        // caller's extra_field_attrs callback can supply the full replacement attr set
209        // (e.g. `pyo3(get, name = "class")` instead of the default `pyo3(get)`).
210        let mut attrs: Vec<String> = if name_override.is_some() && !extra_attrs.is_empty() {
211            extra_attrs
212        } else {
213            let mut a: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
214            a.extend(extra_attrs);
215            a
216        };
217        // Add #[serde(skip)] for opaque fields or sanitized fields — same rationale as in
218        // gen_struct_with_per_field_attrs: sanitized fields have placeholder String types that
219        // cause JSON round-trip failures with "unknown variant ''" errors. Cow-backed strings
220        // are lossless String bindings, so they must remain serializable/deserializable.
221        let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
222        if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
223            attrs.push("serde(skip)".to_string());
224        }
225        let emit_name = name_override.unwrap_or_else(|| field.name.clone());
226        sb.add_field_with_doc(&emit_name, &ty, attrs, &field.doc);
227    }
228    sb.build()
229}
230
231/// Generate a struct definition using the builder.
232pub fn gen_struct(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
233    let mut sb = StructBuilder::new(&typ.name);
234    for attr in cfg.struct_attrs {
235        sb.add_attr(attr);
236    }
237
238    // Check if struct has similar field names (e.g., sub_symbol and sup_symbol)
239    let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
240    if has_similar_names(&field_names) {
241        sb.add_attr("allow(clippy::similar_names)");
242    }
243
244    for d in cfg.struct_derives {
245        sb.add_derive(d);
246    }
247    let _opaque_fields: Vec<&str> = typ
248        .fields
249        .iter()
250        .filter(|f| f.cfg.is_none() && field_references_opaque_type(&f.ty, cfg.opaque_type_names))
251        .map(|f| f.name.as_str())
252        .collect();
253    sb.add_derive("Default");
254    sb.add_derive("serde::Serialize");
255    sb.add_derive("serde::Deserialize");
256    let _has_serde = true;
257    for field in &typ.fields {
258        // Skip cfg-gated fields — they depend on features that may not be enabled
259        // for this binding crate. Including them would require the binding struct to
260        // handle conditional compilation which struct literal initializers can't express.
261        if field.cfg.is_some() {
262            continue;
263        }
264        // When option_duration_on_defaults is set, wrap non-optional Duration fields in
265        // Option<u64> for has_default types so the binding constructor can accept None
266        // and the From conversion falls back to the core type's Default.
267        let force_optional = cfg.option_duration_on_defaults
268            && typ.has_default
269            && !field.optional
270            && matches!(field.ty, TypeRef::Duration);
271        let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
272            mapper.optional(&mapper.map_type(&field.ty))
273        } else {
274            // field.ty is already Optional(T) — mapped type is already Option<T>, don't double-wrap
275            mapper.map_type(&field.ty)
276        };
277        let attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
278        // Only add #[serde(default)] when serde derives are present on the struct
279        // (opaque_fields empty = serde derives added, opaque field needs serde(default))
280        // This can't happen: if opaque_fields is empty, no field matches this check.
281        // If opaque_fields is non-empty, serde derives were suppressed → skip serde attr.
282        // So this block is effectively dead — remove it to prevent stale serde attrs.
283        sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
284    }
285    sb.build()
286}
287
288/// Generate a `Default` impl for a non-opaque binding struct with `has_default`.
289/// All fields use their type's Default::default().
290/// Optional fields use None instead of Default::default().
291/// This enables the struct to be used with `unwrap_or_default()` in config constructors.
292///
293/// WARNING: This assumes all field types implement Default. If a Named field type
294/// doesn't implement Default, this impl will fail to compile. Callers should verify
295/// that the struct's fields can be safely defaulted before calling this function.
296pub fn gen_struct_default_impl(typ: &TypeDef, name_prefix: &str) -> String {
297    let full_name = format!("{}{}", name_prefix, typ.name);
298    let mut out = String::with_capacity(256);
299    writeln!(out, "impl Default for {} {{", full_name).ok();
300    writeln!(out, "    fn default() -> Self {{").ok();
301    writeln!(out, "        Self {{").ok();
302    for field in &typ.fields {
303        if field.cfg.is_some() {
304            continue;
305        }
306        let default_val = match (&field.ty, &field.typed_default) {
307            (_, Some(DefaultValue::BoolLiteral(b))) => b.to_string(),
308            (_, Some(DefaultValue::IntLiteral(n))) => n.to_string(),
309            (_, Some(DefaultValue::StringLiteral(s))) => format!("String::from(\"{}\")", s),
310            (_, Some(DefaultValue::None)) | (TypeRef::Optional(_), _) => "None".to_string(),
311            _ => "Default::default()".to_string(),
312        };
313        writeln!(out, "            {}: {},", field.name, default_val).ok();
314    }
315    writeln!(out, "        }}").ok();
316    writeln!(out, "    }}").ok();
317    write!(out, "}}").ok();
318    out
319}
320
321/// Check if any method on a type takes `&mut self`, meaning the opaque wrapper
322/// must use `Arc<Mutex<T>>` instead of `Arc<T>` to allow interior mutability.
323pub fn type_needs_mutex(typ: &TypeDef) -> bool {
324    typ.methods
325        .iter()
326        .any(|m| m.receiver == Some(alef_core::ir::ReceiverKind::RefMut))
327}
328
329/// Check if a type wrapping `Arc<Mutex<T>>` should use `tokio::sync::Mutex` instead
330/// of `std::sync::Mutex` because every `&mut self` method is `async`.
331///
332/// `std::sync::MutexGuard` is `!Send`, so holding a guard across `.await` makes the
333/// surrounding future `!Send`, which fails to compile in PyO3 / NAPI-RS bindings that
334/// require `Send` futures. `tokio::sync::MutexGuard` IS `Send`, so swapping the lock
335/// type fixes the entire async-locking story for these structs.
336///
337/// The condition is tight: every method that takes `&mut self` MUST be async. If even
338/// one sync method takes `&mut self`, switching to `tokio::sync::Mutex` would break
339/// it (since `tokio::sync::Mutex::lock()` returns a `Future` and cannot be awaited
340/// from sync context). In that mixed case we keep `std::sync::Mutex`.
341pub fn type_needs_tokio_mutex(typ: &TypeDef) -> bool {
342    use alef_core::ir::ReceiverKind;
343    if !type_needs_mutex(typ) {
344        return false;
345    }
346    let refmut_methods = typ.methods.iter().filter(|m| m.receiver == Some(ReceiverKind::RefMut));
347    let mut any = false;
348    for m in refmut_methods {
349        any = true;
350        if !m.is_async {
351            return false;
352        }
353    }
354    any
355}
356
357/// Generate an opaque wrapper struct with `inner: Arc<core::Type>`.
358/// For trait types, uses `Arc<dyn Type + Send + Sync>`.
359/// For types with `&mut self` methods, uses `Arc<Mutex<core::Type>>`.
360///
361/// Special case: if ALL methods on this type are sanitized, the type was created by the
362/// impl-block fallback for a generic core type (e.g. `GraphQLExecutor<Q,M,S>`). Sanitized
363/// methods never access `self.inner` (they emit `gen_unimplemented_body`), so we omit the
364/// `inner` field entirely. This avoids generating `Arc<CoreType>` with missing generic
365/// parameters, which would fail to compile.
366pub fn gen_opaque_struct(typ: &TypeDef, cfg: &RustBindingConfig) -> String {
367    let needs_mutex = type_needs_mutex(typ);
368    // Omit the inner field only when the rust_path contains generic type parameters
369    // (angle brackets), which means the concrete types are unknown at codegen time and
370    // `Arc<CoreType<_, _, _>>` would fail to compile. This typically occurs for types
371    // created from a generic impl block where all methods are sanitized.
372    // We do NOT omit inner solely because all_methods_sanitized is true: even when no
373    // methods delegate to self.inner, the inner field may be required by From impls
374    // generated for non-opaque structs that have this type as a field.
375    let core_path = typ.rust_path.replace('-', "_");
376    let has_unresolvable_generics = core_path.contains('<');
377    let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
378    let omit_inner = all_methods_sanitized && has_unresolvable_generics;
379    let mut out = String::with_capacity(512);
380    if !cfg.struct_derives.is_empty() {
381        writeln!(out, "#[derive(Clone)]").ok();
382    }
383    for attr in cfg.struct_attrs {
384        writeln!(out, "#[{attr}]").ok();
385    }
386    writeln!(out, "pub struct {} {{", typ.name).ok();
387    if !omit_inner {
388        if typ.is_trait {
389            writeln!(out, "    inner: Arc<dyn {core_path} + Send + Sync>,").ok();
390        } else if needs_mutex {
391            writeln!(out, "    inner: Arc<std::sync::Mutex<{core_path}>>,").ok();
392        } else {
393            writeln!(out, "    inner: Arc<{core_path}>,").ok();
394        }
395    }
396    write!(out, "}}").ok();
397    out
398}
399
400/// Generate an opaque wrapper struct with `inner: Arc<core::Type>` and a name prefix.
401/// For types with `&mut self` methods, uses `Arc<Mutex<core::Type>>`.
402///
403/// Special case: if ALL methods on this type are sanitized, omit the `inner` field.
404/// See `gen_opaque_struct` for the rationale.
405pub fn gen_opaque_struct_prefixed(typ: &TypeDef, cfg: &RustBindingConfig, prefix: &str) -> String {
406    let needs_mutex = type_needs_mutex(typ);
407    let core_path = typ.rust_path.replace('-', "_");
408    let has_unresolvable_generics = core_path.contains('<');
409    let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
410    let omit_inner = all_methods_sanitized && has_unresolvable_generics;
411    let mut out = String::with_capacity(512);
412    if !cfg.struct_derives.is_empty() {
413        writeln!(out, "#[derive(Clone)]").ok();
414    }
415    for attr in cfg.struct_attrs {
416        writeln!(out, "#[{attr}]").ok();
417    }
418    writeln!(out, "pub struct {}{} {{", prefix, typ.name).ok();
419    if !omit_inner {
420        if typ.is_trait {
421            writeln!(out, "    inner: Arc<dyn {core_path} + Send + Sync>,").ok();
422        } else if needs_mutex {
423            writeln!(out, "    inner: Arc<std::sync::Mutex<{core_path}>>,").ok();
424        } else {
425            writeln!(out, "    inner: Arc<{core_path}>,").ok();
426        }
427    }
428    write!(out, "}}").ok();
429    out
430}
431
432#[cfg(test)]
433mod tests {
434    use super::{type_needs_mutex, type_needs_tokio_mutex};
435    use alef_core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
436
437    fn method(name: &str, receiver: Option<ReceiverKind>, is_async: bool) -> MethodDef {
438        MethodDef {
439            name: name.into(),
440            params: vec![],
441            return_type: TypeRef::Unit,
442            is_async,
443            is_static: false,
444            error_type: None,
445            doc: String::new(),
446            receiver,
447            sanitized: false,
448            trait_source: None,
449            returns_ref: false,
450            returns_cow: false,
451            return_newtype_wrapper: None,
452            has_default_impl: false,
453        }
454    }
455
456    fn type_with_methods(name: &str, methods: Vec<MethodDef>) -> TypeDef {
457        TypeDef {
458            name: name.into(),
459            rust_path: format!("my_crate::{name}"),
460            original_rust_path: String::new(),
461            fields: vec![],
462            methods,
463            is_opaque: true,
464            is_clone: false,
465            is_copy: false,
466            is_trait: false,
467            has_default: false,
468            has_stripped_cfg_fields: false,
469            is_return_type: false,
470            serde_rename_all: None,
471            has_serde: false,
472            super_traits: vec![],
473            doc: String::new(),
474            cfg: None,
475        }
476    }
477
478    #[test]
479    fn tokio_mutex_when_all_refmut_methods_async() {
480        let typ = type_with_methods(
481            "WebSocketConnection",
482            vec![
483                method("send_text", Some(ReceiverKind::RefMut), true),
484                method("receive_text", Some(ReceiverKind::RefMut), true),
485                method("close", None, true),
486            ],
487        );
488        assert!(type_needs_mutex(&typ));
489        assert!(type_needs_tokio_mutex(&typ));
490    }
491
492    #[test]
493    fn no_tokio_mutex_when_any_refmut_is_sync() {
494        let typ = type_with_methods(
495            "Mixed",
496            vec![
497                method("async_op", Some(ReceiverKind::RefMut), true),
498                method("sync_op", Some(ReceiverKind::RefMut), false),
499            ],
500        );
501        assert!(type_needs_mutex(&typ));
502        assert!(!type_needs_tokio_mutex(&typ));
503    }
504
505    #[test]
506    fn no_tokio_mutex_when_no_refmut() {
507        let typ = type_with_methods("ReadOnly", vec![method("get", Some(ReceiverKind::Ref), true)]);
508        assert!(!type_needs_mutex(&typ));
509        assert!(!type_needs_tokio_mutex(&typ));
510    }
511
512    #[test]
513    fn no_tokio_mutex_when_empty_methods() {
514        let typ = type_with_methods("Empty", vec![]);
515        assert!(!type_needs_mutex(&typ));
516        assert!(!type_needs_tokio_mutex(&typ));
517    }
518}