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