Skip to main content

Module flat

Module flat 

Source
Expand description

The prebindgen source language: one parser from captured records to Elements.

Naming: flat is the source side — the flat API a #[prebindgen] crate may write. The destination adapters (C, JNI) ship as the separate prebindgen-c / prebindgen-jni crates. They are opposite ends of the pipeline.

Source(s) ──items──> Flat ──Elements──> Registry ──> adapters
  raw records          parse +               indexes       classify off `kind`
  (syn::Item)          validate              elements      spell with `spell()`

FlatBuilder::source folds the first arrow in for the common case, so a build script names one directory and gets elements; FlatBuilder::items keeps the arrow itself, for a stream that needs shaping first.

§What an element is

Two things at once, and that pairing is the whole design:

  • a closed modelTypeKind, the field list, which of the two enum shapes an item is — where the type grammar is the accepted Rust syntax and the element structure is the concept above it;
  • one Origin, carrying the exact syntax the node was built from and the source it arrived in.

The Origin is uniform: every node has one, at every level — item, parameter, field, variant, type, array extent. Some levels know less than others (a field has no line of its own, so it shares its item’s), but the shape does not change with the level, and no level copies a piece of provenance down from the one above. That copying is what previously let the same crate name appear under three field names with two meanings.

So the rule for every consumer is:

Classify off kind, spell with spell().

And the model enforces it rather than asking. Origin’s syntax is private, and every route to it — spell(), as_syn() — is visible only inside this crate. Matching a syn::Type or syn::Expr variant outside this module is a classifier, and issue #211 says classification lives here alone; the visibility is what makes that hold rather than a convention anyone has to remember. Code whose job is producing Rust reaches the syntax through Emit, which the registry pipeline (prebindgen-registry’s write_rust) hands only to the emission callbacks.

§What earns a variant

For a type, a Rust form — and nothing else. TypeKind is the accepted subset of syn::Type, so two spellings are two variants even when every destination language would treat them alike. Deciding that &str and String are both “a string” is a destination’s decision, taken in an adapter, on a reading the model provides:

Rust writesThe model saysThe reading, where a consumer wants one
String, strString, Strthe adapter’s, at its own site
Vec<T>, [T]Vec, SliceTypeRef::sequence_elem — one run of T
Box<T>, Cow<'_, T>Boxed, CowTypeRef::unwrapped — a T either way
&mut MaybeUninit<T>Ref over UninitTypeRef::borrow_target — the value, not its slot
no ->, -> ()TypeKind::Unitthe same function
*const Trejecteda source crate is idiomatic Rust; the adapter owns pointers

It buys one property: the syntax is recoverable from the kind (checked, over the whole acceptance corpus, by rebuilding it). Which is the difference between a slice that rides along because it is exact, and one the model cannot do without.

An element is not a type, and there the rule is still the concept:

Rust writesThe model saysBecause
struct S;, struct S {}zero fieldsthe delimiters are spelling
enum E { A(u8) }Varianta sum, identified by position
enum E { A = 7 }Enuma named integer, identified by its value
type X = .., struct X(..)Externnamed here; contents not modelled

The two enum shapes are the clearest case of a concept splitting where Rust has one spelling. Both are enum and both keep a syn::ItemEnum, but a sum’s alternatives are identified by position — the mirror an adapter builds carries no repr and numbers its own arms — while a fieldless enum’s members are identified by the value Rust assigns, which a C header re-states and a Kotlin enum class entry carries. Neither numbering means anything for the other shape, so one model covering both would carry a field that is dead in each direction, and worse than dead: Rust does assign a discriminant to a sum’s alternatives, and using it would be wrong.

The identities follow the same rule: a nominal type is a TypeId — a name — not a syn::Path, so nothing downstream has to take a path apart to learn what a type is. And a name is all it is: a reference carries a name, the declaration carries the origin, so the same type never compares unequal to itself because two source crates mentioned it. The one place a crate name rides with an identity is ConstId, and that is the const’s declaring crate, resolved by lookup — which is exactly what lets an array extent refuse a const from another source.

§Why the syntax rides along

The generated Rust glue is itself a destination artifact, and the only one that needs syntax fidelity: B() must not be re-spelled B, = 0x07 must not become = 7. Carrying the source’s own slice is how it gets that — exactly, and at no modelling cost, so a delimiter and a literal’s base need never become fields.

For a type the slice is no longer where facts go to survive: TypeKind keeps the lifetime, the wrapper and the argument it once dropped, and rebuilding the syntax from it is the round-trip that says so. What is left is the reason a slice beats a reconstruction anywhere — it is what the source wrote, and it is already there.

§Where acceptance is enforced

Lowering is total over the accepted grammar: a form with no variant in TypeKind is a form the language does not accept, so there is no second acceptance list to drift from it. One rule cannot be stated that way and is stated in the lowering instead: Uninit is accepted only directly under a &mut, which is a fact about a position and not about a form.

Parsing diagnoses; ingestion raises. Those are two different points, and the split is what lets one model serve both.

Parsing never fails on a single item: an item the language cannot express becomes Element::Unsupported carrying its diagnosis, and FlatBuilder::build returns the model with it in place. Only whole-stream rules — a duplicate name in the flat namespace — are ParseErrors, because no declaration can make two items with one name unambiguous. So a consumer that wants to inspect what a source crate marked, refusals included, gets exactly that from Flat::unsupported.

Registry ingestion is where the diagnoses are raised. Building a registry from this model fails if any element is Unsupported — all of them at once, so a source crate that needs migrating sees one list rather than one rebuild per item — and it fails before any adapter declaration is examined. A binding is built against a model the frontend could read in full, or it is not built.

No marked item passes through verbatim, because a #[prebindgen] crate marks the items that cross the boundary and leaves the supporting code to the consumer. The proc-macro already enforces that — a use, mod, impl or macro_rules! cannot be marked at all — so the only item kind left that this module does not model is a union, and it is diagnosed like anything else the language cannot express.

The one item that is re-emitted verbatim is a Guard — an anonymous const, which has no address and so cannot be part of an API addressed by name. Today these are the feature checks Source injects on its own behalf. Modelled rather than dropped because this module must be total over what it is handed, and a separate element so nothing that consumes the API has to remember to skip it.

§Declaring a handle

#[prebindgen] pub type X = path::To<Thing>; declares an Extern: it gives a foreign or crate-private type a name in the flat API without claiming anything about its contents. That is what makes the API closable — a handle enters it deliberately rather than by being mentioned — and it is why a reference can be required to resolve. A marked tuple struct declares the same thing, since no adapter has ever crossed its fields.

§Shapes that must be refused rather than approximated

An Element holds what it holds: ordinary parameters, a direct return, no generic binder. A shape with no slot in that structure cannot be partly accepted — the missing piece would simply be dropped, and silently:

ShapeWould becomeSo
async fna function returning ()the future is dropped and the export’s body never runs
fn f(a: u8, ...)a function without the tailthe variadic arguments vanish
struct S<T>, fn f<T>(), struct S<const N: usize>T as a nominal referencea parameter is indistinguishable from an item named T

All three are ItemErrors, carried like any other refusal and raised at registry ingestion. A lifetime binder is not among them: lifetimes are spelling, and the spelling already travels. Nor is impl Trait in argument position — Rust calls it an anonymous type parameter, but it is not a binder in the syntax, so the callback form is untouched.

Modules§

emit
Emit — the capability to render captured Rust syntax.

Structs§

Alternative
One alternative of a Variant.
ArrayExtent
A fixed-size array’s extent: the number, the const identity when the source named one, and the spelling it was written with.
ConstId
A #[prebindgen] const, identified the way the flat namespace identifies everything: by name, plus the crate it was marked in.
Constant
A #[prebindgen] constant.
DuplicateName
The two items of a ParseError::DuplicateName.
Enum
A #[prebindgen] enum whose every alternative is fieldless — the C-style shape, a named set of integers.
EnumValue
One named value of an Enum.
Extern
A type the flat API names but whose contents it does not model.
Field
One field of a Struct or of an Alternative.
Flat
The flat API: every #[prebindgen] item from every ingested source, parsed, indexed by name, and with every type reference resolved.
FlatBuilder
Collects what to parse, then hands over the model.
Function
A #[prebindgen] free function.
Guard
An anonymous const: const _: T = .., whatever produced it.
Origin
The syntax a node was built from, plus where that syntax came from.
Param
One parameter of a Function.
Struct
A #[prebindgen] struct: a product of fields that cross the boundary.
TypeId
A nominal type’s identity: a name, and nothing else.
TypeKey
Canonical type-shape key: identity is the token string of the normalized type. Normalization is a closed rule set — group/paren unwrap, a crate::/self::/source-module path reduced to its final segment, and a prelude path read as the bare name the language knows it by (std::vec::Vec<Foo>Vec<Foo>) — and any spelling it does not cover is kept verbatim.
TypeKeyParseError
Structured failure of TypeKey::parse: the offending input plus the underlying syn parse error.
TypeRef
A type as the language accepted it, plus the exact syntax it came from.
Unsupported
An item the language cannot express.
UnsupportedArrayLen
A length the prebindgen source language does not accept.
UnsupportedType
A type the prebindgen source language does not accept.
Variant
A #[prebindgen] enum whose alternatives carry payloads — a sum type.

Enums§

ArrayLenReason
Why an array length was refused.
Element
One member of the flat API.
ExtentSource
How an ArrayExtent was addressed at its use site.
GenericArg
One generic argument of a Named type, as written.
ItemError
Why one item could not be expressed in the language.
ParseError
A rule of the language that no single item can satisfy on its own, and that no adapter declaration can excuse.
ScalarKind
The primitives the source language accepts. Mirrors the set every adapter already treats as directly representable.
Type
A type the flat API declares.
TypeKind
The accepted syntax of a TypeRef: the subset of syn::Type a #[prebindgen] crate may write, and nothing more.
UnsupportedTypeReason
Why a type was refused.

Constants§

TRANSPARENT_WRAPPERS
Lower one captured type.

Traits§

Name
A name a lookup can be performed with.

Functions§

canonical_spelling
canonical_type as tokens — the string form both indexes use as their key.
canonical_type
A type reduced to the spelling everything keys on: prelude-normalized, so std::option::Option<T> and Option<T> are one entry.
extract_fn_trait_args
If ty is impl Fn(T1, T2, ...) + Send + Sync + 'static, return the Fn argument types in declaration order. Otherwise None.
peel_transparent
Strip one transparent wrapper from a spelling, naming the one removed — Box<Option<T>>("Box", Option<T>).