Skip to main content

prebindgen_registry/registry/
scan.rs

1//! Derive the crossing set: walk what was declared, and register every type
2//! position it reaches.
3//!
4//! Deliberately over-approximating — every nested position, every declared
5//! struct in both directions. What must actually convert is reachability from
6//! the roots, which `order` decides once the graph is complete.
7
8use std::collections::{HashMap, HashSet};
9
10use quote::ToTokens;
11
12use super::*;
13
14/// The canonical `syn::Type` a **declaration** names, off its identity.
15///
16/// A [`TypeKey`] is `canonical_type` already rendered, so re-parsing it yields
17/// the same type `canonical_type(origin.as_syn())` built — without taking the
18/// node. `declared_ty` is a BUILD-SCRIPT declaration reusing `Origin` for a
19/// placeless location — never captured syntax — and `Origin::key` is the answer
20/// it names.
21fn canonical_of(declared_ty: &prebindgen_flat::flat::Origin<syn::Type>) -> syn::Type {
22    syn::parse_str(declared_ty.key().as_str())
23        .expect("a `TypeKey` is a normalized `syn::Type`, so it re-parses")
24}
25
26impl<M> Registry<M> {
27    pub(super) fn scan_declared_items(&mut self, declared: &Declared) -> Result<(), ScanError> {
28        // Source-qualified declared types are a hard error (issue #95). The
29        // key's own normalization already reduced `crate::`/`self::` and std
30        // prelude spellings, so a remaining multi-segment declared path
31        // either qualifies a SOURCE item with its crate name (can never
32        // match — the flat namespace keys are bare) or names a genuinely
33        // foreign type (supported verbatim; warned about below only when it
34        // shadows a captured item's name — the likely-mistake heuristic).
35        //
36        // The two syntax matches below **stay**, and the reason is what they
37        // look at: `declared.types` are types a
38        // *build script author* wrote, and this is a diagnostic about the spelling
39        // they wrote — is it path-qualified, and does its tail shadow a captured
40        // item? No source type is being classified, so there is no element to read
41        // instead; asking the model would answer about a type rather than about the
42        // declaration. This is the "legitimately the adapter's business" case the
43        // integration map (L2, #229) predicts, not a migration still owed.
44        //
45        // It reads the **declaration's own** spelling, canonicalized here. Both
46        // halves of that matter. Normalizing is what the paragraph above relies
47        // on — `crate::Foo` must not read as a qualified path — and it used to
48        // arrive for free because the tokens came out of the key, which is
49        // normalized by construction. Doing it explicitly costs one call and
50        // stops the key from being the source of tokens at all (#291).
51        let mut qualified: Vec<(String, String)> = Vec::new();
52        let mut probed: HashSet<&TypeKey> = HashSet::new();
53        for (key, declared_ty) in declared
54            .types
55            .iter()
56            .chain(declared.decompositions.replaces.iter())
57        {
58            if !probed.insert(key) {
59                continue;
60            }
61            // The canonical form off the declaration's own identity: a
62            // `TypeKey` IS `canonical_type` rendered, so re-parsing it is the
63            // same type this spelled the node to build. `declared_ty` is a
64            // BUILD-SCRIPT declaration reusing `Origin` for a placeless
65            // location — the ledger's documented over-count, and `Origin::key`
66            // is the answer it names.
67            let ty = canonical_of(declared_ty);
68            // Peel one reference level; the qualified head only appears on
69            // path types.
70            let inner = match &ty {
71                syn::Type::Reference(r) => &*r.elem,
72                other => other,
73            };
74            let syn::Type::Path(tp) = inner else { continue };
75            if tp.qself.is_some() || tp.path.segments.len() < 2 {
76                continue;
77            }
78            let head = tp
79                .path
80                .segments
81                .first()
82                .expect("len checked")
83                .ident
84                .to_string();
85            let last = tp.path.segments.last().expect("len checked");
86            if self.flat.source_modules().contains(&head) {
87                qualified.push((key.to_string(), last.to_token_stream().to_string()));
88            } else if self.declares_type(&last.ident) {
89                println!(
90                    "cargo:warning=prebindgen: declared type `{}` is path-qualified, but a \
91                     captured #[prebindgen] item `{}` exists — if you meant the source item, \
92                     declare it by its bare name",
93                    key, last.ident
94                );
95            }
96        }
97        if !qualified.is_empty() {
98            qualified.sort();
99            return Err(ScanError::QualifiedDeclaredTypes { entries: qualified });
100        }
101
102        // Declared-but-missing items are collected across all three loops and
103        // reported together as one hard error (see
104        // [`ScanError::DeclaredNotFound`]).
105        let mut missing: Vec<(&'static str, String)> = Vec::new();
106
107        // Scan declared functions.
108        for ident in &declared.functions {
109            if let Some(func) = self.flat.function(&ident).cloned() {
110                self.scan_fn_signature(&func)?;
111            } else {
112                missing.push(("function", ident.to_string()));
113            }
114        }
115
116        // Helper functions: never emitted, no blanket signature scan (the
117        // adapter registers the specific requirements via
118        // `extra_required_types`) — but they are referenced by name from
119        // adapter declarations, so a missing one is a hard error.
120        for ident in &declared.helper_functions {
121            if self.flat.function(&ident).is_none() {
122                missing.push(("helper function", ident.to_string()));
123            }
124        }
125
126        // Scan declared consts: a const is a nullary source of its type, so
127        // the type is required in the output direction only.
128        for ident in declared.consts.iter().flatten() {
129            // The const's own TYPE, which the element carries. This cloned the
130            // whole `syn::ItemConst` to reach `.ty`.
131            if let Some(ty) = self.flat.constant(&ident).map(|c| c.ty.clone()) {
132                self.intern_reading(Direction::Output, &ty, true);
133            } else {
134                missing.push(("constant", ident.to_string()));
135            }
136        }
137
138        if !missing.is_empty() {
139            missing.sort();
140            return Err(ScanError::DeclaredNotFound { entries: missing });
141        }
142
143        // Declared crossings with no element behind them (a foreign class type,
144        // a synthesized constant's value type), each in its own direction.
145        for (dir, ty) in &declared.crossings {
146            self.intern(*dir, ty, true)?;
147        }
148
149        // Scan declared types. The spelling is the declaration's own — `intern`
150        // needs real tokens for a type that is in no table yet, which is exactly
151        // the case a key cannot answer once it is only an identity (#291).
152        for declared_ty in declared.types.values() {
153            // Canonicalized for the same reason the diagnostic above is: this
154            // is the form the type used to arrive in, and interning the
155            // as-written spelling instead would put a differently-spelled
156            // reading in the cell for the same key.
157            let ty = canonical_of(declared_ty);
158            let mut matched = false;
159            if let Some(ident) = bare_path_ident(&ty) {
160                if let Some(s) = self.flat.struct_type(&ident).cloned() {
161                    self.scan_struct(&s);
162                    self.intern(Direction::Input, &ty, true)?;
163                    self.intern(Direction::Output, &ty, true)?;
164                    matched = true;
165                } else if let Some(e) = self
166                    .flat
167                    .declared_type(&ident)
168                    .filter(|t| {
169                        matches!(
170                            t,
171                            prebindgen_flat::flat::Type::Enum(_)
172                                | prebindgen_flat::flat::Type::Variant(_)
173                        )
174                    })
175                    .cloned()
176                {
177                    self.scan_enum(&e);
178                    self.intern(Direction::Input, &ty, true)?;
179                    self.intern(Direction::Output, &ty, true)?;
180                    matched = true;
181                }
182            }
183            if !matched {
184                // Declared type without an indexed body (e.g.
185                // `ptr_class(ZKeyExpr<'static>)` on a re-exported
186                // foreign type). Still mark required so the resolver
187                // tries to produce a converter for it.
188                self.intern(Direction::Input, &ty, true)?;
189                self.intern(Direction::Output, &ty, true)?;
190            }
191        }
192
193        Ok(())
194    }
195
196    pub(super) fn scan_fn_signature(
197        &mut self,
198        f: &prebindgen_flat::flat::Function,
199    ) -> Result<(), ScanError> {
200        // Mechanical: register every fn-signature type as the user wrote it.
201        // No semantic transformations (no &T→T strip, no ZResult<T>→T strip,
202        // no skip for () / ZResult<()>). The adapter handles structural
203        // wrappers; propagation through `subs` then marks transitive deps
204        // (e.g. &Foo's `&_` converter returns subs=[Foo], so Foo becomes
205        // required).
206        //
207        // The ELEMENT. A signature is a parameter list and a return, both
208        // already classified — so the readings here are the ones `flat`
209        // produced, not ones re-derived by interning a spelling. Two arms this
210        // used to carry are gone with the node: `FnArg::Receiver`, which the
211        // comment below says can never arrive, and `ReturnType::Default`, which
212        // the element normalizes to `TypeKind::Unit`.
213        //
214        // No receiver or non-ident pattern can reach here: a captured item was
215        // refused by the frontend and `from_flat` failed before indexing it, and
216        // a binding-local fn was checked against the same grammar
217        // (`Flat::lower_signature`) when `resolve` synthesized it.
218        for p in &f.params {
219            self.intern_recursive_reading(Direction::Input, &p.ty, true);
220        }
221        self.intern_recursive_reading(Direction::Output, &f.ret, true);
222        Ok(())
223    }
224
225    /// Register a declared struct and every one of its field types.
226    ///
227    /// Takes the **element**: the struct's own type is `Struct::type_ref` — the
228    /// reading the declaration carries — and each field already holds one, so
229    /// nothing here is spelled, keyed or classified on the way in. It used to
230    /// take a `syn::ItemStruct`, rebuild the type from the ident, and walk
231    /// `syn::Fields::Named` to reach types the element had all along.
232    pub(super) fn scan_struct(&mut self, s: &prebindgen_flat::flat::Struct) {
233        // The struct itself can appear in either direction.
234        self.intern_reading(Direction::Input, s.type_ref(), false);
235        self.intern_reading(Direction::Output, s.type_ref(), false);
236
237        for field in &s.fields {
238            self.intern_recursive_reading(Direction::Input, &field.ty, false);
239            self.intern_recursive_reading(Direction::Output, &field.ty, false);
240        }
241    }
242
243    /// Register a declared enum and every payload type its alternatives carry.
244    ///
245    /// The [`Struct`](prebindgen_flat::flat::Struct) twin, and it takes the
246    /// model's split seriously: a fieldless [`Enum`](prebindgen_flat::flat::Enum)
247    /// has no payload to reach, and a [`Variant`](prebindgen_flat::flat::Variant)
248    /// carries its alternatives' fields as readings. Walking `syn`'s `variants`
249    /// could not tell the two apart and had to look at every field to find out.
250    pub(super) fn scan_enum(&mut self, e: &prebindgen_flat::flat::Type) {
251        use prebindgen_flat::flat::Type;
252        let reading = match e {
253            Type::Enum(en) => en.type_ref(),
254            Type::Variant(v) => v.type_ref(),
255            _ => return,
256        };
257        self.intern_reading(Direction::Input, reading, false);
258        self.intern_reading(Direction::Output, reading, false);
259
260        if let Type::Variant(v) = e {
261            for alt in &v.alternatives {
262                for field in &alt.fields {
263                    self.intern_recursive_reading(Direction::Input, &field.ty, false);
264                    self.intern_recursive_reading(Direction::Output, &field.ty, false);
265                }
266            }
267        }
268    }
269
270    /// Register `ty` as a cell in the given direction, then recurse into every
271    /// nested position. `root` applies only to `ty` itself — a nested position is
272    /// never something the binding asked for directly.
273    pub(super) fn register_type_recursive(
274        &mut self,
275        dir: Direction,
276        reading: &prebindgen_flat::flat::TypeRef,
277        root: bool,
278    ) {
279        let mut visited: HashSet<TypeKey> = HashSet::new();
280        self.register_type_inner(dir, reading, root, &mut visited)
281    }
282
283    /// Infallible, and structurally so: every type reached here is a reading —
284    /// the caller's, or one the model already holds for a child — so there is
285    /// nothing left to classify and nothing left to refuse.
286    pub(super) fn register_type_inner(
287        &mut self,
288        dir: Direction,
289        reading: &prebindgen_flat::flat::TypeRef,
290        is_top: bool,
291        visited: &mut HashSet<TypeKey>,
292    ) {
293        let key = reading.key();
294        if !visited.insert(key.clone()) {
295            return; // cycle guard
296        }
297
298        self.ensure_entry(dir, reading, is_top);
299
300        for (child_dir, sub) in self.immediate_edges(dir, &key) {
301            self.register_type_inner(child_dir, &sub, false, visited);
302        }
303    }
304
305    /// Create the cell for `reading` in `dir` if it has none, and mark it a root
306    /// when the binding asked for it directly.
307    ///
308    /// The one place a cell is born, and therefore the one place a type **enters
309    /// the pipeline** — including a spelling the source never wrote, since
310    /// expansion composes those (an `Option<T>` around a `T` it found) and hands
311    /// them straight here via `require_input` / `require_output`.
312    ///
313    /// **The caller's reading is what gets stored.** It is not re-derived from
314    /// the spelling, and that is the point (#281): the reading a caller holds and
315    /// the one `classify` would produce for its spelling are two answers from two
316    /// paths, and nothing was comparing them. Now there is only one answer,
317    /// because there is only one classification.
318    ///
319    /// Which is also why this is **infallible**. It was fallible for exactly one
320    /// reason — `classify` refusing a spelling — and a reading has already been
321    /// through that. Only [`intern`](Self::intern), the door for a spelling
322    /// nobody has classified yet, can still fail.
323    ///
324    /// The model is consulted, never extended: a composed spelling is an
325    /// intermediate in *this binding's* crossing graph, not something the source
326    /// API mentions, so `Flat` stays what the source said while every type the
327    /// pipeline works with has its reading in the table.
328    pub(super) fn ensure_entry(
329        &mut self,
330        dir: Direction,
331        reading: &prebindgen_flat::flat::TypeRef,
332        root: bool,
333    ) {
334        let key = reading.key();
335        // The reading of a given key cannot change, so an existing cell already
336        // holds an equal one — only the root flag can still move.
337        if let Some(cell) = self.type_table_mut(dir).get_mut(&key) {
338            cell.root |= root;
339            return;
340        }
341        self.type_table_mut(dir).insert(
342            key,
343            TypeCell {
344                subject: Box::new(reading.clone()),
345                root,
346                entry: None,
347            },
348        );
349    }
350
351    /// Classify a **spelling** and register it — the one door for a type that
352    /// has no reading yet, and the only fallible way into the table.
353    ///
354    /// Everything the pipeline composes or walks already holds a
355    /// [`TypeRef`](prebindgen_flat::flat::TypeRef) and goes through
356    /// [`ensure_entry`](Self::ensure_entry) instead. What genuinely arrives as
357    /// tokens is a spelling *authored outside the model*: a build script's
358    /// declared crossing, a constant's declared type, a `syn` type the plan
359    /// engines assemble for their own wire shape.
360    ///
361    /// A spelling the grammar refuses is reported by name here, rather than
362    /// becoming a cell that quietly means less than its neighbours.
363    ///
364    /// **`pub(crate)` deliberately**, matching
365    /// `Flat::classify` and the `TypeRef` composers. Classifying a spelling
366    /// *mints a reading*, and #280 sealed that to `api::core`: an adapter under
367    /// `api::lang` must not be able to hand the registry tokens of its own and
368    /// receive a `TypeRef` back. A one-door design that widened the door would
369    /// have re-opened exactly the capability #280 closed — so this must stay no
370    /// wider than the composers it replaces as an entry point.
371    pub(crate) fn intern(
372        &mut self,
373        dir: Direction,
374        ty: &syn::Type,
375        root: bool,
376    ) -> Result<prebindgen_flat::flat::TypeRef, ScanError> {
377        // The registry's own answer first, in EITHER direction — a reading is
378        // direction-free, and a cell that exists already holds the authoritative
379        // one. Classifying anyway would derive a second reading for a key that
380        // has one, which `ensure_entry` would then discard: the same
381        // two-answers-that-never-meet shape this PR removes, surviving as
382        // redundant work rather than as a replaced cell.
383        let key = TypeKey::from_type(ty);
384        if let Some(known) = self
385            .input_types
386            .get(&key)
387            .or_else(|| self.output_types.get(&key))
388            .map(|c| (*c.subject).clone())
389        {
390            self.ensure_entry(dir, &known, root);
391            return Ok(known);
392        }
393        let reading = self
394            .flat
395            .classify(ty)
396            .map_err(|source| ScanError::NotExpressible {
397                entries: vec![NotExpressibleEntry {
398                    name: None,
399                    reason: source.to_string(),
400                    location: SourceLocation::default(),
401                }],
402            })?;
403        self.ensure_entry(dir, &reading, root);
404        Ok(reading)
405    }
406
407    /// [`Self::intern`] for a caller that **already holds the reading**.
408    ///
409    /// `intern` exists to turn a spelling into one: it keys the type, looks for
410    /// a cell, and classifies when there is none. A caller with a reading in
411    /// hand needs none of that — the model already answered, and re-deriving
412    /// would be the "two answers that never meet" shape `intern`'s own comment
413    /// warns about, arriving from the other side.
414    pub(crate) fn intern_reading(
415        &mut self,
416        dir: Direction,
417        reading: &prebindgen_flat::flat::TypeRef,
418        root: bool,
419    ) {
420        let known = self
421            .input_types
422            .get(&reading.key())
423            .or_else(|| self.output_types.get(&reading.key()))
424            .map(|c| (*c.subject).clone());
425        self.ensure_entry(dir, known.as_ref().unwrap_or(reading), root);
426    }
427
428    /// [`Self::intern_recursive`] for a caller that already holds the reading.
429    pub(super) fn intern_recursive_reading(
430        &mut self,
431        dir: Direction,
432        reading: &prebindgen_flat::flat::TypeRef,
433        root: bool,
434    ) {
435        self.intern_reading(dir, reading, root);
436        self.register_type_recursive(dir, reading, root);
437    }
438
439    /// Enumerate the immediate type-graph edges out of `(dir, key)`: the model's
440    /// own children of this type, plus — if `key` names a declared struct or sum —
441    /// the field types of that item.
442    ///
443    /// A callback's argument types flow with `dir.flip()`, because an argument the
444    /// binding *hands to* a callback crosses the other way; everything else
445    /// inherits `dir`. Used by both `register_type_inner` (during scan) and the
446    /// unresolved-descendants BFS in `resolve` (for diagnostics).
447    ///
448    /// **Takes the key, because a key is all it ever used.** This asked for a
449    /// `&syn::Type` and opened by re-keying it, so every caller spelled a key into
450    /// tokens purely so this could undo that — a normalize pass and a token render
451    /// per call, to arrive back where it started. What the walk needs is a table
452    /// lookup, and a table lookup takes an identity (#291).
453    ///
454    /// The children come from [`TypeKind`], not from taking the syntax apart, and
455    /// the difference is load-bearing rather than cosmetic. `&mut MaybeUninit<T>`
456    /// yields `T` — [`borrow_target`](prebindgen_flat::flat::TypeRef::borrow_target)
457    /// sees past the slot — instead of an intermediate `MaybeUninit<T>` that no
458    /// adapter can convert and no table holds. Each edge is still *spelled* from
459    /// the child's own `spell()`, which is what the caller keys the table by.
460    ///
461    /// The reading comes from **this registry's own table**, where `ensure_entry`
462    /// put it before the walk reached this type — so a spelling the binding composed
463    /// is answered exactly like one the source wrote, without asking the model about
464    /// a type it never saw. No cell means the type was never registered, and an
465    /// unregistered type is not part of any crossing to walk.
466    pub(crate) fn immediate_edges(
467        &self,
468        dir: Direction,
469        key: &TypeKey,
470    ) -> Vec<(Direction, prebindgen_flat::flat::TypeRef)> {
471        use prebindgen_flat::flat::TypeKind;
472
473        let mut out: Vec<(Direction, prebindgen_flat::flat::TypeRef)> = Vec::new();
474        if let Some(reading) = self.type_table(dir).get(key).map(|c| &c.subject) {
475            let (children, child_dir): (Vec<&prebindgen_flat::flat::TypeRef>, Direction) =
476                match reading.unwrapped().kind() {
477                    // Through the accessor, not the field: it sees past an
478                    // out-parameter's `MaybeUninit` slot, which is storage rather
479                    // than a type any converter is keyed by.
480                    // `expect`, not a fallible collect: a `Ref` kind always has a
481                    // target, so an empty child list here would mean the accessor
482                    // and the kind disagree — and it would silently truncate the
483                    // graph walk instead of saying so.
484                    TypeKind::Ref { .. } => (
485                        vec![reading
486                            .borrow_target()
487                            .expect("a `Ref` kind has a borrow target")],
488                        dir,
489                    ),
490                    TypeKind::Optional(t)
491                    | TypeKind::Vec(t)
492                    | TypeKind::Slice(t)
493                    | TypeKind::Uninit(t) => (vec![t], dir),
494                    TypeKind::Array { elem, .. } => (vec![elem], dir),
495                    TypeKind::Fallible { ok, err } => (vec![ok, err], dir),
496                    TypeKind::Callback { args } => (args.iter().collect(), dir.flip()),
497                    // A name is a leaf in the type graph: its generic arguments
498                    // belong to the reference, not to a declaration, because no
499                    // declaration takes type parameters. Its *fields* are the
500                    // edges, and they come off the element below.
501                    TypeKind::Named { .. }
502                    | TypeKind::Scalar(_)
503                    | TypeKind::Str
504                    | TypeKind::String
505                    | TypeKind::Unit => (Vec::new(), dir),
506                    // `unwrapped` peeled these off.
507                    TypeKind::Boxed(_) | TypeKind::Cow { .. } => (Vec::new(), dir),
508                };
509            // The child reading itself, not its spelling: it has already been
510            // classified — by the model, or by whoever composed the parent — so
511            // handing back tokens for the caller to re-classify is the discard
512            // this walk exists to avoid (#281).
513            for child in children {
514                out.push((child_dir, child.clone()));
515            }
516        }
517        // A spelling the model **erased wrappers from** depends on the stripped
518        // spelling: whoever converts `Box<T>` does it by delegating to `T`'s own
519        // converter and putting the wrapper back. That is a real edge and the
520        // `kind` walk above cannot see it — `Box<T>` classifies as whatever `T`
521        // is, so the two share a classification and differ only in spelling.
522        //
523        // Without it the dependency existed but the ORDER did not: a converter
524        // that delegates is built in one pass, so it needs its inner already
525        // built, and `subs` says "this is required" rather than "this comes
526        // first". `Box<Payload>` resolved only because some other root's fields
527        // happened to pull `Payload` in earlier — alphabetical luck, which
528        // `Box<ZSample>` did not have.
529        if let Some(cell) = self.type_table(dir).get(key) {
530            let reading = &cell.subject;
531            if !reading.erased_wrappers().is_empty() {
532                let stripped = reading.stripped_key();
533                if stripped != *key {
534                    if let Some(inner) = self.type_table(dir).get(&stripped) {
535                        out.push((dir, (*inner.subject).clone()));
536                    }
537                }
538            }
539        }
540        // A declared type's own fields, read off the element rather than off its
541        // `syn::Fields`: a positional field is an ordinary `Field` there, so the
542        // named-only asymmetry the syntax walk had does not arise. An `Enum` has
543        // no fields and an `Extern` declares none, which is what makes both
544        // contribute nothing here.
545        //
546        // The **name comes from the classification**, not from taking the spelling
547        // apart, and that is what makes a transparent wrapper work: `Box<Node>` is
548        // `Named { id: Node }` — `Box<T>` **is** `T` in this language — so it
549        // reaches `Node`'s fields, where asking the syntax for a bare ident would
550        // have answered `None` and dead-ended the walk.
551        if let Some(name) =
552            self.type_table(dir)
553                .get(key)
554                .and_then(|c| match c.subject.unwrapped().kind() {
555                    TypeKind::Named { id, .. } => Some(id.name.clone()),
556                    _ => None,
557                })
558        {
559            use prebindgen_flat::flat::{Field, Type};
560            let fields: Vec<&Field> = match self.flat.declared_type(name.as_str()) {
561                Some(Type::Struct(s)) => s.fields.iter().collect(),
562                Some(Type::Variant(v)) => v
563                    .alternatives
564                    .iter()
565                    .flat_map(|a| a.fields.iter())
566                    .collect(),
567                Some(Type::Enum(_) | Type::Extern(_)) | None => Vec::new(),
568            };
569            for field in fields {
570                out.push((dir, field.ty.clone()));
571            }
572        }
573        out
574    }
575
576    /// Put a crossing in the table with its conversion already decided — the
577    /// fixture form of "this type crosses, and here is how".
578    ///
579    /// Goes through [`Self::ensure_entry`] rather than building a cell beside it,
580    /// so a fixture table is reached the same way a real one is and a hand-written
581    /// key is held to the same grammar. A test that wants the whole scan builds its
582    /// registry from items instead; this is for the ones that need a specific table
583    /// shape and nothing else.
584    /// Takes the **spelling**, like every other door into the table: interning
585    /// needs real tokens, and a fixture has them — it wrote them (#291).
586    #[cfg(any(test, feature = "testing"))]
587    pub fn insert_crossing(
588        &mut self,
589        dir: Direction,
590        ty: &syn::Type,
591        root: bool,
592        entry: Option<TypeEntry<M>>,
593    ) {
594        self.intern(dir, ty, root).unwrap_or_else(|e| {
595            panic!(
596                "fixture type `{}` is not expressible: {e}",
597                ty.to_token_stream()
598            )
599        });
600        self.type_table_mut(dir)
601            .get_mut(&TypeKey::from_type(ty))
602            .expect("just registered")
603            .entry = entry;
604    }
605
606    /// The reading the scan stored for `ty` — **a lookup, and only a lookup**.
607    ///
608    /// **The registry is the authority on what a type means**, because it is the
609    /// thing that stores readings: `ensure_entry` asks the grammar once when a cell
610    /// is born, and this hands that answer back. `Flat::classify` is its private
611    /// tool, and `ensure_entry` is its only caller.
612    ///
613    /// This used to classify on a miss, which meant there were two sources of
614    /// readings and no way to tell them apart. The fallback fired constantly, and
615    /// on **scalars** — `i64`, `String`, `bool` — which are certainly registered by
616    /// the time a binding is built. That was the tell: the misses were not unknown
617    /// types but an inverted order, [`unfold`](crate::unfold) asking
618    /// about the leaves its caller registers one loop later. Because `classify`
619    /// answered correctly, nothing downstream was wrong and nothing showed it
620    /// (#266). The declarations now carry their own readings, so there is no such
621    /// caller left.
622    ///
623    /// `None` therefore means the type never entered the pipeline — a caller
624    /// asking out of order, not a cache miss to paper over.
625    pub(crate) fn reading(&self, key: &TypeKey) -> Option<prebindgen_flat::flat::TypeRef> {
626        self.input_types
627            .get(key)
628            .or_else(|| self.output_types.get(key))
629            .map(|cell| (*cell.subject).clone())
630    }
631
632    /// Register `reading` (and its nested positions) as a required **input** so
633    /// the resolver produces a converter for it. Used by
634    /// [`crate::expand`] to pull in the leaf types a fold needs.
635    ///
636    /// Takes the **reading**, not its spelling. Every caller already holds one —
637    /// a plan leaf's `ty` — and used to call `.syntax()` on it here, which is the
638    /// discard #281 is about: the registry would then re-classify the tokens and
639    /// store its own answer beside the caller's.
640    pub(crate) fn require_input(&mut self, reading: &prebindgen_flat::flat::TypeRef) {
641        self.register_type_recursive(Direction::Input, reading, true);
642    }
643
644    /// Register `ty` (and its nested positions) as a required **output** so the
645    /// resolver produces a converter for it. The output-side peer of
646    /// [`Self::require_input`]; used by [`crate::unfold`] to pull in
647    /// the leaf types a decomposition delivers.
648    pub(crate) fn require_output(&mut self, reading: &prebindgen_flat::flat::TypeRef) {
649        self.register_type_recursive(Direction::Output, reading, true);
650    }
651
652    /// Register `reading` (and its nested positions) as an **output cell without
653    /// demanding a converter** — a type some plan *names* rather than one that
654    /// crosses.
655    ///
656    /// The third thing a table cell can mean, now said out loud. A cell records
657    /// that a type **entered the pipeline**; `root` records that the binding
658    /// asked for it *directly*; `entry` records that a converter resolved. This
659    /// makes the first without the second, which is exactly what a
660    /// [`SumTag`](crate::unfold::LeafSource::SumTag) selector needs:
661    /// it names *which* sum it chooses between, and that sum has no whole-value
662    /// output converter at all, so requiring one would fail resolution (#282).
663    ///
664    /// **Not [`require_output`](Self::require_output) with a flag.** That one is
665    /// `root = true` by definition — its whole job is to say a converter must
666    /// exist. Registration and demand are separable facts and this is the door
667    /// for the first alone; `ensure_entry`'s `root |= root` means calling it for
668    /// a type the binding did declare cannot weaken anything.
669    pub(crate) fn reference_output(&mut self, reading: &prebindgen_flat::flat::TypeRef) {
670        self.register_type_recursive(Direction::Output, reading, false);
671    }
672
673    /// Drop `ty` from the required-output scan set. The type's table entry is
674    /// left intact (so [`crate::resolve`]'s PASS A still resolves it
675    /// if it can, and emits it when resolved), but a `None` resolution no longer
676    /// counts as an unresolved-required error. Used by
677    /// [`crate::unfold::apply_leaf_vec_folds`]: when a `Vec<T>` /
678    /// `Option<Vec<T>>` return is delivered element-by-element through a fold,
679    /// the whole-collection converter is genuinely not needed — and for a
680    /// `Vec<opaque-handle>` it cannot resolve at all (a `jlong` wire is not
681    /// JObject-shaped), so requiring it would wrongly fail resolution.
682    pub(crate) fn unrequire_output(&mut self, reading: &prebindgen_flat::flat::TypeRef) {
683        self.clear_root(Direction::Output, &reading.key());
684    }
685
686    /// Stop treating `key` as a root. The cell stays, so the resolver still
687    /// fills it if it can — only the demand that it *must* resolve is dropped.
688    ///
689    /// Keyed, because that is genuinely all this needs: un-requiring creates no
690    /// cell and classifies nothing, so it is the one registration-adjacent
691    /// operation with no reading to carry. `unrequire_*` take a `TypeRef` anyway
692    /// — they pair with `require_*`, and a caller holding one should not have to
693    /// know which of the two wants a key.
694    pub(super) fn clear_root(&mut self, dir: Direction, key: &TypeKey) {
695        if let Some(cell) = self.type_table_mut(dir).get_mut(key) {
696            cell.root = false;
697        }
698    }
699
700    /// Direction-indexed read access to the type-resolution tables.
701    pub(crate) fn type_table(&self, dir: Direction) -> &HashMap<TypeKey, TypeCell<M>> {
702        match dir {
703            Direction::Input => &self.input_types,
704            Direction::Output => &self.output_types,
705        }
706    }
707
708    /// Every reading the table holds in one direction.
709    ///
710    /// The adapter-facing view of the type table: a back-end asking what types
711    /// crossed, and in what shape, wants the readings — not the cells they are
712    /// stored in. Handing out the cells instead would make the registry's
713    /// storage part of the public API for the sake of one caller.
714    pub fn readings(
715        &self,
716        dir: Direction,
717    ) -> impl Iterator<Item = &prebindgen_flat::flat::TypeRef> {
718        self.type_table(dir).values().map(|cell| &*cell.subject)
719    }
720
721    /// Direction-indexed mutable access to the type-resolution tables.
722    pub(crate) fn type_table_mut(&mut self, dir: Direction) -> &mut HashMap<TypeKey, TypeCell<M>> {
723        match dir {
724            Direction::Input => &mut self.input_types,
725            Direction::Output => &mut self.output_types,
726        }
727    }
728
729    /// Look up the resolved input entry for `reading`, returning `None` if it
730    /// was never registered or is still unresolved. The returned entry's
731    /// `function.sig.ident` is the converter's call name; `destination` is
732    /// its wire form.
733    ///
734    /// Takes a `TypeRef` for the reason the trait methods do (#284) — and this
735    /// pair matters more than they do, because an **inherent** method wins over
736    /// a trait method on a concrete `Registry`. While these took a spelling they
737    /// were a second door into the table that the trait's signature could not
738    /// close, and every caller with a `Registry` in hand silently used it. The
739    /// same "second door inside the room" that hid `classify` behind
740    /// `Registry::reading` until #267.
741    pub fn input_entry(&self, reading: &prebindgen_flat::flat::TypeRef) -> Option<&TypeEntry<M>> {
742        self.type_table(Direction::Input)
743            .get(&reading.key())?
744            .entry
745            .as_ref()
746    }
747
748    /// Look up the resolved output entry for `reading`. See
749    /// [`Self::input_entry`].
750    pub fn output_entry(&self, reading: &prebindgen_flat::flat::TypeRef) -> Option<&TypeEntry<M>> {
751        self.type_table(Direction::Output)
752            .get(&reading.key())?
753            .entry
754            .as_ref()
755    }
756}