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);