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}