Skip to main content

alef_codegen/
shared.rs

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