Skip to main content

weaveffi_model/model/
ty.rs

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