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(¶m_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(¶m_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;