Skip to main content

prebindgen_registry/
lib.rs

1//! # prebindgen-registry
2//!
3//! Registry-based, **language-agnostic** converter pipeline for
4//! [`prebindgen`](https://docs.rs/prebindgen). This crate turns a stream of
5//! `#[prebindgen]` items — read through [`Source`](::prebindgen::Source) and
6//! parsed by [`flat::Flat`] — into generated Rust FFI bindings plus a fully
7//! resolved table of type converters. It has no knowledge of any particular
8//! destination language — C, JNI/Kotlin, Swift, Python, etc. all plug in the
9//! same way.
10//!
11//! It also re-exports the flat model (`flat`, `shape`, `types_util`) from
12//! the separate [`prebindgen-flat`](https://docs.rs/prebindgen-flat) crate, so
13//! a language adapter names one crate root for the whole pipeline rather than
14//! reaching back into `prebindgen-flat` for half of it.
15//!
16//! # The plug-in point
17//!
18//! Write one generator per destination language. It does two things:
19//!
20//! * **Says how the language represents Rust types on the wire** — it builds a
21//!   [`ConverterImpl`] (a generated converter fn plus its wire type) for each
22//!   crossing the registry hands it, and gives them all back through
23//!   `RegistryBuilder::convert_with`.
24//! * **Emits the wrapper code per item** — `on_function` / `on_struct` /
25//!   `on_enum` / `on_const` on the [`Prebindgen`] trait.
26//!
27//! Everything language-specific that must travel through the pipeline rides in
28//! the back-end's chosen [`Metadata`](Prebindgen::Metadata) type (a JNI
29//! back-end's Kotlin class names and exception info, a C back-end's header
30//! names, …). It is set on each converter, propagated into the registry's
31//! [`TypeEntry`], and read back by the back-end's own emitter — no side
32//! channels. Back-ends needing no extras leave it at the default `()`.
33//!
34//! # Flow
35//!
36//! A build script sees one type — the generator — and never names a `Flat` or a
37//! `Registry`:
38//!
39//! ```ignore
40//! let jni = JniGen::builder()
41//!     .package(package!("io.zenoh"))
42//!     .fun(fun!(session_open))
43//!     .source(zenoh_flat::PREBINDGEN_OUT_DIR)
44//!     .build()?;
45//! jni.write_rust(&rust_dest)?;
46//! jni.write_kotlin(&kotlin_root)?;
47//! ```
48//!
49//! Inside `build()`, the generator does what it alone knows how to do:
50//!
51//! 1. [`flat::Flat::builder`] parses the declared sources into the model, and
52//!    [`Registry::builder`] starts describing a binding over it.
53//! 2. The generator states that binding, then [`RegistryBuilder::crossings`]
54//!    hands over every crossing needing a conversion — inner types first, so
55//!    each one can be built from those already done. `convert_with` answers
56//!    them and `build` names any gap.
57//! 3. The resolved registry becomes a field of the built generator, whose
58//!    `write_*` methods emit the artifacts — Rust wrappers, and whatever else
59//!    that language needs (a C header, Kotlin sources, …).
60//!
61//! # Universality, by example
62//!
63//! The same machinery serves very different languages:
64//!
65//! * **C / cbindgen back-end** (the separate `prebindgen-c` crate): wire types
66//!   are raw pointers and primitive C types; converters are thin transmutes;
67//!   `pre_stages` are usually empty (errors surface as return codes).
68//! * **JNI / Kotlin back-end** (the separate `prebindgen-jni` crate): wire
69//!   types are JNI handles (`jlong`, `JObject`); converters marshal across the
70//!   JVM boundary; `pre_stages` carry fallible steps whose `Err` arms throw
71//!   JVM exceptions (the exception info lives in that back-end's `Metadata`).
72//!
73//! # Macros
74//!
75//! The declaration surface is built almost entirely from exported macros. This
76//! crate defines the language-neutral ones — the domain vocabulary shared by
77//! every adapter, plus the syntax helpers they're built from:
78//!
79//! - Members & constants: [`fun!`](crate::fun)
80//! - Conversions: [`convert!`](crate::convert), [`from!`](crate::from),
81//!   [`try_from!`](crate::try_from), [`into!`](crate::into),
82//!   [`try_into!`](crate::try_into)
83//! - Boundary expansion: [`expand_param!`](crate::expand_param),
84//!   [`expand_return!`](crate::expand_return), [`fields!`](crate::fields)
85//!
86//! **Syntax helpers** produce a bare `syn` node — `Type` / `Path` / `Expr` /
87//! `Signature` / `Ident` — to hand to a declaration method that requires one.
88//! They exist only to sidestep `syn::parse_quote!`'s type-inference ambiguity
89//! (E0283) in a generic argument position, not to express a domain concept:
90//! [`ty!`](crate::ty), [`path!`](crate::path), [`expr!`](crate::expr),
91//! [`sig!`](crate::sig), [`ident!`](crate::ident).
92//!
93//! The JNI/Kotlin-specific declaration macros — `package!`, `ptr_class!`,
94//! `data_class!`, `enum_class!`, `sealed_class!`, `variant!`, `constant!` —
95//! construct a typed `*Decl` for the Kotlin surface and live in the separate
96//! `prebindgen-jni` crate, which hands the result to its `JniGenBuilder`.
97
98pub mod decl;
99pub(crate) mod declared_target;
100mod destination;
101pub mod diagnostics;
102pub mod domain;
103pub mod expand;
104pub mod niches;
105pub mod prebindgen;
106pub mod registry;
107pub(crate) mod resolve;
108#[cfg(test)]
109pub(crate) mod test_util;
110pub mod unfold;
111pub mod write;
112
113/// The flat model itself lives in the separate `prebindgen-flat` crate —
114/// re-exported here so an adapter names one crate root for the whole
115/// pipeline.
116pub use ::prebindgen_flat::{flat, shape, types_util};
117pub use ::prebindgen_flat::{Element, Emit, Flat};
118
119pub use self::{
120    decl::{
121        ConvertDecl, ConvertSourceDecl, ConvertSpec, ExpandDecl, ExpandParamDecl, ExpandReturnDecl,
122        FieldsDecl, FunctionDecl, LocalField, LocalVariant,
123    },
124    diagnostics::{warn_unclaimed, Claimed},
125    domain::{DomainScalar, RepresentationDomain, ScalarValue},
126    niches::{NicheSlot, Niches},
127    prebindgen::{ConverterImpl, NamePredicate, Prebindgen, Stage},
128    registry::{
129        Building, Conversions, Crossing, Decompositions, Direction, DuplicateNameError,
130        NotExpressibleEntry, Registry, RegistryBuilder, ScanError, TypeEntry, TypeKey,
131        TypeKeyParseError, WriteRustError,
132    },
133};
134
135/// Not part of the public API — referenced by the [`ident!`] macro expansion
136/// so callers don't need their own `proc-macro2` dependency just to build a
137/// `Span`, by this crate's own decl macros (`fun!`, `convert!`, …), and by the
138/// `prebindgen-jni` crate's JNI/Kotlin decl macros (`ptr_class!`, `package!`,
139/// …) to parse a bare type token into a concrete `syn::Type`. `pub` (rather
140/// than `pub(crate)`) for exactly that cross-crate macro-expansion reason,
141/// despite `#[doc(hidden)]`.
142#[doc(hidden)]
143pub mod __macro_support {
144    pub use proc_macro2;
145
146    pub fn parse_type(s: &str) -> ::syn::Type {
147        ::syn::parse_str(s).unwrap_or_else(|e| panic!("prebindgen: invalid type `{s}`: {e}"))
148    }
149
150    pub fn parse_path(s: &str) -> ::syn::Path {
151        ::syn::parse_str(s).unwrap_or_else(|e| panic!("prebindgen: invalid path `{s}`: {e}"))
152    }
153
154    pub fn parse_expr(s: &str) -> ::syn::Expr {
155        ::syn::parse_str(s).unwrap_or_else(|e| panic!("prebindgen: invalid expression `{s}`: {e}"))
156    }
157
158    /// Parse a `sig!((params) -> Ret)` body: `s` is the token text between
159    /// the macro's outer parens plus the optional `-> Ret` tail, e.g.
160    /// `"(s: & Summary, verbose: bool) -> String"`. Wrapped into a full fn
161    /// item signature under a placeholder name (replaced by the declaring
162    /// decl's fn ident at synthesis time).
163    pub fn parse_signature(s: &str) -> ::syn::Signature {
164        let full = format!("fn __sig {s}");
165        ::syn::parse_str::<::syn::ItemFn>(&format!("{full} {{ unimplemented!() }}"))
166            .map(|f| f.sig)
167            .unwrap_or_else(|e| panic!("prebindgen: invalid signature `sig!({s})`: {e}"))
168    }
169}
170
171/// Build a `syn::Ident` from a bare identifier token. Unlike
172/// `syn::parse_quote!`, this always yields the concrete type `syn::Ident` —
173/// there's no external context needed to infer it — so it can be passed
174/// directly into a generic `impl Into<T>` parameter without hitting rustc's
175/// "type annotations needed" ambiguity. `syn::parse_quote!`'s output type
176/// has to be pinned by a *concrete* parameter type to infer successfully; a
177/// generic `impl Into<T>` bound doesn't give it anything to unify against.
178///
179/// This is what powers the [`fun!`](crate::fun) decl macro — see that macro
180/// (and the `prebindgen-jni` crate's `ptr_class!`/`enum_class!`/`data_class!`,
181/// which apply the same trick to `syn::Type`) for the primary way this
182/// crate's builders are fed bare Rust names today.
183///
184/// ```
185/// let _: syn::Ident = prebindgen_registry::ident!(z_thing_name);
186/// ```
187#[macro_export]
188macro_rules! ident {
189    ($name:ident) => {
190        ::syn::Ident::new(
191            stringify!($name),
192            $crate::__macro_support::proc_macro2::Span::call_site(),
193        )
194    };
195}