Skip to main content

weaveffi_model/
ty.rs

1//! The **resolved type** every backend consumes, the single taxonomy that
2//! classifies it for the C ABI and the value-buffer wire format, and the
3//! [`TypeIndex`] that maps each user type name to its declaration.
4//!
5//! The IDL's [`TypeRef`](crate::ir::TypeRef) is the type *as written*: a
6//! user-defined type is a bare `Named` string because the parser can't know
7//! whether it names a record, an enum, an interface, or a callback
8//! interface. [`Ty`] is the type *as resolved* by validation: every user
9//! reference carries its kind, and there is no "unresolved" variant for a
10//! backend to trip over. Type names are global (validation rejects two
11//! declarations with one name), so a [`Ty`] carries the bare name and the
12//! [`TypeIndex`] answers where it's declared.
13//!
14//! Three questions about a type are answered once here:
15//!
16//! * [`Ty::family`]: how the type crosses a **call boundary** (by value, as a
17//!   pinned string, as a `(ptr, len)` byte pair, as a serialized value
18//!   buffer, as an object pointer, or as a callback vtable pair). The ABI
19//!   lowering, the marshalling plan, and every backend's argument and return
20//!   handling dispatch on it.
21//! * [`Ty::wire`]: how the type is encoded **inside a value buffer**. Every
22//!   backend's codec emitter dispatches on it.
23//! * [`Ty::contains_user_type`] and [`Ty::contains_object`]: whether encoding
24//!   the type needs a user-defined codec function or object-token support.
25
26use std::collections::BTreeMap;
27use std::fmt;
28
29/// A built-in primitive type: the fixed vocabulary the IDL's
30/// [`TypeRef`](crate::ir::TypeRef) and the resolved [`Ty`] share.
31///
32/// Adding a primitive means adding a variant here; every classification
33/// ([`Ty::family`], [`Ty::wire`], the C ABI lowering) dispatches on it.
34/// Inside a value buffer each primitive is one fixed-width or
35/// length-prefixed run (see the variant docs), and every backend spells the
36/// read/write routine for one the same way modulo casing (`read_i32`,
37/// `readI32`, `ReadI32`), so [`snake`](Self::snake) and
38/// [`pascal`](Self::pascal) let a codec emitter dispatch on the whole set
39/// with one arm.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
41pub enum Prim {
42    /// Boolean (`bool`): one byte, `0` or `1`.
43    Bool,
44    /// Signed 8-bit integer (`i8`): one signed byte.
45    I8,
46    /// Signed 16-bit integer (`i16`): two bytes, little-endian.
47    I16,
48    /// Signed 32-bit integer (`i32`): four bytes, little-endian.
49    I32,
50    /// Signed 64-bit integer (`i64`): eight bytes, little-endian.
51    I64,
52    /// Unsigned 8-bit integer (`u8`): one unsigned byte.
53    U8,
54    /// Unsigned 16-bit integer (`u16`): two bytes, little-endian.
55    U16,
56    /// Unsigned 32-bit integer (`u32`): four bytes, little-endian.
57    U32,
58    /// Unsigned 64-bit integer (`u64`): eight bytes, little-endian.
59    U64,
60    /// 32-bit IEEE 754 floating-point number (`f32`): four bytes.
61    F32,
62    /// 64-bit IEEE 754 floating-point number (`f64`): eight bytes.
63    F64,
64    /// UTF-8 string (`string`): a `u32` byte length followed by UTF-8 bytes,
65    /// no NUL terminator. Borrowed as a parameter, owned as a return.
66    String,
67    /// Byte buffer (`bytes`): a `u32` length followed by raw bytes. Borrowed
68    /// as a parameter, owned as a return.
69    Bytes,
70}
71
72impl Prim {
73    /// Every primitive, for exhaustive tables in runtime preambles.
74    pub const ALL: [Prim; 13] = [
75        Prim::Bool,
76        Prim::I8,
77        Prim::I16,
78        Prim::I32,
79        Prim::I64,
80        Prim::U8,
81        Prim::U16,
82        Prim::U32,
83        Prim::U64,
84        Prim::F32,
85        Prim::F64,
86        Prim::String,
87        Prim::Bytes,
88    ];
89
90    /// The primitive an IDL spelling names (`i32`, `string`, `bytes`), or
91    /// `None` when `name` isn't a primitive.
92    #[must_use]
93    pub fn from_name(name: &str) -> Option<Prim> {
94        Prim::ALL.into_iter().find(|p| p.snake() == name)
95    }
96
97    /// The lower-case IDL spelling (`bool`, `i32`, `string`, `bytes`), also
98    /// the stem of snake-case routine names such as `read_i32`.
99    #[must_use]
100    pub fn snake(self) -> &'static str {
101        match self {
102            Prim::Bool => "bool",
103            Prim::I8 => "i8",
104            Prim::I16 => "i16",
105            Prim::I32 => "i32",
106            Prim::I64 => "i64",
107            Prim::U8 => "u8",
108            Prim::U16 => "u16",
109            Prim::U32 => "u32",
110            Prim::U64 => "u64",
111            Prim::F32 => "f32",
112            Prim::F64 => "f64",
113            Prim::String => "string",
114            Prim::Bytes => "bytes",
115        }
116    }
117
118    /// The capitalized spelling (`Bool`, `I32`, `String`, `Bytes`), the stem
119    /// of camel-case routine names such as `readI32`.
120    #[must_use]
121    pub fn pascal(self) -> &'static str {
122        match self {
123            Prim::Bool => "Bool",
124            Prim::I8 => "I8",
125            Prim::I16 => "I16",
126            Prim::I32 => "I32",
127            Prim::I64 => "I64",
128            Prim::U8 => "U8",
129            Prim::U16 => "U16",
130            Prim::U32 => "U32",
131            Prim::U64 => "U64",
132            Prim::F32 => "F32",
133            Prim::F64 => "F64",
134            Prim::String => "String",
135            Prim::Bytes => "Bytes",
136        }
137    }
138
139    /// `true` for the integer and float primitives (everything but `bool`,
140    /// `string`, and `bytes`).
141    #[must_use]
142    pub fn is_numeric(self) -> bool {
143        !matches!(self, Prim::Bool | Prim::String | Prim::Bytes)
144    }
145
146    /// `true` for the integer primitives.
147    #[must_use]
148    pub fn is_integer(self) -> bool {
149        self.is_numeric() && !matches!(self, Prim::F32 | Prim::F64)
150    }
151}
152
153impl fmt::Display for Prim {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.write_str(self.snake())
156    }
157}
158
159/// A fully resolved type: the shape generators render and the ABI lowers.
160///
161/// User types carry their bare declared name; [`TypeIndex`] (on the
162/// [`Model`](crate::model::Model)) maps the name to its declaration.
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
164pub enum Ty {
165    /// A built-in primitive: a scalar, `bool`, `string`, or `bytes`.
166    Prim(Prim),
167    /// A user record (struct): a plain value type. Crosses the C ABI by value
168    /// as a serialized buffer (`ptr` + `len`), borrowed for a call as a
169    /// parameter and owned (freed with `{prefix}_free_bytes`) as a return.
170    Record(String),
171    /// An algebraic (rich) enum: a sum type with at least one payload-carrying
172    /// variant. A value type that crosses the C ABI as a serialized buffer
173    /// (an `i32` tag followed by the active variant's fields), exactly like a
174    /// [`Record`](Self::Record).
175    RichEnum(String),
176    /// A C-style integer enum (no variant payloads). Lowers by value.
177    Enum(String),
178    /// A user interface: a reference-counted object. As a parameter the
179    /// object is borrowed for the call; as a return (or inside a buffer) the
180    /// receiver adopts one strong reference it must eventually release.
181    Interface(String),
182    /// A callback interface: a method set the consumer implements. Only valid
183    /// as a top-level parameter (bare, or optional as `Cb?`), where it lowers
184    /// to a context pointer plus a vtable pointer.
185    CallbackInterface(String),
186    /// Optional value (`T?`): either the inner type or nothing.
187    Optional(Box<Ty>),
188    /// Homogeneous list (`[T]`) of the inner element type.
189    List(Box<Ty>),
190    /// Map (`{K:V}`) from a key type to a value type.
191    Map(Box<Ty>, Box<Ty>),
192    /// Lazy sequence (`iter<T>`) of the inner type, lowered to a next/destroy
193    /// iterator object rather than a materialized collection.
194    Iterator(Box<Ty>),
195}
196
197/// How a value of some [`Ty`] crosses a **call boundary**: the one
198/// classification the ABI lowering, the marshalling plan, and every backend's
199/// argument and return handling agree on.
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum Family {
202    /// One C slot passed by value: scalars, bools, and C-style enums.
203    Direct,
204    /// A `(ptr, len)` pair of UTF-8 bytes, never NUL-terminated. Borrowed for
205    /// a call as a parameter; producer-owned and released with
206    /// `{prefix}_free_bytes` as a return.
207    String,
208    /// A `(ptr, len)` raw byte pair. Borrowed as a parameter; producer-owned
209    /// and released with `{prefix}_free_bytes` as a return.
210    Bytes,
211    /// A `(ptr, len)` serialized value buffer (record, rich enum, optional,
212    /// list, or map). Borrowed as a parameter; producer-owned and released
213    /// with `{prefix}_free_bytes` after decoding as a return.
214    Buffer,
215    /// An object pointer to an interface. Borrowed as a parameter; one
216    /// strong reference (released with its `_destroy` symbol) as a return.
217    Object {
218        /// `true` for `Interface?`: null is a legal "none" value.
219        nullable: bool,
220    },
221    /// A callback interface: a `void* ctx` plus `const {tag}_vtable*` pair.
222    /// Only ever a top-level parameter.
223    Callback {
224        /// `true` for `Cb?`: a null vtable pointer is a legal "none" value.
225        nullable: bool,
226    },
227    /// An `iter<T>` return: an opaque iterator handle with its own
228    /// `next`/`destroy` protocol. Never a parameter.
229    Iterator,
230}
231
232/// The closed set of shapes a value inside a value buffer can take.
233///
234/// This is the dispatch alphabet for every backend's buffer codec: one
235/// variant per encode/decode primitive of the wire format. The borrowed
236/// references point back into the classified [`Ty`], so classification
237/// allocates nothing.
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum WireType<'a> {
240    /// A fixed-width primitive, a string, or a byte blob.
241    Prim(Prim),
242    /// An object token: eight bytes, little-endian, unsigned, holding the
243    /// object pointer and carrying one strong reference. Carries the
244    /// interface name so backends can wrap the adopted pointer in the right
245    /// class and, when writing, call the right `_clone` symbol.
246    Object(&'a str),
247    /// A C-style enum: an `i32` discriminant. Carries the enum name so
248    /// backends can wrap the integer in their typed enum.
249    Enum(&'a str),
250    /// A record or rich enum: the named type's own codec applies (fields in
251    /// declaration order for a record; an `i32` tag plus the active
252    /// variant's fields for a rich enum). Backends emit one codec function
253    /// per user type and delegate here by name.
254    User(&'a str),
255    /// A one-byte presence flag (`0` absent, `1` present) followed by the
256    /// inner value when present.
257    Optional(&'a Ty),
258    /// A `u32` element count followed by each element.
259    List(&'a Ty),
260    /// A `u32` entry count followed by alternating key and value.
261    Map(&'a Ty, &'a Ty),
262}
263
264impl Ty {
265    /// How this type crosses a call boundary. Total over every `Ty`.
266    pub fn family(&self) -> Family {
267        match self {
268            Ty::Prim(Prim::String) => Family::String,
269            Ty::Prim(Prim::Bytes) => Family::Bytes,
270            Ty::Prim(_) | Ty::Enum(_) => Family::Direct,
271            Ty::Record(_) | Ty::RichEnum(_) | Ty::List(_) | Ty::Map(_, _) => Family::Buffer,
272            Ty::Interface(_) => Family::Object { nullable: false },
273            // The one optional that is not buffered: `Interface?` stays a
274            // nullable pointer so the common "maybe an object" shape needs no
275            // encoding step.
276            Ty::Optional(inner) if matches!(inner.as_ref(), Ty::Interface(_)) => {
277                Family::Object { nullable: true }
278            }
279            // Likewise `Cb?` stays a `(ctx, vtable)` pair with a null vtable
280            // meaning none; callback interfaces never enter a value buffer.
281            Ty::Optional(inner) if matches!(inner.as_ref(), Ty::CallbackInterface(_)) => {
282                Family::Callback { nullable: true }
283            }
284            Ty::Optional(_) => Family::Buffer,
285            Ty::CallbackInterface(_) => Family::Callback { nullable: false },
286            Ty::Iterator(_) => Family::Iterator,
287        }
288    }
289
290    /// `true` when this type crosses the C ABI as a serialized value buffer
291    /// (`const uint8_t*` + `size_t`) rather than as dedicated C slots.
292    pub fn is_buffered(&self) -> bool {
293        self.family() == Family::Buffer
294    }
295
296    /// The referenced user-type name for a record, rich enum, enum,
297    /// interface, or callback interface, or `None` for every other type.
298    pub fn user_name(&self) -> Option<&str> {
299        match self {
300            Ty::Record(n)
301            | Ty::RichEnum(n)
302            | Ty::Enum(n)
303            | Ty::Interface(n)
304            | Ty::CallbackInterface(n) => Some(n),
305            _ => None,
306        }
307    }
308
309    /// The interface name inside a bare or optional interface type, or
310    /// `None` when the type is not an object reference.
311    pub fn interface_name(&self) -> Option<&str> {
312        match self {
313            Ty::Interface(n) => Some(n),
314            Ty::Optional(inner) => inner.interface_name(),
315            _ => None,
316        }
317    }
318
319    /// The callback interface name inside a bare or optional callback
320    /// interface type, or `None` for every other type.
321    pub fn callback_interface_name(&self) -> Option<&str> {
322        match self {
323            Ty::CallbackInterface(n) => Some(n),
324            Ty::Optional(inner) => inner.callback_interface_name(),
325            _ => None,
326        }
327    }
328
329    /// Classify this type's encoding inside a value buffer.
330    ///
331    /// Total over every type validation admits inside a buffered position.
332    /// Callback interfaces and iterators never appear inside value buffers
333    /// (validation rejects them there), so those inputs are bugs in the
334    /// caller's pipeline, not user errors.
335    ///
336    /// # Panics
337    ///
338    /// Panics when `self` is a callback interface or an iterator, neither of
339    /// which can legally appear inside a value buffer.
340    pub fn wire(&self) -> WireType<'_> {
341        match self {
342            Ty::Prim(p) => WireType::Prim(*p),
343            Ty::Interface(n) => WireType::Object(n),
344            Ty::Enum(name) => WireType::Enum(name),
345            Ty::Record(name) | Ty::RichEnum(name) => WireType::User(name),
346            Ty::Optional(inner) => WireType::Optional(inner),
347            Ty::List(inner) => WireType::List(inner),
348            Ty::Map(k, v) => WireType::Map(k, v),
349            Ty::CallbackInterface(_) | Ty::Iterator(_) => {
350                panic!("{self} cannot appear inside value buffers")
351            }
352        }
353    }
354
355    /// `true` when a value of this type needs a user-defined codec function
356    /// somewhere in its encoding: it is (or transitively contains) a record
357    /// or rich enum.
358    pub fn contains_user_type(&self) -> bool {
359        self.any(&|t| matches!(t, Ty::Record(_) | Ty::RichEnum(_)))
360    }
361
362    /// `true` when this type is (or transitively contains) an interface, so
363    /// encoding it needs object-token support in the codec.
364    pub fn contains_object(&self) -> bool {
365        self.any(&|t| matches!(t, Ty::Interface(_)))
366    }
367
368    /// `true` when `pred` holds for this type or any type nested inside it
369    /// (optional payloads, list and iterator elements, map keys and values).
370    pub fn any(&self, pred: &dyn Fn(&Ty) -> bool) -> bool {
371        if pred(self) {
372            return true;
373        }
374        match self {
375            Ty::Optional(inner) | Ty::List(inner) | Ty::Iterator(inner) => inner.any(pred),
376            Ty::Map(k, v) => k.any(pred) || v.any(pred),
377            _ => false,
378        }
379    }
380
381    /// The element type of an `iter<T>`, or `None` for any other type.
382    pub fn iterator_elem(&self) -> Option<&Ty> {
383        match self {
384            Ty::Iterator(inner) => Some(inner),
385            _ => None,
386        }
387    }
388}
389
390impl fmt::Display for Ty {
391    /// Renders the IDL spelling (`i32`, `[string]`, `{string:i32}`,
392    /// `Contact?`, `iter<Contact>`), which is what diagnostics and generated
393    /// doc comments quote.
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        match self {
396            Ty::Prim(p) => f.write_str(p.snake()),
397            Ty::Record(n)
398            | Ty::RichEnum(n)
399            | Ty::Enum(n)
400            | Ty::Interface(n)
401            | Ty::CallbackInterface(n) => f.write_str(n),
402            Ty::Optional(inner) => write!(f, "{inner}?"),
403            Ty::List(inner) => write!(f, "[{inner}]"),
404            Ty::Map(k, v) => write!(f, "{{{k}:{v}}}"),
405            Ty::Iterator(inner) => write!(f, "iter<{inner}>"),
406        }
407    }
408}
409
410/// What kind of declaration a user type name refers to.
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412pub enum TypeKind {
413    /// A `structs:` entry.
414    Record,
415    /// An `enums:` entry with no payload-carrying variant.
416    Enum,
417    /// An `enums:` entry with at least one payload-carrying variant.
418    RichEnum,
419    /// An `interfaces:` entry.
420    Interface,
421    /// A `callback_interfaces:` entry.
422    CallbackInterface,
423}
424
425/// Where a user type is declared.
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub struct TypeDecl {
428    /// The declaration kind.
429    pub kind: TypeKind,
430    /// Position of the declaring module in
431    /// [`Model::modules`](crate::model::Model::modules).
432    pub module: usize,
433    /// Position of the declaration in its module's list for its kind
434    /// (`structs`, `enums`, `interfaces`, or `callback_interfaces`).
435    pub index: usize,
436}
437
438/// Every user type name in an API, mapped to its declaration.
439///
440/// Type names are unique across the whole API, so a bare name identifies
441/// one declaration. The [`Model`](crate::model::Model) owns the index and
442/// exposes typed lookups (`interface`, `record`, `owner`, and so on) on top
443/// of it.
444#[derive(Debug, Clone, Default, PartialEq, Eq)]
445pub struct TypeIndex {
446    decls: BTreeMap<String, TypeDecl>,
447    /// Underscore-joined C path of every module, by position.
448    module_paths: Vec<String>,
449}
450
451impl TypeIndex {
452    /// Register a module (in [`Model::modules`](crate::model::Model::modules)
453    /// order) by its underscore-joined C path, returning its position.
454    pub(crate) fn push_module(&mut self, path: String) -> usize {
455        self.module_paths.push(path);
456        self.module_paths.len() - 1
457    }
458
459    /// Record a declaration. The first declaration of a name wins;
460    /// validation reports any later one.
461    pub(crate) fn declare(&mut self, name: &str, decl: TypeDecl) {
462        self.decls.entry(name.to_string()).or_insert(decl);
463    }
464
465    /// The declaration `name` refers to, or `None` for an undeclared name.
466    pub fn get(&self, name: &str) -> Option<&TypeDecl> {
467        self.decls.get(name)
468    }
469
470    /// The kind of the declaration `name` refers to.
471    pub fn kind(&self, name: &str) -> Option<TypeKind> {
472        self.get(name).map(|d| d.kind)
473    }
474
475    /// The underscore-joined C path of the module declaring `name` (the
476    /// `outer_inner` in `{prefix}_outer_inner_Name`).
477    pub fn module_path(&self, name: &str) -> Option<&str> {
478        self.get(name).map(|d| self.module_paths[d.module].as_str())
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    const I32: Ty = Ty::Prim(Prim::I32);
487    const STRING: Ty = Ty::Prim(Prim::String);
488
489    #[test]
490    fn families_are_total_and_agree_with_the_abi_contract() {
491        assert_eq!(I32.family(), Family::Direct);
492        assert_eq!(Ty::Prim(Prim::Bool).family(), Family::Direct);
493        assert_eq!(Ty::Enum("Color".into()).family(), Family::Direct);
494        assert_eq!(STRING.family(), Family::String);
495        assert_eq!(Ty::Prim(Prim::Bytes).family(), Family::Bytes);
496        for ty in [
497            Ty::Record("C".into()),
498            Ty::RichEnum("S".into()),
499            Ty::List(Box::new(I32)),
500            Ty::List(Box::new(Ty::Interface("Store".into()))),
501            Ty::Map(Box::new(STRING), Box::new(I32)),
502            Ty::Optional(Box::new(I32)),
503            Ty::Optional(Box::new(STRING)),
504            Ty::Optional(Box::new(Ty::Record("C".into()))),
505        ] {
506            assert_eq!(ty.family(), Family::Buffer, "{ty}");
507            assert!(ty.is_buffered());
508        }
509        assert_eq!(
510            Ty::Interface("Store".into()).family(),
511            Family::Object { nullable: false }
512        );
513        assert_eq!(
514            Ty::Optional(Box::new(Ty::Interface("Store".into()))).family(),
515            Family::Object { nullable: true }
516        );
517        assert_eq!(
518            Ty::CallbackInterface("Listener".into()).family(),
519            Family::Callback { nullable: false }
520        );
521        assert_eq!(
522            Ty::Optional(Box::new(Ty::CallbackInterface("Listener".into()))).family(),
523            Family::Callback { nullable: true }
524        );
525        assert_eq!(Ty::Iterator(Box::new(I32)).family(), Family::Iterator);
526    }
527
528    #[test]
529    fn wire_shapes() {
530        for p in Prim::ALL {
531            assert_eq!(Ty::Prim(p).wire(), WireType::Prim(p));
532        }
533        assert_eq!(
534            Ty::Interface("Store".into()).wire(),
535            WireType::Object("Store")
536        );
537        assert_eq!(Ty::RichEnum("Shape".into()).wire(), WireType::User("Shape"));
538        assert_eq!(Ty::Enum("Color".into()).wire(), WireType::Enum("Color"));
539        let list = Ty::List(Box::new(I32));
540        assert_eq!(list.wire(), WireType::List(&I32));
541    }
542
543    #[test]
544    #[should_panic(expected = "cannot appear inside value buffers")]
545    fn callback_interfaces_have_no_wire_shape() {
546        Ty::CallbackInterface("Listener".into()).wire();
547    }
548
549    #[test]
550    fn containment_recurses() {
551        assert!(Ty::Record("C".into()).contains_user_type());
552        assert!(Ty::Map(
553            Box::new(STRING),
554            Box::new(Ty::Optional(Box::new(Ty::RichEnum("S".into()))))
555        )
556        .contains_user_type());
557        assert!(!Ty::List(Box::new(Ty::Enum("Color".into()))).contains_user_type());
558        assert!(Ty::List(Box::new(Ty::Interface("Store".into()))).contains_object());
559        assert!(!Ty::List(Box::new(Ty::Record("C".into()))).contains_object());
560    }
561
562    #[test]
563    fn display_is_the_idl_spelling() {
564        let ty = Ty::List(Box::new(Ty::Map(
565            Box::new(STRING),
566            Box::new(Ty::Optional(Box::new(Ty::Record("Contact".into())))),
567        )));
568        assert_eq!(ty.to_string(), "[{string:Contact?}]");
569        assert_eq!(
570            Ty::Iterator(Box::new(Ty::Interface("Store".into()))).to_string(),
571            "iter<Store>"
572        );
573        assert_eq!(Prim::String.snake(), "string");
574        assert_eq!(Prim::I32.pascal(), "I32");
575    }
576
577    #[test]
578    fn primitive_names_round_trip() {
579        for p in Prim::ALL {
580            assert_eq!(Prim::from_name(p.snake()), Some(p));
581        }
582        assert_eq!(Prim::from_name("usize"), None);
583        assert!(Prim::U8.is_integer() && !Prim::F64.is_integer() && !Prim::Bool.is_numeric());
584    }
585}