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