Skip to main content

prebindgen_registry/
expand.rs

1//! Constructor expansion — fold a value's construction into the wire
2//! signature of the function that consumes it, so the foreign side builds
3//! the value and calls the function in a single FFI crossing.
4//!
5//! A *constructor* is any `#[prebindgen]` function `f(p0, …) -> T` (or
6//! `-> Result<T, E>`) that builds a target type `T`. A type's input flatten
7//! (a type-level `expand_param!` `.variant*` list, or the per-fn
8//! `.expand_param(param, …)` override) replaces a parameter of that type —
9//! in the generated foreign signature only — with the constructor's inputs,
10//! flattened. The generated wrapper decodes those inputs, runs the
11//! constructor Rust-side (the **fold**), and passes the built value to the
12//! underlying call.
13//!
14//! * **One `Ctor` variant** (no identity): the parameter becomes `f`'s
15//!   parameters directly (no selector) — the plain "single" form.
16//! * **Two or more variants** (or an identity arm): the parameter becomes a
17//!   runtime selector (`i32`) plus one `Option`-wrapped input group per variant.
18//!   The identity variant passes an already-built `T` straight through.
19//!
20//! Everything here is **language-agnostic**: the fold is pure Rust and the
21//! per-leaf wire encode/decode is delegated to the adapter's existing
22//! converters. Resolution turns the declarations into [`FoldPlan`]s (stored on
23//! the registry, keyed by `(fn, param)`) and registers each leaf type as a
24//! required input so the resolver produces its converter. [`emit_fold`]
25//! emits the dispatch expression at the parameter-emission site.
26
27use std::collections::HashSet;
28
29use prebindgen_flat::types_util::ident;
30use proc_macro2::TokenStream;
31use quote::quote;
32
33use crate::{
34    declared_target::check_declared_target,
35    registry::{Registry, TypeKey},
36};
37
38mod error;
39mod plan;
40
41pub use self::{
42    error::{ExpandDeclError, ExpandError},
43    plan::{FoldArg, FoldBuild, FoldLeaf, FoldPlan, FoldShape, FoldVariant},
44};
45
46// ──────────────────────────────────────────────────────────────────────
47// Declarations (populated by the language builder)
48// ──────────────────────────────────────────────────────────────────────
49
50/// One variant of a constructor — a selector-dispatched alternative for the
51/// expanded parameter. A constructor with a single `Ctor` variant (and no
52/// `Identity`) is the degenerate "single" form: applied unconditionally with no
53/// selector. Two or more variants (or an `Identity` arm) get a runtime selector.
54#[derive(Clone)]
55pub enum Variant {
56    /// Build the target by calling this constructor function.
57    Ctor(syn::Ident),
58    /// Pass an already-built target value straight through.
59    Identity,
60}
61
62/// A type-level constructor declaration (`expand_param!(T).variant*`): the
63/// complete, ordered variant list for building `target` from flattened
64/// leaves. An immutable record — the variant order is the declaration order
65/// of the `variants` vector.
66#[derive(Clone)]
67pub struct ConstructorDecl {
68    /// The type being built, as an **identity**. Every use keyed it; none
69    /// spelled it.
70    pub target: TypeKey,
71    pub variants: Vec<Variant>,
72    /// Auto-`construct` every matching param of every declared fn. Always
73    /// `true` for type-level default (`expand_param!` `.variant*`) declarations.
74    pub default: bool,
75}
76
77/// How a construct declaration chooses the variants for a parameter.
78#[derive(Clone)]
79pub enum ExpandSel {
80    /// Use the target type's default constructor (error if none/ambiguous).
81    TopLevel,
82    /// Per-fn override (`.expand_param`): use exactly these build-from
83    /// variants (constructor fns and/or the identity/self arm).
84    Subset(Vec<Variant>),
85}
86
87/// A per-fn input expansion (`.expand_param(param, expand_param!(T)…)`) —
88/// construct `param` of `func` from the explicit variant list. Recorded as
89/// an explicit decl so the auto-`default` skips it; an identity-only list
90/// lowers to the skip-default plain form at resolution. Not related to the
91/// jnigen declaration-DSL type of the same name — this is the lowered core
92/// record.
93#[derive(Clone)]
94pub struct ExpandDecl {
95    pub func: syn::Ident,
96    pub param: syn::Ident,
97    /// The type the per-fn decl was declared for (`expand_param!(T)`) —
98    /// cross-checked against the named param's peeled type at resolution.
99    /// `None` for the internal `TopLevel` form (the type comes from the
100    /// param itself).
101    pub declared_target: Option<TypeKey>,
102    pub sel: ExpandSel,
103}
104
105/// Constructor / expansion declarations gathered from a language builder —
106/// an immutable record set: complete values, no build protocol. Declaration
107/// order is the vector order. Handed to the registry as
108/// [`Decompositions::expansions`](crate::Decompositions::expansions); empty
109/// or duplicate declarations are diagnosed at resolution (collected), not at
110/// construction.
111#[derive(Clone, Default)]
112pub struct Expansions {
113    pub constructors: Vec<ConstructorDecl>,
114    pub expands: Vec<ExpandDecl>,
115    /// `.skip_default_construct(param)` opt-outs: `(fn, param)` excluded from a
116    /// constructor `.default()` auto-apply — the lowered form of an
117    /// identity-only per-fn variant set (the plain handle, no selector).
118    pub skip_construct: std::collections::HashSet<(syn::Ident, syn::Ident)>,
119}
120
121// ──────────────────────────────────────────────────────────────────────
122// apply
123// ──────────────────────────────────────────────────────────────────────
124
125/// Structural validation of the declaration records — empty variant lists
126/// and duplicate targets. Collects EVERY offender before failing, so a
127/// build surfaces all declaration problems at once.
128fn validate_declarations(exp: &Expansions) -> Result<(), ExpandError> {
129    let mut entries: Vec<ExpandDeclError> = Vec::new();
130    let mut ctor_targets: HashSet<String> = HashSet::new();
131    for c in &exp.constructors {
132        let target = c.target.as_str().to_string();
133        if c.variants.is_empty() {
134            entries.push(ExpandDeclError::EmptyConstructor {
135                target: target.clone(),
136            });
137        }
138        if !ctor_targets.insert(target.clone()) {
139            entries.push(ExpandDeclError::DuplicateConstructor { target });
140        }
141    }
142    let mut expand_keys: HashSet<(String, String)> = HashSet::new();
143    for ed in &exp.expands {
144        if let ExpandSel::Subset(v) = &ed.sel {
145            if v.is_empty() {
146                entries.push(ExpandDeclError::EmptySubset {
147                    func: ed.func.clone(),
148                    param: ed.param.clone(),
149                });
150            }
151        }
152        if !expand_keys.insert((ed.func.to_string(), ed.param.to_string())) {
153            entries.push(ExpandDeclError::DuplicateExpand {
154                func: ed.func.clone(),
155                param: ed.param.clone(),
156            });
157        }
158    }
159    if entries.is_empty() {
160        Ok(())
161    } else {
162        Err(ExpandError::InvalidDeclarations { entries })
163    }
164}
165
166/// Resolve every `.construct` declaration (explicit + `.default()`
167/// auto-applied) into a [`FoldPlan`], register each plan's leaf types as required
168/// inputs, and store the plans on the registry. `declared_fns` is the adapter's
169/// claimed `#[prebindgen]` fn set — the domain over which `.default()`
170/// constructors auto-apply.
171///
172/// Runs inside the builder's scan, before any conversion is built, so
173/// leaf converters resolve through the normal rank machinery.
174pub(crate) fn apply<M>(
175    registry: &mut Registry<M>,
176    exp: &Expansions,
177    declared_fns: &std::collections::HashSet<syn::Ident>,
178    accessor_fns: &std::collections::HashSet<syn::Ident>,
179    method_receivers: &std::collections::HashMap<syn::Ident, TypeKey>,
180) -> Result<(), ExpandError> {
181    validate_declarations(exp)?;
182    let mut done: HashSet<(String, String)> = HashSet::new();
183    let mut skip_construct = exp.skip_construct.clone();
184    for ed in &exp.expands {
185        // A `.fun_accessor` is never parameter-composed — an explicit
186        // `.construct(param)` on one is a build error.
187        if accessor_fns.contains(&ed.func) {
188            return Err(ExpandError::ConstructOnAccessor {
189                func: ed.func.clone(),
190            });
191        }
192        // Per-fn decl cross-check: the named param must exist and its peeled
193        // (`Option`/`&`) type must equal the decl's declared type — the
194        // typo guard for both coordinates of `.expand_param(name, decl)`.
195        if let Some(declared) = &ed.declared_target {
196            let param_ty = param_reading(registry, &ed.func, &ed.param)?;
197            let bare = constructed_value(&param_ty).key();
198            if bare != *declared {
199                return Err(ExpandError::ParamTypeMismatch {
200                    func: ed.func.clone(),
201                    param: ed.param.clone(),
202                    declared: declared.as_str().to_string(),
203                    actual: bare.as_str().to_string(),
204                });
205            }
206        }
207        // Identity-only variant set = the plain form: no selector, the param
208        // crosses as the bare value — lowered to the skip-default opt-out
209        // (the complete-set rule: "the set is {self}").
210        if let ExpandSel::Subset(v) = &ed.sel {
211            if matches!(v.as_slice(), [Variant::Identity]) {
212                skip_construct.insert((ed.func.clone(), ed.param.clone()));
213                done.insert((ed.func.to_string(), ed.param.to_string()));
214                continue;
215            }
216        }
217        process_expand(registry, exp, ed)?;
218        done.insert((ed.func.to_string(), ed.param.to_string()));
219    }
220
221    // `.default()` auto-apply: `construct` every matching param of every declared
222    // fn whose type peeled of `Option`/`&` equals a defaulted constructor target.
223    for c in &exp.constructors {
224        if !c.default {
225            continue;
226        }
227        let ckey = c.target.clone();
228        for func in declared_fns {
229            // Read accessors are excluded from the composer.
230            if accessor_fns.contains(func) {
231                continue;
232            }
233            let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
234                continue;
235            };
236            // A method's receiver (first param of its class type) binds to `this`
237            // and is never input-flattened; skip exactly that one param.
238            let receiver_key = method_receivers.get(func);
239            let mut receiver_skipped = false;
240            for (pname, pty) in params.iter().map(|p| (p.name.clone(), p.ty.clone())) {
241                let bare_key = constructed_value(&pty).key();
242                if !receiver_skipped && receiver_key == Some(&bare_key) {
243                    receiver_skipped = true;
244                    continue;
245                }
246                if bare_key != ckey {
247                    continue;
248                }
249                if skip_construct.contains(&(func.clone(), pname.clone())) {
250                    continue;
251                }
252                if !done.insert((func.to_string(), pname.to_string())) {
253                    continue;
254                }
255                let ed = ExpandDecl {
256                    func: func.clone(),
257                    param: pname,
258                    declared_target: None,
259                    sel: ExpandSel::TopLevel,
260                };
261                process_expand(registry, exp, &ed)?;
262            }
263        }
264    }
265    Ok(())
266}
267
268/// `(name, type)` of each typed parameter.
269/// The **reading** of a declared function's parameter.
270///
271/// `Param::ty` is a `TypeRef` computed at parse time. Reaching into the item's
272/// `spell()` and digging the parameter out of `sig.inputs` — what these
273/// three sites used to do — re-derives a fact the model was already handing over,
274/// which is `origin` used for reasoning rather than for emission.
275fn param_reading<M>(
276    registry: &Registry<M>,
277    func: &syn::Ident,
278    param: &syn::Ident,
279) -> Result<prebindgen_flat::flat::TypeRef, ExpandError> {
280    registry
281        .flat()
282        .function(&func)
283        .ok_or_else(|| ExpandError::UnknownFunction(func.clone()))?
284        .params
285        .iter()
286        .find(|p| &p.name == param)
287        .map(|p| p.ty.clone())
288        .ok_or_else(|| ExpandError::UnknownParam(func.clone(), param.clone()))
289}
290
291/// Build + store the fold plan for one `.construct` declaration.
292fn process_expand<M>(
293    registry: &mut Registry<M>,
294    exp: &Expansions,
295    ed: &ExpandDecl,
296) -> Result<(), ExpandError> {
297    let param_ty = param_reading(registry, &ed.func, &ed.param)?;
298
299    // The boundary layers: `Option<&T>` → optional + by_ref, `Option<T>` →
300    // optional, `&T` → by_ref, and `target` is what is left under them.
301    let (optional, by_ref, target) = constructed_value_layers(&param_ty);
302    let target_key = target.key();
303
304    let variants = resolve_constructor(exp, registry, &target_key, ed)?;
305    let mut visited: HashSet<TypeKey> = HashSet::new();
306    let plan = build_plan(
307        exp,
308        registry,
309        ed,
310        optional,
311        by_ref,
312        &target,
313        &variants,
314        &mut visited,
315    )?;
316
317    for leaf in &plan.leaves {
318        registry.require_input(&leaf.ty);
319    }
320    registry
321        .expansion_plans
322        .insert((ed.func.clone(), ed.param.clone()), plan);
323    Ok(())
324}
325
326/// Pick the constructor (its variants) for one `.expand`/`.expand_with`
327/// declaration. A constructor is keyed by its declared `target`; `TopLevel`
328/// requires it to be unique for the parameter's target type.
329fn resolve_constructor<M>(
330    exp: &Expansions,
331    _registry: &Registry<M>,
332    target_key: &TypeKey,
333    ed: &ExpandDecl,
334) -> Result<Vec<Variant>, ExpandError> {
335    match &ed.sel {
336        ExpandSel::Subset(variants) => Ok(variants.clone()),
337        // Unique per target: `ensure_default_constructor` dedups by type key.
338        ExpandSel::TopLevel => exp
339            .constructors
340            .iter()
341            .find(|c| c.target == *target_key)
342            .map(|c| c.variants.clone())
343            .ok_or_else(|| ExpandError::NoConstructor {
344                func: ed.func.clone(),
345                param: ed.param.clone(),
346                target: target_key.to_string(),
347            }),
348    }
349}
350
351/// Constructor signature: parameter `(name, type)` pairs and whether it is
352/// fallible (`-> Result<_, _>`). The produced (`Ok`) target type is *checked*
353/// here rather than returned — see below.
354///
355/// `expected` is the type the declaration is *for*, and the returned signature
356/// is one already proven to produce it. Taking it as a parameter rather than
357/// leaving the caller to check afterwards is the point: a declarator cannot
358/// reach a constructor's signature without saying what that constructor is
359/// supposed to build, so the check cannot be the thing a new declarator forgets
360/// (#223). The comparison is [`check_declared_target`], shared with the output
361/// side's accessor lookup.
362fn ctor_signature<M>(
363    registry: &Registry<M>,
364    func: &syn::Ident,
365    expected: &TypeKey,
366) -> Result<CtorSig, ExpandError> {
367    // Read off the element rather than re-walked from the signature: `params`
368    // and `ret` are the same facts, already decided once — including that an
369    // elided return and a written `-> ()` are one thing.
370    let f = registry
371        .flat()
372        .function(&func)
373        .ok_or_else(|| ExpandError::UnknownConstructor(func.clone()))?;
374
375    let params: Vec<(syn::Ident, prebindgen_flat::flat::TypeRef)> = f
376        .params
377        .iter()
378        .map(|p| (p.name.clone(), p.ty.clone()))
379        .collect();
380    // The model already read this return; `fallible_parts` is that reading, not a
381    // second look at the spelling.
382    let (target, fallible) = match f.ret.fallible_parts() {
383        Some((ok, _)) => (ok.key(), true),
384        None => (f.ret.key(), false),
385    };
386    check_declared_target(func, &target, expected)?;
387    Ok(CtorSig { params, fallible })
388}
389
390struct CtorSig {
391    /// Readings, not spellings: they come off `Function::params`, and a consumer
392    /// that needs the spelling takes it at the point it stores one.
393    params: Vec<(syn::Ident, prebindgen_flat::flat::TypeRef)>,
394    fallible: bool,
395}
396
397/// Build the [`FoldPlan`] for a chosen construction. A single `Ctor` variant
398/// (no identity) is the plain/unconditional form (no selector); anything else is
399/// selector-dispatched — so a "single" constructor and a 1-variant combined emit
400/// identical code.
401#[allow(clippy::too_many_arguments)]
402fn build_plan<M>(
403    exp: &Expansions,
404    registry: &Registry<M>,
405    ed: &ExpandDecl,
406    optional: bool,
407    by_ref: bool,
408    target: &prebindgen_flat::flat::TypeRef,
409    variants: &[Variant],
410    visited: &mut HashSet<TypeKey>,
411) -> Result<FoldPlan, ExpandError> {
412    let param = &ed.param;
413    let mut leaves: Vec<FoldLeaf> = Vec::new();
414
415    // Optional (`Option<T>`/`Option<&T>`) param. No recursion under `Optional`.
416    //  * single single-arg ctor → one nullable leaf (`Option<arg>`) decides
417    //    presence.
418    //  * single multi-arg ctor  → an explicit leading `present: bool` flag +
419    //    one plain (non-`Option`) leaf per arg. The flag keeps nullable
420    //    primitive args (e.g. an `Option<i32>` id) from boxing on the wire.
421    //  * combined (≥2 variants) → the same selector dispatch as a non-optional
422    //    param, with the selector ALSO encoding absence: `-1` = `None`,
423    //    `0..n-1` = the taken arm (no separate present flag — not-taken arms'
424    //    leaves are null exactly as in the non-optional selector case).
425    if optional {
426        let [Variant::Ctor(func)] = variants else {
427            // Combined-selector dispatch under `Optional`.
428            visited.insert(target.key());
429            let prefix = param.to_string();
430            let (selector, fold_variants) = build_core(
431                exp,
432                registry,
433                ed,
434                target,
435                variants,
436                by_ref,
437                &prefix,
438                &mut leaves,
439                visited,
440            )?;
441            visited.remove(&target.key());
442            return Ok(FoldPlan {
443                target: target.clone(),
444                by_ref,
445                shape: FoldShape::Optional((), Box::new(FoldShape::Base)),
446                leaves,
447                selector,
448                present: None,
449                variants: fold_variants,
450            });
451        };
452        let sig = ctor_signature(registry, func, &target.key())?;
453        if sig.params.len() == 1 {
454            let (_pn, pty) = &sig.params[0];
455            leaves.push(FoldLeaf {
456                name: param.clone(),
457                ty: pty.optional(),
458            });
459            return Ok(FoldPlan {
460                target: target.clone(),
461                by_ref,
462                shape: FoldShape::Optional((), Box::new(FoldShape::Base)),
463                leaves,
464                selector: None,
465                present: None,
466                variants: vec![FoldVariant {
467                    ctor: Some(func.clone()),
468                    fallible: sig.fallible,
469                    clone: false,
470                    inputs: vec![FoldArg::Leaf(0, false)],
471                }],
472            });
473        }
474        // Multi-arg: presence flag (leaf 0) + one flat leaf per ctor arg.
475        leaves.push(FoldLeaf {
476            name: ident(&format!("{}_present", param)),
477            // A presence flag no source wrote — placeless by construction.
478            ty: prebindgen_flat::flat::TypeRef::scalar(prebindgen_flat::flat::ScalarKind::Bool),
479        });
480        let prefix = param.to_string();
481        let mut inputs = Vec::new();
482        for (pname, pty) in &sig.params {
483            let name = ident(&format!("{}_{}", prefix, pname));
484            let arg = build_arg(
485                exp,
486                registry,
487                ed,
488                pty,
489                name,
490                /*dispatched=*/ false,
491                &mut leaves,
492                visited,
493            )?;
494            if matches!(arg, FoldArg::Build(_)) {
495                return Err(ExpandError::UnsupportedOptional {
496                    func: ed.func.clone(),
497                    param: ed.param.clone(),
498                    reason: "nested-buildable constructor arguments cannot be optional",
499                });
500            }
501            inputs.push(arg);
502        }
503        return Ok(FoldPlan {
504            target: target.clone(),
505            by_ref,
506            shape: FoldShape::Optional((), Box::new(FoldShape::Base)),
507            leaves,
508            selector: None,
509            present: Some(0),
510            variants: vec![FoldVariant {
511                ctor: Some(func.clone()),
512                fallible: sig.fallible,
513                clone: false,
514                inputs,
515            }],
516        });
517    }
518
519    // Non-optional: build the (possibly recursive) construct core. The target is
520    // on the cycle chain so a constructor parameter of the same type is rejected.
521    visited.insert(target.key());
522    let prefix = param.to_string();
523    let (selector, fold_variants) = build_core(
524        exp,
525        registry,
526        ed,
527        target,
528        variants,
529        by_ref,
530        &prefix,
531        &mut leaves,
532        visited,
533    )?;
534    visited.remove(&target.key());
535    Ok(FoldPlan {
536        target: target.clone(),
537        by_ref,
538        shape: FoldShape::Base,
539        leaves,
540        selector,
541        present: None,
542        variants: fold_variants,
543    })
544}
545
546/// Build a construct core (selector + dispatch arms) for `target` from its
547/// `variants`, appending wire leaves to `leaves`. Recursive: a constructor
548/// parameter whose type has its OWN default constructor is built as a nested
549/// [`FoldArg::Build`] (recursive input). Used by both the top-level [`build_plan`]
550/// and each nested build. `prefix` disambiguates leaf names across the tree.
551#[allow(clippy::too_many_arguments)]
552fn build_core<M>(
553    exp: &Expansions,
554    registry: &Registry<M>,
555    ed: &ExpandDecl,
556    target: &prebindgen_flat::flat::TypeRef,
557    variants: &[Variant],
558    by_ref: bool,
559    prefix: &str,
560    leaves: &mut Vec<FoldLeaf>,
561    visited: &mut HashSet<TypeKey>,
562) -> Result<(Option<usize>, Vec<FoldVariant>), ExpandError> {
563    if let [Variant::Ctor(func)] = variants {
564        // Single constructor — no selector; args passed directly (not Option-wrapped).
565        let sig = ctor_signature(registry, func, &target.key())?;
566        let np = sig.params.len();
567        let mut args = Vec::new();
568        for (pname, pty) in &sig.params {
569            let name = if np == 1 {
570                ident(prefix)
571            } else {
572                ident(&format!("{}_{}", prefix, pname))
573            };
574            args.push(build_arg(
575                exp, registry, ed, pty, name, false, leaves, visited,
576            )?);
577        }
578        Ok((
579            None,
580            vec![FoldVariant {
581                ctor: Some(func.clone()),
582                fallible: sig.fallible,
583                clone: false,
584                inputs: args,
585            }],
586        ))
587    } else {
588        // Combined — selector leaf, then `Option`-wrapped per-arm inputs.
589        let sel_idx = leaves.len();
590        leaves.push(FoldLeaf {
591            name: ident(&format!("{}_sel", prefix)),
592            // The selector, likewise composed and placeless.
593            ty: prebindgen_flat::flat::TypeRef::scalar(prebindgen_flat::flat::ScalarKind::I32),
594        });
595        let mut fold_variants: Vec<FoldVariant> = Vec::new();
596        for (vi, v) in variants.iter().enumerate() {
597            match v {
598                Variant::Ctor(func) => {
599                    let sig = ctor_signature(registry, func, &target.key())?;
600                    let np = sig.params.len();
601                    let mut args = Vec::new();
602                    for (pi, (_pname, pty)) in sig.params.iter().enumerate() {
603                        let name = if np == 1 {
604                            ident(&format!("{}_{}", prefix, vi))
605                        } else {
606                            ident(&format!("{}_{}_{}", prefix, vi, pi))
607                        };
608                        // `dispatched = true`: a combined arm's leaves are
609                        // `Option`-wrapped (selector presence). Recursive nesting
610                        // under a combined arm is rejected by `build_arg`.
611                        args.push(build_arg(
612                            exp, registry, ed, pty, name, true, leaves, visited,
613                        )?);
614                    }
615                    fold_variants.push(FoldVariant {
616                        ctor: Some(func.clone()),
617                        fallible: sig.fallible,
618                        clone: false,
619                        inputs: args,
620                    });
621                }
622                Variant::Identity => {
623                    let idx = leaves.len();
624                    let leaf_ty = if by_ref {
625                        target.borrowed().optional()
626                    } else {
627                        target.optional()
628                    };
629                    leaves.push(FoldLeaf {
630                        name: ident(&format!("{}_{}", prefix, vi)),
631                        ty: leaf_ty,
632                    });
633                    fold_variants.push(FoldVariant {
634                        ctor: None,
635                        fallible: false,
636                        clone: by_ref,
637                        inputs: vec![FoldArg::Leaf(idx, false)],
638                    });
639                }
640            }
641        }
642        Ok((Some(sel_idx), fold_variants))
643    }
644}
645
646/// Build one constructor-parameter input. If the parameter's (peeled) type has
647/// its own default constructor, recurse into a nested [`FoldArg::Build`]
648/// (recursive input); otherwise it is a flat wire [`FoldArg::Leaf`].
649#[allow(clippy::too_many_arguments)]
650fn build_arg<M>(
651    exp: &Expansions,
652    registry: &Registry<M>,
653    ed: &ExpandDecl,
654    pty: &prebindgen_flat::flat::TypeRef,
655    name: syn::Ident,
656    dispatched: bool,
657    leaves: &mut Vec<FoldLeaf>,
658    visited: &mut HashSet<TypeKey>,
659) -> Result<FoldArg, ExpandError> {
660    // The boundary layers down to the parameter's core type.
661    let (popt, pby_ref, bare) = constructed_value_layers(pty);
662    let key = bare.key();
663    // A default constructor for the parameter's type ⇒ recursive nested build.
664    let canon = exp
665        .constructors
666        .iter()
667        .find(|c| c.target == key && !c.variants.is_empty());
668    if let Some(c) = canon {
669        if dispatched {
670            return Err(ExpandError::UnsupportedRecursive {
671                func: ed.func.clone(),
672                reason: "recursive input under a selector-dispatched constructor variant",
673            });
674        }
675        if popt {
676            return Err(ExpandError::UnsupportedRecursive {
677                func: ed.func.clone(),
678                reason: "recursive input on an Option<…> parameter",
679            });
680        }
681        if !visited.insert(key.clone()) {
682            return Err(ExpandError::InputCycle {
683                ty: key.to_string(),
684            });
685        }
686        let variants = c.variants.clone();
687        let (selector, vars) = build_core(
688            exp,
689            registry,
690            ed,
691            &bare,
692            &variants,
693            pby_ref,
694            &name.to_string(),
695            leaves,
696            visited,
697        )?;
698        visited.remove(&key);
699        Ok(FoldArg::Build(Box::new(FoldBuild {
700            target: bare.clone(),
701            by_ref: pby_ref,
702            selector,
703            variants: vars,
704        })))
705    } else {
706        let idx = leaves.len();
707        // A dispatched (selector-presence) arm `Option`-wraps its leaves — but
708        // an argument that is itself `Option<…>` passes through with its own
709        // type: `None` is a legitimate value for the taken arm, and the wire
710        // cannot represent the double `Option` anyway. Marked `passthrough` so
711        // the emit side skips the selector-presence unwrap.
712        let passthrough = dispatched && popt;
713        leaves.push(FoldLeaf {
714            name,
715            ty: if dispatched && !passthrough {
716                pty.optional()
717            } else {
718                pty.clone()
719            },
720        });
721        Ok(FoldArg::Leaf(idx, passthrough))
722    }
723}
724
725/// The shared mismatch, in this direction's vocabulary: an input constructor is
726/// declared to **produce** the parameter's target.
727impl From<crate::declared_target::TargetMismatch> for ExpandError {
728    fn from(m: crate::declared_target::TargetMismatch) -> Self {
729        ExpandError::TargetMismatch {
730            ctor: m.func,
731            produces: m.actual,
732            expected: m.expected,
733        }
734    }
735}
736
737// ──────────────────────────────────────────────────────────────────────
738// emit_fold
739// ──────────────────────────────────────────────────────────────────────
740
741/// Emit the fold expression for an expanded parameter. `leaf_locals` are the
742/// already-decoded Rust locals (1:1 with `plan.leaves`); `qualify` maps a
743/// constructor ident to its call path (e.g. prefixing the source module).
744///
745/// The returned expression has type `Result<<shaped> plan.target, String>`
746/// (`Result<Target>`, `Result<Option<Target>>`, …). The adapter routes its
747/// `Err(String)` through its own error channel. Folds the [`FoldShape`] layers
748/// top-down over one shared core construct — the value
749/// analog of how `Option<_>`/`Vec<_>` wrappers compose at the wire.
750pub fn emit_fold(
751    plan: &FoldPlan,
752    leaf_locals: &[syn::Ident],
753    qualify: &dyn Fn(&syn::Ident) -> syn::Path,
754) -> syn::Expr {
755    fold_shape(&plan.shape, plan, leaf_locals, None, qualify)
756}
757
758/// Recurse over one [`FoldShape`] layer. `bound` is `Some(var)` when an
759/// enclosing `Optional`/`Iterable` layer has unwrapped the structured leaf and
760/// bound its element to `var` — the inner construct then builds from `var`
761/// instead of reading `leaf_locals`.
762fn fold_shape(
763    shape: &FoldShape,
764    plan: &FoldPlan,
765    leaf_locals: &[syn::Ident],
766    bound: Option<&syn::Ident>,
767    qualify: &dyn Fn(&syn::Ident) -> syn::Path,
768) -> syn::Expr {
769    match shape {
770        FoldShape::Base => emit_core_construct(plan, leaf_locals, bound, qualify),
771        FoldShape::Optional((), inner) => {
772            if let Some(sidx) = plan.selector {
773                // Combined-selector dispatch under `Optional`: the selector
774                // ALSO encodes absence — `-1` = `None`, `0..n-1` = the taken
775                // arm (dispatched by the shared construct core; an out-of-range
776                // selector still hits its `Err` default arm).
777                let sel_local = &leaf_locals[sidx];
778                let inner_expr = emit_core_construct(plan, leaf_locals, None, qualify);
779                syn::parse_quote!(if #sel_local < 0 {
780                    ::core::result::Result::Ok(::core::option::Option::None)
781                } else {
782                    (#inner_expr).map(::core::option::Option::Some)
783                })
784            } else if let Some(pidx) = plan.present {
785                // Multi-arg: an explicit `present: bool` flag decides presence;
786                // the construct reads its plain arg leaves directly (`bound =
787                // None`), the flag leaf is consumed only by this `if`.
788                let present_local = &leaf_locals[pidx];
789                let inner_expr = emit_core_construct(plan, leaf_locals, None, qualify);
790                syn::parse_quote!(if #present_local {
791                    (#inner_expr).map(::core::option::Option::Some)
792                } else {
793                    ::core::result::Result::Ok(::core::option::Option::None)
794                })
795            } else {
796                // Single-arg: presence rides the sole shaped leaf's `Option`.
797                // The structured value is the enclosing bound var, or — at the
798                // top — that leaf's decoded local (`leaf_locals[0]`).
799                let value = bound.unwrap_or(&leaf_locals[0]);
800                let inner_ident = ident("__inner");
801                let inner_expr = fold_shape(inner, plan, leaf_locals, Some(&inner_ident), qualify);
802                syn::parse_quote!(match #value {
803                    ::core::option::Option::Some(#inner_ident) => {
804                        (#inner_expr).map(::core::option::Option::Some)
805                    }
806                    ::core::option::Option::None => {
807                        ::core::result::Result::Ok(::core::option::Option::None)
808                    }
809                })
810            }
811        }
812        FoldShape::Iterable(inner) => {
813            let value = bound.unwrap_or(&leaf_locals[0]);
814            let elem_ident = ident("__elem");
815            let inner_expr = fold_shape(inner, plan, leaf_locals, Some(&elem_ident), qualify);
816            syn::parse_quote!(
817                #value
818                    .into_iter()
819                    .map(|#elem_ident| #inner_expr)
820                    .collect::<::core::result::Result<::std::vec::Vec<_>, _>>()
821            )
822        }
823    }
824}
825
826/// Emit the innermost construct → `Result<Target, String>`. With `bound =
827/// Some(v)` (under an `Optional`/`Iterable` layer ⇒ single, single-arg ctor)
828/// the ctor is applied to `v`; with `bound = None` (top level) it reads the
829/// leaves — a single constructor (any arity) or a combined-selector dispatch.
830fn emit_core_construct(
831    plan: &FoldPlan,
832    leaf_locals: &[syn::Ident],
833    bound: Option<&syn::Ident>,
834    qualify: &dyn Fn(&syn::Ident) -> syn::Path,
835) -> syn::Expr {
836    if let Some(v) = bound {
837        // Shaped construct: a single, single-arg constructor applied to the
838        // unwrapped element. (`apply` guarantees this shape — never identity,
839        // never combined, never multi-arg under a shape layer.)
840        let var = &plan.variants[0];
841        let func = var
842            .ctor
843            .as_ref()
844            .expect("shaped expansion is single-constructor (never identity)");
845        return ctor_call_result(&qualify(func), std::slice::from_ref(v), var.fallible);
846    }
847    emit_dispatch(plan.selector, &plan.variants, leaf_locals, qualify)
848}
849
850/// Emit a construct dispatch → `Result<Target, String>`: a single variant
851/// applied directly (no selector), or a `match` over the selector leaf. Shared
852/// by the top-level [`emit_core_construct`] and each nested [`emit_build`].
853fn emit_dispatch(
854    selector: Option<usize>,
855    variants: &[FoldVariant],
856    leaf_locals: &[syn::Ident],
857    qualify: &dyn Fn(&syn::Ident) -> syn::Path,
858) -> syn::Expr {
859    match selector {
860        None => variant_result_expr(
861            &variants[0],
862            leaf_locals,
863            qualify,
864            /*dispatched=*/ false,
865        ),
866        Some(si) => {
867            let sel = &leaf_locals[si];
868            let arms: Vec<TokenStream> = variants
869                .iter()
870                .enumerate()
871                .map(|(vi, v)| {
872                    let lit = vi as i32;
873                    let body =
874                        variant_result_expr(v, leaf_locals, qualify, /*dispatched=*/ true);
875                    quote!(#lit => #body,)
876                })
877                .collect();
878            syn::parse_quote!({
879                match #sel {
880                    #(#arms)*
881                    __sel => ::core::result::Result::Err(::std::format!(
882                        "invalid constructor selector: {}",
883                        __sel
884                    )),
885                }
886            })
887        }
888    }
889}
890
891/// Emit a nested recursive-input build → `Result<SubTarget, String>` (the dual
892/// of [`emit_core_construct`] for a [`FoldArg::Build`] parameter).
893fn emit_build(
894    b: &FoldBuild,
895    leaf_locals: &[syn::Ident],
896    qualify: &dyn Fn(&syn::Ident) -> syn::Path,
897) -> syn::Expr {
898    emit_dispatch(b.selector, &b.variants, leaf_locals, qualify)
899}
900
901/// Build a `Result<Target, String>` expression for one core variant. When
902/// `dispatched` (a combined-selector arm), the variant's input leaves are
903/// `Option<_>` — only the selected arm's inputs are present — so they are
904/// unwrapped (a missing input yields `Err`); otherwise they are passed
905/// directly. (This `Option`-ness is *selector presence*, distinct from
906/// [`FoldShape::Optional`], which is whole-param presence handled by the
907/// enclosing fold.)
908fn variant_result_expr(
909    v: &FoldVariant,
910    leaf_locals: &[syn::Ident],
911    qualify: &dyn Fn(&syn::Ident) -> syn::Path,
912    dispatched: bool,
913) -> syn::Expr {
914    // A Leaf arg's decoded local. Identity arms and combined-dispatched arms are
915    // Leaf-only (recursive `Build` args appear only in a non-dispatched single
916    // constructor — `build_arg` rejects nesting under a dispatched variant).
917    let leaf = |a: &FoldArg| -> &syn::Ident {
918        match a {
919            FoldArg::Leaf(i, _) => &leaf_locals[*i],
920            FoldArg::Build(_) => {
921                unreachable!("recursive Build arg only in a non-dispatched single constructor")
922            }
923        }
924    };
925
926    match &v.ctor {
927        None => {
928            // Identity: the sole input is the target value (or a borrow of it
929            // that we clone, for `&T` consumers — preserving the caller's handle).
930            let loc = leaf(&v.inputs[0]);
931            // `&*__v` derefs through whatever the borrow leaf decoded to (a
932            // plain `&T`, or an adapter smart-pointer like jnigen's
933            // `OwnedObject<T>`) down to `T`, then clones — keeping the caller's
934            // handle alive without the core knowing the adapter's borrow type.
935            let some_val: syn::Expr = if v.clone {
936                syn::parse_quote!(::core::result::Result::Ok(::core::clone::Clone::clone(
937                    &*__v
938                )))
939            } else {
940                syn::parse_quote!(::core::result::Result::Ok(__v))
941            };
942            if dispatched {
943                syn::parse_quote!(match #loc {
944                    ::core::option::Option::Some(__v) => #some_val,
945                    ::core::option::Option::None => ::core::result::Result::Err(
946                        ::std::string::String::from("identity variant value missing")
947                    ),
948                })
949            } else if v.clone {
950                syn::parse_quote!(::core::result::Result::Ok(::core::clone::Clone::clone(&*#loc)))
951            } else {
952                syn::parse_quote!(::core::result::Result::Ok(#loc))
953            }
954        }
955        Some(func) => {
956            let path = qualify(func);
957            if dispatched {
958                // Combined arm — Leaf-only inputs. Selector-presence-wrapped
959                // inputs are unwrapped (missing ⇒ `Err`); **passthrough**
960                // inputs (constructor args that are themselves `Option<…>`)
961                // pass their decoded local directly — `None` is a legitimate
962                // value for the taken arm.
963                let mut wrapped_locals: Vec<&syn::Ident> = Vec::new();
964                let mut wrapped_binds: Vec<syn::Ident> = Vec::new();
965                let mut call_args: Vec<syn::Expr> = Vec::new();
966                for (i, a) in v.inputs.iter().enumerate() {
967                    let loc = leaf(a);
968                    if matches!(a, FoldArg::Leaf(_, true)) {
969                        call_args.push(syn::parse_quote!(#loc));
970                    } else {
971                        let b = ident(&format!("__p{}", i));
972                        wrapped_locals.push(loc);
973                        wrapped_binds.push(b.clone());
974                        call_args.push(syn::parse_quote!(#b));
975                    }
976                }
977                let call = ctor_call_result(&path, &call_args, v.fallible);
978                let missing = quote!(::core::result::Result::Err(::std::string::String::from(
979                    "constructor variant input missing"
980                )));
981                match wrapped_locals.len() {
982                    // All-passthrough arm: the selector alone decides; call directly.
983                    0 => call,
984                    1 => {
985                        // `match a { Some(p0) => <call>, None => Err }`
986                        let loc = wrapped_locals[0];
987                        let p0 = &wrapped_binds[0];
988                        syn::parse_quote!(match #loc {
989                            ::core::option::Option::Some(#p0) => #call,
990                            ::core::option::Option::None => #missing,
991                        })
992                    }
993                    _ => {
994                        // `match (a, b, …) { (Some(p0), Some(p1), …) => <call>, _ => Err }`
995                        let some_pats: Vec<TokenStream> = wrapped_binds
996                            .iter()
997                            .map(|b| quote!(::core::option::Option::Some(#b)))
998                            .collect();
999                        syn::parse_quote!(match ( #(#wrapped_locals),* ) {
1000                            ( #(#some_pats),* ) => #call,
1001                            _ => #missing,
1002                        })
1003                    }
1004                }
1005            } else if v.inputs.iter().all(|a| matches!(a, FoldArg::Leaf(..))) {
1006                // Non-dispatched, flat (no recursion): call directly — identical
1007                // to the pre-recursion form.
1008                let args: Vec<&syn::Ident> = v.inputs.iter().map(&leaf).collect();
1009                ctor_call_result(&path, &args, v.fallible)
1010            } else {
1011                // Non-dispatched with ≥1 recursive `Build` arg: bind each arg
1012                // (Leaf = the decoded value; Build = the nested construct,
1013                // `?`-unwrapped) in an IIFE that provides the `Result` context.
1014                let mut stmts: Vec<TokenStream> = Vec::new();
1015                let mut args: Vec<TokenStream> = Vec::new();
1016                for (i, a) in v.inputs.iter().enumerate() {
1017                    let ai = ident(&format!("__a{}", i));
1018                    match a {
1019                        FoldArg::Leaf(li, _) => {
1020                            let loc = &leaf_locals[*li];
1021                            stmts.push(quote!(let #ai = #loc;));
1022                            args.push(quote!(#ai));
1023                        }
1024                        FoldArg::Build(b) => {
1025                            // Pin the nested build's error type to `String` so a
1026                            // non-fallible inner ctor's bare `Ok(..)` infers `E`.
1027                            let be = emit_build(b, leaf_locals, qualify);
1028                            stmts.push(quote!(
1029                                let #ai = {
1030                                    let __r: ::core::result::Result<_, ::std::string::String> = #be;
1031                                    __r?
1032                                };
1033                            ));
1034                            if b.by_ref {
1035                                args.push(quote!(&#ai));
1036                            } else {
1037                                args.push(quote!(#ai));
1038                            }
1039                        }
1040                    }
1041                }
1042                let call = ctor_call_result(&path, &args, v.fallible);
1043                syn::parse_quote!({
1044                    (|| -> ::core::result::Result<_, ::std::string::String> {
1045                        #(#stmts)*
1046                        #call
1047                    })()
1048                })
1049            }
1050        }
1051    }
1052}
1053
1054/// `path(args…)` lifted to `Result<Target, String>` (mapping a fallible
1055/// constructor's error via `Display`).
1056fn ctor_call_result<I: quote::ToTokens>(path: &syn::Path, args: &[I], fallible: bool) -> syn::Expr {
1057    if fallible {
1058        syn::parse_quote!(#path( #(#args),* ).map_err(|__e| ::std::format!("{}", __e)))
1059    } else {
1060        syn::parse_quote!(::core::result::Result::Ok(#path( #(#args),* )))
1061    }
1062}
1063
1064// ──────────────────────────────────────────────────────────────────────
1065// Small helpers
1066// ──────────────────────────────────────────────────────────────────────
1067
1068/// The value a constructor builds: `Option` off, then the borrow, and **nothing
1069/// else** — read off the model's classification rather than by taking the
1070/// spelling apart.
1071///
1072/// `Option<&T>`, `&T` and `T` all answer `T`, which is what every caller here
1073/// wants: they are matching a declared target, and a declaration names the type,
1074/// not the way a particular parameter happens to wrap it.
1075///
1076/// **`Vec<T>` answers `Vec<T>`, deliberately.** Expansion builds one value —
1077/// `FoldPlan`'s shape is `Base` or `Optional(Base)`, with no iterable arm — so
1078/// peeling a `Sequence` here would let a `Vec<T>` parameter match a `T`
1079/// constructor and emit a wrapper that reconstructs a single `T` and hands it to
1080/// a parameter expecting the collection. Leaving the `Sequence` on the core is
1081/// what makes that a non-match instead of a miscompile, and it is the reason this
1082/// is not [`TypeRef::layers`], which peels all three.
1083///
1084/// A type the grammar cannot express answers itself — the identity, not a
1085/// fallback classifier. Nothing reaching here can be one: every signature in play
1086/// was accepted by the frontend before the scan registered it.
1087fn constructed_value(reading: &prebindgen_flat::flat::TypeRef) -> &prebindgen_flat::flat::TypeRef {
1088    let after_opt = reading.optional_inner().unwrap_or(reading);
1089    after_opt.borrow_target().unwrap_or(after_opt)
1090}
1091
1092/// [`constructed_value`], plus which of the two layers were there.
1093fn constructed_value_layers(
1094    reading: &prebindgen_flat::flat::TypeRef,
1095) -> (bool, bool, prebindgen_flat::flat::TypeRef) {
1096    let optional = reading.optional_inner().is_some();
1097    let after_opt = reading.optional_inner().unwrap_or(reading);
1098    let by_ref = after_opt.borrow_target().is_some();
1099    let core = after_opt.borrow_target().unwrap_or(after_opt);
1100    // The core READING, not its spelling: the plan composes `Option<&T>` over
1101    // it, and composing from a reading keeps the kind and the syntax paired.
1102    (optional, by_ref, core.clone())
1103}
1104
1105// `opt` lived here — `parse_quote!(Option<#ty>)` — and built a spelling with no
1106// classification beside it, so every consumer had to hand it back to the
1107// registry to learn it was an optional. `TypeRef::optional` composes both at
1108// once (#275).
1109
1110#[cfg(test)]
1111mod tests;