plotnik_lib/ir/
type_metadata.rs

1//! Type metadata for code generation and validation.
2//!
3//! Type metadata is descriptive, not prescriptive—it describes what
4//! transitions produce, not how they execute.
5
6use super::Slice;
7use super::ids::{STRING_NONE, StringId, TypeId};
8
9/// First composite type ID (after primitives 0-2).
10pub const TYPE_COMPOSITE_START: TypeId = 3;
11
12/// Type definition in the compiled query.
13///
14/// The `members` field has dual semantics based on `kind`:
15/// - Wrappers (Optional/ArrayStar/ArrayPlus): `members.start_index` is inner TypeId
16/// - Composites (Record/Enum): `members` is slice into type_members segment
17#[repr(C)]
18#[derive(Debug, Clone, Copy)]
19pub struct TypeDef {
20    pub kind: TypeKind,
21    _pad: u8,
22    /// Synthetic or explicit type name. `STRING_NONE` for unnamed wrappers.
23    pub name: StringId,
24    /// See struct-level docs for dual semantics.
25    pub members: Slice<TypeMember>,
26}
27
28// Size is 12 bytes: kind(1) + pad(1) + name(2) + members(8) = 12
29// Alignment is 4 due to Slice<T> having align 4
30const _: () = assert!(size_of::<TypeDef>() == 12);
31const _: () = assert!(align_of::<TypeDef>() == 4);
32
33impl TypeDef {
34    /// Create a wrapper type (Optional, ArrayStar, ArrayPlus).
35    pub fn wrapper(kind: TypeKind, inner: TypeId) -> Self {
36        debug_assert!(matches!(
37            kind,
38            TypeKind::Optional | TypeKind::ArrayStar | TypeKind::ArrayPlus
39        ));
40        Self {
41            kind,
42            _pad: 0,
43            name: STRING_NONE,
44            members: Slice::from_inner_type(inner),
45        }
46    }
47
48    /// Create a composite type (Record, Enum).
49    pub fn composite(kind: TypeKind, name: StringId, members: Slice<TypeMember>) -> Self {
50        debug_assert!(matches!(kind, TypeKind::Record | TypeKind::Enum));
51        Self {
52            kind,
53            _pad: 0,
54            name,
55            members,
56        }
57    }
58
59    /// For wrapper types, returns the inner type ID.
60    pub fn inner_type(&self) -> Option<TypeId> {
61        match self.kind {
62            TypeKind::Optional | TypeKind::ArrayStar | TypeKind::ArrayPlus => {
63                Some(self.members.start_index() as TypeId)
64            }
65            TypeKind::Record | TypeKind::Enum => None,
66        }
67    }
68
69    /// For composite types, returns the members slice.
70    pub fn members_slice(&self) -> Option<Slice<TypeMember>> {
71        match self.kind {
72            TypeKind::Record | TypeKind::Enum => Some(self.members),
73            TypeKind::Optional | TypeKind::ArrayStar | TypeKind::ArrayPlus => None,
74        }
75    }
76
77    pub fn is_wrapper(&self) -> bool {
78        matches!(
79            self.kind,
80            TypeKind::Optional | TypeKind::ArrayStar | TypeKind::ArrayPlus
81        )
82    }
83
84    pub fn is_composite(&self) -> bool {
85        matches!(self.kind, TypeKind::Record | TypeKind::Enum)
86    }
87}
88
89/// Discriminant for type definitions.
90#[repr(u8)]
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum TypeKind {
93    /// `T?` — nullable wrapper
94    Optional = 0,
95    /// `T*` — zero or more elements
96    ArrayStar = 1,
97    /// `T+` — one or more elements (non-empty)
98    ArrayPlus = 2,
99    /// Struct with named fields
100    Record = 3,
101    /// Tagged union (discriminated)
102    Enum = 4,
103}
104
105/// Member of a Record (field) or Enum (variant).
106#[repr(C)]
107#[derive(Debug, Clone, Copy)]
108pub struct TypeMember {
109    /// Field name or variant tag.
110    pub name: StringId,
111    /// Field type or variant payload. `TYPE_VOID` for unit variants.
112    pub ty: TypeId,
113}
114
115const _: () = assert!(size_of::<TypeMember>() == 4);
116const _: () = assert!(align_of::<TypeMember>() == 2);
117
118impl TypeMember {
119    pub fn new(name: StringId, ty: TypeId) -> Self {
120        Self { name, ty }
121    }
122}