Skip to main content

alef_codegen/
shared.rs

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