Skip to main content

alef_codegen/
shared.rs

1use ahash::AHashSet;
2use alef_core::ir::{DefaultValue, FieldDef, MethodDef, ParamDef, 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))
24        && is_delegatable_return(&func.return_type)
25}
26
27/// Check if all params and return type are delegatable.
28pub fn can_auto_delegate(method: &MethodDef, opaque_types: &AHashSet<String>) -> bool {
29    !method.sanitized
30        && method
31            .params
32            .iter()
33            .all(|p| !p.sanitized && is_delegatable_param(&p.ty, opaque_types))
34        && is_delegatable_return(&method.return_type)
35}
36
37/// A param type is delegatable if it's simple, or a Named type (opaque → Arc unwrap, non-opaque → .into()).
38pub fn is_delegatable_param(ty: &TypeRef, _opaque_types: &AHashSet<String>) -> bool {
39    match ty {
40        TypeRef::Primitive(_)
41        | TypeRef::String
42        | TypeRef::Char
43        | TypeRef::Bytes
44        | TypeRef::Path
45        | TypeRef::Unit
46        | TypeRef::Duration => true,
47        TypeRef::Named(_) => true, // Opaque: &*param.inner; non-opaque: .into()
48        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_delegatable_param(inner, _opaque_types),
49        TypeRef::Map(k, v) => is_delegatable_param(k, _opaque_types) && is_delegatable_param(v, _opaque_types),
50        TypeRef::Json => false,
51    }
52}
53
54/// Return types are more permissive — Named types work via .into() (core→binding From exists).
55pub fn is_delegatable_return(ty: &TypeRef) -> bool {
56    match ty {
57        TypeRef::Primitive(_)
58        | TypeRef::String
59        | TypeRef::Char
60        | TypeRef::Bytes
61        | TypeRef::Path
62        | TypeRef::Unit
63        | TypeRef::Duration => true,
64        TypeRef::Named(_) => true, // core→binding From impl generated for all convertible types
65        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_delegatable_return(inner),
66        TypeRef::Map(k, v) => is_delegatable_return(k) && is_delegatable_return(v),
67        TypeRef::Json => false,
68    }
69}
70
71/// A type is delegatable if it can cross the binding boundary without From impls.
72/// Named types are NOT delegatable as function params (may lack From impls).
73/// For opaque methods, Named types are handled separately via Arc wrap/unwrap.
74pub fn is_delegatable_type(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(_) => false, // Requires From impl which may not exist
84        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_delegatable_type(inner),
85        TypeRef::Map(k, v) => is_delegatable_type(k) && is_delegatable_type(v),
86        TypeRef::Json => false,
87    }
88}
89
90/// Check if a type is delegatable in the opaque method context.
91/// Opaque methods can handle Named params via Arc unwrap and Named returns via Arc wrap.
92pub fn is_opaque_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(_) => true, // Opaque: Arc unwrap/wrap. Non-opaque: .into()
102        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_opaque_delegatable_type(inner),
103        TypeRef::Map(k, v) => is_opaque_delegatable_type(k) && is_opaque_delegatable_type(v),
104        TypeRef::Json => false,
105    }
106}
107
108/// Check if a type is "simple" — can be passed without any conversion.
109pub fn is_simple_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::Optional(inner) | TypeRef::Vec(inner) => is_simple_type(inner),
119        TypeRef::Map(k, v) => is_simple_type(k) && is_simple_type(v),
120        TypeRef::Named(_) | TypeRef::Json => false,
121    }
122}
123
124/// Partition methods into (instance, static).
125pub fn partition_methods(methods: &[MethodDef]) -> (Vec<&MethodDef>, Vec<&MethodDef>) {
126    let instance: Vec<_> = methods.iter().filter(|m| m.receiver.is_some()).collect();
127    let statics: Vec<_> = methods.iter().filter(|m| m.receiver.is_none()).collect();
128    (instance, statics)
129}
130
131/// Build a constructor parameter list string.
132/// Returns (param_list, signature_with_defaults, field_assignments).
133/// If param_list exceeds 100 chars, uses multiline format with trailing commas.
134pub fn constructor_parts(fields: &[FieldDef], type_mapper: &dyn Fn(&TypeRef) -> String) -> (String, String, String) {
135    // Sort fields: required first, then optional.
136    // Many FFI frameworks (PyO3, NAPI) require required params before optional ones.
137    // Skip cfg-gated fields — they depend on features that may not be enabled.
138    let mut sorted_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.cfg.is_none()).collect();
139    sorted_fields.sort_by_key(|f| f.optional as u8);
140
141    let params: Vec<String> = sorted_fields
142        .iter()
143        .map(|f| {
144            let ty = if f.optional {
145                format!("Option<{}>", type_mapper(&f.ty))
146            } else {
147                type_mapper(&f.ty)
148            };
149            format!("{}: {}", f.name, ty)
150        })
151        .collect();
152
153    let defaults: Vec<String> = sorted_fields
154        .iter()
155        .map(|f| {
156            if f.optional {
157                format!("{}=None", f.name)
158            } else {
159                f.name.clone()
160            }
161        })
162        .collect();
163
164    // Assignments keep original field order (for struct literal), excluding cfg-gated
165    let assignments: Vec<String> = fields
166        .iter()
167        .filter(|f| f.cfg.is_none())
168        .map(|f| f.name.clone())
169        .collect();
170
171    // Format param_list with line wrapping if needed
172    let single_line = params.join(", ");
173    let param_list = if single_line.len() > 100 {
174        format!("\n        {},\n    ", params.join(",\n        "))
175    } else {
176        single_line
177    };
178
179    (param_list, defaults.join(", "), assignments.join(", "))
180}
181
182/// Build a function parameter list.
183pub fn function_params(params: &[ParamDef], type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
184    // After the first optional param, all subsequent params must also be optional
185    // to satisfy PyO3's signature constraint (required params can't follow optional ones).
186    let mut seen_optional = false;
187    params
188        .iter()
189        .map(|p| {
190            if p.optional {
191                seen_optional = true;
192            }
193            let ty = if p.optional || seen_optional {
194                format!("Option<{}>", type_mapper(&p.ty))
195            } else {
196                type_mapper(&p.ty)
197            };
198            format!("{}: {}", p.name, ty)
199        })
200        .collect::<Vec<_>>()
201        .join(", ")
202}
203
204/// Build a function signature defaults string (for pyo3 signature etc.).
205pub fn function_sig_defaults(params: &[ParamDef]) -> String {
206    // After the first optional param, all subsequent params must also use =None
207    // to satisfy PyO3's signature constraint (required params can't follow optional ones).
208    let mut seen_optional = false;
209    params
210        .iter()
211        .map(|p| {
212            if p.optional {
213                seen_optional = true;
214            }
215            if p.optional || seen_optional {
216                format!("{}=None", p.name)
217            } else {
218                p.name.clone()
219            }
220        })
221        .collect::<Vec<_>>()
222        .join(", ")
223}
224
225/// Format a DefaultValue as Rust code for the target language.
226/// Used by backends generating config constructors with defaults.
227pub fn format_default_value(default: &DefaultValue) -> String {
228    match default {
229        DefaultValue::BoolLiteral(b) => format!("{}", b),
230        DefaultValue::StringLiteral(s) => format!("\"{}\".to_string()", s.escape_default()),
231        DefaultValue::IntLiteral(i) => format!("{}", i),
232        DefaultValue::FloatLiteral(f) => format!("{}", f),
233        DefaultValue::EnumVariant(v) => v.clone(),
234        DefaultValue::Empty => "Default::default()".to_string(),
235        DefaultValue::None => "None".to_string(),
236    }
237}
238
239/// Generate constructor parameter and assignment lists for types with has_default.
240/// All fields become Option<T> with None defaults for optional fields,
241/// or unwrap_or_else with actual defaults for required fields.
242///
243/// Returns (param_list, signature_defaults, assignments).
244/// This is used by PyO3 and similar backends that need signature annotations.
245pub fn config_constructor_parts(
246    fields: &[FieldDef],
247    type_mapper: &dyn Fn(&TypeRef) -> String,
248) -> (String, String, String) {
249    let mut sorted_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.cfg.is_none()).collect();
250    sorted_fields.sort_by_key(|f| f.optional as u8);
251
252    let params: Vec<String> = sorted_fields
253        .iter()
254        .map(|f| {
255            let ty = type_mapper(&f.ty);
256            // All fields become Option<T>
257            format!("{}: Option<{}>", f.name, ty)
258        })
259        .collect();
260
261    // All fields have None default in signature
262    let defaults = sorted_fields
263        .iter()
264        .map(|f| format!("{}=None", f.name))
265        .collect::<Vec<_>>()
266        .join(", ");
267
268    // Assignments use unwrap_or_else with the typed default
269    let assignments: Vec<String> = fields
270        .iter()
271        .filter(|f| f.cfg.is_none())
272        .map(|f| {
273            if f.optional || matches!(&f.ty, TypeRef::Optional(_)) {
274                // Optional fields: passthrough (both param and field are Option<T>)
275                format!("{}: {}", f.name, f.name)
276            } else if let Some(ref typed_default) = f.typed_default {
277                // For EnumVariant and Empty defaults, use unwrap_or_default()
278                // because we can't generate qualified Rust paths here.
279                match typed_default {
280                    DefaultValue::EnumVariant(_) | DefaultValue::Empty => {
281                        format!("{}: {}.unwrap_or_default()", f.name, f.name)
282                    }
283                    _ => {
284                        let default_val = format_default_value(typed_default);
285                        // Use unwrap_or() for Copy literals (bool, int, float) to avoid
286                        // clippy::unnecessary_lazy_evaluations; use unwrap_or_else for heap types.
287                        match typed_default {
288                            DefaultValue::BoolLiteral(_)
289                            | DefaultValue::IntLiteral(_)
290                            | DefaultValue::FloatLiteral(_) => {
291                                format!("{}: {}.unwrap_or({})", f.name, f.name, default_val)
292                            }
293                            _ => {
294                                format!("{}: {}.unwrap_or_else(|| {})", f.name, f.name, default_val)
295                            }
296                        }
297                    }
298                }
299            } else {
300                // All binding types should impl Default (enums default to first variant,
301                // structs default via From<CoreType::default()>). unwrap_or_default() works.
302                format!("{}: {}.unwrap_or_default()", f.name, f.name)
303            }
304        })
305        .collect();
306
307    let single_line = params.join(", ");
308    let param_list = if single_line.len() > 100 {
309        format!("\n        {},\n    ", params.join(",\n        "))
310    } else {
311        single_line
312    };
313
314    (param_list, defaults, assignments.join(", "))
315}