prebindgen_registry/unfold.rs
1//! Output (data) expansion — the dual of constructor expansion
2//! (`api/core/expand.rs`). A function returning a rich type is *decomposed* by a
3//! **deconstructor** into a set of leaf values.
4//!
5//! A **deconstructor** (a type-level `expand_return!` `.field*` list,
6//! or the per-fn `.expand_return` override) is a
7//! **deterministic product**: every record always runs and contributes its leaf
8//! — there is no selector (unlike a *constructor*, whose selector picks one
9//! variant). A record's accessor is a `#[prebindgen]` function `f(&T) -> &F` (a
10//! reference return where possible, for zero-copy); an accessor whose return
11//! type has its own deconstructor splices the child's records with prefixed
12//! leaf names.
13//!
14//! Two **deliveries** (see [`Delivery`]), derived from the resolved leaf count:
15//! * `Callback` — replaces the return with a foreign **callback** receiving
16//! all the leaves (any leaf count).
17//! * `Return` — **returns** the single leaf value directly (no callback);
18//! requires a single-leaf decomposition.
19//!
20//! Resolution is language-agnostic: it turns the declarations into
21//! [`UnfoldPlan`]s (stored on the registry, keyed by function ident) and
22//! registers every leaf's `out_ty` as a required **output** so the resolver
23//! produces its converter (and projection). The jnigen adapter reads the
24//! plan at the return-emission site.
25//!
26//! [`Iterable`]: UnfoldShape::Iterable
27
28use std::collections::HashSet;
29
30use crate::{
31 declared_target::check_declared_target,
32 registry::{Registry, TypeKey},
33};
34
35mod error;
36mod plan;
37
38pub use self::{
39 error::{UnfoldDeclError, UnfoldError},
40 plan::{
41 steps_are_movable, DeconId, DeconSpec, Hoist, LeafSource, PathStep, UnfoldLeaf, UnfoldPlan,
42 UnfoldShape,
43 },
44};
45
46// ──────────────────────────────────────────────────────────────────────
47// Declarations (populated by the language builder)
48// ──────────────────────────────────────────────────────────────────────
49
50/// One record (field) of a deconstructor. A deconstructor is a product: every
51/// record contributes a leaf.
52// large_enum_variant: a handful of records exist per binding — boxing the
53// syn payloads would only complicate the arms (same trade-off as
54// `ConvertSourceKind`).
55#[allow(clippy::large_enum_variant)]
56#[derive(Clone)]
57pub enum DeconRecord {
58 /// Read this field by calling the accessor function `f(&T) -> &F`. `name`
59 /// is the author-supplied leaf name, used **literally** (no casing /
60 /// stripping); it may not contain the reserved `"__"` chain separator.
61 /// An accessor whose return type has its own deconstructor splices that
62 /// child's records with the leaf names prefixed `name__<child>`.
63 Acc { func: syn::Ident, name: String },
64 /// Read this field by calling a **custom, locally-defined** accessor: any
65 /// callable in the binding crate (`path`) with a STATED return type
66 /// (`ty`) — there is no `#[prebindgen]` item behind it, so the signature
67 /// cannot be looked up. The adapter's `local_functions()` pre-pass
68 /// synthesizes a registry entry from the stated signature (so call
69 /// qualification and `Option`-nesting checks work unchanged); splicing
70 /// follows [`Self::Acc`] rules except that a self-referential field (its
71 /// type already being decomposed) degrades to a plain converter leaf
72 /// instead of a cycle error — that is what lets such a field re-deliver
73 /// (part of) the value itself, e.g. under a binding-defined condition.
74 LocalAcc { path: syn::Path, name: String },
75 /// The value itself — the handle/identity leaf (cloned for a `&T` return,
76 /// moved for an owned `T`). At most one per
77 /// deconstructor.
78 Identity,
79 /// Read the fields of the type's **value form**: call `func` once
80 /// (`f(&T) -> TStruct`) and contribute one record per [`FieldRecord`],
81 /// reached by field access on the returned struct. The language adapter
82 /// builds the field list (it knows which structs are declared classes and
83 /// therefore inline); this record only says how to get there.
84 ///
85 /// Each field then decomposes exactly like an [`Acc`](Self::Acc) record's
86 /// return does — its own `records` if the
87 /// declaration overrode it, else its type's own deconstructor if it has
88 /// one, else one leaf — so a value form and a hand-written field list
89 /// produce the same leaves.
90 Fields {
91 func: syn::Ident,
92 /// The accessor **consumes** its receiver (`f(T) -> TStruct`): the
93 /// value is moved in and each field moved *out* into its leaf, instead
94 /// of being cloned out of a borrow. Declared by the adapter rather than
95 /// read off the signature — giving the value away is a boundary
96 /// decision — and cross-checked against the signature when the records
97 /// are flattened, so the two cannot drift.
98 consuming: bool,
99 fields: Vec<FieldRecord>,
100 },
101}
102
103/// One field of a value form (see [`DeconRecord::Fields`]).
104#[derive(Clone)]
105pub struct FieldRecord {
106 /// Field-access chain from the value form's returned struct. More than one
107 /// element when the adapter inlined a nested declared class.
108 pub members: Vec<syn::Ident>,
109 /// The leaf name (already `__`-joined across inlined nesting).
110 pub name: String,
111 /// The field's **reading**, `Option` / `Vec` layers included — and its
112 /// syntax with it, which is what a leaf's `out_ty` spells.
113 ///
114 /// The declaration carries this rather than naming a `syn::Type` for the
115 /// walk to look up, because there was nothing to look up: a field record is
116 /// built from an element whose every field already has a reading, and the
117 /// types it names are the ones the caller registers *after* the walk
118 /// returns. Asking the registry here was asking before registration
119 /// (#266) — a lookup that could only miss, answered by a second source of
120 /// readings that hid the ordering.
121 pub ty: prebindgen_flat::flat::TypeRef,
122 /// How this field decomposes.
123 pub decon: FieldDecon,
124}
125
126/// How one [`FieldRecord`] decomposes.
127#[derive(Clone)]
128pub enum FieldDecon {
129 /// By the field type's own deconstructor if it has one, else one leaf —
130 /// the same default a [`DeconRecord::Acc`] record's return follows.
131 Default,
132 /// Explicit records, replacing the type default wholesale (the declaration
133 /// stated this field's complete leaf set).
134 Records(Vec<DeconRecord>),
135 /// Leaves the **adapter** built, appended with this field's path and name
136 /// prefixed onto each. For shapes whose leaf structure only the adapter
137 /// knows — a decomposed sum, which is a selector plus one group per
138 /// alternative rather than a product of records.
139 Leaves(Vec<UnfoldLeaf>),
140}
141
142impl DeconRecord {
143 /// The fn ident of a [`Self::LocalAcc`] — its path's last segment (the
144 /// name the emitted call resolves to under the path-prefix origin).
145 fn local_ident(path: &syn::Path) -> syn::Ident {
146 path.segments
147 .last()
148 .expect("field!(...).with(...): empty accessor path")
149 .ident
150 .clone()
151 }
152}
153
154/// A type-level deconstructor declaration (`expand_return!(T).field*`): the
155/// complete, ordered record list decomposing `target`. An immutable record —
156/// the leaf order is the declaration order of the `records` vector.
157#[derive(Clone)]
158pub struct DeconstructorDecl {
159 /// The type being decomposed, as an **identity** — see
160 /// [`ConstructorDecl::target`](crate::expand::ConstructorDecl::target).
161 pub target: TypeKey,
162 pub records: Vec<DeconRecord>,
163 /// Auto-apply this deconstructor to every matching declared fn (`Some`
164 /// carries the inferred `(target-position, delivery)` to use). Always
165 /// `Some` for type-level default (`expand_return!`) declarations.
166 pub default: Option<(DeconTarget, Delivery)>,
167}
168
169/// How an output expansion chooses the deconstructor for a function's return
170/// type: the type's default (`expand_return!`-declared) or a per-fn
171/// inline record list (`.expand_return`).
172#[derive(Clone)]
173pub enum DeconSel {
174 /// Use the return type's unique deconstructor (error if ambiguous).
175 TopLevel,
176 /// Per-fn override (`.expand_return`): use exactly these
177 /// accessor-fn records.
178 Inline(Vec<DeconRecord>),
179}
180
181/// Which value of a function the deconstructor decomposes: its success return
182/// (`Output`) or its `Result<_, E>` domain error (`Error`).
183#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
184pub enum DeconTarget {
185 Output,
186 Error,
187}
188
189/// How the decomposed value(s) are delivered to the foreign side. Derived
190/// from the resolved leaf count (1 ⇒ `Return`, N ⇒ `Callback`); errors are
191/// always `Callback`-shaped.
192#[derive(Clone, Copy, PartialEq, Eq, Debug)]
193pub enum Delivery {
194 /// Deliver the leaves to a foreign **callback** (builder / fold). Any
195 /// leaf count.
196 Callback,
197 /// **Return**/deliver the single decomposed value (no builder). Requires
198 /// exactly one leaf and a non-`Iterable` shape.
199 Return,
200}
201
202/// A per-fn output expansion (`.expand_return(expand_return!(T)…)`) —
203/// decompose `func`'s return (or error position) via the record list.
204/// Recorded as an explicit decl so the auto-`default` skips it; an
205/// identity-only record list lowers to the raw whole-value return at
206/// resolution.
207#[derive(Clone)]
208pub struct OutputDecl {
209 pub func: syn::Ident,
210 pub sel: DeconSel,
211 pub target: DeconTarget,
212 pub delivery: Delivery,
213 /// The type the per-fn decl was declared for (`expand_return!(T)`) —
214 /// cross-checked against the fn's peeled return type at resolution.
215 /// `None` for internally-synthesized decls (the type comes from the
216 /// return itself).
217 pub declared_source: Option<TypeKey>,
218}
219
220/// Deconstructor / output-expansion declarations gathered from a language
221/// builder — an immutable record set: complete values, no build protocol.
222/// Declaration order is the vector order; leaf order is each record
223/// vector's order. Handed to the registry as
224/// [`Decompositions::deconstructors`](crate::Decompositions::deconstructors);
225/// empty or duplicate declarations are diagnosed at resolution (collected),
226/// not at construction.
227#[derive(Clone, Default)]
228pub struct Deconstructors {
229 pub deconstructors: Vec<DeconstructorDecl>,
230 pub outputs: Vec<OutputDecl>,
231 /// Identity-only per-fn field-set opt-outs: fns excluded from the
232 /// default auto-apply (the raw whole-value return).
233 pub skip_output: std::collections::HashSet<syn::Ident>,
234}
235
236// ──────────────────────────────────────────────────────────────────────
237// apply
238// ──────────────────────────────────────────────────────────────────────
239
240/// Structural validation of the declaration records — duplicate targets,
241/// collected (EVERY offender before failing) so a build surfaces all
242/// declaration problems at once. Empty record lists are NOT diagnosed:
243/// an empty inline list is the valid whole-element delivery form.
244fn validate_declarations(acc: &Deconstructors) -> Result<(), UnfoldError> {
245 let mut entries: Vec<UnfoldDeclError> = Vec::new();
246 let mut decon_targets: std::collections::HashSet<String> = std::collections::HashSet::new();
247 for d in &acc.deconstructors {
248 let target = d.target.as_str().to_string();
249 if !decon_targets.insert(target.clone()) {
250 entries.push(UnfoldDeclError::DuplicateDeconstructor { target });
251 }
252 }
253 let mut output_keys: std::collections::HashSet<(String, DeconTarget)> =
254 std::collections::HashSet::new();
255 for od in &acc.outputs {
256 if !output_keys.insert((od.func.to_string(), od.target)) {
257 entries.push(UnfoldDeclError::DuplicateOutput {
258 func: od.func.clone(),
259 target: od.target,
260 });
261 }
262 }
263 if entries.is_empty() {
264 Ok(())
265 } else {
266 Err(UnfoldError::InvalidDeclarations { entries })
267 }
268}
269
270/// Resolve every output-expansion declaration (explicit + `.default()`
271/// auto-applied) into an [`UnfoldPlan`], register each leaf's `out_ty` as a
272/// required output, and store the plans on the registry (`unfold_plans` for
273/// `Output`, `error_plans` for `Error`).
274///
275/// `declared_fns` is the adapter's claimed `#[prebindgen]` fn set — the domain
276/// over which `.default()` deconstructors are auto-applied. `accessor_fns` is
277/// the `.fun_accessor` subset — the only functions a decomposer record may
278/// reference.
279///
280/// Runs inside `write_rust` after `expand::apply` and before `resolve`, so leaf
281/// converters resolve through the normal rank machinery.
282pub(crate) fn apply<M>(
283 registry: &mut Registry<M>,
284 acc: &Deconstructors,
285 declared_fns: &std::collections::HashSet<syn::Ident>,
286 accessor_fns: &std::collections::HashSet<syn::Ident>,
287) -> Result<(), UnfoldError> {
288 validate_declarations(acc)?;
289 // Binding-local accessors (`LocalAcc` records) resolve through registry
290 // entries synthesized by the adapter's `local_functions()` pre-pass in
291 // the builder's scan — by this point they read exactly like
292 // `#[prebindgen]` accessors.
293
294 // Gate: every accessor-function record of every declared deconstructor must
295 // be a `.fun_accessor` (the single source of truth for "accessor").
296 // Binding-local records skip the gate — there is no `#[prebindgen]` item
297 // behind them — but keep the reserved-separator name check.
298 for d in &acc.deconstructors {
299 check_records(&d.records, accessor_fns)?;
300 }
301
302 // Explicit decls first; they take precedence over (and suppress) a default
303 // for the same `(fn, target)`.
304 let mut done: std::collections::HashSet<(syn::Ident, DeconTarget)> = Default::default();
305 for ed in &acc.outputs {
306 // Per-fn decl cross-check: the decl's declared type must match the
307 // fn's peeled (`Option`/`Vec`/`&`) return type — the typo guard for
308 // `.expand_return(expand_return!(T)…)`.
309 if let Some(declared) = &ed.declared_source {
310 let ret = registry
311 .flat()
312 .function(&ed.func)
313 .map(|f| f.ret.clone())
314 .ok_or_else(|| UnfoldError::UnknownFunction(ed.func.clone()))?;
315 if !returns_type(&ret, declared) {
316 return Err(UnfoldError::ReturnTypeMismatch {
317 func: ed.func.clone(),
318 declared: declared.as_str().to_string(),
319 actual: {
320 let s = ret.spell();
321 quote::quote!(#s).to_string()
322 },
323 });
324 }
325 }
326 // Identity-only field set = the raw whole-value return (the
327 // complete-set rule: "the set is {self}"): no plan — mark done so the
328 // type default doesn't re-apply, and let the return cross through the
329 // type's ordinary output converter (borrowed-`&T`-capable).
330 if let DeconSel::Inline(records) = &ed.sel {
331 if matches!(records.as_slice(), [DeconRecord::Identity]) {
332 done.insert((ed.func.clone(), ed.target));
333 continue;
334 }
335 }
336 process_decl(registry, acc, ed)?;
337 done.insert((ed.func.clone(), ed.target));
338 }
339
340 // Default auto-apply: a type's deconstructor (`expand_return!`) is
341 // applied to every declared fn that returns it (Output) or has it as a
342 // `Result<_, E>` error (Error), unless the fn is `fun_accessor` or has a
343 // per-fn override. `Delivery` is recomputed from leaf count inside
344 // `process_decl` for Output (1 ⇒ Return, N ⇒ Callback).
345 for d in &acc.deconstructors {
346 if d.default.is_none() {
347 continue;
348 }
349 let dkey = d.target.clone();
350 let sel = DeconSel::TopLevel;
351 for func in declared_fns {
352 // Read accessors are never output-decomposed (they ARE the records).
353 if accessor_fns.contains(func) {
354 continue;
355 }
356 let Some(ret) = registry.flat().function(&func).map(|f| f.ret.clone()) else {
357 continue;
358 };
359 // Error position: fn returns `Result<_, E>` and `E == d.target`.
360 if let Some(err_ty) = ret.fallible_parts().map(|(_, e)| e) {
361 if err_ty.key() == dkey && done.insert((func.clone(), DeconTarget::Error)) {
362 process_decl(
363 registry,
364 acc,
365 &OutputDecl {
366 func: func.clone(),
367 sel: sel.clone(),
368 target: DeconTarget::Error,
369 delivery: Delivery::Callback,
370 declared_source: None,
371 },
372 )?;
373 }
374 }
375 // Output position: fn returns `T` / `&T` / `Option<T|&T>` / `Vec<T>`
376 // with `T == d.target` (Result returns keep a handle — factories).
377 if returns_type(&ret, &dkey)
378 && !acc.skip_output.contains(func)
379 && done.insert((func.clone(), DeconTarget::Output))
380 {
381 process_decl(
382 registry,
383 acc,
384 &OutputDecl {
385 func: func.clone(),
386 sel: sel.clone(),
387 target: DeconTarget::Output,
388 delivery: Delivery::Callback,
389 declared_source: None,
390 },
391 )?;
392 }
393 }
394 }
395
396 // Callback-argument decomposition: each `T` of a declared fn's
397 // `impl Fn(T, …)` parameter is delivered per `T`'s default deconstructor —
398 // the same default output a *return* of `T` would use — so the foreign
399 // callback receives the flattened leaves in one crossing instead of a
400 // whole value. Plans are type-level (keyed by `T`, fn-independent) with
401 // `by_ref = false` (the trampoline owns the value, so a root identity
402 // record moves it). Delivery is always `Callback` regardless of leaf count
403 // (there is no return-value lane in a callback invocation). A type without
404 // a default deconstructor gets no plan and is delivered whole.
405 for func in declared_fns {
406 let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
407 continue;
408 };
409 for param in ¶ms {
410 // The callback's argument types, read off the parameter's
411 // classification. `TypeKind::Callback` carries them as `TypeRef`s, so
412 // there is nothing to re-extract from the signature's syntax.
413 let prebindgen_flat::flat::TypeKind::Callback { args } = param.ty.kind() else {
414 continue;
415 };
416 for arg_ty in args {
417 // A borrowed arg (`impl Fn(&T)`) decomposes through the same
418 // machinery as a `&T` return: strip the leading `&` to reach the
419 // deconstructor target and set `by_ref` so the leaves are read
420 // (cloned) through the reference instead of by move. The plan is
421 // keyed under the ACTUAL arg type (`&T`) — that is what
422 // `callback_input`/`callback_iface_spec` look up.
423 let (by_ref, core_ty) = peel_borrow(arg_ty);
424 // Only a NAMED core can match a deconstructor target: an
425 // `Option<T>` / `Vec<T>` / tuple arg is delivered whole. The model
426 // says which, and `unwrapped` is where a wrapper the destination
427 // cannot see — `Box<T>` — stops reading as un-nameable.
428 if !matches!(
429 core_ty.unwrapped().kind(),
430 prebindgen_flat::flat::TypeKind::Named { .. }
431 ) {
432 continue;
433 }
434 let key = arg_ty.key();
435 if registry.callback_arg_plans.contains_key(&key) {
436 continue;
437 }
438 let core_key = core_ty.key();
439 let Some(d) = acc
440 .deconstructors
441 .iter()
442 .find(|d| d.default.is_some() && d.target == core_key)
443 else {
444 continue;
445 };
446 let ed = OutputDecl {
447 func: func.clone(),
448 sel: DeconSel::TopLevel,
449 target: DeconTarget::Output,
450 delivery: Delivery::Callback,
451 declared_source: None,
452 };
453 let decon = decl_id(&core_key, d);
454 let records = d.records.clone();
455 register_decon_spec(registry, acc, &decon, &records, core_ty)?;
456 let plan = build_plan(
457 acc,
458 registry,
459 &ed,
460 by_ref,
461 core_ty,
462 UnfoldShape::Base,
463 &records,
464 decon,
465 )?;
466 if plan.leaves.is_empty() {
467 continue;
468 }
469 for leaf in &plan.leaves {
470 registry.require_output(&leaf.out_ty);
471 }
472 registry.callback_arg_plans.insert(key, plan);
473 }
474 }
475 }
476 Ok(())
477}
478
479/// A synthesized by-value `data_class` decomposition, produced by the language
480/// adapter (which knows the per-field encoding — projections, enums, nested
481/// classes) and handed over as
482/// [`Decompositions::value_structs`](crate::Decompositions::value_structs).
483/// Its [`leaves`](Self::leaves)
484/// are [`LeafSource::Field`] leaves: each crosses the boundary as its own field
485/// value and the foreign side reassembles the object (no Java object is built
486/// on the Rust side).
487pub struct ValueDecon {
488 /// Canonical key of the value struct (the `DeconId::Default` key).
489 pub key: TypeKey,
490 /// The struct type (owned) the leaves decompose.
491 pub source: prebindgen_flat::flat::TypeRef,
492 /// Field-access leaves in foreign-signature / `fromParts` order.
493 pub leaves: Vec<UnfoldLeaf>,
494}
495
496/// Wire the synthesized by-value `data_class` decompositions into the registry:
497/// register each as a `DeconId::Default` [`DeconSpec`], then build a
498/// **fixed-builder** [`UnfoldPlan`] for every declared function that returns the
499/// struct (`T` / `&T` / `Option<T>` / `Vec<T>`) and a callback-arg plan for
500/// every `impl Fn(&T)` / `impl Fn(T)` parameter. Each leaf's `out_ty` is
501/// registered as a required output. Mirrors the per-function matching of
502/// [`apply`], but the builder/folder is a fixed foreign singleton
503/// (`fixed_builder = true`) reconstructing the concrete class, so delivery is
504/// always `Callback` (never the single-leaf `Return` shortcut) and the wrapper
505/// stays non-generic.
506///
507/// Runs in `write_rust` right after [`apply`] and before `resolve`.
508pub(crate) fn apply_value_structs<M>(
509 registry: &mut Registry<M>,
510 decons: Vec<ValueDecon>,
511 declared_fns: &std::collections::HashSet<syn::Ident>,
512) -> Result<(), UnfoldError> {
513 for vd in &decons {
514 let decon = wire_fixed_decon(registry, &vd.key, &vd.source, &vd.leaves)?;
515
516 // Output position: a declared fn returning the struct
517 // (`T` / `&T` / `Option<T|&T>` / `Vec<T|&T>`) decomposes into a
518 // fixed-builder plan. (`Result<T, E>` is left to the whole-value
519 // converter — the synthesizer covers the infallible returns.)
520 wire_fixed_returns(registry, vd, &decon, declared_fns, false);
521
522 // Callback-argument position: an `impl Fn(&T)` / `impl Fn(T)` parameter
523 // of a declared fn delivers the flattened leaves to the foreign
524 // callback, which reassembles the whole value via the data class's
525 // `fromParts` before invoking the user's typed callback (the group
526 // reassembly lives in the JNI adapter's `asRaw` proxy).
527 wire_fixed_callbacks(registry, vd, &decon, declared_fns)?;
528 }
529 Ok(())
530}
531
532/// A synthesized **sum** decomposition, produced by the language adapter (which
533/// knows how each payload encodes) and handed over as
534/// [`Decompositions::sums`](crate::Decompositions::sums) — the
535/// selector-carrying sibling of [`ValueDecon`].
536///
537/// Its [`leaves`](Self::leaves) are a [`LeafSource::SumTag`] selector followed
538/// by one **group** per alternative ([`LeafSource::VariantField`] leaves
539/// carrying [`UnfoldLeaf::group`]). Exactly one group is live per value; the
540/// emitter reads the whole list as ONE `match` over the value, filling every
541/// inert slot with its wire default. The foreign side picks the live group by
542/// the tag and rebuilds the alternative, so no object is built on the Rust
543/// side.
544pub struct SumDecon {
545 /// Canonical key of the sum type (the `DeconId::Default` key).
546 pub key: TypeKey,
547 /// The enum type (owned) the leaves decompose.
548 pub source: prebindgen_flat::flat::TypeRef,
549 /// The tag leaf followed by every variant's group, in tag order.
550 pub leaves: Vec<UnfoldLeaf>,
551}
552
553/// Wire the synthesized **sum** decompositions into the registry — the
554/// [`apply_value_structs`] analog for a value whose alternatives are chosen at
555/// runtime instead of being a fixed product.
556///
557/// For every declared function returning the sum (`E` / `&E` / `Option<E>` /
558/// `Vec<E>`) and every `impl Fn(E)` / `impl Fn(&E)` callback parameter, builds a
559/// **fixed-builder** [`UnfoldPlan`] over the tag + group leaves, registering
560/// each leaf's `out_ty` as a required output.
561///
562/// A sum has no converter of its own (it is boundary-only: a tag plus groups is
563/// not a single wire), so the declared return's scan-time output requirement —
564/// including the `Option<E>` / `Vec<E>` layers, which the boundary-only pass
565/// does not reach — is dropped here as the plan takes over.
566///
567/// Put every leaf's `out_ty` in the table, and demand a converter for the ones
568/// that need one.
569///
570/// **Every leaf is registered; only a converter-bearing leaf is a root** (#282).
571/// The two are separate facts and this is the one place a sum plan states both:
572/// a cell says the type entered the pipeline, a root says the binding needs its
573/// conversion to resolve. The `SumTag` selector is registered and not required —
574/// it names *which* sum it chooses between, and a sum has no whole-value output
575/// converter, so requiring one would fail resolution over a type that never
576/// crosses whole.
577///
578/// This used to `filter` the selector out entirely, which left its `out_ty`
579/// with a cell only when the adapter happened to declare the sum separately —
580/// true for jnigen via `export_type`, and not true at all for a registry
581/// assembled without declarations. The invariant holds by construction now
582/// rather than by declaration order.
583fn register_leaves<M>(registry: &mut crate::registry::Registry<M>, leaves: &[UnfoldLeaf]) {
584 for leaf in leaves {
585 if leaf.has_converter() {
586 registry.require_output(&leaf.out_ty);
587 } else {
588 registry.reference_output(&leaf.out_ty);
589 }
590 }
591}
592
593/// Runs in `write_rust` right after [`apply_value_structs`] and before `resolve`.
594pub(crate) fn apply_sum_returns<M>(
595 registry: &mut Registry<M>,
596 decons: Vec<SumDecon>,
597 declared_fns: &std::collections::HashSet<syn::Ident>,
598) -> Result<(), UnfoldError> {
599 for sd in &decons {
600 let decon = wire_fixed_decon(registry, &sd.key, &sd.source, &sd.leaves)?;
601 let vd = ValueDecon {
602 key: sd.key.clone(),
603 source: sd.source.clone(),
604 leaves: sd.leaves.clone(),
605 };
606 wire_fixed_returns(registry, &vd, &decon, declared_fns, true);
607 wire_fixed_callbacks(registry, &vd, &decon, declared_fns)?;
608 }
609 Ok(())
610}
611
612/// Register the declaration-canonical [`DeconSpec`] of a synthesized
613/// decomposition (first writer wins) and return its identity.
614fn wire_fixed_decon<M>(
615 registry: &mut Registry<M>,
616 key: &TypeKey,
617 source: &prebindgen_flat::flat::TypeRef,
618 leaves: &[UnfoldLeaf],
619) -> Result<DeconId, UnfoldError> {
620 let decon = DeconId::Default(key.to_string());
621 require_unique_leaf_names(source, leaves)?;
622 registry
623 .decon_plans
624 .entry(decon.clone())
625 .or_insert_with(|| DeconSpec {
626 source: source.clone(),
627 leaves: leaves.to_vec(),
628 });
629 Ok(decon)
630}
631
632/// Build the fixed-builder output plan for every declared fn returning the
633/// decomposed type (`T` / `&T` / `Option<T|&T>` / `Vec<T|&T>`). `no_converter`
634/// marks a type that has no whole-value converter at all (a sum), so the
635/// declared return's scan-time output requirement is dropped as the plan
636/// replaces it.
637fn wire_fixed_returns<M>(
638 registry: &mut Registry<M>,
639 vd: &ValueDecon,
640 decon: &DeconId,
641 declared_fns: &std::collections::HashSet<syn::Ident>,
642 no_converter: bool,
643) {
644 for func in declared_fns {
645 let Some(ret) = registry.flat().function(&func).map(|f| f.ret.clone()) else {
646 continue;
647 };
648 if !returns_type(&ret, &vd.key) || registry.unfold_plans.contains_key(func) {
649 continue;
650 }
651 // Shape over the leaf decomposition: peel an outer `Option`, then a
652 // `Vec`, then a leading `&`. `Vec<T|&T>` ⇒ Iterable (a **fixed
653 // folder**: each element's leaves cross raw and the foreign folder
654 // rebuilds it + appends, so no Java object is built on the Rust
655 // side); `Option<…>` wraps the inner shape in Optional (`None` ⇒ a
656 // null result). `element: None` keeps the decomposed-leaf path. The
657 // element/inner borrow-ness sets `by_ref` (the reach clones either
658 // way).
659 let layers = peel(&ret);
660 let by_ref = layers.by_ref;
661 // The model's layer stack is the plan's shape — `UnfoldShape` is `Shape`
662 // — so there is nothing to rebuild here.
663 let shape = layers.shape.clone();
664 if no_converter {
665 // The plan delivers the return leaf-by-leaf, so no converter is
666 // needed for the declared return — and for a sum none can exist.
667 // Drop the scan-time registrations of every layer (the boundary-only
668 // pass only reaches the bare type), so the missing converters are not
669 // flagged as unresolved-required.
670 // EVERY layer, the `Vec` element included. The shape fold peels here,
671 // so the matching unrequire belongs here; leaving the element out made
672 // the invariant depend on the adapter's `boundary_only_types` covering
673 // it — true for JniGenBuilder today, and the only reason a
674 // `Vec<sum>`-only declaration resolves.
675 for layer in &layers.layer_types {
676 registry.unrequire_output(layer);
677 }
678 }
679 register_leaves(registry, &vd.leaves);
680 let plan = UnfoldPlan {
681 source: vd.source.clone(),
682 decon: Some(decon.clone()),
683 by_ref,
684 shape,
685 leaves: vd.leaves.clone(),
686 element: None,
687 delivery: Delivery::Callback,
688 convert_out_ty: None,
689 fixed_builder: true,
690 hoists: Vec::new(),
691 };
692 registry.unfold_plans.insert(func.clone(), plan);
693 }
694}
695
696/// Build a fixed-builder callback-arg plan for every `impl Fn(&T)` /
697/// `impl Fn(T)` parameter (of a declared fn) whose value is the decomposed type
698/// `vd`. The foreign callback receives the flattened leaves (reassembled there)
699/// instead of a whole value built on the Rust side. Separate from the
700/// output-position wiring so the callback path (which needs the foreign-side
701/// group-reassembly adapter) can be enabled on its own.
702fn wire_fixed_callbacks<M>(
703 registry: &mut Registry<M>,
704 vd: &ValueDecon,
705 decon: &DeconId,
706 declared_fns: &std::collections::HashSet<syn::Ident>,
707) -> Result<(), UnfoldError> {
708 for func in declared_fns {
709 let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
710 continue;
711 };
712 for param in ¶ms {
713 // The callback's argument types, read off the parameter's
714 // classification. `TypeKind::Callback` carries them as `TypeRef`s, so
715 // there is nothing to re-extract from the signature's syntax.
716 let prebindgen_flat::flat::TypeKind::Callback { args } = param.ty.kind() else {
717 continue;
718 };
719 for arg_ty in args {
720 // Peel a leading `&`, then detect a slice element. An
721 // `impl Fn(&T)` / `impl Fn(T)` arg of the value struct decomposes
722 // into a `Base` fixed builder (foreign side reassembles the whole
723 // value via `fromParts`); an `impl Fn(&[T])` / `impl Fn([T])` arg
724 // becomes an `Iterable` fixed FOLDER (the trampoline folds each
725 // element's leaves into a foreign list — see the callback emitter).
726 let (by_ref, after_ref) = peel_borrow(arg_ty);
727 // A run of `T` is an Iterable fold over the element; anything else
728 // is a Base fold over the value itself. `Sequence` is the one
729 // question, and it covers `[T]` and `Vec<T>` alike.
730 let (shape, matches_key) = match after_ref.sequence_elem() {
731 Some(elem) => (
732 UnfoldShape::Iterable(Box::new(UnfoldShape::Base)),
733 elem.key() == vd.key,
734 ),
735 None => (UnfoldShape::Base, after_ref.key() == vd.key),
736 };
737 if !matches_key {
738 continue;
739 }
740 let key = arg_ty.key();
741 if registry.callback_arg_plans.contains_key(&key) {
742 continue;
743 }
744 register_leaves(registry, &vd.leaves);
745 let plan = UnfoldPlan {
746 source: vd.source.clone(),
747 decon: Some(decon.clone()),
748 by_ref,
749 shape,
750 leaves: vd.leaves.clone(),
751 element: None,
752 delivery: Delivery::Callback,
753 convert_out_ty: None,
754 fixed_builder: true,
755 hoists: Vec::new(),
756 };
757 registry.callback_arg_plans.insert(key, plan);
758 }
759 }
760 }
761 Ok(())
762}
763
764/// Wire **whole-element** `Iterable` fold plans for bare `Vec<T>` /
765/// `Option<Vec<T>>` returns and `impl Fn(&[T])` callback args whose element `T`
766/// is a single leaf (String, scalar, opaque handle) nominated by the adapter
767/// via `Prebindgen::leaf_vec_fold_elements`. Each
768/// such position crosses as decoupled raw leaves folded into a **foreign-built**
769/// list — the single-leaf dual of [`apply_value_structs`] (which handles
770/// multi-field `data_class` elements). The fold is a **fixed** foreign singleton
771/// (`fixed_builder = true`): the wrapper allocates the list, passes the hoisted
772/// appender, and returns the concrete `List<T>` (never a caller `fold` param), so
773/// no `java.util.ArrayList` is built on the Rust side.
774///
775/// Runs right after [`apply_value_structs`]; skips any function/arg that already
776/// carries a plan (an explicit `.deconstruct_output`, a `data_class` fold, …) so
777/// declared decompositions and value-struct folds win.
778pub(crate) fn apply_leaf_vec_folds<M>(
779 registry: &mut Registry<M>,
780 elements: Vec<TypeKey>,
781 declared_fns: &std::collections::HashSet<syn::Ident>,
782) -> Result<(), UnfoldError> {
783 if elements.is_empty() {
784 return Ok(());
785 }
786 let elem_keys = elements;
787 // Is the leading-`&`-peeled `bare` one of the nominated single-leaf elements?
788 let is_nominated = |bare: &prebindgen_flat::flat::TypeRef| elem_keys.contains(&bare.key());
789 for func in declared_fns {
790 let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
791 continue;
792 };
793 // Output position: `Vec<T>` / `Option<Vec<T>>` return. Skip if a plan
794 // already exists (declared deconstructor / value-struct fold).
795 if !registry.unfold_plans.contains_key(func) {
796 let Some(ret) = registry.flat().function(&func).map(|f| f.ret.clone()) else {
797 continue;
798 };
799 let (optional, after_opt) = match ret.optional_inner() {
800 Some(inner) => (true, inner),
801 None => (false, &ret),
802 };
803 if let Some(vec_elem) = after_opt.sequence_elem() {
804 let bare = peel_borrow(vec_elem).1;
805 if is_nominated(bare) {
806 let inner_shape = UnfoldShape::Iterable(Box::new(UnfoldShape::Base));
807 let shape = if optional {
808 UnfoldShape::Optional((), Box::new(inner_shape))
809 } else {
810 inner_shape
811 };
812 registry.require_output(vec_elem);
813 // The fold delivers the return element-by-element, so the
814 // whole `Vec<T>` / `Option<Vec<T>>` converter is not needed.
815 // De-require it: for String / scalar elements it still
816 // resolves (and is emitted as harmless dead code); for an
817 // opaque-handle element it cannot resolve (`jlong` wire isn't
818 // JObject-shaped), and de-requiring keeps that `None` from
819 // being flagged as an unresolved-required error.
820 registry.unrequire_output(&ret);
821 registry
822 .unfold_plans
823 .insert(func.clone(), whole_leaf_fold_plan(vec_elem, shape));
824 }
825 }
826 }
827 // Callback-arg position: `impl Fn(&[T])` / `impl Fn([T])`.
828 for param in ¶ms {
829 // The callback's argument types, read off the parameter's
830 // classification. `TypeKind::Callback` carries them as `TypeRef`s, so
831 // there is nothing to re-extract from the signature's syntax.
832 let prebindgen_flat::flat::TypeKind::Callback { args } = param.ty.kind() else {
833 continue;
834 };
835 for arg_ty in args {
836 let (_, after_ref) = peel_borrow(arg_ty);
837 let Some(elem) = after_ref.sequence_elem() else {
838 continue;
839 };
840 if !is_nominated(peel_borrow(elem).1) {
841 continue;
842 }
843 let key = arg_ty.key();
844 if registry.callback_arg_plans.contains_key(&key) {
845 continue;
846 }
847 registry.require_output(elem);
848 let plan =
849 whole_leaf_fold_plan(elem, UnfoldShape::Iterable(Box::new(UnfoldShape::Base)));
850 registry.callback_arg_plans.insert(key, plan);
851 }
852 }
853 }
854 Ok(())
855}
856
857/// Build a fixed-builder whole-element fold [`UnfoldPlan`] for a single-leaf
858/// element `vec_elem` (the `Vec`/slice element as written, keeping any leading
859/// `&` so `into_iter()`'s yield matches the element's own output converter).
860fn whole_leaf_fold_plan(
861 vec_elem: &prebindgen_flat::flat::TypeRef,
862 shape: UnfoldShape,
863) -> UnfoldPlan {
864 UnfoldPlan {
865 source: vec_elem.clone(),
866 decon: None,
867 by_ref: peel_borrow(vec_elem).0,
868 shape,
869 leaves: vec![],
870 element: Some(vec_elem.clone()),
871 delivery: Delivery::Callback,
872 convert_out_ty: None,
873 fixed_builder: true,
874 hoists: Vec::new(),
875 }
876}
877
878/// The deconstructor gate: every accessor-function record must be a declared
879/// `.fun_accessor` (the single source of truth for "accessor"), and no author
880/// leaf name may contain the reserved `"__"` chain separator. Binding-local
881/// records skip the accessor check — there is no `#[prebindgen]` item behind
882/// them — but keep the name check.
883///
884/// Recurses into a value form's per-field override records, so an override is
885/// held to the same rules as the declaration it replaces.
886fn check_records(
887 records: &[DeconRecord],
888 accessor_fns: &HashSet<syn::Ident>,
889) -> Result<(), UnfoldError> {
890 for rec in records {
891 let (func, name) = match rec {
892 DeconRecord::Acc { func, name } => (Some(func), name),
893 DeconRecord::LocalAcc { name, .. } => (None, name),
894 DeconRecord::Identity => continue,
895 // A value form's field names come from struct idents, not from the
896 // author, so the `"__"` in an inlined nested name is the separator
897 // doing its job. An author-supplied rename is checked where it is
898 // declared.
899 DeconRecord::Fields { func, fields, .. } => {
900 if !accessor_fns.contains(func) {
901 return Err(UnfoldError::RecordNotAccessor { func: func.clone() });
902 }
903 for fr in fields {
904 if let FieldDecon::Records(recs) = &fr.decon {
905 check_records(recs, accessor_fns)?;
906 }
907 }
908 continue;
909 }
910 };
911 if name.contains("__") {
912 return Err(UnfoldError::ReservedSeparator { name: name.clone() });
913 }
914 if let Some(func) = func {
915 if !accessor_fns.contains(func) {
916 return Err(UnfoldError::RecordNotAccessor { func: func.clone() });
917 }
918 }
919 }
920 Ok(())
921}
922
923/// The arity layers over `ty`, the types they wrap, and the value underneath.
924///
925/// A thin owned view over
926/// [`TypeRef::layer_stack`](prebindgen_flat::flat::TypeRef::layer_stack) and
927/// [`layer_types`](prebindgen_flat::flat::TypeRef::layer_types): the borrows are
928/// resolved to clones because these feed plan fields and registry calls that own
929/// their types. The classification is the model's; only the copying is local, and
930/// it happens **once**, where a value is stored — not on every question asked.
931///
932/// The stack **is** the plan's shape — `UnfoldShape` is `Shape` — so a caller
933/// stores it rather than rebuilding one from flags.
934struct Layered {
935 /// The arity layers, outermost first.
936 shape: UnfoldShape,
937 /// Every type on the way down, outermost first — what a registration walks.
938 layer_types: Vec<prebindgen_flat::flat::TypeRef>,
939 /// Past the borrow too: what actually crosses — as an **identity**, which
940 /// is all its one consumer ever asked of it.
941 core: TypeKey,
942 /// Whether the core is reached through a borrow.
943 by_ref: bool,
944}
945
946/// The layers of a type the model has **already read**.
947///
948/// Takes a `&TypeRef`, not a `&syn::Type`, and that is the whole point: a caller
949/// must hold a reading, and the ways to hold one are to take it off an element or
950/// to be the scan admitting a type with no element. Re-deriving a reading from
951/// `spell()` — the round trip this signature makes impossible — is reasoning
952/// from the spelling, which is what `origin` is not for.
953fn peel(ty: &prebindgen_flat::flat::TypeRef) -> Layered {
954 let (shape, layered) = ty.layer_stack();
955 let borrowed = layered.borrow_target();
956 Layered {
957 shape,
958 layer_types: ty.layer_types().into_iter().cloned().collect(),
959 core: borrowed.unwrap_or(layered).key(),
960 by_ref: borrowed.is_some(),
961 }
962}
963
964/// Just the borrow: whether `ty` is one, and what it borrows.
965///
966/// Deliberately **not** [`peel`]: a site that peels only the borrow means it,
967/// because the layer underneath is the thing it is about to classify. `Vec<T>`
968/// answers `(false, Vec<T>)` here and `(iterable, T)` there, and confusing the two
969/// turns "this arg is a collection" into "this arg is a T".
970fn peel_borrow(ty: &prebindgen_flat::flat::TypeRef) -> (bool, &prebindgen_flat::flat::TypeRef) {
971 match ty.borrow_target() {
972 Some(inner) => (true, inner),
973 None => (false, ty),
974 }
975}
976
977/// True when `ret` is `T` / `&T` / `Option<T|&T>` / `Vec<T|&T>` with
978/// `T == key` — the default-output match. `Result<_, _>` is NOT peeled, so a
979/// fallible factory (`-> Result<T, E>`) keeps its handle return; the error
980/// position is matched separately on `E`.
981fn returns_type(ret: &prebindgen_flat::flat::TypeRef, key: &TypeKey) -> bool {
982 peel(ret).core == *key
983}
984
985/// Build one output/error plan for `ed` and store it in the right registry map.
986fn process_decl<M>(
987 registry: &mut Registry<M>,
988 acc: &Deconstructors,
989 ed: &OutputDecl,
990) -> Result<(), UnfoldError> {
991 {
992 // The value to decompose: the success return (`Output`) or the
993 // `Result<_, E>` domain error `E` (`Error`).
994 let fn_ret = registry
995 .flat()
996 .function(&ed.func)
997 .map(|f| f.ret.clone())
998 .ok_or_else(|| UnfoldError::UnknownFunction(ed.func.clone()))?;
999 let ret_ty = match ed.target {
1000 DeconTarget::Output => fn_ret,
1001 DeconTarget::Error => {
1002 fn_ret
1003 .fallible_parts()
1004 .map(|(_, e)| e.clone())
1005 .ok_or_else(|| UnfoldError::Unsupported {
1006 func: ed.func.clone(),
1007 reason: "convert_error/deconstruct_error on a non-Result return",
1008 })?
1009 }
1010 };
1011
1012 // Peel an outer `Option` off the success return BEFORE probing for a
1013 // `Vec`, so `Option<Vec<T>>` composes as `Optional(Iterable)` — the
1014 // fold is skipped and a null result delivered for `None` (issue
1015 // #105). The scalar arm below reuses this peel. Error targets keep
1016 // the historical probe order (the `Vec` probe runs on `E` itself), so
1017 // an `Option<Vec<E>>` error stays whole.
1018 let (optional, after_opt) = match ed.target {
1019 DeconTarget::Output => match ret_ty.optional_inner() {
1020 Some(inner) => (true, inner),
1021 None => (false, &ret_ty),
1022 },
1023 DeconTarget::Error => (false, &ret_ty),
1024 };
1025 // `Vec<T>` / `Option<Vec<T>>` return → `Iterable` (± an `Optional`
1026 // layer). Two element-delivery modes:
1027 // * **decomposed** (M5): the element type has an accessor →
1028 // flatten it into leaves, fold `(acc, leaf0, …) -> acc`.
1029 // * **whole** (M4): no accessor → deliver each element whole
1030 // via its own output converter + projection, fold `(acc, T) -> acc`.
1031 // The other shapes (`Option`/scalar) decompose via an accessor
1032 // (M1–M3). `Vec<Option<…>>` is not supported.
1033 let plan = if let Some(inner) = after_opt.sequence_elem() {
1034 if inner.optional_inner().is_some() {
1035 return Err(UnfoldError::Unsupported {
1036 func: ed.func.clone(),
1037 reason: "Vec<Option<…>> returns",
1038 });
1039 }
1040 let iterable = UnfoldShape::Iterable(Box::new(UnfoldShape::Base));
1041 let shape = if optional {
1042 UnfoldShape::Optional((), Box::new(iterable))
1043 } else {
1044 iterable
1045 };
1046 // The fold delivers the return element-by-element, so the
1047 // whole-collection converter is not needed — and for an
1048 // opaque-handle element it cannot resolve at all (a `jlong` wire
1049 // isn't JObject-shaped). De-require the scan-time registrations
1050 // (the declared return and, under `Option`, the inner `Vec` its
1051 // recursive registration also required) — same reasoning as
1052 // [`apply_leaf_vec_folds`] for the fixed folds.
1053 if ed.target == DeconTarget::Output {
1054 registry.unrequire_output(&ret_ty);
1055 if optional {
1056 registry.unrequire_output(after_opt);
1057 }
1058 }
1059 // Element type peeled of a leading `&` (accessors take `&Element`).
1060 let (by_ref, element) = peel_borrow(inner);
1061 let ekey = element.key();
1062 if let Some(d) = find_deconstructor_by_type(acc, &ekey) {
1063 // Decomposed: reuse the shared flatten (M3 nesting composes).
1064 let records = d.records.clone();
1065 let decon = decl_id(&ekey, d);
1066 register_decon_spec(registry, acc, &decon, &records, element)?;
1067 let plan = build_plan(acc, registry, ed, by_ref, element, shape, &records, decon)?;
1068 for leaf in &plan.leaves {
1069 registry.require_output(&leaf.out_ty);
1070 }
1071 plan
1072 } else {
1073 // Whole element: keep the type exactly as written so the
1074 // element's own output converter matches `into_iter()`'s yield.
1075 // No declaration is involved (`decon: None`) — the element
1076 // crosses whole through its own converter.
1077 let by_ref = peel_borrow(inner).0;
1078 registry.require_output(inner);
1079 UnfoldPlan {
1080 source: inner.clone(),
1081 decon: None,
1082 by_ref,
1083 shape,
1084 leaves: vec![],
1085 element: Some(inner.clone()),
1086 delivery: ed.delivery,
1087 convert_out_ty: None,
1088 fixed_builder: false,
1089 hoists: Vec::new(),
1090 }
1091 }
1092 } else {
1093 // Scalar/decomposed arm. The `Option` peel already happened above
1094 // for `Output` (exactly one layer — `Option<Option<…>>` is NOT
1095 // re-peeled and fails as "no deconstructor" for the inner
1096 // `Option`); for `Error` it happens here, unchanged.
1097 let (optional, core_ty) = match ed.target {
1098 DeconTarget::Output => (optional, after_opt),
1099 DeconTarget::Error => match after_opt.optional_inner() {
1100 Some(inner) => (true, inner),
1101 None => (false, after_opt),
1102 },
1103 };
1104 let (by_ref, source) = peel_borrow(core_ty);
1105 let source_key = source.key();
1106 let shape = if optional {
1107 UnfoldShape::Optional((), Box::new(UnfoldShape::Base))
1108 } else {
1109 UnfoldShape::Base
1110 };
1111 let (records, decon) = resolve_deconstructor(acc, &source_key, ed)?;
1112 register_decon_spec(registry, acc, &decon, &records, source)?;
1113 let plan = build_plan(acc, registry, ed, by_ref, source, shape, &records, decon)?;
1114 for leaf in &plan.leaves {
1115 registry.require_output(&leaf.out_ty);
1116 }
1117 plan
1118 };
1119 // Delivery is by **leaf count**, not a per-decl flag:
1120 // * Output, single non-nullable leaf, non-Iterable ⇒ Return (wrapper
1121 // returns the value via its ordinary output converter —
1122 // `convert_out_ty`).
1123 // * Output, multiple leaves or Iterable (at any layer — an
1124 // `Optional(Iterable)` fold has no single value to return) ⇒
1125 // Callback (builder / fold).
1126 // * Error ⇒ always Callback-shaped: every leaf is a `ze` arg after the
1127 // fixed `je` (no return-value path; `convert_out_ty` stays None).
1128 //
1129 // A NULLABLE leaf is one whose path passes through an `Option` that
1130 // something is decomposed below (`Option<Handle>` reached by
1131 // `.field_self()`, a nested value form behind an `Option`). Returning it
1132 // has nowhere to put the absent case: a return value is one expression,
1133 // so there is no `None` arm, and `convert_out_ty` names the leaf's own
1134 // type rather than an optional of it. Callback delivery has that arm
1135 // already — the leaf crosses as a boxed `Long` / JVM null — so the
1136 // shape goes there instead of being composed into Rust that hands
1137 // `&Option<T>` to a converter typed for `T`.
1138 let single_return = ed.target == DeconTarget::Output
1139 && !plan.shape.has_iterable_layer()
1140 && plan.leaves.len() == 1
1141 && !plan.leaves[0].nullable;
1142 let plan = if single_return {
1143 // Composed with the model's own layering rather than by spelling
1144 // `Option<#leaf_ty>` and handing the tokens over: `optional()` pairs
1145 // the `kind` with its spelling in one place, so the reading that
1146 // reaches the table is the one this plan carries (#281).
1147 let cv = if matches!(plan.shape, UnfoldShape::Optional((), _)) {
1148 plan.leaves[0].out_ty.optional()
1149 } else {
1150 plan.leaves[0].out_ty.clone()
1151 };
1152 registry.require_output(&cv);
1153 UnfoldPlan {
1154 delivery: Delivery::Return,
1155 convert_out_ty: Some(cv.clone()),
1156 ..plan
1157 }
1158 } else {
1159 UnfoldPlan {
1160 delivery: Delivery::Callback,
1161 ..plan
1162 }
1163 };
1164 match ed.target {
1165 DeconTarget::Output => registry.unfold_plans.insert(ed.func.clone(), plan),
1166 DeconTarget::Error => registry.error_plans.insert(ed.func.clone(), plan),
1167 };
1168 }
1169 Ok(())
1170}
1171
1172/// The identity of a found declaration — the type's default deconstructor.
1173fn decl_id(type_key: &TypeKey, _decl: &DeconstructorDecl) -> DeconId {
1174 DeconId::Default(type_key.to_string())
1175}
1176
1177/// Register the declaration-default [`DeconSpec`] for `decon` (no-op when
1178/// already present): re-flatten the records with normalized inputs —
1179/// borrowed identity, no outer shape — so the stored spec is independent of
1180/// the using function's return shape and of processing order.
1181fn register_decon_spec<M>(
1182 registry: &mut Registry<M>,
1183 acc: &Deconstructors,
1184 decon: &DeconId,
1185 records: &[DeconRecord],
1186 source: &prebindgen_flat::flat::TypeRef,
1187) -> Result<(), UnfoldError> {
1188 if registry.decon_plans.contains_key(decon) {
1189 return Ok(());
1190 }
1191 let mut leaves: Vec<UnfoldLeaf> = Vec::new();
1192 let mut visited: HashSet<TypeKey> = HashSet::new();
1193 visited.insert(source.key());
1194 flatten(
1195 acc,
1196 registry,
1197 records,
1198 source,
1199 &[],
1200 &[],
1201 true,
1202 false,
1203 &mut visited,
1204 &mut leaves,
1205 // A `DeconSpec` describes the leaf list only — signature artifacts are
1206 // derived from it, never emitted code — so its hoists are discarded.
1207 &mut Vec::new(),
1208 )?;
1209 require_unique_leaf_names(source, &leaves)?;
1210 registry.decon_plans.insert(
1211 decon.clone(),
1212 DeconSpec {
1213 source: source.clone(),
1214 leaves,
1215 },
1216 );
1217 Ok(())
1218}
1219
1220/// Pick the deconstructor (its records + declaration identity) for one
1221/// output expansion.
1222fn resolve_deconstructor(
1223 acc: &Deconstructors,
1224 source_key: &TypeKey,
1225 ed: &OutputDecl,
1226) -> Result<(Vec<DeconRecord>, DeconId), UnfoldError> {
1227 match &ed.sel {
1228 DeconSel::Inline(records) => Ok((
1229 records.clone(),
1230 DeconId::PerFn(source_key.to_string(), ed.func.to_string()),
1231 )),
1232 DeconSel::TopLevel => find_deconstructor_by_type(acc, source_key)
1233 .map(|d| (d.records.clone(), DeconId::Default(source_key.to_string())))
1234 .ok_or_else(|| UnfoldError::NoDeconstructor {
1235 func: ed.func.clone(),
1236 target: source_key.to_string(),
1237 }),
1238 }
1239}
1240
1241/// Find the deconstructor whose target is `type_key` (unique per type:
1242/// `ensure_default_deconstructor` dedups by type key). Used for both the
1243/// top-level output expansion and nested-record splicing.
1244fn find_deconstructor_by_type<'a>(
1245 acc: &'a Deconstructors,
1246 type_key: &TypeKey,
1247) -> Option<&'a DeconstructorDecl> {
1248 acc.deconstructors.iter().find(|c| c.target == *type_key)
1249}
1250
1251/// Build the [`UnfoldPlan`] for a chosen accessor. `shape` is the outer
1252/// shape over the core decomposition (`Decompose` for `T`/`&T`,
1253/// `Optional(Decompose)` for `Option<T>`/`Option<&T>`). The records are
1254/// recursively flattened ([`flatten`]) — nested accessors contribute
1255/// their leaves with the access path prefixed.
1256#[allow(clippy::too_many_arguments)]
1257fn build_plan<M>(
1258 acc: &Deconstructors,
1259 registry: &Registry<M>,
1260 ed: &OutputDecl,
1261 by_ref: bool,
1262 source: &prebindgen_flat::flat::TypeRef,
1263 shape: UnfoldShape,
1264 records: &[DeconRecord],
1265 decon: DeconId,
1266) -> Result<UnfoldPlan, UnfoldError> {
1267 let mut leaves: Vec<UnfoldLeaf> = Vec::new();
1268 let mut visited: HashSet<TypeKey> = HashSet::new();
1269 visited.insert(source.key());
1270 let mut hoists: Vec<Hoist> = Vec::new();
1271 flatten(
1272 acc,
1273 registry,
1274 records,
1275 source,
1276 &[],
1277 &[],
1278 by_ref,
1279 false,
1280 &mut visited,
1281 &mut leaves,
1282 &mut hoists,
1283 )?;
1284 require_unique_leaf_names(source, &leaves)?;
1285 require_root_identity_last(by_ref, source, &leaves)?;
1286
1287 Ok(UnfoldPlan {
1288 source: source.clone(),
1289 decon: Some(decon),
1290 by_ref,
1291 shape,
1292 leaves,
1293 element: None,
1294 delivery: ed.delivery,
1295 convert_out_ty: None,
1296 fixed_builder: false,
1297 hoists,
1298 })
1299}
1300
1301/// Error when an **owned** decomposition emits the root identity leaf before a
1302/// nested identity leaf. Leaves are emitted in declaration order, and the root
1303/// identity MOVES the owned value while a nested identity clones from a borrow
1304/// of it — the wrong order generates non-compiling Rust ("use of moved value")
1305/// with a cryptic rustc message. Caught here instead, with the fix in the
1306/// error: declare `.field_self()` after the nested-identity fields. (Borrowed
1307/// decompositions clone the root identity, so any order is fine.)
1308fn require_root_identity_last(
1309 by_ref: bool,
1310 source: &prebindgen_flat::flat::TypeRef,
1311 leaves: &[UnfoldLeaf],
1312) -> Result<(), UnfoldError> {
1313 if by_ref {
1314 return Ok(());
1315 }
1316 let root_at = leaves.iter().position(|l| l.identity && l.path.is_empty());
1317 let last_nested_at = leaves
1318 .iter()
1319 .rposition(|l| l.identity && !l.path.is_empty());
1320 if let (Some(root), Some(nested)) = (root_at, last_nested_at) {
1321 if root < nested {
1322 return Err(UnfoldError::RootIdentityBeforeNested {
1323 target: source.key().to_string(),
1324 });
1325 }
1326 }
1327 Ok(())
1328}
1329
1330/// Recursively flatten an accessor's records into [`UnfoldLeaf`]s.
1331///
1332/// * `source` — the type whose accessor `records` belong to (the root
1333/// on the first call, a nested child type on recursion).
1334/// * `path_prefix` — accessor chain from the root value to `source` (empty at
1335/// the root; `[…, nesting_accessor]` when recursing into a nested child).
1336/// * `by_ref` — the top-level return/element borrow-ness. The identity leaf is
1337/// **owned** (`source`) only at the root of an owned value (`path_prefix`
1338/// empty && `!by_ref`) — a `Copy` value delivers itself by copy and an
1339/// opaque handle moves; everywhere else it is **borrowed** (`&source`,
1340/// cloned).
1341/// * `nullable` — `true` once any nesting accessor on the path returned
1342/// `Option` (the reached value may be absent ⇒ the leaf is `null`).
1343/// * `visited` — type keys on the current nesting chain (cycle guard; entries
1344/// are removed after each nested recursion so sibling records may reuse a type).
1345#[allow(clippy::too_many_arguments)]
1346fn flatten<M>(
1347 acc: &Deconstructors,
1348 registry: &Registry<M>,
1349 records: &[DeconRecord],
1350 source: &prebindgen_flat::flat::TypeRef,
1351 path_prefix: &[PathStep],
1352 name_prefix: &[String],
1353 by_ref: bool,
1354 nullable: bool,
1355 visited: &mut HashSet<TypeKey>,
1356 leaves: &mut Vec<UnfoldLeaf>,
1357 hoists: &mut Vec<Hoist>,
1358) -> Result<(), UnfoldError> {
1359 let source_key = source.key();
1360 // The author-supplied (literal) leaf-name segment at this level, appended
1361 // to the inherited chain prefix. Segments are joined with `"__"`.
1362 let seg_name = |name: &str| -> Vec<String> {
1363 let mut v = name_prefix.to_vec();
1364 v.push(name.to_string());
1365 v
1366 };
1367 // Identity uniqueness is per accessor (one move/clone of the value
1368 // at this level); nested levels each get their own identity budget.
1369 let mut seen_identity = false;
1370
1371 for rec in records {
1372 match rec {
1373 DeconRecord::Identity => {
1374 if seen_identity {
1375 return Err(UnfoldError::MultipleIdentity {
1376 target: source_key.to_string(),
1377 });
1378 }
1379 seen_identity = true;
1380 // Owned where the value is OURS to give: the root of an owned
1381 // plan (a `Copy` blob copies / an opaque handle moves), or a
1382 // field of a value form that CONSUMED its value — that form was
1383 // handed the value, so its fields move out like every other
1384 // field of it. Borrowed (clone) otherwise. The adapter-side type
1385 // + projection come from this `out_ty`'s output converter, so
1386 // this is what decides whether the leaf is boxed by move or
1387 // cloned through the borrowed-opaque one.
1388 // A plan field: the drop to spelling happens here, where the value
1389 // is stored for emission, and the borrowed form is composed rather
1390 // than looked up because no source wrote it.
1391 // The borrowed form is COMPOSED — no source wrote it — and the
1392 // composition pairs the kind with its own spelling, so nothing
1393 // downstream has to look either up.
1394 let out_ty = if place_is_owned(hoists, path_prefix, by_ref) {
1395 source.clone()
1396 } else {
1397 source.borrowed()
1398 };
1399 leaves.push(UnfoldLeaf {
1400 name: if path_prefix.is_empty() {
1401 "handle".to_string()
1402 } else {
1403 name_prefix.join("__")
1404 },
1405 path: path_prefix.to_vec(),
1406 out_ty,
1407 identity: true,
1408 nullable,
1409 source: LeafSource::Accessor,
1410 group: None,
1411 });
1412 }
1413 DeconRecord::Fields {
1414 func,
1415 consuming,
1416 fields,
1417 } => {
1418 let consuming = *consuming;
1419 // The value form is called once; every field hangs off that one
1420 // call, so the whole record shares a single `Call` step and the
1421 // emitter can hoist it.
1422 accessor_signature(registry, func, &source.key())?;
1423 // The declarator states whether the value is given away; the
1424 // signature has to agree, or the emitted call would not compile
1425 // in the consumer's crate. Checked rather than inferred so that
1426 // declaring `.fields_self_into(..)` on a borrowing accessor is a
1427 // named error instead of a silently downgraded boundary.
1428 if consuming != accessor_consumes(registry, func) {
1429 return Err(UnfoldError::Unsupported {
1430 func: func.clone(),
1431 reason: if consuming {
1432 "declared as a CONSUMING value form (`.fields_self_into(..)`) but the \
1433 accessor borrows its receiver — declare it with `.fields(..)`, or \
1434 name the by-value accessor"
1435 } else {
1436 "declared as a BORROWING value form (`.fields(..)`) but the accessor \
1437 takes its receiver by value — declare it with `.fields_self_into(..)`, or \
1438 name the `&Self` accessor"
1439 },
1440 });
1441 }
1442 let mut root_path = path_prefix.to_vec();
1443 root_path.push(PathStep::call(func.clone(), false, false));
1444 // A hoist below an optional step is CONDITIONAL: it binds an
1445 // `Option<TStruct>` local (built only in the `Some` arm) and
1446 // every leaf under it is null when the value is absent — which
1447 // is the nullability `flatten` already propagates down here.
1448 // What it cannot do is nest: composing a second hoist off a
1449 // conditional one would have to reach through the outer
1450 // `Option`, and the binder has no arm to put that in. One level
1451 // is the shape real bindings need (`Option<&Sample>` delivering
1452 // a sample's value form), so implement that and name the rest.
1453 //
1454 // A top-level `Option<T>` is represented by
1455 // `UnfoldShape::Optional`, not by a path step, and is unaffected.
1456 if root_path.iter().any(PathStep::is_optional)
1457 && hoists.iter().any(|h| {
1458 h.prefix.len() < root_path.len() && root_path.starts_with(&h.prefix)
1459 })
1460 {
1461 return Err(UnfoldError::Unsupported {
1462 func: func.clone(),
1463 reason: "a value form nested under another one that is reached through \
1464 `Option` — conditional hoists do not nest",
1465 });
1466 }
1467 // A consuming value form DESTROYS the value into its parts, so
1468 // a sibling record — `.field_self()` or another `.field()` —
1469 // would read what it just gave away. jnigen refuses this in the
1470 // declarator, where the author can see it; this is the backstop
1471 // for records built directly against core.
1472 //
1473 // Being reached through ANOTHER value form is fine: a hoisted
1474 // value form is an owned struct and its fields are disjoint, so
1475 // the parent's field is handed over by move.
1476 if consuming && records.len() > 1 {
1477 return Err(UnfoldError::Unsupported {
1478 func: func.clone(),
1479 reason: "a consuming value form must be the only record of its \
1480 declaration — it moves the value, so `.field_self()` or \
1481 a sibling `.field()` would read a moved value",
1482 });
1483 }
1484 // Evaluate this value form ONCE. Recorded at the prefix it sits
1485 // at rather than as a lone accessor, so a nested value form
1486 // (this record reached through another one's field) gets its own
1487 // hoist instead of being rebuilt per child leaf. `path_prefix`
1488 // grows as `flatten` descends, so the list comes out
1489 // outermost-first.
1490 hoists.push(Hoist {
1491 prefix: root_path.clone(),
1492 consuming,
1493 });
1494
1495 for fr in fields {
1496 // The declaration carries the field's reading, so nothing is
1497 // looked up and nothing is re-classified.
1498 //
1499 // A field's own `Option` makes everything under it nullable,
1500 // exactly as an `Option`-returning accessor step does.
1501 let (opt, core) = match fr.ty.optional_inner() {
1502 Some(inner) => (true, inner),
1503 None => (false, &fr.ty),
1504 };
1505 let child_ty = core.borrow_target().unwrap_or(core);
1506 let child_key = child_ty.key();
1507
1508 // Same three-way choice a `.field()` record makes: declared
1509 // override, else the field type's own deconstructor, else
1510 // one leaf — with the adapter able to pre-build the leaves
1511 // for a shape only it can describe.
1512 let child_records = match &fr.decon {
1513 FieldDecon::Records(recs) => Some(recs.clone()),
1514 FieldDecon::Leaves(_) => None,
1515 FieldDecon::Default => match find_deconstructor_by_type(acc, &child_key) {
1516 Some(child_decl) if !visited.contains(&child_key) => {
1517 Some(child_decl.records.clone())
1518 }
1519 Some(_) => {
1520 return Err(UnfoldError::Cycle {
1521 target: child_key.to_string(),
1522 });
1523 }
1524 None => None,
1525 },
1526 };
1527 let decomposed =
1528 child_records.is_some() || matches!(fr.decon, FieldDecon::Leaves(_));
1529
1530 // The field's own `Option` is a nullable NESTING step only
1531 // when something is decomposed below it. For a plain leaf
1532 // the whole `Option<F>` is what the converter takes — the
1533 // same rule that makes a terminal accessor's `Option` ride
1534 // its converter instead of being unwrapped.
1535 let mut field_path = root_path.clone();
1536 let (last, lead) = fr
1537 .members
1538 .split_last()
1539 .expect("a field record addresses at least one member");
1540 // Only the LAST member can be optional — an inlined nested
1541 // class is reached directly, never through an `Option`.
1542 field_path.extend(lead.iter().map(|m| PathStep::field(m.clone(), false)));
1543 field_path.push(PathStep::field(last.clone(), opt && decomposed));
1544
1545 // Adapter-built leaves: rebase each onto this field's path
1546 // and name. Their internal structure (a selector plus its
1547 // groups) is opaque here and passes through untouched.
1548 if let FieldDecon::Leaves(built) = &fr.decon {
1549 for l in built {
1550 let mut path = field_path.clone();
1551 path.extend(l.path.iter().cloned());
1552 let mut name = seg_name(&fr.name);
1553 name.push(l.name.clone());
1554 leaves.push(UnfoldLeaf {
1555 name: name.join("__"),
1556 path,
1557 nullable: l.nullable || nullable || opt,
1558 ..l.clone()
1559 });
1560 }
1561 continue;
1562 }
1563
1564 if let Some(child_records) = child_records {
1565 visited.insert(child_key.clone());
1566 flatten(
1567 acc,
1568 registry,
1569 &child_records,
1570 // The declaration's reading, peeled — not a new one.
1571 child_ty,
1572 &field_path,
1573 &seg_name(&fr.name),
1574 by_ref,
1575 nullable || opt,
1576 visited,
1577 leaves,
1578 hoists,
1579 )?;
1580 visited.remove(&child_key);
1581 } else {
1582 // A plain field leaf: the value is CLONED out of the
1583 // struct, so its converter takes the owned field type as
1584 // written — `Option` and all, which is why a terminal
1585 // `Option` step is not a nesting step for it.
1586 leaves.push(UnfoldLeaf {
1587 name: seg_name(&fr.name).join("__"),
1588 path: field_path,
1589 out_ty: fr.ty.clone(),
1590 identity: false,
1591 nullable,
1592 source: LeafSource::Field,
1593 group: None,
1594 });
1595 }
1596 }
1597 }
1598 DeconRecord::Acc { name, .. } | DeconRecord::LocalAcc { name, .. } => {
1599 // A binding-local record resolves through its synthesized
1600 // registry entry (see `synthesize_local_accessors`), so both
1601 // kinds read one signature source; only the cycle rule below
1602 // differs.
1603 let (func, local) = match rec {
1604 DeconRecord::Acc { func, .. } => (func.clone(), false),
1605 DeconRecord::LocalAcc { path, .. } => (DeconRecord::local_ident(path), true),
1606 DeconRecord::Identity | DeconRecord::Fields { .. } => unreachable!(),
1607 };
1608 let ret = accessor_signature(registry, &func, &source.key())?;
1609 // Default unwrap: if the return type has its own deconstructor,
1610 // splice it (recurse); otherwise the return is one leaf. Peel an
1611 // `Option` (value may be absent) + leading `&` to reach the child.
1612 // This site peels an `Option` only — an accessor returning a run
1613 // of values is not spliced — so it asks the model for that one
1614 // layer rather than the whole stack.
1615 let after_opt = ret.optional_inner();
1616 let opt = after_opt.is_some();
1617 let core = after_opt.unwrap_or(&ret);
1618 let (core_by_ref, child_ty) = peel_borrow(core);
1619 let child_key = child_ty.key();
1620 // A child already on the nesting chain: for a `#[prebindgen]`
1621 // accessor that is an authoring cycle (hard error); a
1622 // binding-local field re-delivering (part of) its own type
1623 // under a binding-defined condition is the POINT — degrade to
1624 // a plain converter leaf instead of splicing.
1625 let splice = match find_deconstructor_by_type(acc, &child_key) {
1626 Some(child_decl) if !visited.contains(&child_key) => Some(child_decl),
1627 Some(_) if local => None,
1628 Some(_) => {
1629 return Err(UnfoldError::Cycle {
1630 target: child_key.to_string(),
1631 });
1632 }
1633 None => None,
1634 };
1635 if let Some(child_decl) = splice {
1636 visited.insert(child_key.clone());
1637 let child_records = child_decl.records.clone();
1638 let mut child_path = path_prefix.to_vec();
1639 child_path.push(PathStep::call(func.clone(), opt, !core_by_ref));
1640 flatten(
1641 acc,
1642 registry,
1643 &child_records,
1644 child_ty,
1645 &child_path,
1646 &seg_name(name),
1647 by_ref,
1648 nullable || opt,
1649 visited,
1650 leaves,
1651 hoists,
1652 )?;
1653 visited.remove(&child_key);
1654 } else {
1655 // Leaf: the return value as written (`&str`, enum, `i64`, …).
1656 // One exception: a binding-local field returning an
1657 // OPTIONAL BORROW (`Option<&T>`) is the conditional
1658 // HANDLE-delivery idiom — structurally a spliced identity
1659 // behind an `Option`-returning step (cf. an
1660 // `Option`-returning nesting accessor + the child's
1661 // `field_self`), so it contributes a nullable IDENTITY
1662 // leaf of the borrowed type: the reach path unwraps the
1663 // final `Option` (the synthesized signature keeps the
1664 // full return) and the value clones through its handle
1665 // projection, `None` delivering null. It shares the
1666 // one-identity-per-deconstructor budget with
1667 // `.field_self()` — two handle deliveries of one value
1668 // make no sense.
1669 let cond_handle = local && opt && core_by_ref;
1670 if cond_handle {
1671 if seen_identity {
1672 return Err(UnfoldError::MultipleIdentity {
1673 target: source_key.to_string(),
1674 });
1675 }
1676 seen_identity = true;
1677 }
1678 // A plan field: the spelling is taken here, once, where the
1679 // leaf is stored for emission.
1680 let (out_ty, nullable, identity) = if cond_handle {
1681 (core.clone(), true, true)
1682 } else {
1683 (ret.clone(), nullable, false)
1684 };
1685 let mut path = path_prefix.to_vec();
1686 path.push(PathStep::call(func.clone(), opt, !core_by_ref));
1687 leaves.push(UnfoldLeaf {
1688 name: seg_name(name).join("__"),
1689 path,
1690 out_ty,
1691 identity,
1692 nullable,
1693 source: LeafSource::Accessor,
1694 group: None,
1695 });
1696 }
1697 }
1698 }
1699 }
1700
1701 Ok(())
1702}
1703
1704/// Error if two leaves of one flattened deconstructor share a name. Author leaf
1705/// names are explicit and emitted literally, so a collision is a declaration
1706/// bug — never auto-resolved.
1707fn require_unique_leaf_names(
1708 source: &prebindgen_flat::flat::TypeRef,
1709 leaves: &[UnfoldLeaf],
1710) -> Result<(), UnfoldError> {
1711 let mut seen: HashSet<&str> = HashSet::new();
1712 for l in leaves {
1713 if !seen.insert(l.name.as_str()) {
1714 return Err(UnfoldError::DuplicateLeafName {
1715 target: source.key().to_string(),
1716 name: l.name.clone(),
1717 });
1718 }
1719 }
1720 Ok(())
1721}
1722
1723/// Make a signature's name list unique: a duplicate gets a numeric suffix
1724/// (`name2`, `name3`, …). Adapters run this over the final per-signature
1725/// list (after their own casing), since one signature may concatenate the
1726/// leaves of several plans.
1727pub fn dedup_names(names: &mut [String]) {
1728 let mut seen: HashSet<String> = HashSet::new();
1729 for n in names.iter_mut() {
1730 if !seen.insert(n.clone()) {
1731 let mut k = 2;
1732 while !seen.insert(format!("{n}{k}")) {
1733 k += 1;
1734 }
1735 *n = format!("{n}{k}");
1736 }
1737 }
1738}
1739
1740/// An accessor `f(&T) -> R`: returns its return type `R` as written (a
1741/// reference where possible).
1742///
1743/// `expected` is the type the deconstructor decomposes, and what comes back is
1744/// an accessor already proven to take it. Taking it as a parameter rather than
1745/// leaving the caller to check afterwards is the point: a declarator cannot
1746/// reach an accessor's signature without saying what that accessor is supposed
1747/// to be about, so the check cannot be the thing a new declarator forgets
1748/// (#223). The comparison is [`check_declared_target`], shared with the input
1749/// side's constructor lookup.
1750fn accessor_signature<M>(
1751 registry: &Registry<M>,
1752 func: &syn::Ident,
1753 expected: &TypeKey,
1754) -> Result<prebindgen_flat::flat::TypeRef, UnfoldError> {
1755 let f = registry
1756 .flat()
1757 .function(&func)
1758 .ok_or_else(|| UnfoldError::UnknownAccessor(func.clone()))?;
1759
1760 // First parameter is the receiver `&T`; peel the borrow to get `T`.
1761 // `borrow_target` is the model's own answer, so the peel reads a
1762 // classification instead of re-deciding it from `syn::Type::Reference`.
1763 let first = f
1764 .params
1765 .first()
1766 .ok_or_else(|| UnfoldError::UnknownAccessor(func.clone()))?;
1767 // The receiver's identity, keyed so the comparison below cannot fail on a
1768 // spelling difference that does not change which type this is about.
1769 let takes = match first.ty.borrow_target() {
1770 Some(inner) => inner.key(),
1771 None => first.ty.key(),
1772 };
1773 check_declared_target(func, &takes, expected)?;
1774 Ok(f.ret.clone())
1775}
1776
1777/// Whether the value sitting at `path_prefix` is the plan's **to give away**:
1778/// the root of an owned plan, or a field of a value form that consumed its
1779/// value and is reached by a movable run of field steps.
1780///
1781/// Consulted where a leaf's `out_ty` is chosen, so the ownership decision is
1782/// made ONCE, in the plan, rather than re-derived by each emitter — a leaf
1783/// whose `out_ty` is the owned type is boxed by move, one whose `out_ty` is a
1784/// borrow is cloned through the borrowed-opaque converter.
1785fn place_is_owned(hoists: &[Hoist], path_prefix: &[PathStep], by_ref: bool) -> bool {
1786 if path_prefix.is_empty() {
1787 return !by_ref;
1788 }
1789 hoists
1790 .iter()
1791 .filter(|h| h.prefix.len() <= path_prefix.len() && path_prefix.starts_with(&h.prefix))
1792 .max_by_key(|h| h.prefix.len())
1793 .is_some_and(|h| h.consuming && steps_are_movable(&path_prefix[h.prefix.len()..]))
1794}
1795
1796/// Whether an accessor takes its receiver **by value** — a *consuming* value
1797/// form, which destroys the object into its parts instead of cloning them out
1798/// of a borrow.
1799///
1800/// Asked separately because [`accessor_signature`] peels the `&` in order to
1801/// compare target types, so `f(v: T)` and `f(v: &T)` are indistinguishable
1802/// there by design.
1803fn accessor_consumes<M>(registry: &Registry<M>, func: &syn::Ident) -> bool {
1804 registry
1805 .flat()
1806 .function(&func)
1807 .and_then(|f| f.params.first())
1808 .is_some_and(|p| p.ty.borrow_target().is_none())
1809}
1810
1811/// The shared mismatch, in this direction's vocabulary: an output accessor is
1812/// declared to **take** the type the deconstructor decomposes.
1813impl From<crate::declared_target::TargetMismatch> for UnfoldError {
1814 fn from(m: crate::declared_target::TargetMismatch) -> Self {
1815 UnfoldError::AccessorTargetMismatch {
1816 accessor: m.func,
1817 takes: m.actual,
1818 expected: m.expected,
1819 }
1820 }
1821}
1822
1823#[cfg(test)]
1824mod tests;