Skip to main content

alef/codegen/
shared.rs

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