Skip to main content

prebindgen_flat/flat/
element.rs

1//! The elements: one variant per structure the source language allows.
2//!
3//! Every node — an item, a parameter, a field, a variant, a type — carries one
4//! [`Origin`], so generated Rust names the source by re-emitting what the source
5//! wrote, nothing re-parses a whole item to find a part of it, and no level has
6//! to copy a piece of provenance down from the level above.
7//!
8//! **Structure only.** Everything that turns an element back into Rust tokens
9//! lives in [`spell`](super::spell), so the shape of an element says nothing
10//! about the language it came from.
11
12use prebindgen::SourceLocation;
13
14use super::{origin::Origin, ty::TypeRef};
15
16/// One member of the flat API.
17///
18/// Three modelled kinds — a function, a type, a constant — plus
19/// [`Element::Guard`] for an anonymous const and [`Element::Unsupported`] for
20/// anything the language cannot express. No *marked*
21/// item passes through verbatim: a `#[prebindgen]` crate marks the items that
22/// cross the boundary, and the supporting code around them is the consumer
23/// crate's job — the proc-macro enforces that already, refusing to mark a `use`,
24/// `mod`, `impl` or `macro_rules!` at all.
25#[derive(Clone, Debug)]
26pub enum Element {
27    Function(Function),
28    /// A type declaration: a struct, either enum shape, or an opaque handle.
29    Type(Type),
30    Constant(Constant),
31    /// An anonymous const — infrastructure re-emitted verbatim. Carries no API
32    /// surface: nothing can name it, declare it, or cross it.
33    Guard(Guard),
34    /// An item the language cannot express — a parameter type outside the
35    /// grammar, a `self` receiver, a reference to a type the flat API never
36    /// declares, or a whole item kind it does not model such as a `union`.
37    ///
38    /// Indexed under its name so nothing else can claim it, with the diagnosis
39    /// riding along. Parsing carries it; building a
40    /// `Registry` from a model holding one fails, reporting
41    /// every offender at once. See the [module docs](super) on where acceptance
42    /// is enforced.
43    Unsupported(Unsupported),
44}
45
46impl Element {
47    /// The item's name, which is also its address: `#[prebindgen]` names live
48    /// in one flat namespace across every ingested source crate.
49    ///
50    /// `None` when the item has no address — a [`Guard`], or an item kind with
51    /// no identifier at all.
52    pub fn name(&self) -> Option<&syn::Ident> {
53        match self {
54            Element::Function(f) => Some(&f.name),
55            Element::Type(t) => Some(t.name()),
56            Element::Constant(c) => Some(&c.name),
57            Element::Guard(_) => None,
58            Element::Unsupported(u) => u.name.as_ref(),
59        }
60    }
61
62    /// Where the item was captured, including the crate that marked it.
63    ///
64    /// The same location every component of this item carries — they share one
65    /// [`Origin::location`], because one captured record is one item.
66    pub fn location(&self) -> &SourceLocation {
67        match self {
68            Element::Function(f) => &f.origin.location,
69            Element::Type(t) => t.location(),
70            Element::Constant(c) => &c.origin.location,
71            Element::Guard(g) => &g.origin.location,
72            Element::Unsupported(u) => &u.origin.location,
73        }
74    }
75
76    /// The whole item as `syn` — **the escape**, at the item level. See
77    /// [`Origin::as_syn`](super::Origin::as_syn).
78    ///
79    /// It builds a `syn::Item` rather than borrowing one, because each variant
80    /// keeps the item kind it was parsed as. That makes it the natural route for
81    /// an emitter re-stating a whole item, and the ledger's **item** bucket is
82    /// where those land.
83    pub(crate) fn as_syn(&self) -> syn::Item {
84        match self {
85            Element::Function(f) => syn::Item::Fn(f.origin.as_syn().clone()),
86            Element::Type(t) => t.as_syn(),
87            Element::Constant(c) => syn::Item::Const(c.origin.as_syn().clone()),
88            Element::Guard(g) => syn::Item::Const(g.origin.as_syn().clone()),
89            Element::Unsupported(u) => u.origin.as_syn().clone(),
90        }
91    }
92}
93
94/// A type the flat API declares.
95///
96/// Four shapes, and the classification is what a destination language acts on: a
97/// product of fields, a sum, a named set of integers, or a handle whose contents
98/// do not cross.
99#[derive(Clone, Debug)]
100pub enum Type {
101    Struct(Struct),
102    /// An enum whose alternatives carry payloads — a sum type.
103    Variant(Variant),
104    /// An enum whose every alternative is fieldless — a named set of integers.
105    Enum(Enum),
106    Extern(Extern),
107}
108
109impl Type {
110    pub fn name(&self) -> &syn::Ident {
111        match self {
112            Type::Struct(s) => &s.name,
113            Type::Variant(v) => &v.name,
114            Type::Enum(e) => &e.name,
115            Type::Extern(e) => &e.name,
116        }
117    }
118
119    pub fn location(&self) -> &SourceLocation {
120        self.location_rc()
121    }
122
123    /// The shared location itself, for building a sibling node's [`Origin`].
124    pub(super) fn location_rc(&self) -> &std::rc::Rc<SourceLocation> {
125        match self {
126            Type::Struct(s) => &s.origin.location,
127            Type::Variant(v) => &v.origin.location,
128            Type::Enum(e) => &e.origin.location,
129            Type::Extern(e) => &e.origin.location,
130        }
131    }
132
133    /// The whole item as `syn` — **the escape**. See [`Element::as_syn`].
134    pub(crate) fn as_syn(&self) -> syn::Item {
135        match self {
136            Type::Struct(s) => syn::Item::Struct(s.origin.as_syn().clone()),
137            Type::Variant(v) => syn::Item::Enum(v.origin.as_syn().clone()),
138            Type::Enum(e) => syn::Item::Enum(e.origin.as_syn().clone()),
139            Type::Extern(e) => e.origin.as_syn().clone(),
140        }
141    }
142}
143
144/// A type the flat API **names** but whose contents it does not model.
145///
146/// Two spellings declare one thing, because what the frontend records is the fact
147/// rather than the Rust shape that carried it:
148///
149/// * `#[prebindgen] pub type X = path::To<Thing>;` — how a foreign or
150///   crate-private type gets a name here. A **one-way road**: the name is
151///   thereafter the only way to spell that type inside the flat API, and the
152///   qualified path stays refused. This declares a name; it is not an equivalence
153///   between spellings: the normalization that makes `std::vec::Vec<T>` and
154///   `Vec<T>` one key covers the names the *language* predeclares, and a crate's
155///   own alias is never one of those — treating it as one is a category error.
156/// * `#[prebindgen] pub struct X(..);` — a tuple struct, whose fields no adapter
157///   has ever crossed.
158///
159/// Not necessarily a *handle*: `#[prebindgen] pub type Duration =
160/// std::time::Duration;` crosses by value through a `convert!`, erased to a plain
161/// integer. What it becomes — an opaque pointer, a `ptr_class`, a conversion — is
162/// the adapter's decision, and this says only that the frontend does not model the
163/// contents.
164#[derive(Clone, Debug)]
165pub struct Extern {
166    pub name: syn::Ident,
167    /// What the declaration points at, for an alias — `std::time::Duration`,
168    /// `zenoh::Session`, `handles::Storage`. `None` for a tuple struct, which is
169    /// itself the definition.
170    ///
171    /// Informational, and deliberately **not** classified. `Error` is
172    /// `Box<dyn std::error::Error + Send + Sync>` behind a `zenoh::` alias in one
173    /// crate and spelled openly in another, so being "a std type" is a property of
174    /// the spelling, not of the type. An adapter that wants to recognise a target
175    /// may; the frontend does not decide for it.
176    pub target: Option<String>,
177    /// The declaring item — a type alias or a tuple struct.
178    pub origin: Origin<syn::Item>,
179}
180
181/// A `#[prebindgen]` free function.
182#[derive(Clone, Debug)]
183pub struct Function {
184    pub name: syn::Ident,
185    /// Parameters in declaration order.
186    pub params: Vec<Param>,
187    /// What the function returns. An elided return is
188    /// [`TypeKind::Unit`](super::TypeKind), exactly as a written `-> ()` is:
189    /// they mean the same thing, differ only in spelling, and every consumer
190    /// today already normalizes one to the other on the spot.
191    pub ret: TypeRef,
192    /// The whole item: attributes, `cfg`, doc comments, body.
193    pub origin: Origin<syn::ItemFn>,
194}
195
196impl Function {
197    /// A synthesized **nullary getter**: `pub fn <ident>() -> <ret>`.
198    ///
199    /// The model's own constructor for the one element an adapter legitimately
200    /// needs to invent — a declared `const`'s accessor, whose type flows through
201    /// the ordinary output-converter machinery and so has to arrive as a
202    /// `Function` like any other.
203    ///
204    /// It lives here because building one means **spelling** `ret`, and #280
205    /// says a `TypeRef` is the model's to mint. An adapter that built this
206    /// itself needed a spelling for a model element — which dragged the
207    /// emission capability into validation and Kotlin rendering, both of which
208    /// only wanted the resulting `Function`.
209    ///
210    /// The body is `unimplemented!()` and is never emitted: only the signature
211    /// is read.
212    pub fn synthetic_getter(ident: syn::Ident, ret: TypeRef) -> Self {
213        let ret_syntax = ret.spell();
214        let item: syn::ItemFn = syn::parse_quote! {
215            pub fn #ident() -> #ret_syntax {
216                unimplemented!()
217            }
218        };
219        Self {
220            name: ident,
221            params: Vec::new(),
222            origin: ret.origin_with(item),
223            ret,
224        }
225    }
226}
227
228/// One parameter of a [`Function`].
229#[derive(Clone, Debug)]
230pub struct Param {
231    pub name: syn::Ident,
232    pub ty: TypeRef,
233    /// The parameter as written — `mode: Mode`.
234    pub origin: Origin<syn::PatType>,
235}
236
237/// A `#[prebindgen]` struct: a product of fields that cross the boundary.
238///
239/// A struct whose contents do *not* cross is an [`Extern`], not a `Struct` with
240/// nothing in it — so `fields` is a plain list, and empty means the source wrote
241/// a struct with no fields.
242///
243/// Whether the fields are named or positional is not recorded: a [`Field`]
244/// already knows its own address, and the delimiters are spelling, read off the
245/// syntax when the struct is spelled.
246#[derive(Clone, Debug)]
247pub struct Struct {
248    pub name: syn::Ident,
249    pub fields: Vec<Field>,
250    pub origin: Origin<syn::ItemStruct>,
251    /// This struct **as a type**, taken at parse time — the twin of
252    /// [`Variant::reading`], stored and `pub(super)` for the same two reasons.
253    pub(super) reading: TypeRef,
254}
255
256impl Struct {
257    /// This struct as a type reference — what the **declaration** answers when
258    /// something needs a reading naming it.
259    ///
260    /// The alternative is composing one from the name at the call site, which
261    /// an adapter cannot do (minting is sealed to this crate) and which would
262    /// be phase-dependent if routed through the registry instead: a
263    /// decomposition is declared before anything is interned. The declaration
264    /// is the one thing that can always say. Same reasoning as
265    /// [`Variant::type_ref`].
266    pub fn type_ref(&self) -> &TypeRef {
267        &self.reading
268    }
269}
270
271/// A `#[prebindgen]` enum whose alternatives carry payloads — a sum type.
272///
273/// Distinct from [`Enum`], which is the fieldless shape, because the two are
274/// consumed as different constructs and **numbered differently**. A sum's
275/// alternatives are identified by position: the mirror an adapter builds carries
276/// no `repr` and numbers its own arms, so a Rust discriminant would be the wrong
277/// answer here — which is why there is no slot for one.
278///
279/// Both shapes are spelled `enum` in Rust and both keep a `syn::ItemEnum` in
280/// their origin. Which one an item *is* is the classification, and it is decided
281/// once: any alternative with a field makes it a `Variant`.
282#[derive(Clone, Debug)]
283pub struct Variant {
284    pub name: syn::Ident,
285    /// Alternatives in declaration order; `alternatives[i].index == i`.
286    pub alternatives: Vec<Alternative>,
287    pub origin: Origin<syn::ItemEnum>,
288    /// This sum **as a type**, taken at parse time — see [`Self::type_ref`].
289    ///
290    /// **Stored, not computed**, and that is what makes the accessor safe: a
291    /// method composing `TypeRef::named(&self.name)` would answer for whatever
292    /// name a caller put in the struct, so a `Variant` named `String` would
293    /// yield `Named` over the spelling `String` — which the model reads as
294    /// `Str`. A stored reading cannot disagree with the model, because the
295    /// model is what put it there, and an assembler has no way to mint a
296    /// different one.
297    ///
298    /// `pub(super)` is the second line, not the first: it also stops a
299    /// `Variant` being assembled at all outside `flat` (`E0451`), so `name` and
300    /// `reading` cannot be paired inconsistently with *each other*.
301    pub(super) reading: TypeRef,
302}
303
304impl Variant {
305    /// A reference to this sum **as a type** — what a consumer needs when it
306    /// has to name the sum rather than walk it (jnigen's `SumTag` selector,
307    /// which carries *which* sum it chooses between).
308    ///
309    /// The **declaration** answers, so no consumer has to mint a reading from
310    /// the name and hope it matches what the model would have said.
311    ///
312    /// This returns state the parser took, **not** a fresh composition, and the
313    /// difference is the difference between sealing and appearing to.
314    ///
315    /// A version that composed `TypeRef::named(&self.name)` would hand a
316    /// `Variant` assembled with the name `String` a
317    /// [`Named`](super::TypeKind::Named) over the spelling `String` — which the
318    /// model reads as [`Str`](super::TypeKind::Str). That is the `kind`/`syntax`
319    /// disagreement [`TypeRef`]'s private fields exist to prevent, and it was
320    /// reachable from outside the crate while being invisible to every doctest
321    /// there, because assembling the *element* is not minting the *type*.
322    ///
323    /// Reading a stored value closes it: whatever a caller does with the other
324    /// fields, the reading here is the one the model made, and no caller can
325    /// mint a different one to put in its place.
326    ///
327    /// The `Variant` is sealed as well — its `reading` field is `pub(super)` —
328    /// so the two cannot even be paired inconsistently:
329    ///
330    /// ```compile_fail
331    /// # use prebindgen_flat::flat::{Origin, Variant};
332    /// let assembled = Variant {
333    ///     name: syn::parse_str("String").unwrap(),
334    ///     alternatives: vec![],
335    ///     origin: Origin::new(
336    ///         syn::parse_str("enum String { A(u8) }").unwrap(),
337    ///         std::rc::Rc::new(Default::default()),
338    ///     ),
339    /// };
340    /// let mismatched = assembled.type_ref();
341    /// ```
342    ///
343    /// That doctest pins *"a consumer cannot assemble a `Variant`"* and nothing
344    /// finer: measured, it still fails with the field made `pub` — as `E0063`
345    /// (missing field) rather than `E0451` (private field), since a consumer
346    /// cannot produce a `TypeRef` to supply either way. The visibility itself
347    /// is the check the compiler runs on every build.
348    pub fn type_ref(&self) -> &TypeRef {
349        &self.reading
350    }
351}
352
353/// One alternative of a [`Variant`].
354#[derive(Clone, Debug)]
355pub struct Alternative {
356    pub name: syn::Ident,
357    /// Position within its sum, `0..N-1` — the same fact a [`Field`] carries,
358    /// for the same reason: a node handed out on its own still knows where it
359    /// sits.
360    ///
361    /// This is the *only* numbering a sum has. What a destination language does
362    /// with it is its own business: one may transmit it to say which alternative
363    /// is live, another may send a name instead.
364    pub index: usize,
365    /// The alternative's payload, in declaration order. May be empty — a sum can
366    /// mix payload-carrying and payload-free alternatives, and only the presence
367    /// of *some* payload makes the type a `Variant`.
368    pub fields: Vec<Field>,
369    /// The alternative as written: delimiters, attributes, doc comments.
370    pub origin: Origin<syn::Variant>,
371}
372
373impl Alternative {
374    /// True when this alternative carries no payload.
375    ///
376    /// The *group* question, not the syntax one: `B`, `B()` and `B {}` are all
377    /// empty by this test; what keeps their delimiters apart is the spelling,
378    /// not this.
379    pub fn is_empty(&self) -> bool {
380        self.fields.is_empty()
381    }
382}
383
384/// A `#[prebindgen]` enum whose every alternative is fieldless — the C-style
385/// shape, a named set of integers.
386///
387/// Distinct from [`Variant`] because the identity of a member here is the value
388/// Rust **assigns** it, not where it sits: a C header re-states each `= expr`
389/// and a Kotlin `enum class` entry is `NAME(7)`. A sum has no such value, which
390/// is why the two are separate entities rather than one with a dead field each.
391#[derive(Clone, Debug)]
392pub struct Enum {
393    pub name: syn::Ident,
394    /// This enum **as a type**, taken at parse time — the twin of
395    /// [`Variant::reading`] and [`Struct::reading`], stored and `pub(super)`
396    /// for the same two reasons.
397    pub(super) reading: TypeRef,
398    /// Values in declaration order; `values[i].index == i`.
399    pub values: Vec<EnumValue>,
400    pub origin: Origin<syn::ItemEnum>,
401}
402
403impl Enum {
404    /// Every value paired with the number Rust assigns it, or the first value
405    /// whose discriminant could not be evaluated.
406    ///
407    /// This is the numbering a destination language needs when it has no way to
408    /// reference a Rust constant: a Kotlin `enum class` entry is `NAME(3)`, and
409    /// the generated `int → value` decode matches on the same numbers, so both
410    /// come from here and cannot drift. An `Err` is a refusal for *that*
411    /// consumer only — one that re-emits the source spelling never asks.
412    pub fn discriminant_values(&self) -> Result<Vec<(&syn::Ident, i64)>, &syn::Ident> {
413        self.values
414            .iter()
415            .map(|v| match v.discriminant {
416                Some(n) => Ok((&v.name, n)),
417                None => Err(&v.name),
418            })
419            .collect()
420    }
421}
422
423impl Enum {
424    /// This enum as a type reference — what the **declaration** answers.
425    /// See [`Variant::type_ref`].
426    pub fn type_ref(&self) -> &TypeRef {
427        &self.reading
428    }
429}
430
431/// One named value of an [`Enum`].
432#[derive(Clone, Debug)]
433
434pub struct EnumValue {
435    pub name: syn::Ident,
436    /// Position within its enum, `0..N-1`. Not the identity — see
437    /// [`Self::discriminant`] — but the same "where it sits" fact every node in
438    /// an ordered list carries, and what a consumer falls back to when a
439    /// discriminant cannot be evaluated.
440    pub index: usize,
441    /// The value Rust assigns — an explicit `= N` sets it, an implicit value
442    /// takes the previous plus one, starting at 0. **This shape's identity.**
443    ///
444    /// `None` once a spelling the frontend cannot evaluate (a `const`, a `cfg`,
445    /// arithmetic) has broken the chain, or once the chain has run out of `i64`.
446    /// That is not a failure: only a consumer that needs the *number* is
447    /// affected, and one that re-emits the *spelling* reads
448    /// [`Self::origin`]`.syntax.discriminant` instead.
449    pub discriminant: Option<i64>,
450    /// The value as written: `= 0x07`, attributes, doc comments — and its
451    /// delimiters, since `B` and `B()` are both fieldless and still spelled
452    /// differently.
453    pub origin: Origin<syn::Variant>,
454}
455
456/// One field of a [`Struct`] or of an [`Alternative`].
457#[derive(Clone, Debug)]
458pub struct Field {
459    /// The field's name, or `None` for a positional one.
460    pub name: Option<syn::Ident>,
461    /// Position within its struct or alternative, `0..N-1` — the same fact an
462    /// [`Alternative`] carries.
463    ///
464    /// The address of a positional field. A named field has one too, and simply
465    /// does not need it: it is addressed by name, so this is available rather
466    /// than used — the same way it carries its item's location.
467    pub index: usize,
468    pub ty: TypeRef,
469    /// The field as written — `pub id: u64`, attributes and docs included.
470    pub origin: Origin<syn::Field>,
471}
472
473/// A `#[prebindgen]` constant.
474///
475/// Always named: an unnamed `const _` is a [`Guard`], not a constant with no
476/// address.
477#[derive(Clone, Debug)]
478pub struct Constant {
479    pub name: syn::Ident,
480    pub ty: TypeRef,
481    /// The whole item — the initializer expression included, which is where a
482    /// consumer that re-emits the value reads it from.
483    pub origin: Origin<syn::ItemConst>,
484}
485
486/// An **anonymous const**: `const _: T = ..`, whatever produced it.
487///
488/// The definition is the shape, not the origin. A const with no name has no
489/// address, so nothing can declare it, reference it, or emit it as an alias —
490/// which is what puts it outside the flat API rather than in it, and that holds
491/// however the item arrived: synthesized, hand-fed to [`FlatBuilder`](super::FlatBuilder), or written
492/// as `#[prebindgen] const _: () = ..` in a source crate.
493///
494/// **Today's producer** is [`Source`](prebindgen::Source)'s cfg filter, which
495/// synthesizes `const _: () = { konst::assertc_eq!(..) }` to assert that a source
496/// crate's `FEATURES` match what the build script asked for. Nothing *marked*
497/// that one — it is prebindgen's own item riding the same stream — but it is not
498/// the only thing that can land here, and the model does not claim otherwise.
499///
500/// **Cardinality is zero or more.** `enable_feature_filtering(None)` produces
501/// none, and each item iterator taken from a `Source` emits its own, so composing
502/// two iterators from one crate yields two.
503///
504/// It carries no type, unlike a [`Constant`]. The item is emitted verbatim, so
505/// what its types mean is the consumer crate's business; modelling them would let
506/// a guard that names something undeclared refuse the whole element.
507#[derive(Clone, Debug)]
508pub struct Guard {
509    /// Emitted verbatim, so the item is all there is.
510    pub origin: Origin<syn::ItemConst>,
511}
512
513/// An item the language cannot express.
514#[derive(Clone, Debug)]
515pub struct Unsupported {
516    /// The item's identifier, or `None` for an item kind that has none.
517    pub name: Option<syn::Ident>,
518    /// What could not be expressed, ready to be raised by whatever declares
519    /// this item. Boxed: it is the size outlier among the elements, and this
520    /// one is the rare variant.
521    pub error: Box<super::ItemError>,
522    /// The item as written, so a diagnosis can quote the source.
523    pub origin: Origin<syn::Item>,
524}
525
526/// An item's `///` documentation, read off the attributes it was captured
527/// with: `#[doc = " …"]` lines in order, one leading space stripped per line,
528/// joined with `\n`; `None` when there are none. `*/` is defanged so the text
529/// is safe inside a `/** … */` block, which is what every destination that
530/// re-emits prose needs.
531///
532/// **Here rather than in an adapter.** A doc comment is something the *source*
533/// said, so it is the model's to report — and reading it was the last common
534/// reason an emitter reached for a captured item's node. Two adapters wanting
535/// the same prose is one function, not a copy each.
536fn docs_from(attrs: &[syn::Attribute]) -> Option<String> {
537    let mut lines: Vec<String> = Vec::new();
538    for attr in attrs {
539        if !attr.path().is_ident("doc") {
540            continue;
541        }
542        let syn::Meta::NameValue(nv) = &attr.meta else {
543            continue;
544        };
545        let syn::Expr::Lit(syn::ExprLit {
546            lit: syn::Lit::Str(s),
547            ..
548        }) = &nv.value
549        else {
550            continue;
551        };
552        let raw = s.value();
553        let line = raw.strip_prefix(' ').unwrap_or(&raw);
554        lines.push(line.replace("*/", "*\u{200B}/"));
555    }
556    (!lines.is_empty()).then(|| lines.join("\n"))
557}
558
559macro_rules! docs_accessor {
560    ($($ty:ident),+ $(,)?) => {$(
561        impl $ty {
562            /// This item's `///` documentation.
563            pub fn docs(&self) -> Option<String> {
564                docs_from(&self.origin.syntax.attrs)
565            }
566        }
567    )+};
568}
569
570docs_accessor!(
571    Function,
572    Struct,
573    Enum,
574    Variant,
575    Constant,
576    Field,
577    Alternative,
578    EnumValue
579);