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::{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    // Default is always derived — all core types (including data enums) implement Default.
106    // Serialize and Deserialize are only derived when no field references an opaque type
107    // (Arc-wrapped), since opaque types don't implement those traits.
108    sb.add_derive("Default");
109    let has_opaque_field = typ.fields.iter().any(|f| {
110        if f.cfg.is_some() {
111            return false;
112        }
113        field_references_opaque_type(&f.ty, cfg.opaque_type_names)
114    });
115    if !has_opaque_field {
116        sb.add_derive("serde::Serialize");
117        sb.add_derive("serde::Deserialize");
118    }
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        sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
136    }
137    sb.build()
138}
139
140/// Generate a struct definition using the builder.
141pub fn gen_struct(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
142    let mut sb = StructBuilder::new(&typ.name);
143    for attr in cfg.struct_attrs {
144        sb.add_attr(attr);
145    }
146
147    // Check if struct has similar field names (e.g., sub_symbol and sup_symbol)
148    let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
149    if has_similar_names(&field_names) {
150        sb.add_attr("allow(clippy::similar_names)");
151    }
152
153    for d in cfg.struct_derives {
154        sb.add_derive(d);
155    }
156    // Default is always derived — all core types (including data enums) implement Default.
157    // Serialize and Deserialize are only derived when no field references an opaque type.
158    sb.add_derive("Default");
159    let has_opaque_field = typ.fields.iter().any(|f| {
160        if f.cfg.is_some() {
161            return false;
162        }
163        field_references_opaque_type(&f.ty, cfg.opaque_type_names)
164    });
165    if !has_opaque_field {
166        sb.add_derive("serde::Serialize");
167        sb.add_derive("serde::Deserialize");
168    }
169    for field in &typ.fields {
170        // Skip cfg-gated fields — they depend on features that may not be enabled
171        // for this binding crate. Including them would require the binding struct to
172        // handle conditional compilation which struct literal initializers can't express.
173        if field.cfg.is_some() {
174            continue;
175        }
176        // When option_duration_on_defaults is set, wrap non-optional Duration fields in
177        // Option<u64> for has_default types so the binding constructor can accept None
178        // and the From conversion falls back to the core type's Default.
179        let force_optional = cfg.option_duration_on_defaults
180            && typ.has_default
181            && !field.optional
182            && matches!(field.ty, TypeRef::Duration);
183        let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
184            mapper.optional(&mapper.map_type(&field.ty))
185        } else {
186            // field.ty is already Optional(T) — mapped type is already Option<T>, don't double-wrap
187            mapper.map_type(&field.ty)
188        };
189        let attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
190        sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
191    }
192    sb.build()
193}
194
195/// Generate a `Default` impl for a non-opaque binding struct with `has_default`.
196/// All fields use their type's Default::default().
197/// Optional fields use None instead of Default::default().
198/// This enables the struct to be used with `unwrap_or_default()` in config constructors.
199///
200/// WARNING: This assumes all field types implement Default. If a Named field type
201/// doesn't implement Default, this impl will fail to compile. Callers should verify
202/// that the struct's fields can be safely defaulted before calling this function.
203pub fn gen_struct_default_impl(typ: &TypeDef, name_prefix: &str) -> String {
204    let full_name = format!("{}{}", name_prefix, typ.name);
205    let mut out = String::with_capacity(256);
206    writeln!(out, "impl Default for {} {{", full_name).ok();
207    writeln!(out, "    fn default() -> Self {{").ok();
208    writeln!(out, "        Self {{").ok();
209    for field in &typ.fields {
210        if field.cfg.is_some() {
211            continue;
212        }
213        let default_val = match &field.ty {
214            TypeRef::Optional(_) => "None".to_string(),
215            _ => "Default::default()".to_string(),
216        };
217        writeln!(out, "            {}: {},", field.name, default_val).ok();
218    }
219    writeln!(out, "        }}").ok();
220    writeln!(out, "    }}").ok();
221    write!(out, "}}").ok();
222    out
223}
224
225/// Check if any method on a type takes `&mut self`, meaning the opaque wrapper
226/// must use `Arc<Mutex<T>>` instead of `Arc<T>` to allow interior mutability.
227pub fn type_needs_mutex(typ: &TypeDef) -> bool {
228    typ.methods
229        .iter()
230        .any(|m| m.receiver == Some(alef_core::ir::ReceiverKind::RefMut))
231}
232
233/// Generate an opaque wrapper struct with `inner: Arc<core::Type>`.
234/// For trait types, uses `Arc<dyn Type + Send + Sync>`.
235/// For types with `&mut self` methods, uses `Arc<Mutex<core::Type>>`.
236///
237/// Special case: if ALL methods on this type are sanitized, the type was created by the
238/// impl-block fallback for a generic core type (e.g. `GraphQLExecutor<Q,M,S>`). Sanitized
239/// methods never access `self.inner` (they emit `gen_unimplemented_body`), so we omit the
240/// `inner` field entirely. This avoids generating `Arc<CoreType>` with missing generic
241/// parameters, which would fail to compile.
242pub fn gen_opaque_struct(typ: &TypeDef, cfg: &RustBindingConfig) -> String {
243    let needs_mutex = type_needs_mutex(typ);
244    // Omit the inner field only when the rust_path contains generic type parameters
245    // (angle brackets), which means the concrete types are unknown at codegen time and
246    // `Arc<CoreType<_, _, _>>` would fail to compile. This typically occurs for types
247    // created from a generic impl block where all methods are sanitized.
248    // We do NOT omit inner solely because all_methods_sanitized is true: even when no
249    // methods delegate to self.inner, the inner field may be required by From impls
250    // generated for non-opaque structs that have this type as a field.
251    let core_path = typ.rust_path.replace('-', "_");
252    let has_unresolvable_generics = core_path.contains('<');
253    let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
254    let omit_inner = all_methods_sanitized && has_unresolvable_generics;
255    let mut out = String::with_capacity(512);
256    if !cfg.struct_derives.is_empty() {
257        writeln!(out, "#[derive(Clone)]").ok();
258    }
259    for attr in cfg.struct_attrs {
260        writeln!(out, "#[{attr}]").ok();
261    }
262    writeln!(out, "pub struct {} {{", typ.name).ok();
263    if !omit_inner {
264        if typ.is_trait {
265            writeln!(out, "    inner: Arc<dyn {core_path} + Send + Sync>,").ok();
266        } else if needs_mutex {
267            writeln!(out, "    inner: Arc<std::sync::Mutex<{core_path}>>,").ok();
268        } else {
269            writeln!(out, "    inner: Arc<{core_path}>,").ok();
270        }
271    }
272    write!(out, "}}").ok();
273    out
274}
275
276/// Generate an opaque wrapper struct with `inner: Arc<core::Type>` and a name prefix.
277/// For types with `&mut self` methods, uses `Arc<Mutex<core::Type>>`.
278///
279/// Special case: if ALL methods on this type are sanitized, omit the `inner` field.
280/// See `gen_opaque_struct` for the rationale.
281pub fn gen_opaque_struct_prefixed(typ: &TypeDef, cfg: &RustBindingConfig, prefix: &str) -> String {
282    let needs_mutex = type_needs_mutex(typ);
283    let core_path = typ.rust_path.replace('-', "_");
284    let has_unresolvable_generics = core_path.contains('<');
285    let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
286    let omit_inner = all_methods_sanitized && has_unresolvable_generics;
287    let mut out = String::with_capacity(512);
288    if !cfg.struct_derives.is_empty() {
289        writeln!(out, "#[derive(Clone)]").ok();
290    }
291    for attr in cfg.struct_attrs {
292        writeln!(out, "#[{attr}]").ok();
293    }
294    writeln!(out, "pub struct {}{} {{", prefix, typ.name).ok();
295    if !omit_inner {
296        if typ.is_trait {
297            writeln!(out, "    inner: Arc<dyn {core_path} + Send + Sync>,").ok();
298        } else if needs_mutex {
299            writeln!(out, "    inner: Arc<std::sync::Mutex<{core_path}>>,").ok();
300        } else {
301            writeln!(out, "    inner: Arc<{core_path}>,").ok();
302        }
303    }
304    write!(out, "}}").ok();
305    out
306}