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) => {
233            let s = format!("{}", f);
234            // Ensure the literal is a valid Rust float (must contain '.' or 'e'/'E')
235            if s.contains('.') || s.contains('e') || s.contains('E') {
236                s
237            } else {
238                format!("{s}.0")
239            }
240        }
241        DefaultValue::EnumVariant(v) => v.clone(),
242        DefaultValue::Empty => "Default::default()".to_string(),
243        DefaultValue::None => "None".to_string(),
244    }
245}
246
247/// Generate constructor parameter and assignment lists for types with has_default.
248/// All fields become Option<T> with None defaults for optional fields,
249/// or unwrap_or_else with actual defaults for required fields.
250///
251/// Returns (param_list, signature_defaults, assignments).
252/// This is used by PyO3 and similar backends that need signature annotations.
253/// Like `config_constructor_parts` but with extra options.
254/// When `option_duration_on_defaults` is true, non-optional Duration fields are stored
255/// as `Option<u64>` in the binding struct, so the constructor assignment is a passthrough
256/// (the From conversion will handle the None → core default mapping).
257pub fn config_constructor_parts_with_options(
258    fields: &[FieldDef],
259    type_mapper: &dyn Fn(&TypeRef) -> String,
260    option_duration_on_defaults: bool,
261) -> (String, String, String) {
262    config_constructor_parts_inner(fields, type_mapper, option_duration_on_defaults)
263}
264
265pub fn config_constructor_parts(
266    fields: &[FieldDef],
267    type_mapper: &dyn Fn(&TypeRef) -> String,
268) -> (String, String, String) {
269    config_constructor_parts_inner(fields, type_mapper, false)
270}
271
272fn config_constructor_parts_inner(
273    fields: &[FieldDef],
274    type_mapper: &dyn Fn(&TypeRef) -> String,
275    option_duration_on_defaults: bool,
276) -> (String, String, String) {
277    let mut sorted_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.cfg.is_none()).collect();
278    sorted_fields.sort_by_key(|f| f.optional as u8);
279
280    let params: Vec<String> = sorted_fields
281        .iter()
282        .map(|f| {
283            let ty = type_mapper(&f.ty);
284            // All fields become Option<T>
285            format!("{}: Option<{}>", f.name, ty)
286        })
287        .collect();
288
289    // All fields have None default in signature
290    let defaults = sorted_fields
291        .iter()
292        .map(|f| format!("{}=None", f.name))
293        .collect::<Vec<_>>()
294        .join(", ");
295
296    // Assignments use unwrap_or_else with the typed default
297    let assignments: Vec<String> = fields
298        .iter()
299        .filter(|f| f.cfg.is_none())
300        .map(|f| {
301            // Duration fields on has_default types are stored as Option<u64> when
302            // option_duration_on_defaults is set — treat them as passthrough.
303            if option_duration_on_defaults && matches!(f.ty, TypeRef::Duration) {
304                return format!("{}: {}", f.name, f.name);
305            }
306            if f.optional || matches!(&f.ty, TypeRef::Optional(_)) {
307                // Optional fields: passthrough (both param and field are Option<T>)
308                format!("{}: {}", f.name, f.name)
309            } else if let Some(ref typed_default) = f.typed_default {
310                // For EnumVariant and Empty defaults, use unwrap_or_default()
311                // because we can't generate qualified Rust paths here.
312                match typed_default {
313                    DefaultValue::EnumVariant(_) | DefaultValue::Empty => {
314                        format!("{}: {}.unwrap_or_default()", f.name, f.name)
315                    }
316                    _ => {
317                        let default_val = format_default_value(typed_default);
318                        // Use unwrap_or() for Copy literals (bool, int, float) to avoid
319                        // clippy::unnecessary_lazy_evaluations; use unwrap_or_else for heap types.
320                        match typed_default {
321                            DefaultValue::BoolLiteral(_)
322                            | DefaultValue::IntLiteral(_)
323                            | DefaultValue::FloatLiteral(_) => {
324                                format!("{}: {}.unwrap_or({})", f.name, f.name, default_val)
325                            }
326                            _ => {
327                                format!("{}: {}.unwrap_or_else(|| {})", f.name, f.name, default_val)
328                            }
329                        }
330                    }
331                }
332            } else {
333                // All binding types should impl Default (enums default to first variant,
334                // structs default via From<CoreType::default()>). unwrap_or_default() works.
335                format!("{}: {}.unwrap_or_default()", f.name, f.name)
336            }
337        })
338        .collect();
339
340    let single_line = params.join(", ");
341    let param_list = if single_line.len() > 100 {
342        format!("\n        {},\n    ", params.join(",\n        "))
343    } else {
344        single_line
345    };
346
347    (param_list, defaults, assignments.join(", "))
348}