prebindgen_flat/flat/mod.rs
1//! The prebindgen **source language**: one parser from captured records to
2//! [`Element`]s.
3//!
4//! > Naming: `flat` is the *source* side — the flat API a
5//! > `#[prebindgen]` crate may write. The destination adapters (C, JNI) ship
6//! > as the separate `prebindgen-c` / `prebindgen-jni` crates. They are
7//! > opposite ends of the pipeline.
8//!
9//! ```text
10//! Source(s) ──items──> Flat ──Elements──> Registry ──> adapters
11//! raw records parse + indexes classify off `kind`
12//! (syn::Item) validate elements spell with `spell()`
13//! ```
14//!
15//! [`FlatBuilder::source`] folds the first arrow in for the common case, so a build
16//! script names one directory and gets elements; [`FlatBuilder::items`] keeps the
17//! arrow itself, for a stream that needs shaping first.
18//!
19//! # What an element is
20//!
21//! Two things at once, and that pairing is the whole design:
22//!
23//! * a **closed model** — [`TypeKind`], the field list, which of the two enum
24//! shapes an item is — where the type grammar is the accepted Rust syntax and
25//! the element structure is the concept above it;
26//! * one [`Origin`], carrying the **exact syntax** the node was built from and
27//! the source it arrived in.
28//!
29//! The `Origin` is uniform: every node has one, at every level — item,
30//! parameter, field, variant, type, array extent. Some levels know less than
31//! others (a field has no line of its own, so it shares its item's), but the
32//! shape does not change with the level, and no level copies a piece of
33//! provenance down from the one above. That copying is what previously let the
34//! same crate name appear under three field names with two meanings.
35//!
36//! So the rule for every consumer is:
37//!
38//! > **Classify off `kind`, spell with [`spell()`](Origin::spell).**
39//!
40//! And the model enforces it rather than asking. [`Origin`]'s syntax is
41//! private, and every route to it — `spell()`, `as_syn()` — is visible only
42//! inside this crate. Matching a `syn::Type` or `syn::Expr` variant outside
43//! this module is a classifier, and issue #211 says classification lives here
44//! alone; the visibility is what makes that hold rather than a convention
45//! anyone has to remember. Code whose job *is* producing Rust reaches the
46//! syntax through [`Emit`](crate::Emit), which the registry pipeline
47//! (`prebindgen-registry`'s `write_rust`) hands only to the emission
48//! callbacks.
49//!
50//! # What earns a variant
51//!
52//! For a **type**, a Rust form — and nothing else. [`TypeKind`] is the accepted
53//! subset of `syn::Type`, so two spellings are two variants even when every
54//! destination language would treat them alike. Deciding that `&str` and
55//! `String` are both "a string" is a destination's decision, taken in an
56//! adapter, on a reading the model provides:
57//!
58//! | Rust writes | The model says | The reading, where a consumer wants one |
59//! |---|---|---|
60//! | `String`, `str` | [`String`](TypeKind::String), [`Str`](TypeKind::Str) | the adapter's, at its own site |
61//! | `Vec<T>`, `[T]` | [`Vec`](TypeKind::Vec), [`Slice`](TypeKind::Slice) | [`TypeRef::sequence_elem`] — one run of `T` |
62//! | `Box<T>`, `Cow<'_, T>` | [`Boxed`](TypeKind::Boxed), [`Cow`](TypeKind::Cow) | [`TypeRef::unwrapped`] — a `T` either way |
63//! | `&mut MaybeUninit<T>` | `Ref` over [`Uninit`](TypeKind::Uninit) | [`TypeRef::borrow_target`] — the value, not its slot |
64//! | no `->`, `-> ()` | [`TypeKind::Unit`] | the same function |
65//! | `*const T` | *rejected* | a source crate is idiomatic Rust; the adapter owns pointers |
66//!
67//! It buys one property: the syntax is **recoverable from the kind**
68//! (checked, over the whole acceptance corpus, by rebuilding it). Which is
69//! the difference between a slice that rides along because it is exact, and one
70//! the model cannot do without.
71//!
72//! An **element** is not a type, and there the rule is still the concept:
73//!
74//! | Rust writes | The model says | Because |
75//! |---|---|---|
76//! | `struct S;`, `struct S {}` | zero fields | the delimiters are spelling |
77//! | `enum E { A(u8) }` | [`Variant`] | a sum, identified by position |
78//! | `enum E { A = 7 }` | [`Enum`] | a named integer, identified by its value |
79//! | `type X = ..`, `struct X(..)` | [`Extern`] | named here; contents not modelled |
80//!
81//! The two enum shapes are the clearest case of a *concept* splitting where Rust
82//! has one spelling. Both are `enum` and both keep a `syn::ItemEnum`, but a sum's
83//! alternatives are identified by **position** — the mirror an adapter builds
84//! carries no `repr` and numbers its own arms — while a fieldless enum's members
85//! are identified by the **value Rust assigns**, which a C header re-states and a
86//! Kotlin `enum class` entry carries. Neither numbering means anything for the
87//! other shape, so one model covering both would carry a field that is dead in
88//! each direction, and worse than dead: Rust does assign a discriminant to a
89//! sum's alternatives, and using it would be wrong.
90//!
91//! The identities follow the same rule: a nominal type is a [`TypeId`] — a
92//! name — not a `syn::Path`, so nothing downstream has to take a path apart to
93//! learn what a type is. And a name is *all* it is: **a reference carries a
94//! name, the declaration carries the origin**, so the same type never compares
95//! unequal to itself because two source crates mentioned it. The one place a
96//! crate name rides with an identity is [`ConstId`], and that is the const's
97//! *declaring* crate, resolved by lookup — which is exactly what lets an array
98//! extent refuse a const from another source.
99//!
100//! # Why the syntax rides along
101//!
102//! The generated Rust glue is itself a destination artifact, and the only one
103//! that needs syntax fidelity: `B()` must not be re-spelled `B`, `= 0x07` must
104//! not become `= 7`. Carrying the source's own slice is how it gets that —
105//! exactly, and at no modelling cost, so a delimiter and a literal's base need
106//! never become fields.
107//!
108//! For a **type** the slice is no longer where facts go to survive:
109//! [`TypeKind`] keeps the lifetime, the wrapper and the argument it once
110//! dropped, and rebuilding the syntax from it is the round-trip that says
111//! so. What is
112//! left is the reason a slice beats a reconstruction anywhere — it is what the
113//! source wrote, and it is already there.
114//!
115//! # Where acceptance is enforced
116//!
117//! Lowering is **total over the accepted grammar**: a form with no variant in
118//! [`TypeKind`] is a form the language does not accept, so there is no second
119//! acceptance list to drift from it. One rule cannot be stated that way and is
120//! stated in the lowering instead: [`Uninit`](TypeKind::Uninit) is accepted only
121//! directly under a `&mut`, which is a fact about a **position** and not about a
122//! form.
123//!
124//! **Parsing diagnoses; ingestion raises.** Those are two different points, and
125//! the split is what lets one model serve both.
126//!
127//! Parsing never fails on a single item: an item the language cannot express
128//! becomes [`Element::Unsupported`] carrying its diagnosis, and
129//! [`FlatBuilder::build`] returns the model with it in place. Only whole-stream
130//! rules — a duplicate name in the flat namespace — are [`ParseError`]s, because
131//! no declaration can make two items with one name unambiguous. So a consumer
132//! that wants to *inspect* what a source crate marked, refusals included, gets
133//! exactly that from [`Flat::unsupported`].
134//!
135//! `Registry` ingestion is where the diagnoses are raised.
136//! Building a registry from this model **fails if any element is
137//! `Unsupported`** — all of them at once, so a source crate that needs migrating
138//! sees one list rather than one rebuild per item — and it fails before any
139//! adapter declaration is examined. A binding is built against a model the
140//! frontend could read in full, or it is not built.
141//!
142//! No **marked** item passes through verbatim, because a `#[prebindgen]` crate
143//! marks the items that cross the boundary and leaves the supporting code to the
144//! consumer. The proc-macro already enforces that — a `use`, `mod`, `impl` or
145//! `macro_rules!` cannot be marked at all — so the only item kind left that this
146//! module does not model is a `union`, and it is diagnosed like anything else the
147//! language cannot express.
148//!
149//! The one item that *is* re-emitted verbatim is a [`Guard`] — an anonymous
150//! const, which has no address and so cannot be part of an API addressed by name.
151//! Today these are the feature checks [`Source`](prebindgen::Source) injects on its own
152//! behalf. Modelled rather than dropped because this module must be total over
153//! what it is handed, and a separate element so nothing that consumes the API has
154//! to remember to skip it.
155//!
156//! # Declaring a handle
157//!
158//! `#[prebindgen] pub type X = path::To<Thing>;` declares an [`Extern`]: it gives
159//! a foreign or crate-private type a **name in the flat API** without claiming
160//! anything about its contents. That is what makes the API closable — a handle
161//! enters it deliberately rather than by being mentioned — and it is why a
162//! reference can be required to resolve. A marked tuple struct declares the same
163//! thing, since no adapter has ever crossed its fields.
164//!
165//! # Shapes that must be refused rather than approximated
166//!
167//! An [`Element`] holds what it holds: ordinary parameters, a direct return, no
168//! generic binder. A shape with no slot in that structure cannot be *partly*
169//! accepted — the missing piece would simply be dropped, and silently:
170//!
171//! | Shape | Would become | So |
172//! |---|---|---|
173//! | `async fn` | a function returning `()` | the future is dropped and the export's body never runs |
174//! | `fn f(a: u8, ...)` | a function without the tail | the variadic arguments vanish |
175//! | `struct S<T>`, `fn f<T>()`, `struct S<const N: usize>` | `T` as a nominal reference | a parameter is indistinguishable from an item named `T` |
176//!
177//! All three are [`ItemError`]s, carried like any other refusal and raised at
178//! registry ingestion. A
179//! **lifetime** binder is not among them: lifetimes are spelling, and the
180//! spelling already travels. Nor is `impl Trait` in argument position — Rust
181//! calls it an anonymous type parameter, but it is not a binder in the syntax,
182//! so the callback form is untouched.
183
184use std::{fmt, rc::Rc};
185
186use quote::ToTokens;
187
188mod array_len;
189mod element;
190pub mod emit;
191mod key;
192mod origin;
193pub(crate) mod spell;
194pub(crate) mod spelling;
195mod ty;
196
197#[cfg(test)]
198mod tests;
199
200use prebindgen::SourceLocation;
201
202use self::{array_len::ConstIndex, ty::lower_type};
203pub use self::{
204 array_len::{ArrayExtent, ArrayLenReason, ConstId, ExtentSource, UnsupportedArrayLen},
205 element::{
206 Alternative, Constant, Element, Enum, EnumValue, Extern, Field, Function, Guard, Param,
207 Struct, Type, Unsupported, Variant,
208 },
209 key::{TypeKey, TypeKeyParseError},
210 origin::Origin,
211 spelling::{canonical_spelling, canonical_type},
212 ty::{
213 peel_transparent, GenericArg, ScalarKind, TypeId, TypeKind, TypeRef, UnsupportedType,
214 UnsupportedTypeReason, TRANSPARENT_WRAPPERS,
215 },
216};
217
218/// Collects what to parse, then hands over the model.
219///
220/// Carries no configuration about what the language *accepts* — that is a
221/// property of the language, not of the call site. What it carries is **what to
222/// parse**: feed the inputs, then [`build`](Self::build) once.
223///
224/// # Reading a source directory
225///
226/// A build script's whole job, in one expression — the
227/// [`Source`](prebindgen::Source) step included. Pass
228/// `<source_crate>::PREBINDGEN_OUT_DIR`:
229///
230/// ```
231/// # prebindgen::Source::init_doctest_simulate();
232/// use prebindgen_flat::Flat;
233///
234/// let flat = Flat::builder().source("source_ffi").build()?;
235/// assert!(flat.function("test_function").is_some());
236/// assert!(flat.declared_type("TestStruct").is_some());
237/// # Ok::<_, prebindgen_flat::flat::ParseError>(())
238/// ```
239///
240/// # Reading a stream
241///
242/// [`Self::items`] takes any `(syn::Item, SourceLocation)` iterator, so
243/// everything a [`Source`](prebindgen::Source) can express still composes — a group
244/// selection, a renamed dependency, several sources at once. The feeders
245/// accumulate, so mix them freely:
246///
247/// ```
248/// # prebindgen::Source::init_doctest_simulate();
249/// use prebindgen::Source;
250/// use prebindgen_flat::Flat;
251///
252/// // A dependency renamed in Cargo.toml needs the name THIS crate uses, so it
253/// // is configured rather than named by directory.
254/// let helpers = Source::builder("source_ffi").crate_name("helpers").build();
255/// let flat = Flat::builder()
256/// .items(helpers.items_in_groups(&["functions"]))
257/// .build()?;
258/// assert_eq!(flat.functions().count(), 1);
259/// # Ok::<_, prebindgen_flat::flat::ParseError>(())
260/// ```
261///
262/// # Why accumulate, rather than parse each input
263///
264/// The rules that make a parse fail are **whole-stream** rules: one flat
265/// namespace across every ingested crate, one const index an array length may
266/// reach into, one set of source modules to normalize paths against, and every
267/// type reference resolving against every declaration. None can be
268/// decided per input, so every input is in hand before any of it is classified.
269#[derive(Debug, Default, Clone)]
270pub struct FlatBuilder {
271 items: Vec<(syn::Item, SourceLocation)>,
272}
273
274impl FlatBuilder {
275 /// Every `#[prebindgen]` item captured in `dir`.
276 ///
277 /// Sugar for [`Self::items`] over [`Source::items_all`](prebindgen::Source::items_all),
278 /// which is the whole of what a build script normally needs — pass
279 /// `<source_crate>::PREBINDGEN_OUT_DIR`. Reach for a
280 /// [`Source`](prebindgen::Source) directly, and feed it through [`Self::items`],
281 /// only when it needs configuring.
282 ///
283 /// Panics the way [`Source::new`](prebindgen::Source::new) does if `dir` is not
284 /// readable prebindgen output: a build script has nothing to recover with.
285 ///
286 /// ```
287 /// # prebindgen::Source::init_doctest_simulate();
288 /// use prebindgen_flat::Flat;
289 ///
290 /// let flat = Flat::builder().source("source_ffi").build()?;
291 /// assert!(flat.function("test_function").is_some());
292 /// assert!(flat.declared_type("TestStruct").is_some());
293 /// # Ok::<_, prebindgen_flat::flat::ParseError>(())
294 /// ```
295 pub fn source<P: AsRef<std::path::Path>>(self, dir: P) -> Self {
296 let source = prebindgen::Source::new(dir);
297 self.items(source.items_all())
298 }
299
300 /// The same, for a dependency this crate **renames** in `Cargo.toml`.
301 ///
302 /// The origin recorded at capture time is the dependency's real package name,
303 /// which will not resolve from a crate that refers to it by another name.
304 /// `crate_name` is the name *this* crate uses.
305 ///
306 /// Per directory, deliberately: an override on the whole parse could only fix
307 /// one module, and a flat API may layer several sources.
308 pub fn source_named<P: AsRef<std::path::Path>>(
309 self,
310 dir: P,
311 crate_name: impl Into<String>,
312 ) -> Self {
313 let source = prebindgen::Source::builder(dir)
314 .crate_name(crate_name)
315 .build();
316 self.items(source.items_all())
317 }
318
319 /// Add a captured item stream.
320 ///
321 /// The general feeder: any `(syn::Item, SourceLocation)` iterator, so
322 /// item-level selection and multi-source composition stay upstream where
323 /// they already are. Call it as often as needed; the streams accumulate.
324 ///
325 /// ```
326 /// # prebindgen::Source::init_doctest_simulate();
327 /// use prebindgen::Source;
328 /// use prebindgen_flat::Flat;
329 ///
330 /// let source = Source::new("source_ffi");
331 /// let flat = Flat::builder()
332 /// .items(source.items_in_groups(&["structs"]))
333 /// .build()?;
334 /// assert_eq!(flat.types().count(), 1);
335 /// # Ok::<_, prebindgen_flat::flat::ParseError>(())
336 /// ```
337 pub fn items<I>(mut self, items: I) -> Self
338 where
339 I: IntoIterator<Item = (syn::Item, SourceLocation)>,
340 {
341 self.items.extend(items);
342 self
343 }
344
345 /// Parse everything collected so far into the model.
346 ///
347 /// **Transactional**: an `Err` yields no model at all, so a refused stream
348 /// cannot leave a half-built one behind.
349 ///
350 /// Order-independent: source modules are gathered, consts indexed, and every
351 /// item lowered before any reference is resolved — so a type reference, an
352 /// array length and a cross-source mention may each name something declared
353 /// later, in this input or another.
354 pub fn build(self) -> Result<Flat, ParseError> {
355 let mut items = self.items;
356
357 // Pass 0: normalize every item's types to the canonical flat spelling
358 // before a single one is classified. `std::option::Option<T>` is an
359 // `Option`, `source_a::TypeA` is `TypeA`, and `zenoh::Session` is whatever
360 // an alias named it — decisions this module owns, so it must be the one to
361 // see the reduced form. Gathering EVERY module and alias first is what
362 // makes a reference in an earlier item normalize the same as in a later
363 // one.
364 //
365 // The consequence is deliberate and stated on `Origin`: a slice
366 // is the spelling generation must EMIT, which is the normalized one —
367 // the flat namespace is what the generated crate can actually name.
368 let normalization = crate::flat::spelling::Normalization::from_items(&items);
369 for (item, _) in &mut items {
370 crate::flat::spelling::normalize_item_types(item, &normalization);
371 }
372
373 // Pass 1: the consts an array length may name. Unnamed items are
374 // excluded because no length can name one — the same fact that makes
375 // them `Guard`s. This is the one place that tests the spelling rather
376 // than the classification, and it has to: it runs before Pass 2, so no
377 // classification exists yet.
378 let consts = ConstIndex::new(items.iter().filter_map(|(item, loc)| match item {
379 syn::Item::Const(c) if c.ident != "_" => Some((
380 c.ident.to_string(),
381 (*c.expr).clone(),
382 loc.crate_name.clone(),
383 )),
384 _ => None,
385 }));
386
387 // Pass 2: lower, checking the flat namespace as we go.
388 let mut elements: Vec<Element> = Vec::with_capacity(items.len());
389 let mut seen: Vec<(syn::Ident, SourceLocation)> = Vec::new();
390 for (item, loc) in items {
391 let element = lower_item(item, loc, &consts);
392 if let Some(name) = element.name() {
393 if let Some((first_name, first)) = seen.iter().find(|(n, _)| n == name) {
394 return Err(ParseError::DuplicateName(Box::new(DuplicateName {
395 name: first_name.clone(),
396 first: first.clone(),
397 second: element.location().clone(),
398 first_crate: first.crate_name.clone(),
399 second_crate: element.location().crate_name.clone(),
400 })));
401 }
402 seen.push((name.clone(), element.location().clone()));
403 }
404 elements.push(element);
405 }
406
407 // Pass 3: resolve references, now that every declaration is in hand.
408 resolve_references(&mut elements);
409
410 // Indexed after resolution, because refusing an item can change its kind
411 // (a `Type` becomes `Unsupported`) though never its name.
412 let by_name = elements
413 .iter()
414 .enumerate()
415 .filter_map(|(i, e)| e.name().map(|n| (n.to_string(), i)))
416 .collect();
417 // Frozen here, from the captured stream alone. See the field's docs.
418 let mut source_modules: Vec<String> = Vec::new();
419 for element in &elements {
420 if let Some(crate_name) = element.location().crate_name.as_ref() {
421 let module = crate_name.replace('-', "_");
422 if !source_modules.contains(&module) {
423 source_modules.push(module);
424 }
425 }
426 }
427 let mut flat = Flat {
428 elements,
429 by_name,
430 source_modules,
431 by_type: std::collections::HashMap::new(),
432 };
433 for i in 0..flat.elements.len() {
434 flat.index_types_of(i);
435 }
436 Ok(flat)
437 }
438}
439
440/// The flat API: every `#[prebindgen]` item from every ingested source, parsed,
441/// indexed by name, and with every type reference resolved.
442///
443/// # Direct access, not a stream
444///
445/// Names are unique across the whole model — a duplicate is a
446/// [`ParseError::DuplicateName`] — so a name is a complete address, and the
447/// model answers by it. That is what every later stage needs: an adapter asks
448/// what a declared name *is*, rather than scanning a list for it.
449///
450/// # References are already resolved
451///
452/// Every [`TypeKind::Named`] in a surviving element denotes a [`Type`] this model
453/// holds, and [`Self::resolve`] hands it over. An item that named something the
454/// flat API does not declare is [`Element::Unsupported`] with
455/// [`ItemError::UnresolvedType`], exactly like every other refusal — carried
456/// here, raised by `Registry` ingestion.
457///
458/// Resolving here rather than in the adapters is the point of #211: a dangling
459/// name used to surface much later as an unresolved-converter error, from
460/// whichever adapter happened to look first.
461#[derive(Debug, Default)]
462pub struct Flat {
463 /// Source order, so iteration reports items as the sources were fed.
464 elements: Vec<Element>,
465 /// Module name of every **captured** source, in first-seen order (crate
466 /// names, dashes normalized to underscores). The first doubles as the
467 /// default module for a reference with no recorded origin.
468 ///
469 /// Computed once in [`FlatBuilder::build`] and frozen: it is a property of
470 /// the ingested stream, so a binding-local function added later must not
471 /// extend it — that would change which module an unqualified reference
472 /// resolves against.
473 source_modules: Vec<String>,
474 /// Name → position in [`Self::elements`].
475 ///
476 /// A map rather than a scan because every typed accessor and every
477 /// [`Self::resolve`] routes through it, and later stages resolve references
478 /// in a loop — a linear scan would make that quadratic in the size of the
479 /// API. Positions rather than clones, so there is one copy of each element
480 /// and source order stays available.
481 by_name: std::collections::HashMap<String, usize>,
482 /// Normalized type spelling → this module's reading of it.
483 ///
484 /// Every type the API **mentions** — a parameter, a return, a field, a
485 /// constant's type, and everything nested inside those — keyed so a consumer
486 /// holding a `syn::Type` can ask what the frontend made of it without
487 /// lowering it a second time.
488 ///
489 /// A type mentioned in several places keeps the **first mention in element
490 /// order**, a property of the model rather than of ingestion order.
491 ///
492 /// Unlike [`Self::source_modules`] this *is* extended by
493 /// [`Self::add_local_function`]: a binding-local fn's parameter types have
494 /// readings like any others, and a lookup that missed them would report
495 /// "no reading" for one that exists.
496 by_type: std::collections::HashMap<String, TypeRef>,
497}
498
499/// A name a lookup can be performed with.
500///
501/// Exists because callers hold different spellings of the same fact: an adapter
502/// walking captured items has a `syn::Ident`, a resolved reference has the
503/// `String` inside a [`TypeId`], and a test has a literal. One accessor takes all
504/// three rather than each call site converting.
505///
506/// **The conversion is moved, not removed.** `proc_macro2::Ident` hashes by
507/// `to_string()` and offers no borrow as `str`, so an `Ident` lookup allocates
508/// wherever it happens; doing it here keeps `&str` and `&String` callers — among
509/// them the per-edge and per-reference lookups in the scan and the resolver —
510/// allocation-free.
511///
512/// Sealed: what may name an element is the language's business, not a caller's.
513///
514/// ```
515/// # prebindgen::Source::init_doctest_simulate();
516/// use prebindgen_flat::flat::Flat;
517///
518/// let flat = Flat::builder().source("source_ffi").build()?;
519/// let ident = quote::format_ident!("test_function");
520///
521/// // The same element, whichever spelling the caller happens to hold.
522/// assert!(flat.function("test_function").is_some());
523/// assert!(flat.function(&ident).is_some());
524/// # Ok::<_, prebindgen_flat::flat::ParseError>(())
525/// ```
526pub trait Name: sealed::Sealed {
527 /// The name as a string, borrowed when the caller already holds one.
528 fn as_name(&self) -> std::borrow::Cow<'_, str>;
529}
530
531mod sealed {
532 pub trait Sealed {}
533 impl Sealed for str {}
534 impl Sealed for String {}
535 impl Sealed for syn::Ident {}
536 impl<T: ?Sized + Sealed> Sealed for &T {}
537}
538
539impl Name for str {
540 fn as_name(&self) -> std::borrow::Cow<'_, str> {
541 std::borrow::Cow::Borrowed(self)
542 }
543}
544
545impl Name for String {
546 fn as_name(&self) -> std::borrow::Cow<'_, str> {
547 std::borrow::Cow::Borrowed(self)
548 }
549}
550
551impl Name for syn::Ident {
552 fn as_name(&self) -> std::borrow::Cow<'_, str> {
553 std::borrow::Cow::Owned(self.to_string())
554 }
555}
556
557/// So a caller already holding a reference does not have to reborrow.
558impl<T: ?Sized + Name> Name for &T {
559 fn as_name(&self) -> std::borrow::Cow<'_, str> {
560 T::as_name(self)
561 }
562}
563
564impl Flat {
565 /// Start collecting what to parse.
566 pub fn builder() -> FlatBuilder {
567 FlatBuilder { items: Vec::new() }
568 }
569
570 /// Every element, in the order the sources were fed.
571 pub fn elements(&self) -> impl Iterator<Item = &Element> {
572 self.elements.iter()
573 }
574
575 /// The element with this name, whatever kind it is — including an
576 /// [`Element::Unsupported`], which still holds its name against the
577 /// namespace.
578 pub fn element<N: Name + ?Sized>(&self, name: &N) -> Option<&Element> {
579 self.elements
580 .get(*self.by_name.get(name.as_name().as_ref())?)
581 }
582
583 pub fn function<N: Name + ?Sized>(&self, name: &N) -> Option<&Function> {
584 match self.element(name)? {
585 Element::Function(f) => Some(f),
586 _ => None,
587 }
588 }
589
590 /// The type declared under this name.
591 ///
592 /// Named `declared_type` because `type` is a keyword; it is the accessor a
593 /// resolved [`TypeKind::Named`] reference leads to, and [`Self::resolve`] is
594 /// the same lookup taking a [`TypeId`].
595 pub fn declared_type<N: Name + ?Sized>(&self, name: &N) -> Option<&Type> {
596 match self.element(name)? {
597 Element::Type(t) => Some(t),
598 _ => None,
599 }
600 }
601
602 pub fn constant<N: Name + ?Sized>(&self, name: &N) -> Option<&Constant> {
603 match self.element(name)? {
604 Element::Constant(c) => Some(c),
605 _ => None,
606 }
607 }
608
609 pub fn functions(&self) -> impl Iterator<Item = &Function> {
610 self.elements.iter().filter_map(|e| match e {
611 Element::Function(f) => Some(f),
612 _ => None,
613 })
614 }
615
616 pub fn types(&self) -> impl Iterator<Item = &Type> {
617 self.elements.iter().filter_map(|e| match e {
618 Element::Type(t) => Some(t),
619 _ => None,
620 })
621 }
622
623 pub fn constants(&self) -> impl Iterator<Item = &Constant> {
624 self.elements.iter().filter_map(|e| match e {
625 Element::Constant(c) => Some(c),
626 _ => None,
627 })
628 }
629
630 /// The `struct` declared under this name, or `None` for any other shape.
631 ///
632 /// A tuple struct is an [`Extern`] rather than a `Struct`, so this answers
633 /// only for a product of fields that cross the boundary.
634 pub fn struct_type<N: Name + ?Sized>(&self, name: &N) -> Option<&Struct> {
635 match self.declared_type(name)? {
636 Type::Struct(s) => Some(s),
637 _ => None,
638 }
639 }
640
641 /// The `syn::ItemEnum` behind **either** enum shape.
642 ///
643 /// A sum and a C-style enum are different elements — numbered differently
644 /// and consumed as different constructs — but both were spelled `enum` in
645 /// Rust and both keep that item. A consumer re-emitting the source wants the
646 /// item without caring which shape it is; one that acts on the distinction
647 /// reaches for [`Self::declared_type`].
648 // Test-only since S42: `unit_enum`, `payload_enum`, `enum_alternatives` and
649 // `declared_member_names` each ask the model which shape a declared enum is
650 // and get the element that answers, so nothing in a built crate needs the
651 // item. The registry pipeline's own tests (now in the separate
652 // `prebindgen-registry` crate) still exercise it, which is why this is
653 // `pub` rather than `pub(crate)` — see `TypeRef`'s doc for
654 // why that seal is now a convention rather than a compiler check.
655 #[allow(dead_code)]
656 pub fn enum_item<N: Name + ?Sized>(&self, name: &N) -> Option<&syn::ItemEnum> {
657 match self.declared_type(name)? {
658 Type::Variant(v) => Some(v.origin.as_syn()),
659 Type::Enum(e) => Some(e.origin.as_syn()),
660 _ => None,
661 }
662 }
663
664 /// Module name of every captured source, in first-seen order.
665 ///
666 /// The first entry is the default module for a reference with no recorded
667 /// origin. Empty for a hand-built stream that carried no crate stamps.
668 pub fn source_modules(&self) -> &[String] {
669 &self.source_modules
670 }
671
672 /// Every anonymous const, in stream order — **zero or more**.
673 ///
674 /// Not part of the flat API — see [`Guard`] — but ingested with it, and a
675 /// consumer that re-emits the source must re-emit these too.
676 pub fn guards(&self) -> impl Iterator<Item = &Guard> {
677 self.elements.iter().filter_map(|e| match e {
678 Element::Guard(g) => Some(g),
679 _ => None,
680 })
681 }
682
683 /// This module's reading of `ty`, if the flat API mentions that type.
684 ///
685 /// `None` means no captured item and no binding-local fn writes this type —
686 /// it is one the binding invented, and there is nothing for the frontend to
687 /// have decided about it.
688 ///
689 /// The argument is normalized the way [`TypeKey`] does
690 /// before lookup, so an adapter-authored spelling finds the same entry a
691 /// captured one does.
692 pub fn type_ref(&self, ty: &syn::Type) -> Option<&TypeRef> {
693 self.by_type.get(&crate::flat::canonical_spelling(ty))
694 }
695
696 /// This module's reading of `ty` — the index's if the source wrote it, freshly
697 /// lowered if not.
698 ///
699 /// **Answers without remembering, and that is deliberate.** The model is what
700 /// the source said, and stays that way: [`Self::type_ref`]'s index means *every
701 /// type the API mentions*, so growing it with a spelling no source wrote would
702 /// destroy the one thing it is good for. This is the grammar being consulted,
703 /// not the model being extended.
704 ///
705 /// It is therefore **not** the peer of [`Self::add_local_function`], which does
706 /// extend the model — a binding-local `sig!(..)` is an API item, a function the
707 /// binding declares as if it had been marked. A composed type is not an API
708 /// item; it is an intermediate in some binding's crossing graph, and it belongs
709 /// in the table that tracks crossings.
710 ///
711 /// **The scan's entry point, and nowhere else's.** A caller holding an element
712 /// already has the reading — `Function::ret`, `Param::ty`, `Field::ty` are
713 /// `TypeRef`s computed at parse time — and re-deriving one from
714 /// `spell()` is reasoning from the spelling, which is what `origin` is
715 /// not for. This exists for the one case with no element behind it: a type a
716 /// build script declared, or one expansion composed. `ensure_entry` is its
717 /// only caller in the registry pipeline.
718 ///
719 /// Whoever asks is expected to keep the answer. The registry does: a reading is
720 /// taken once when a type-table cell is born, and lives in that cell — and
721 /// `Registry::reading` (in the registry layer above) hands
722 /// back only what is in one, so a second source of readings cannot reappear
723 /// here (#266).
724 ///
725 /// `Err` means the spelling is outside the accepted grammar — a real diagnosis
726 /// about a type the *binding* built, not a cache miss.
727 ///
728 /// **`pub`, not `pub(crate)`.** The registry pipeline that is
729 /// this method's sole legitimate caller now lives in the separate
730 /// `prebindgen-registry` crate, so a module-path seal can no longer express
731 /// "the pipeline, and nothing else" — there is no path inside this crate for
732 /// it to name. The seal is now a documented convention (this doc comment)
733 /// rather than a compiler-enforced one; #280's intent (an adapter must not
734 /// mint a `TypeRef` from tokens of its own) is no longer structurally
735 /// guaranteed and would need a real API (e.g. a sealed trait token minted
736 /// only by `prebindgen-registry`) to restore.
737 pub fn classify(&self, ty: &syn::Type) -> Result<TypeRef, UnsupportedType> {
738 if let Some(indexed) = self.type_ref(ty) {
739 return Ok(indexed.clone());
740 }
741 // Rebuilt rather than kept, for the reason `lower_signature` gives: a
742 // stored index would be a second copy of what `constants()` says.
743 let consts = ConstIndex::new(self.constants().map(|c| {
744 (
745 c.name.to_string(),
746 (*c.origin.as_syn().expr).clone(),
747 c.origin.crate_name().map(str::to_owned),
748 )
749 }));
750 // No file wrote this one; `has_position` already gates what a diagnostic
751 // prints for a positionless location.
752 let at = Rc::new(SourceLocation::default());
753 lower_type(ty, &consts, &at)
754 }
755
756 /// Index every type the element at `pos` writes. Idempotent per key: the
757 /// first mention in element order wins.
758 fn index_types_of(&mut self, pos: usize) {
759 let refs: Vec<TypeRef> = element_type_refs(&self.elements[pos])
760 .into_iter()
761 .flat_map(TypeRef::walk)
762 .cloned()
763 .collect();
764 for ty in refs {
765 self.by_type
766 .entry(crate::flat::canonical_spelling(ty.origin.as_syn()))
767 .or_insert(ty);
768 }
769 }
770
771 /// Every item the language could not express, with its diagnosis.
772 ///
773 /// Present in the model so a consumer can inspect what a source crate marked
774 /// — building a `Registry` from a model holding any of
775 /// these fails, and reports all of them. See the [module docs](self) on where
776 /// acceptance is enforced.
777 pub fn unsupported(&self) -> impl Iterator<Item = &Unsupported> {
778 self.elements.iter().filter_map(|e| match e {
779 Element::Unsupported(u) => Some(u),
780 _ => None,
781 })
782 }
783
784 /// Lower a function signature written outside the captured stream.
785 ///
786 /// For the **one input that does not come through this module**: a binding's
787 /// `local_functions`, whose signatures are written by hand in a build script
788 /// and inserted straight into the registry. Everything else was already
789 /// lowered here, so this exists to keep the grammar decided in one place
790 /// rather than re-checked at the far end.
791 ///
792 /// Grammar only, and it **validates by lowering**: an `Err` is a shape the
793 /// language cannot express, an `Ok` is the element to admit. Whether the types
794 /// it names are *declared* is a whole-model question, settled when the model
795 /// is built, and a binding-local fn may legitimately name types the source
796 /// crate never did.
797 pub fn lower_signature(&self, f: &syn::ItemFn) -> Result<Function, ItemError> {
798 // Rebuilt from the model rather than kept: this runs once per local fn,
799 // and a stored index would be a second copy of what `constants()` says.
800 let consts = ConstIndex::new(self.constants().map(|c| {
801 (
802 c.name.to_string(),
803 (*c.origin.as_syn().expr).clone(),
804 c.origin.crate_name().map(str::to_owned),
805 )
806 }));
807 // A synthesized fn has no captured location, but it does have an origin
808 // crate — the caller supplies it, and `add_local_function` records it.
809 let at = Rc::new(SourceLocation::default());
810 lower_fn(f, &at, &consts)
811 }
812
813 /// Admit a binding-local function: one a build script wrote via `sig!(..)`
814 /// rather than one a source crate marked.
815 ///
816 /// The model is the pipeline's only index, so a function nothing captured
817 /// still has to live here or nothing downstream can find it. `crate_name` is
818 /// the module its generated call qualifies against, stamped onto the
819 /// element's location where [`Element::location`] already looks for it.
820 ///
821 /// Deliberately does **not** extend [`Self::source_modules`]: see that
822 /// field's docs.
823 ///
824 /// `pub`: its caller (`RegistryBuilder::fun`, on a binding-local
825 /// [`fun!`](https://docs.rs/prebindgen-registry/latest/prebindgen_registry/macro.fun.html)
826 /// path) now lives in the separate `prebindgen-registry` crate.
827 pub fn add_local_function(&mut self, mut f: Function, crate_name: String) {
828 f.origin.location = Rc::new(SourceLocation {
829 crate_name: Some(crate_name),
830 ..SourceLocation::default()
831 });
832 self.by_name.insert(f.name.to_string(), self.elements.len());
833 self.elements.push(Element::Function(f));
834 self.index_types_of(self.elements.len() - 1);
835 }
836
837 /// The declaration a reference denotes.
838 ///
839 /// Infallible in practice for any reference reached from a surviving element:
840 /// [`FlatBuilder::build`] made unresolvable references into
841 /// [`ItemError::UnresolvedType`], so what is left resolves.
842 pub fn resolve(&self, id: &TypeId) -> Option<&Type> {
843 self.declared_type(&id.name)
844 }
845}
846
847/// Turn every element that names an undeclared type into an
848/// [`Element::Unsupported`], **transitively**.
849///
850/// Runs once every declaration is in hand, so the order sources were fed in does
851/// not matter and a reference may point forward or across crates.
852///
853/// # Why this iterates
854///
855/// Refusing a type *removes a declaration*, which can strand its dependents:
856///
857/// ```ignore
858/// pub struct Broken { pub field: Missing } // refused: `Missing` undeclared
859/// pub fn use_broken(value: Broken) {} // `Broken` is now gone too
860/// ```
861///
862/// A single pass against a snapshot of the initial declarations would keep
863/// `use_broken`, and [`Flat::resolve`] would then return `None` for its parameter
864/// — breaking the invariant that a surviving element's references all resolve.
865/// So this runs to a fixed point: each round drops the declarations it refused,
866/// and stops when a round refuses nothing. Chains of any length collapse, in
867/// either declaration order, because the set only ever shrinks.
868fn resolve_references(elements: &mut [Element]) {
869 let mut declared: std::collections::HashSet<String> = elements
870 .iter()
871 .filter_map(|e| match e {
872 Element::Type(t) => Some(t.name().to_string()),
873 _ => None,
874 })
875 .collect();
876
877 loop {
878 let mut refused = Vec::new();
879 for (i, element) in elements.iter().enumerate() {
880 if let Some(unresolved) = first_unresolved(element, &declared) {
881 refused.push((i, unresolved));
882 }
883 }
884 if refused.is_empty() {
885 return;
886 }
887 for (i, unresolved) in refused {
888 // A refused type stops being a declaration, which is what lets the
889 // next round see its dependents as unresolved.
890 if let Element::Type(t) = &elements[i] {
891 declared.remove(&t.name().to_string());
892 }
893 let element = &mut elements[i];
894 let name = element.name().cloned();
895 let origin = Origin::new(
896 element.as_syn(),
897 Rc::clone(match element {
898 Element::Function(f) => &f.origin.location,
899 Element::Type(t) => t.location_rc(),
900 Element::Constant(c) => &c.origin.location,
901 Element::Guard(g) => &g.origin.location,
902 Element::Unsupported(u) => &u.origin.location,
903 }),
904 );
905 *element = Element::Unsupported(Unsupported {
906 name,
907 error: Box::new(ItemError::UnresolvedType { name: unresolved }),
908 origin,
909 });
910 }
911 }
912}
913
914/// Every type slot this element writes, outermost only — a parameter, a return,
915/// a field, a constant's type.
916///
917/// The one place the slots are enumerated, so a new element shape is taught to
918/// every consumer at once instead of drifting between them.
919fn element_type_refs(element: &Element) -> Vec<&TypeRef> {
920 let mut refs: Vec<&TypeRef> = Vec::new();
921 match element {
922 Element::Function(f) => {
923 refs.extend(f.params.iter().map(|p| &p.ty));
924 refs.push(&f.ret);
925 }
926 Element::Constant(c) => refs.push(&c.ty),
927 Element::Type(Type::Struct(s)) => refs.extend(s.fields.iter().map(|f| &f.ty)),
928 Element::Type(Type::Variant(v)) => refs.extend(
929 v.alternatives
930 .iter()
931 .flat_map(|a| a.fields.iter().map(|f| &f.ty)),
932 ),
933 // An enum names nothing, an extern hides what it names, a guard is
934 // emitted verbatim so its types are the consumer's business, and an
935 // unsupported item already has a diagnosis worth keeping.
936 Element::Type(Type::Enum(_) | Type::Extern(_))
937 | Element::Guard(_)
938 | Element::Unsupported(_) => {}
939 }
940 refs
941}
942
943/// The first type this element names that the flat API does not declare.
944fn first_unresolved(
945 element: &Element,
946 declared: &std::collections::HashSet<String>,
947) -> Option<String> {
948 element_type_refs(element)
949 .into_iter()
950 .find_map(|r| r.first_unresolved(declared))
951}
952
953/// If `ty` is `impl Fn(T1, T2, ...) + Send + Sync + 'static`, return the `Fn`
954/// argument types in declaration order. Otherwise `None`.
955///
956/// A callback **returns nothing**, and that is checked, not assumed: a written
957/// `-> ()` is the same thing spelled out, and any other return is refused.
958/// [`TypeKind::Callback`] has no slot for one, so accepting `impl Fn() -> u8`
959/// would drop a fact a destination language needs — and drop it silently, which
960/// is worse than the refusal.
961///
962/// The callback grammar, and the language's alone: [`TypeKind::Callback`] is
963/// exactly what this accepts, so acceptance cannot drift from classification.
964/// The registry re-exports it for the consumers that have not migrated yet.
965pub fn extract_fn_trait_args(ty: &syn::Type) -> Option<Vec<syn::Type>> {
966 let syn::Type::ImplTrait(it) = ty else {
967 return None;
968 };
969 let mut args: Option<Vec<syn::Type>> = None;
970 let mut has_send = false;
971 let mut has_sync = false;
972 let mut has_static = false;
973 for bound in &it.bounds {
974 match bound {
975 syn::TypeParamBound::Trait(tb) => {
976 let last = tb.path.segments.last()?;
977 let name = last.ident.to_string();
978 match name.as_str() {
979 "Fn" => {
980 let syn::PathArguments::Parenthesized(p) = &last.arguments else {
981 return None;
982 };
983 match &p.output {
984 syn::ReturnType::Default => {}
985 syn::ReturnType::Type(_, t) if ty::is_unit_type(t) => {}
986 syn::ReturnType::Type(..) => return None,
987 }
988 args = Some(p.inputs.iter().cloned().collect());
989 }
990 "Send" => has_send = true,
991 "Sync" => has_sync = true,
992 _ => return None,
993 }
994 }
995 syn::TypeParamBound::Lifetime(lt) if lt.ident == "static" => has_static = true,
996 _ => return None,
997 }
998 }
999 if has_send && has_sync && has_static {
1000 args
1001 } else {
1002 None
1003 }
1004}
1005
1006/// A rule of the language that no single item can satisfy on its own, and that
1007/// no adapter declaration can excuse.
1008#[derive(Clone, Debug)]
1009pub enum ParseError {
1010 /// Two `#[prebindgen]` items share a name. Names live in one flat namespace
1011 /// across every ingested source crate, so this is ambiguous however the
1012 /// crates are arranged.
1013 DuplicateName(Box<DuplicateName>),
1014}
1015
1016/// The two items of a [`ParseError::DuplicateName`].
1017#[derive(Clone, Debug)]
1018pub struct DuplicateName {
1019 pub name: syn::Ident,
1020 pub first: SourceLocation,
1021 pub second: SourceLocation,
1022 /// The crate each was marked in. A captured file path is crate-relative
1023 /// (both are `src/lib.rs`), so these are the only unambiguous coordinates
1024 /// when two sources collide.
1025 pub first_crate: Option<String>,
1026 pub second_crate: Option<String>,
1027}
1028
1029impl fmt::Display for ParseError {
1030 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1031 match self {
1032 ParseError::DuplicateName(d) => {
1033 let at = |loc: &SourceLocation, krate: &Option<String>| match krate {
1034 Some(k) => format!("{loc} (crate `{k}`)"),
1035 None => loc.to_string(),
1036 };
1037 write!(
1038 f,
1039 "duplicate `#[prebindgen]` name `{}`: first at {}, again at {} — marked items \
1040 share one flat namespace across all source crates",
1041 d.name,
1042 at(&d.first, &d.first_crate),
1043 at(&d.second, &d.second_crate)
1044 )
1045 }
1046 }
1047 }
1048}
1049
1050impl std::error::Error for ParseError {}
1051
1052/// Why one item could not be expressed in the language.
1053///
1054/// Carried by [`Element::Unsupported`] rather than raised at parse time: see
1055/// the [module docs](self) on where acceptance is enforced.
1056#[derive(Clone, Debug)]
1057pub enum ItemError {
1058 /// A `self` receiver. `#[prebindgen]` captures free functions only.
1059 UnsupportedReceiver,
1060 /// A parameter pattern that is not a plain name — `(a, b): (u8, u8)`.
1061 UnsupportedParamPattern { pattern: String },
1062 /// A parameter's type is not in the language.
1063 ParamType {
1064 param: syn::Ident,
1065 source: UnsupportedType,
1066 },
1067 /// A return type is not in the language.
1068 ReturnType { source: UnsupportedType },
1069 /// A named struct field's type is not in the language.
1070 FieldType {
1071 field: syn::Ident,
1072 source: UnsupportedType,
1073 },
1074 /// A variant payload's type is not in the language.
1075 VariantFieldType {
1076 variant: syn::Ident,
1077 /// The field's name, or its position for a tuple variant.
1078 field: String,
1079 source: UnsupportedType,
1080 },
1081 /// A const's type is not in the language.
1082 ConstType { source: UnsupportedType },
1083 /// An `async fn`.
1084 ///
1085 /// The most dangerous shape to accept quietly: [`Function`] has a direct
1086 /// return, so an `async fn ping()` lowers as one returning `()`, and a
1087 /// generated wrapper calls it, drops the future and exports a function whose
1088 /// body never runs.
1089 UnsupportedAsync,
1090 /// A C-variadic tail — `fn f(a: u8, ...)`.
1091 ///
1092 /// [`Function`] holds ordinary parameters only, so the tail would simply be
1093 /// dropped from the signature.
1094 UnsupportedVariadic,
1095 /// A type or const generic parameter on the item.
1096 ///
1097 /// The elements have no generic binder, so a `T` in a field or parameter
1098 /// would lower as [`TypeKind::Named`] — an ordinary nominal reference into
1099 /// the flat namespace, indistinguishable from a real item called `T`. That
1100 /// loses the scoping every downstream resolver needs, and no destination
1101 /// language can express an uninstantiated parameter anyway.
1102 ///
1103 /// A lifetime parameter is *not* this: lifetimes are spelling and already
1104 /// travel in the syntax.
1105 UnsupportedGenericParam {
1106 param: String,
1107 /// `a type parameter` / `a const generic parameter`.
1108 kind: &'static str,
1109 },
1110 /// The item names a type the flat API does not declare.
1111 ///
1112 /// The flat API is closed over its own names: a handle enters it through
1113 /// `#[prebindgen] pub type X = ..`, so a name with no declaration is either a
1114 /// missing marker or a typo. Reporting it here replaces discovering it much
1115 /// later as an unresolved converter, from whichever adapter looked first.
1116 UnresolvedType { name: String },
1117 /// A whole item kind the language does not model — a `union`, a type alias.
1118 ///
1119 /// The proc-macro refuses to mark a `use`, `mod`, `impl` or `macro_rules!`
1120 /// at all, so only the kinds it accepts can reach here.
1121 UnsupportedItemKind { kind: &'static str },
1122}
1123
1124impl fmt::Display for ItemError {
1125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1126 match self {
1127 ItemError::UnsupportedReceiver => write!(
1128 f,
1129 "takes a `self` receiver; `#[prebindgen]` captures free functions only"
1130 ),
1131 ItemError::UnsupportedParamPattern { pattern } => write!(
1132 f,
1133 "parameter pattern `{pattern}` is not a plain name — bind each parameter to one \
1134 identifier"
1135 ),
1136 ItemError::ParamType { param, source } => {
1137 write!(f, "parameter `{param}`: {source}")
1138 }
1139 ItemError::ReturnType { source } => write!(f, "return type: {source}"),
1140 ItemError::FieldType { field, source } => write!(f, "field `{field}`: {source}"),
1141 ItemError::VariantFieldType {
1142 variant,
1143 field,
1144 source,
1145 } => write!(f, "variant `{variant}` field `{field}`: {source}"),
1146 ItemError::ConstType { source } => write!(f, "const type: {source}"),
1147 ItemError::UnsupportedAsync => write!(
1148 f,
1149 "is an `async fn`; the boundary has no way to drive a future, and the generated \
1150 wrapper would drop it and export a function whose body never runs — expose a \
1151 blocking wrapper instead"
1152 ),
1153 ItemError::UnsupportedVariadic => write!(
1154 f,
1155 "has a C-variadic tail, which the prebindgen source language does not model — \
1156 take a slice, or one parameter per value"
1157 ),
1158 ItemError::UnsupportedGenericParam { param, kind } => write!(
1159 f,
1160 "declares `{param}`, {kind}: the prebindgen source language has no generic \
1161 binder, so an uninstantiated parameter is indistinguishable from a nominal type \
1162 of the same name and no destination language can express it — write the \
1163 concrete types, one marked item per instantiation (a newtype is the usual way)"
1164 ),
1165 ItemError::UnresolvedType { name } if name.contains("::") => write!(
1166 f,
1167 "names the type `{name}`, which the flat API does not declare \u{2014} and being \
1168 path-qualified it never could, because marked items live in one flat namespace \
1169 of bare names. Give the type a name here with `#[prebindgen] pub type <Name> = \
1170 {name};` and refer to that"
1171 ),
1172 ItemError::UnresolvedType { name } => write!(
1173 f,
1174 "names the type `{name}`, which the flat API does not declare \u{2014} mark its \
1175 declaration `#[prebindgen]`, or, for a foreign or crate-private type used as a \
1176 handle, give it a name here with `#[prebindgen] pub type {name} = ..;`"
1177 ),
1178 ItemError::UnsupportedItemKind { kind } => write!(
1179 f,
1180 "is {kind}; the prebindgen source language models functions, structs, enums and \
1181 consts — everything else belongs in the consumer crate"
1182 ),
1183 }
1184 }
1185}
1186
1187impl std::error::Error for ItemError {}
1188
1189/// Lower one captured item. Total: every item becomes an element, and an item
1190/// whose contents the language cannot express becomes [`Element::Unsupported`]
1191/// rather than failing the parse.
1192fn lower_item(item: syn::Item, loc: SourceLocation, consts: &ConstIndex) -> Element {
1193 // One captured record is one item, so this is allocated once and shared by
1194 // the item and every node lowered out of it.
1195 let at = Rc::new(loc);
1196 match item {
1197 syn::Item::Fn(f) => match lower_fn(&f, &at, consts) {
1198 Ok(func) => Element::Function(func),
1199 Err(error) => unsupported(f.sig.ident.clone(), syn::Item::Fn(f), &at, error),
1200 },
1201 syn::Item::Struct(s) => match lower_struct(&s, &at, consts) {
1202 Ok(ty) => Element::Type(ty),
1203 Err(error) => unsupported(s.ident.clone(), syn::Item::Struct(s), &at, error),
1204 },
1205 syn::Item::Enum(e) => match lower_enum(&e, &at, consts) {
1206 Ok(ty) => Element::Type(ty),
1207 Err(error) => unsupported(e.ident.clone(), syn::Item::Enum(e), &at, error),
1208 },
1209 // `#[prebindgen] pub type X = path;` DECLARES an opaque type: it gives a
1210 // foreign or crate-private type a name in the flat API, without claiming
1211 // anything about its contents. That is the only way a handle enters the
1212 // API deliberately, and the reason references can be required to resolve.
1213 syn::Item::Type(t) => match reject_generic_params(&t.generics) {
1214 // `Extern` has no binder and no arity, so a generic alias would be
1215 // accepted as one declaration that `Handle<u8>` then resolves against
1216 // — losing exactly the scoped-parameter distinction every other item
1217 // kind refuses. It is also why `MaybeUninit` needed grammar support
1218 // rather than an alias.
1219 Err(error) => unsupported(t.ident.clone(), syn::Item::Type(t), &at, error),
1220 Ok(()) => {
1221 let target = Some(t.ty.to_token_stream().to_string());
1222 Element::Type(Type::Extern(Extern {
1223 name: t.ident.clone(),
1224 target,
1225 origin: Origin::new(syn::Item::Type(t), at),
1226 }))
1227 }
1228 },
1229 // An unnamed const is a `Guard`, not a constant: nothing can name it, so
1230 // it is not part of the API, and several sources' guards coexist because
1231 // none of them has an address to collide on.
1232 syn::Item::Const(c) if c.ident == "_" => Element::Guard(Guard {
1233 origin: Origin::new(c, at),
1234 }),
1235 syn::Item::Const(c) => match lower_type(&c.ty, consts, &at) {
1236 Ok(ty) => Element::Constant(Constant {
1237 name: c.ident.clone(),
1238 ty,
1239 origin: Origin::new(c, at),
1240 }),
1241 Err(source) => unsupported(
1242 c.ident.clone(),
1243 syn::Item::Const(c),
1244 &at,
1245 ItemError::ConstType { source },
1246 ),
1247 },
1248 // An item kind the language does not model. The proc-macro accepts only
1249 // six kinds and the five above cover the rest, so in practice this is a
1250 // `union` — never written by any source crate. It is diagnosed rather
1251 // than carried: a `#[prebindgen]` crate marks what crosses the boundary,
1252 // and the code around that belongs to the consumer.
1253 other => {
1254 let (name, kind) = match &other {
1255 syn::Item::Union(u) => (Some(u.ident.clone()), "a union"),
1256 _ => (None, "an item kind"),
1257 };
1258 unsupported(name, other, &at, ItemError::UnsupportedItemKind { kind })
1259 }
1260 }
1261}
1262
1263fn unsupported(
1264 name: impl Into<Option<syn::Ident>>,
1265 syntax: syn::Item,
1266 at: &Rc<SourceLocation>,
1267 error: ItemError,
1268) -> Element {
1269 Element::Unsupported(Unsupported {
1270 name: name.into(),
1271 error: Box::new(error),
1272 origin: Origin::new(syntax, Rc::clone(at)),
1273 })
1274}
1275
1276/// Refuse a type or const generic parameter, naming the first one found.
1277///
1278/// Lifetimes pass: they say nothing a destination language can act on, and the
1279/// spelling that needs them is already in the syntax — the same call
1280/// [`lower_type`] makes for a lifetime *argument*.
1281fn reject_generic_params(generics: &syn::Generics) -> Result<(), ItemError> {
1282 for param in &generics.params {
1283 let (name, kind) = match param {
1284 syn::GenericParam::Lifetime(_) => continue,
1285 syn::GenericParam::Type(t) => (t.ident.to_string(), "a type parameter"),
1286 syn::GenericParam::Const(c) => (c.ident.to_string(), "a const generic parameter"),
1287 };
1288 return Err(ItemError::UnsupportedGenericParam { param: name, kind });
1289 }
1290 Ok(())
1291}
1292
1293fn lower_fn(
1294 f: &syn::ItemFn,
1295 at: &Rc<SourceLocation>,
1296 consts: &ConstIndex,
1297) -> Result<Function, ItemError> {
1298 // Shapes `Function` has no slot for, and would therefore drop in silence.
1299 if f.sig.asyncness.is_some() {
1300 return Err(ItemError::UnsupportedAsync);
1301 }
1302 if f.sig.variadic.is_some() {
1303 return Err(ItemError::UnsupportedVariadic);
1304 }
1305 reject_generic_params(&f.sig.generics)?;
1306 let mut params = Vec::with_capacity(f.sig.inputs.len());
1307 for input in &f.sig.inputs {
1308 let pt = match input {
1309 syn::FnArg::Receiver(_) => return Err(ItemError::UnsupportedReceiver),
1310 syn::FnArg::Typed(pt) => pt,
1311 };
1312 let syn::Pat::Ident(pat) = &*pt.pat else {
1313 return Err(ItemError::UnsupportedParamPattern {
1314 pattern: pt.pat.to_token_stream().to_string(),
1315 });
1316 };
1317 let name = pat.ident.clone();
1318 let ty = lower_type(&pt.ty, consts, at).map_err(|source| ItemError::ParamType {
1319 param: name.clone(),
1320 source,
1321 })?;
1322 params.push(Param {
1323 name,
1324 ty,
1325 origin: Origin::new(pt.clone(), Rc::clone(at)),
1326 });
1327 }
1328 // An elided return and a written `-> ()` are the same function. The model
1329 // says so once, here, instead of leaving every consumer to normalize one to
1330 // the other — which is what they all do today, in eight separate copies.
1331 let ret = match &f.sig.output {
1332 syn::ReturnType::Default => TypeRef {
1333 kind: TypeKind::Unit,
1334 origin: Origin::new(syn::parse_quote!(()), Rc::clone(at)),
1335 },
1336 syn::ReturnType::Type(_, t) => {
1337 lower_type(t, consts, at).map_err(|source| ItemError::ReturnType { source })?
1338 }
1339 };
1340 Ok(Function {
1341 name: f.sig.ident.clone(),
1342 params,
1343 ret,
1344 origin: Origin::new(f.clone(), Rc::clone(at)),
1345 })
1346}
1347
1348/// Lower a `struct` item to whichever of the two shapes it is.
1349///
1350/// A **tuple struct** is an [`Extern`]: no adapter has ever crossed its fields,
1351/// so they are deliberately not lowered and a field type outside the grammar is
1352/// not an error. Anything else is a product of fields that do cross.
1353fn lower_struct(
1354 s: &syn::ItemStruct,
1355 at: &Rc<SourceLocation>,
1356 consts: &ConstIndex,
1357) -> Result<Type, ItemError> {
1358 reject_generic_params(&s.generics)?;
1359 let fields = match &s.fields {
1360 syn::Fields::Named(named) => {
1361 let mut out = Vec::with_capacity(named.named.len());
1362 for (index, f) in named.named.iter().enumerate() {
1363 let name = f.ident.clone().expect("named fields have idents");
1364 let ty = lower_type(&f.ty, consts, at).map_err(|source| ItemError::FieldType {
1365 field: name.clone(),
1366 source,
1367 })?;
1368 out.push(Field {
1369 name: Some(name),
1370 index,
1371 ty,
1372 origin: Origin::new(f.clone(), Rc::clone(at)),
1373 });
1374 }
1375 out
1376 }
1377 // Its contents are not a boundary surface, so nothing is lowered.
1378 syn::Fields::Unnamed(_) => {
1379 return Ok(Type::Extern(Extern {
1380 name: s.ident.clone(),
1381 // A tuple struct IS the definition; it points at nothing.
1382 target: None,
1383 origin: Origin::new(syn::Item::Struct(s.clone()), Rc::clone(at)),
1384 }));
1385 }
1386 syn::Fields::Unit => Vec::new(),
1387 };
1388 Ok(Type::Struct(Struct {
1389 reading: TypeRef::named(&s.ident),
1390 name: s.ident.clone(),
1391 fields,
1392 origin: Origin::new(s.clone(), Rc::clone(at)),
1393 }))
1394}
1395
1396/// Lower an `enum` item to whichever of the two shapes it is.
1397///
1398/// **The classification**: any alternative with a field makes it a [`Variant`] —
1399/// a sum, numbered by position. Otherwise it is an [`Enum`] — a named set of
1400/// integers, identified by the value Rust assigns. Both are spelled `enum` in
1401/// Rust and both keep the `syn::ItemEnum`; only what a destination language can
1402/// do with them differs, and that is what the model records.
1403///
1404/// `enum E {}` has no alternative carrying anything, so it is the degenerate
1405/// `Enum`.
1406fn lower_enum(
1407 e: &syn::ItemEnum,
1408 at: &Rc<SourceLocation>,
1409 consts: &ConstIndex,
1410) -> Result<Type, ItemError> {
1411 reject_generic_params(&e.generics)?;
1412
1413 if e.variants.iter().any(|v| !v.fields.is_empty()) {
1414 return Ok(Type::Variant(lower_variant(e, at, consts)?));
1415 }
1416 Ok(Type::Enum(lower_c_enum(e, at)))
1417}
1418
1419/// The payload-carrying shape. Position is the only numbering a sum has, so no
1420/// discriminant is evaluated: the mirror an adapter builds numbers its own arms.
1421fn lower_variant(
1422 e: &syn::ItemEnum,
1423 at: &Rc<SourceLocation>,
1424 consts: &ConstIndex,
1425) -> Result<Variant, ItemError> {
1426 let mut alternatives = Vec::with_capacity(e.variants.len());
1427 for (index, v) in e.variants.iter().enumerate() {
1428 let mut fields = Vec::with_capacity(v.fields.len());
1429 for (field_index, f) in v.fields.iter().enumerate() {
1430 let ty =
1431 lower_type(&f.ty, consts, at).map_err(|source| ItemError::VariantFieldType {
1432 variant: v.ident.clone(),
1433 field: match &f.ident {
1434 Some(id) => id.to_string(),
1435 None => field_index.to_string(),
1436 },
1437 source,
1438 })?;
1439 fields.push(Field {
1440 name: f.ident.clone(),
1441 index: field_index,
1442 ty,
1443 origin: Origin::new(f.clone(), Rc::clone(at)),
1444 });
1445 }
1446 alternatives.push(Alternative {
1447 name: v.ident.clone(),
1448 index,
1449 fields,
1450 origin: Origin::new(v.clone(), Rc::clone(at)),
1451 });
1452 }
1453 Ok(Variant {
1454 reading: TypeRef::named(&e.ident),
1455 name: e.ident.clone(),
1456 alternatives,
1457 origin: Origin::new(e.clone(), Rc::clone(at)),
1458 })
1459}
1460
1461/// The fieldless shape. Nothing here can fail to lower — there are no field
1462/// types — so an unevaluable discriminant ends the numeric chain rather than
1463/// refusing the item.
1464fn lower_c_enum(e: &syn::ItemEnum, at: &Rc<SourceLocation>) -> Enum {
1465 let mut values = Vec::with_capacity(e.variants.len());
1466 // Rust's own numbering rule: an explicit `= N` sets the value, an implicit
1467 // one takes the previous plus one, starting at 0.
1468 let mut next: Option<i64> = Some(0);
1469 for (index, v) in e.variants.iter().enumerate() {
1470 let discriminant = match v.discriminant.as_ref() {
1471 Some((_, expr)) => int_literal(expr),
1472 None => next,
1473 };
1474 // `checked_add`: a discriminant at the top of the range is valid Rust
1475 // (`#[repr(u64)] enum E { A = i64::MAX as u64, B }`), so running out of
1476 // `i64` ends the numeric chain exactly as an unevaluable spelling does.
1477 // The spelling is untouched either way — it is in `EnumValue::origin`.
1478 next = discriminant.and_then(|n| n.checked_add(1));
1479
1480 values.push(EnumValue {
1481 name: v.ident.clone(),
1482 index,
1483 discriminant,
1484 origin: Origin::new(v.clone(), Rc::clone(at)),
1485 });
1486 }
1487 Enum {
1488 reading: TypeRef::named(&e.ident),
1489 name: e.ident.clone(),
1490 values,
1491 origin: Origin::new(e.clone(), Rc::clone(at)),
1492 }
1493}
1494
1495/// Pull a signed integer out of a literal expression (`5`, `-3`, `0x07`).
1496/// `None` for anything else — a `const`, a path, arithmetic.
1497fn int_literal(expr: &syn::Expr) -> Option<i64> {
1498 i64::try_from(int_literal_wide(expr)?).ok()
1499}
1500
1501/// [`int_literal`] before the range check.
1502///
1503/// The magnitude is parsed **wider than the result** so the sign can be applied
1504/// first: `-9223372036854775808` is `i64::MIN` and a valid Rust discriminant,
1505/// but its magnitude is one past `i64::MAX`, so parsing the digits as `i64`
1506/// would reject the whole literal. A magnitude too large for `i128` fails here
1507/// and is reported as an unevaluable discriminant, which is the existing
1508/// contract for anything the frontend cannot reduce to a number.
1509fn int_literal_wide(expr: &syn::Expr) -> Option<i128> {
1510 match expr {
1511 syn::Expr::Lit(lit) => match &lit.lit {
1512 syn::Lit::Int(int) => int.base10_parse::<i128>().ok(),
1513 _ => None,
1514 },
1515 syn::Expr::Unary(syn::ExprUnary {
1516 op: syn::UnOp::Neg(_),
1517 expr,
1518 ..
1519 }) => int_literal_wide(expr).map(|v| -v),
1520 _ => None,
1521 }
1522}