Skip to main content

alef_codegen/
shared.rs

1use ahash::AHashSet;
2use alef_core::ir::{DefaultValue, FieldDef, MethodDef, ParamDef, PrimitiveType, ReceiverKind, TypeRef};
3
4/// Returns true if this parameter is required but must be promoted to optional
5/// because it follows an optional parameter in the list.
6/// PyO3 requires that required params come before all optional params.
7pub fn is_promoted_optional(params: &[ParamDef], idx: usize) -> bool {
8    if params[idx].optional {
9        return false; // naturally optional
10    }
11    // Check if any earlier param is optional
12    params[..idx].iter().any(|p| p.optional)
13}
14
15/// Check if a free function can be auto-delegated to the core crate.
16/// Opaque Named params are allowed (unwrapped via Arc). Non-opaque Named params are not
17/// (require From impls that may not exist for types with sanitized fields).
18pub fn can_auto_delegate_function(func: &alef_core::ir::FunctionDef, opaque_types: &AHashSet<String>) -> bool {
19    !func.sanitized
20        && func
21            .params
22            .iter()
23            .all(|p| !p.sanitized && is_delegatable_param(&p.ty, opaque_types) && !is_named_ref_param(p, opaque_types))
24        && is_delegatable_return(&func.return_type)
25}
26
27/// Check if all params and return type are delegatable.
28/// For opaque types, skip methods with RefMut receiver (cannot borrow Arc mutably).
29pub fn can_auto_delegate(method: &MethodDef, opaque_types: &AHashSet<String>) -> bool {
30    // Skip RefMut methods on opaque types (Arc doesn't allow mutable access)
31    if matches!(method.receiver, Some(ReceiverKind::RefMut)) && method.trait_source.is_none() {
32        return false;
33    }
34    !method.sanitized
35        && method
36            .params
37            .iter()
38            .all(|p| !p.sanitized && is_delegatable_param(&p.ty, opaque_types) && !is_named_ref_param(p, opaque_types))
39        && is_delegatable_return(&method.return_type)
40}
41
42/// A Named param with is_ref=true needs a let-binding (can't inline .into() + borrow).
43/// A Vec<String> param with is_ref=true needs conversion to Vec<&str>.
44fn is_named_ref_param(p: &alef_core::ir::ParamDef, opaque_types: &AHashSet<String>) -> bool {
45    if !p.is_ref {
46        return false;
47    }
48    match &p.ty {
49        TypeRef::Named(name) => !opaque_types.contains(name.as_str()),
50        TypeRef::Vec(inner) => matches!(inner.as_ref(), TypeRef::String | TypeRef::Char),
51        _ => false,
52    }
53}
54
55/// A param type is delegatable if it's simple, or a Named type (opaque → Arc unwrap, non-opaque → .into()).
56pub fn is_delegatable_param(ty: &TypeRef, _opaque_types: &AHashSet<String>) -> bool {
57    match ty {
58        TypeRef::Primitive(_)
59        | TypeRef::String
60        | TypeRef::Char
61        | TypeRef::Bytes
62        | TypeRef::Path
63        | TypeRef::Unit
64        | TypeRef::Duration => true,
65        TypeRef::Named(_) => true, // Opaque: &*param.inner; non-opaque: .into()
66        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_delegatable_param(inner, _opaque_types),
67        TypeRef::Map(k, v) => is_delegatable_param(k, _opaque_types) && is_delegatable_param(v, _opaque_types),
68        TypeRef::Json => false,
69    }
70}
71
72/// Return types are more permissive — Named types work via .into() (core→binding From exists).
73pub fn is_delegatable_return(ty: &TypeRef) -> bool {
74    match ty {
75        TypeRef::Primitive(_)
76        | TypeRef::String
77        | TypeRef::Char
78        | TypeRef::Bytes
79        | TypeRef::Path
80        | TypeRef::Unit
81        | TypeRef::Duration => true,
82        TypeRef::Named(_) => true, // core→binding From impl generated for all convertible types
83        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_delegatable_return(inner),
84        TypeRef::Map(k, v) => is_delegatable_return(k) && is_delegatable_return(v),
85        TypeRef::Json => false,
86    }
87}
88
89/// A type is delegatable if it can cross the binding boundary without From impls.
90/// Named types are NOT delegatable as function params (may lack From impls).
91/// For opaque methods, Named types are handled separately via Arc wrap/unwrap.
92pub fn is_delegatable_type(ty: &TypeRef) -> bool {
93    match ty {
94        TypeRef::Primitive(_)
95        | TypeRef::String
96        | TypeRef::Char
97        | TypeRef::Bytes
98        | TypeRef::Path
99        | TypeRef::Unit
100        | TypeRef::Duration => true,
101        TypeRef::Named(_) => false, // Requires From impl which may not exist
102        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_delegatable_type(inner),
103        TypeRef::Map(k, v) => is_delegatable_type(k) && is_delegatable_type(v),
104        TypeRef::Json => false,
105    }
106}
107
108/// Check if a type is delegatable in the opaque method context.
109/// Opaque methods can handle Named params via Arc unwrap and Named returns via Arc wrap.
110pub fn is_opaque_delegatable_type(ty: &TypeRef) -> bool {
111    match ty {
112        TypeRef::Primitive(_)
113        | TypeRef::String
114        | TypeRef::Char
115        | TypeRef::Bytes
116        | TypeRef::Path
117        | TypeRef::Unit
118        | TypeRef::Duration => true,
119        TypeRef::Named(_) => true, // Opaque: Arc unwrap/wrap. Non-opaque: .into()
120        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_opaque_delegatable_type(inner),
121        TypeRef::Map(k, v) => is_opaque_delegatable_type(k) && is_opaque_delegatable_type(v),
122        TypeRef::Json => false,
123    }
124}
125
126/// Check if a type is "simple" — can be passed without any conversion.
127pub fn is_simple_type(ty: &TypeRef) -> bool {
128    match ty {
129        TypeRef::Primitive(_)
130        | TypeRef::String
131        | TypeRef::Char
132        | TypeRef::Bytes
133        | TypeRef::Path
134        | TypeRef::Unit
135        | TypeRef::Duration => true,
136        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_simple_type(inner),
137        TypeRef::Map(k, v) => is_simple_type(k) && is_simple_type(v),
138        TypeRef::Named(_) | TypeRef::Json => false,
139    }
140}
141
142/// Partition methods into (instance, static).
143pub fn partition_methods(methods: &[MethodDef]) -> (Vec<&MethodDef>, Vec<&MethodDef>) {
144    let instance: Vec<_> = methods.iter().filter(|m| m.receiver.is_some()).collect();
145    let statics: Vec<_> = methods.iter().filter(|m| m.receiver.is_none()).collect();
146    (instance, statics)
147}
148
149/// Build a constructor parameter list string.
150/// Returns (param_list, signature_with_defaults, field_assignments).
151/// If param_list exceeds 100 chars, uses multiline format with trailing commas.
152pub fn constructor_parts(fields: &[FieldDef], type_mapper: &dyn Fn(&TypeRef) -> String) -> (String, String, String) {
153    // Sort fields: required first, then optional.
154    // Many FFI frameworks (PyO3, NAPI) require required params before optional ones.
155    // Skip cfg-gated fields — they depend on features that may not be enabled.
156    let mut sorted_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.cfg.is_none()).collect();
157    sorted_fields.sort_by_key(|f| f.optional as u8);
158
159    let params: Vec<String> = sorted_fields
160        .iter()
161        .map(|f| {
162            let ty = if f.optional {
163                format!("Option<{}>", type_mapper(&f.ty))
164            } else {
165                type_mapper(&f.ty)
166            };
167            format!("{}: {}", f.name, ty)
168        })
169        .collect();
170
171    let defaults: Vec<String> = sorted_fields
172        .iter()
173        .map(|f| {
174            if f.optional {
175                format!("{}=None", f.name)
176            } else {
177                f.name.clone()
178            }
179        })
180        .collect();
181
182    // Assignments keep original field order (for struct literal), excluding cfg-gated
183    let assignments: Vec<String> = fields
184        .iter()
185        .filter(|f| f.cfg.is_none())
186        .map(|f| f.name.clone())
187        .collect();
188
189    // Format param_list with line wrapping if needed
190    let single_line = params.join(", ");
191    let param_list = if single_line.len() > 100 {
192        format!("\n        {},\n    ", params.join(",\n        "))
193    } else {
194        single_line
195    };
196
197    (param_list, defaults.join(", "), assignments.join(", "))
198}
199
200/// Build a function parameter list.
201pub fn function_params(params: &[ParamDef], type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
202    // After the first optional param, all subsequent params must also be optional
203    // to satisfy PyO3's signature constraint (required params can't follow optional ones).
204    let mut seen_optional = false;
205    params
206        .iter()
207        .map(|p| {
208            if p.optional {
209                seen_optional = true;
210            }
211            let ty = if p.optional || seen_optional {
212                format!("Option<{}>", type_mapper(&p.ty))
213            } else {
214                type_mapper(&p.ty)
215            };
216            format!("{}: {}", p.name, ty)
217        })
218        .collect::<Vec<_>>()
219        .join(", ")
220}
221
222/// Build a function signature defaults string (for pyo3 signature etc.).
223pub fn function_sig_defaults(params: &[ParamDef]) -> String {
224    // After the first optional param, all subsequent params must also carry a default
225    // to satisfy PyO3's signature constraint (required params can't follow optional ones).
226    // For optional params and Named/non-primitive promoted params: use `=None`.
227    // For promoted non-optional primitive params: use a type-appropriate zero/false default
228    // so PyO3 does not wrap the Rust type in Option<T> (which would cause a `?` unwrap error).
229    let mut seen_optional = false;
230    params
231        .iter()
232        .map(|p| {
233            if p.optional {
234                seen_optional = true;
235            }
236            if p.optional {
237                format!("{}=None", p.name)
238            } else if seen_optional {
239                // Promoted non-optional param: emit a type-appropriate default instead of None
240                // so PyO3 keeps the Rust parameter type as T (not Option<T>).
241                let default = match &p.ty {
242                    TypeRef::Primitive(PrimitiveType::Bool) => "false",
243                    TypeRef::Primitive(_) => "0",
244                    _ => "None",
245                };
246                format!("{}={}", p.name, default)
247            } else {
248                p.name.clone()
249            }
250        })
251        .collect::<Vec<_>>()
252        .join(", ")
253}
254
255/// Format a DefaultValue as Rust code for the target language.
256/// Used by backends generating config constructors with defaults.
257pub fn format_default_value(default: &DefaultValue) -> String {
258    match default {
259        DefaultValue::BoolLiteral(b) => format!("{}", b),
260        DefaultValue::StringLiteral(s) => format!("\"{}\".to_string()", s.escape_default()),
261        DefaultValue::IntLiteral(i) => format!("{}", i),
262        DefaultValue::FloatLiteral(f) => {
263            let s = format!("{}", f);
264            // Ensure the literal is a valid Rust float (must contain '.' or 'e'/'E')
265            if s.contains('.') || s.contains('e') || s.contains('E') {
266                s
267            } else {
268                format!("{s}.0")
269            }
270        }
271        DefaultValue::EnumVariant(v) => v.clone(),
272        DefaultValue::Empty => "Default::default()".to_string(),
273        DefaultValue::None => "None".to_string(),
274    }
275}
276
277/// Generate constructor parameter and assignment lists for types with has_default.
278/// All fields become Option<T> with None defaults for optional fields,
279/// or unwrap_or_else with actual defaults for required fields.
280///
281/// Returns (param_list, signature_defaults, assignments).
282/// This is used by PyO3 and similar backends that need signature annotations.
283/// Like `config_constructor_parts` but with extra options.
284/// When `option_duration_on_defaults` is true, non-optional Duration fields are stored
285/// as `Option<u64>` in the binding struct, so the constructor assignment is a passthrough
286/// (the From conversion will handle the None → core default mapping).
287pub fn config_constructor_parts_with_options(
288    fields: &[FieldDef],
289    type_mapper: &dyn Fn(&TypeRef) -> String,
290    option_duration_on_defaults: bool,
291) -> (String, String, String) {
292    config_constructor_parts_inner(fields, type_mapper, option_duration_on_defaults)
293}
294
295pub fn config_constructor_parts(
296    fields: &[FieldDef],
297    type_mapper: &dyn Fn(&TypeRef) -> String,
298) -> (String, String, String) {
299    config_constructor_parts_inner(fields, type_mapper, false)
300}
301
302fn config_constructor_parts_inner(
303    fields: &[FieldDef],
304    type_mapper: &dyn Fn(&TypeRef) -> String,
305    option_duration_on_defaults: bool,
306) -> (String, String, String) {
307    let mut sorted_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.cfg.is_none()).collect();
308    sorted_fields.sort_by_key(|f| f.optional as u8);
309
310    let params: Vec<String> = sorted_fields
311        .iter()
312        .map(|f| {
313            let ty = type_mapper(&f.ty);
314            // All fields become Option<T>, but avoid Option<Option<T>> for already-optional fields.
315            // When f.ty is TypeRef::Optional(X), type_mapper already returns "Option<X>".
316            // Wrapping it again would yield Option<Option<X>>, making `None` ambiguous in PyO3
317            // signatures (E0283: type annotations needed).
318            if matches!(f.ty, TypeRef::Optional(_)) {
319                format!("{}: {}", f.name, ty)
320            } else {
321                format!("{}: Option<{}>", f.name, ty)
322            }
323        })
324        .collect();
325
326    // All fields have None default in signature
327    let defaults = sorted_fields
328        .iter()
329        .map(|f| format!("{}=None", f.name))
330        .collect::<Vec<_>>()
331        .join(", ");
332
333    // Assignments use unwrap_or_else with the typed default
334    let assignments: Vec<String> = fields
335        .iter()
336        .filter(|f| f.cfg.is_none())
337        .map(|f| {
338            // Duration fields on has_default types are stored as Option<u64> when
339            // option_duration_on_defaults is set — treat them as passthrough.
340            if option_duration_on_defaults && matches!(f.ty, TypeRef::Duration) {
341                return format!("{}: {}", f.name, f.name);
342            }
343            if f.optional || matches!(&f.ty, TypeRef::Optional(_)) {
344                // Optional fields: passthrough (both param and field are Option<T>)
345                format!("{}: {}", f.name, f.name)
346            } else if let Some(ref typed_default) = f.typed_default {
347                // For EnumVariant and Empty defaults, use unwrap_or_default()
348                // because we can't generate qualified Rust paths here.
349                match typed_default {
350                    DefaultValue::EnumVariant(_) | DefaultValue::Empty => {
351                        format!("{}: {}.unwrap_or_default()", f.name, f.name)
352                    }
353                    _ => {
354                        let default_val = format_default_value(typed_default);
355                        // Use unwrap_or() for Copy literals (bool, int, float) to avoid
356                        // clippy::unnecessary_lazy_evaluations; use unwrap_or_else for heap types.
357                        match typed_default {
358                            DefaultValue::BoolLiteral(_)
359                            | DefaultValue::IntLiteral(_)
360                            | DefaultValue::FloatLiteral(_) => {
361                                format!("{}: {}.unwrap_or({})", f.name, f.name, default_val)
362                            }
363                            _ => {
364                                format!("{}: {}.unwrap_or_else(|| {})", f.name, f.name, default_val)
365                            }
366                        }
367                    }
368                }
369            } else {
370                // All binding types should impl Default (enums default to first variant,
371                // structs default via From<CoreType::default()>). unwrap_or_default() works.
372                format!("{}: {}.unwrap_or_default()", f.name, f.name)
373            }
374        })
375        .collect();
376
377    let single_line = params.join(", ");
378    let param_list = if single_line.len() > 100 {
379        format!("\n        {},\n    ", params.join(",\n        "))
380    } else {
381        single_line
382    };
383
384    (param_list, defaults, assignments.join(", "))
385}