Skip to main content

prebindgen_registry/registry/
mod.rs

1//! Which type conversions a binding needs, and whether it has them all.
2//!
3//! # The boundary
4//!
5//! [`Flat`](prebindgen_flat::flat::Flat) describes the source Rust code. A binding
6//! puts a wrapper on each side of an FFI boundary — generated Rust that the
7//! destination language can call, and destination-language code shaped to match
8//! it (`#[repr(C)]` structs and a C header; JNI externs and Kotlin classes).
9//!
10//! ```text
11//!    source flat API              generated wrapper            destination
12//!    (idiomatic Rust)                                            language
13//!    ────────────────             ─────────────────            ────────────
14//!    fn ledger_filed(&Ledger) ──► #[no_mangle] extern fn  ◄──►  external fun
15//!         -> Option<Report>         (jlong) -> jlong             fun filed(): Report?
16//!                                       ▲
17//!                                       └── the boundary: the WIRE
18//!                                           jlong / jint / jobject  (JNI)
19//!                                           *const T / size_t       (C)
20//! ```
21//!
22//! The wrapper's **body** speaks source Rust; its **signature** speaks wire. The
23//! translation between the two is a *conversion*, and collecting them is this
24//! module's whole job.
25//!
26//! # What a conversion is
27//!
28//! A [`TypeEntry`]: a `destination` (the wire type), a wire-facing `function`,
29//! and `pre_stages` — the Rust-side stages that compose with it. **A chain, not a
30//! function**, which is how composition works: `Option<Handle>`'s chain embeds
31//! `Handle`'s.
32//!
33//! A composite need not cross whole. `Option<T>` may cross as a `T` carrying a
34//! niche value, as a `(bool, T)` pair, or as leaves delivered separately — which,
35//! is the adapter's choice, and the registry records it so the emitter can call
36//! it by name and the destination side can be written to match.
37//!
38//! Conversions are **directional**, which is why [`Direction`] is half of a
39//! [`Crossing`] rather than a name prefix on two tables. `&str` inbound is a
40//! `jstring` to decode, outbound a `jstring` to allocate, and one direction may
41//! be convertible while the other is not. A callback flips it — `impl
42//! Fn(Sample)` is an *input* whose argument crosses *outbound*.
43//!
44//! # What the registry does
45//!
46//! It **derives** the set, then **checks it is complete**.
47//!
48//! A binding names a surface: these functions, these types, these consts. Far
49//! more types than that must convert — parameter and return types, type
50//! arguments, struct fields, enum payloads, callback arguments in the flipped
51//! direction, and the leaves a decomposed value arrives in. Computing that
52//! closure is the work; completeness is meaningful precisely because the set is
53//! derived here rather than handed over.
54//!
55//! **It never writes a conversion.** It cannot — only a language adapter knows
56//! what a `jlong` handle or a `*const T` is. The registry decides *which* are
57//! needed, asks the adapter for each, and fails naming any that could not be
58//! supplied.
59//!
60//! # In, and out
61//!
62//! | in | |
63//! |---|---|
64//! | the model | [`Flat`](prebindgen_flat::flat::Flat) — what the source offers |
65//! | the crossings | which `(direction, type)` pairs actually cross |
66//! | the decompositions | how a composite crosses in pieces: which leaf crossings that adds, and which whole-value crossing it removes |
67//! | a conversion builder | the [`Prebindgen`] adapter |
68//!
69//! Out: a conversion for every type in the closure — or a failure naming the
70//! ones that must convert and cannot. The emitter then writes the file: the
71//! conversions, and the per-item wrappers that call them.
72//!
73//! # Using a registry
74//!
75//! **Describe it, hand over the answers, read it.** Two types, because those
76//! are two different things: a [`RegistryBuilder`] is still being described,
77//! and a [`Registry`] is finished and answerable.
78//!
79//! ```text
80//!   describe    builder(flat) · export · cross · decompose · depends
81//!      ↓
82//!   the demand  crossings()      → every crossing needing a conversion,
83//!      ↓                           sorted so each type's inners come first
84//!   the answers convert_with(f)  → one call per crossing, in that order
85//!      ↓        conversions(map) → or hand over a map you built yourself
86//!      ↓
87//!   close       build()          → fails naming any reachable crossing with
88//!      ↓                           no conversion
89//!   read        flat · exports · conversion(dir, ty) · decomposition(site) · …
90//! ```
91//!
92//! Most types need no declaring: they are reached by walking a declared
93//! element's signature, and deriving them per **usage** is what keeps an
94//! output-only type from being demanded as an input too. Measured: dropping the
95//! declaration-as-root for every type with a captured body leaves the generated
96//! output byte-identical.
97//!
98//! But a type with **no captured item behind it** — `ptr_class!(zenoh::KeyExpr<'static>)`
99//! on a re-exported foreign type — appears in no signature this model can walk,
100//! so nothing derives it and the declaration is the only statement that it
101//! crosses at all. That is what `cross` is for, and why the input cannot be
102//! elements alone.
103//!
104//! ```ignore
105//! let mut builder = Registry::builder(flat)?;
106//! for name in &self.exported    { builder = builder.export(name); }
107//! for ty in &self.foreign_types { builder = builder.cross(Direction::Output, ty); }
108//!
109//! let registry = builder
110//!     .decompose(self.decompositions())
111//!     // `built` already holds everything this crossing composes from: that is
112//!     // what sorted means.
113//!     .convert_with(|crossing, built, emit| self.convert(crossing, built, emit))?
114//!     .build()?;
115//!
116//! self.emit(&registry, out)   // read-only from here
117//! ```
118//!
119//! Prefer to drive the walk yourself? `crossings()` hands over the same list in
120//! the same order, and `conversions(map)` takes the result — the two compose,
121//! and neither is a second mechanism.
122//!
123//! **Nothing here calls back into the generator** — not by trait hook, and not
124//! by a `next_request`/`supply` pull loop either, which is the same protocol
125//! with the arrow flipped. `convert_with` is not that: the walk finishes before
126//! it returns, the closure is the caller's, and the builder chooses nothing
127//! about when it runs. It is `crossings()` plus a `for` loop, written once.
128//!
129//! What makes a single hand-off possible is the **sort**. The demand's edges
130//! (`immediate_edges` — generic arguments, tuple/reference/slice targets,
131//! declared struct fields, and `impl Fn` arguments with the direction flipped)
132//! are structural, so they are known without asking anyone. Ordering
133//! the closure by them means a generator building `Option<Handle>` already holds
134//! `Handle`, which is why it can work from a flat list instead of being called
135//! back per type. It also means each crossing is offered exactly once: a
136//! generator's `None` says *cannot*, never *not yet*.
137//!
138//! A `None` is not itself a failure. The scan over-approximates deliberately —
139//! every nested position, every declared struct in both directions — so whether
140//! a gap matters is reachability from the exports, which `build` decides.
141//!
142//! The structure covers almost every dependency, because an `Option<T>`
143//! visibly contains a `T`. What it cannot show is one a *declaration* creates —
144//! a `convert!` chaining through a helper's parameter type, or a callback
145//! argument delivered as plan leaves. Those are stated with `depends`, and
146//! getting one wrong is not silent: the conversion that needed the missing one
147//! cannot be built, and `build` names it.
148//!
149//! **Cycles** are the one place the order cannot be honoured: a self-referential
150//! type (`struct Node { next: Option<Box<Node>> }`) has none. `crossings` breaks
151//! such a cycle at its entry, so exactly one member is offered before an inner
152//! it contains. A generator that cannot build it omits it, and it is reported
153//! like any other gap.
154//!
155//! Direction is a **parameter**, never part of a name: [`Direction`] already
156//! carries it, and one `conversion(dir, ty)` cannot drift the way an
157//! `input_`/`output_` pair can — as `required_output_types`, which never grew an
158//! input peer, shows.
159
160use std::collections::{HashMap, HashSet};
161
162use prebindgen::SourceLocation;
163use prebindgen_flat::{flat::Origin, types_util::bare_path_ident};
164
165use crate::{
166    niches::Niches,
167    prebindgen::{Prebindgen, Stage},
168};
169
170mod cell;
171pub(crate) use self::cell::TypeCell;
172mod declare;
173mod error;
174mod model;
175mod order;
176mod run;
177mod scan;
178mod view;
179
180/// The canonical type identity, which the source model owns — re-exported
181/// here because the registry's tables are keyed by it.
182pub use prebindgen_flat::flat::{TypeKey, TypeKeyParseError};
183
184pub use self::{
185    cell::{Direction, TypeEntry},
186    declare::RegistryBuilder,
187    error::{DuplicateNameError, NotExpressibleEntry, ScanError, WriteRustError},
188    view::{Building, Conversions, Crossing},
189};
190
191/// Single owner of everything parsed from the prebindgen source stream.
192///
193/// The metadata parameter `M` is the language adapter's per-converter
194/// extra type, supplied via
195/// [`crate::prebindgen::Prebindgen::Metadata`]. Each
196/// [`TypeEntry`] carries one `M` copied in by the resolver from the
197/// [`crate::prebindgen::ConverterImpl`] that produced it.
198/// Adapters that don't carry extras leave `M = ()`.
199pub struct Registry<M = ()> {
200    /// The parsed model these maps project. Held rather than discarded, so a
201    /// later stage can ask it what a name means through the registry it already
202    /// has — see [`Self::flat`].
203    flat: prebindgen_flat::flat::Flat,
204    /// What the binding declared, pushed in through `RegistryBuilder`'s
205    /// `export` / `export_type` / `cross` / `reference` before its `build`.
206    ///
207    /// Stored rather than asked for: the registry never calls the generator to
208    /// find out what to build. It is also read after resolution — `write`'s
209    /// emission gate is "did the binding declare this item" — so it outlives
210    /// the scan that consumes it.
211    declared: Declared,
212    /// Type tables, one per direction. Each scanned type gets a [`TypeCell`]
213    /// holding what the key names, whether the binding asks for it directly, and
214    /// the conversion once the generator supplies one.
215    ///
216    /// **Crate-internal.** Outside, a table is reached through
217    /// [`Conversions::conversion`] — which is what makes direction a parameter
218    /// rather than half of a field name, and what stops anyone observing a cell
219    /// before `RegistryBuilder::build` has graded it.
220    pub(crate) input_types: HashMap<TypeKey, TypeCell<M>>,
221    pub(crate) output_types: HashMap<TypeKey, TypeCell<M>>,
222
223    /// Resolved constructor-expansion plans, keyed by `(function, parameter)`.
224    /// Filled by [`crate::expand::apply`] before resolution; read
225    /// by language adapters at the parameter-emission site. Empty unless the
226    /// adapter declared expansions.
227    pub(crate) expansion_plans: HashMap<(syn::Ident, syn::Ident), crate::expand::FoldPlan>,
228
229    /// Resolved output-expansion plans, keyed by function ident. Filled by
230    /// [`crate::unfold::apply`] before resolution; read by language
231    /// adapters at the return-emission site. Empty unless the adapter declared
232    /// deconstructors.
233    pub(crate) unfold_plans: HashMap<syn::Ident, crate::unfold::UnfoldPlan>,
234
235    /// Resolved **error**-position expansion plans, keyed by function ident: the
236    /// decomposition of a fallible fn's `Result<_, E>` domain error `E` (from
237    /// `.convert_error` / `.deconstruct_error`). Separate from
238    /// [`Self::unfold_plans`] — a fn may have both an output and an error plan.
239    pub(crate) error_plans: HashMap<syn::Ident, crate::unfold::UnfoldPlan>,
240
241    /// Default decomposition of a **callback argument** type — the `T` of a
242    /// declared fn's `impl Fn(T, …)` parameter — keyed by the bare arg type
243    /// (type-level, fn-independent). Filled by
244    /// [`crate::unfold::apply`] from the type's default
245    /// deconstructor (`by_ref = false`: the trampoline owns the value); read by
246    /// language adapters when emitting the callback trampoline. A type without
247    /// a default deconstructor has no entry and is delivered whole.
248    pub(crate) callback_arg_plans: HashMap<TypeKey, crate::unfold::UnfoldPlan>,
249
250    /// The declaration-default decomposition per deconstructor declaration
251    /// ([`crate::unfold::DeconId`]) — resolved once with
252    /// normalized inputs, independent of using functions and processing
253    /// order. The single source language adapters derive declaration-keyed
254    /// signature artifacts (e.g. generated callback interfaces) from, so
255    /// every function selecting the same declaration sees one signature by
256    /// construction.
257    pub(crate) decon_plans: HashMap<crate::unfold::DeconId, crate::unfold::DeconSpec>,
258}
259
260// Opaque — exists so `Result<Registry, _>::expect_err` works in tests, the way
261// `Generation`'s did before the generators took ownership of the built object.
262impl<M> std::fmt::Debug for Registry<M> {
263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264        f.write_str("Registry(..)")
265    }
266}
267
268impl<M> Registry<M> {
269    /// An empty registry: no model, no items, no types.
270    ///
271    /// **Not public.** A `Registry` is a projection of a [`Flat`](prebindgen_flat::Flat), and one built
272    /// this way projects nothing — [`Self::flat`] would hand a later stage an
273    /// empty model that claims to be this registry's source. Outside this crate
274    /// the entry point is [`Self::builder`], which has a model behind it.
275    /// [`Self::empty`] for the test suite of an out-of-crate adapter.
276    ///
277    /// Gated on the non-default `testing` feature, so the "not public" rule
278    /// above is unchanged for every ordinary build; a fixture that wants a
279    /// registry projecting nothing is the one caller it was never aimed at.
280    #[cfg(any(test, feature = "testing"))]
281    pub fn empty_for_test() -> Self {
282        Self::empty()
283    }
284
285    /// Whether `key` is registered in `dir`, and if so whether the binding
286    /// asked for it directly (the cell's root flag).
287    ///
288    /// Test support, `testing`-gated: it answers the one question a fixture
289    /// asks of a cell without handing out [`TypeCell`], which is the
290    /// registry's storage and no part of the public surface.
291    #[cfg(any(test, feature = "testing"))]
292    pub fn is_root_for_test(&self, dir: Direction, key: &TypeKey) -> Option<bool> {
293        self.type_table(dir).get(key).map(|cell| cell.root)
294    }
295
296    /// Whether `key`'s cell in `dir` has a resolved converter.
297    ///
298    /// Test support, `testing`-gated. The public [`Self::input_entry`] /
299    /// [`Self::output_entry`] answer this from a reading; a fixture that built
300    /// the type itself holds only its identity.
301    #[cfg(any(test, feature = "testing"))]
302    pub fn has_entry_for_test(&self, dir: Direction, key: &TypeKey) -> Option<bool> {
303        self.type_table(dir)
304            .get(key)
305            .map(|cell| cell.entry.is_some())
306    }
307
308    /// The unfold plans built for callback arguments.
309    ///
310    /// Test support, `testing`-gated. [`UnfoldPlan`](crate::unfold::UnfoldPlan)
311    /// is already public, so this exposes no new type — only the map the
312    /// registry keeps them in.
313    #[cfg(any(test, feature = "testing"))]
314    pub fn callback_arg_plans_for_test(&self) -> impl Iterator<Item = &crate::unfold::UnfoldPlan> {
315        self.callback_arg_plans.values()
316    }
317
318    pub(crate) fn empty() -> Self {
319        Self {
320            flat: prebindgen_flat::flat::Flat::default(),
321            declared: Declared::default(),
322            input_types: Default::default(),
323            output_types: Default::default(),
324            expansion_plans: HashMap::new(),
325            unfold_plans: HashMap::new(),
326            error_plans: HashMap::new(),
327            callback_arg_plans: HashMap::new(),
328            decon_plans: HashMap::new(),
329        }
330    }
331}
332
333/// Everything the caller declares about what a binding emits.
334///
335/// **The registry's construction input.** It used to be assembled by calling
336/// twenty-one getters back into the adapter from inside `resolve`, which put
337/// "configuring" and "using" in the same call — and that is what let a converter
338/// read a half-built registry, which is what made `None` ambiguous between
339/// *defer* and *cannot*. The caller fills this first; `resolve` then passes or
340/// fails.
341#[derive(Default)]
342pub(crate) struct Declared {
343    pub(crate) functions: HashSet<syn::Ident>,
344    /// Signature-scanned but not emitted — see [`Prebindgen::helper_functions`].
345    pub(crate) helper_functions: HashSet<syn::Ident>,
346    pub(crate) accessors: HashSet<syn::Ident>,
347    pub(crate) method_receivers: HashMap<syn::Ident, TypeKey>,
348    /// Exported types, each **with the spelling its declaration was written
349    /// with**.
350    ///
351    /// Keyed by identity, because that is what a declaration is looked up by —
352    /// and carrying the `syn::Type` anyway, because the scan needs real tokens
353    /// for these: to `intern` a type that is not yet in any table, and to say
354    /// whether the build script path-qualified it. Recovering those *from the
355    /// key* was the wrong direction — a build script wrote a `syn::Type`, and
356    /// the declaration simply discarded it (#291).
357    pub(crate) types: HashMap<TypeKey, Origin<syn::Type>>,
358    /// Consts to scan and emit, or `None` when the adapter has no const
359    /// declaration mechanism — then every captured const is re-emitted
360    /// verbatim (see the const gate in [`crate::write`]).
361    ///
362    /// The two are identical for the *crossing set* — neither scans anything —
363    /// so this would be a plain `HashSet` if scanning were all it drove. It is
364    /// emission that needs the distinction, which is why the sentinel outlives
365    /// the skip warnings it also used to gate.
366    pub(crate) consts: Option<HashSet<syn::Ident>>,
367    /// Crossings with no `#[prebindgen]` element behind them, each in the one
368    /// direction it actually crosses — see [`Registry::cross`].
369    pub(crate) crossings: Vec<(Direction, syn::Type)>,
370    /// How composites cross in pieces — see [`Registry::decompose`].
371    pub(crate) decompositions: Decompositions,
372    /// Ordering edges no syntax shows — see [`Registry::depends`].
373    pub(crate) edges: Vec<(Crossing, Crossing)>,
374}
375
376/// How a binding's composites cross **in pieces** instead of whole.
377///
378/// One value, pushed once through `RegistryBuilder::decompose`, in place of the five
379/// separate hooks the registry used to call back for (`expansions`,
380/// `deconstructors`, `value_struct_decons`, `sum_decons`,
381/// `leaf_vec_fold_elements`). All five are implemented by one adapter and none
382/// of them ever needed more than the model, which is what makes stating them up
383/// front possible.
384///
385/// The fields are still the five declaration families, because unifying the
386/// plan IRs behind them is its own problem (see issue #223) and pretending
387/// otherwise here would only move the seam. What this settles is *when* they
388/// are stated and *by whom*.
389#[derive(Default)]
390pub struct Decompositions {
391    /// Parameter-side: values built on the Rust side from ingredients that
392    /// cross separately.
393    pub expansions: Option<crate::expand::Expansions>,
394    /// Return/error-side: values delivered as leaves the far side reassembles.
395    pub deconstructors: Option<crate::unfold::Deconstructors>,
396    /// By-value struct decompositions whose leaves the adapter computed.
397    pub value_structs: Vec<crate::unfold::ValueDecon>,
398    /// The selector-carrying sibling: a tag plus one leaf group per
399    /// alternative.
400    pub sums: Vec<crate::unfold::SumDecon>,
401    /// Element types of a `Vec<T>`/`&[T]` delivered element-by-element.
402    pub leaf_vec_elements: Vec<TypeKey>,
403    /// The whole-value crossings these decompositions make unnecessary.
404    ///
405    /// Stated **with** the decompositions rather than beside them: a type
406    /// crosses only in pieces *because* something decomposes it, and once the
407    /// plans are applied its own direct converter is genuinely not needed — for
408    /// a type with no destination representation, not even resolvable.
409    ///
410    /// Carries each declaration's own spelling for the same reason the declared
411    /// types do — these are build-script-authored types the scan diagnoses
412    /// before anything has classified them.
413    pub replaces: HashMap<TypeKey, Origin<syn::Type>>,
414}
415
416#[cfg(test)]
417mod tests;