Skip to main content

shape_value/
ids.rs

1//! Strongly-typed newtype wrappers for numeric identifiers.
2//!
3//! These newtypes prevent accidental misuse of raw `u16`/`u32`/`u64` values
4//! in different identifier domains (function IDs, string pool indices, schema IDs, etc.).
5
6use serde::{Deserialize, Serialize};
7
8/// A function identifier. Indexes into `BytecodeProgram::functions`.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
10#[repr(transparent)]
11pub struct FunctionId(pub u16);
12
13impl FunctionId {
14    /// Create a new FunctionId from a raw u16.
15    #[inline]
16    pub const fn new(id: u16) -> Self {
17        Self(id)
18    }
19
20    /// Get the raw u16 value.
21    #[inline]
22    pub const fn raw(self) -> u16 {
23        self.0
24    }
25
26    /// Convert to usize for indexing.
27    #[inline]
28    pub const fn index(self) -> usize {
29        self.0 as usize
30    }
31}
32
33impl From<u16> for FunctionId {
34    #[inline]
35    fn from(id: u16) -> Self {
36        Self(id)
37    }
38}
39
40impl From<FunctionId> for u16 {
41    #[inline]
42    fn from(id: FunctionId) -> u16 {
43        id.0
44    }
45}
46
47impl From<FunctionId> for usize {
48    #[inline]
49    fn from(id: FunctionId) -> usize {
50        id.0 as usize
51    }
52}
53
54impl std::fmt::Display for FunctionId {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "fn#{}", self.0)
57    }
58}
59
60/// A string pool identifier. Indexes into `BytecodeProgram::strings`.
61///
62/// Using `StringId` instead of a heap-allocated `String` makes
63/// `Operand` (and therefore `Instruction`) `Copy`.
64///
65/// # Current status
66///
67/// `StringId` is used by bytecode instructions (`Operand`) to reference interned strings
68/// in `BytecodeProgram::strings`, and it is fully integrated with the bytecode compiler
69/// and VM executor. However, many runtime paths (e.g. `HeapValue::String`, method dispatch
70/// keys, and serialization) still use `Arc<String>` rather than intern IDs. A crate-level
71/// `InternPool` that maps `StringId <-> &str` would allow these paths to avoid heap
72/// allocation and use O(1) integer comparison instead of string comparison.
73///
74/// TODO: Evaluate adding an `InternPool` struct here (owned by the VM or compilation
75/// context) that bridges the gap between `StringId` in opcodes and `Arc<String>` in
76/// `HeapValue`. Key considerations:
77/// - The pool must be thread-safe if shared across async tasks.
78/// - `HeapValue::String` could hold `StringId` instead of `Arc<String>` for short strings.
79/// - Method registry PHF keys could use `StringId` for faster dispatch.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
81#[repr(transparent)]
82pub struct StringId(pub u32);
83
84impl StringId {
85    /// Create a new StringId from a raw u32.
86    #[inline]
87    pub const fn new(id: u32) -> Self {
88        Self(id)
89    }
90
91    /// Get the raw u32 value.
92    #[inline]
93    pub const fn raw(self) -> u32 {
94        self.0
95    }
96
97    /// Convert to usize for indexing.
98    #[inline]
99    pub const fn index(self) -> usize {
100        self.0 as usize
101    }
102}
103
104impl From<u32> for StringId {
105    #[inline]
106    fn from(id: u32) -> Self {
107        Self(id)
108    }
109}
110
111impl From<StringId> for u32 {
112    #[inline]
113    fn from(id: StringId) -> u32 {
114        id.0
115    }
116}
117
118impl From<StringId> for usize {
119    #[inline]
120    fn from(id: StringId) -> usize {
121        id.0 as usize
122    }
123}
124
125impl std::fmt::Display for StringId {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        write!(f, "str#{}", self.0)
128    }
129}
130
131/// A type schema identifier. Indexes into the type schema registry.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
133#[repr(transparent)]
134pub struct SchemaId(pub u32);
135
136impl SchemaId {
137    /// Create a new SchemaId from a raw u32.
138    #[inline]
139    pub const fn new(id: u32) -> Self {
140        Self(id)
141    }
142
143    /// Get the raw u32 value.
144    #[inline]
145    pub const fn raw(self) -> u32 {
146        self.0
147    }
148
149    /// Convert to usize for indexing.
150    #[inline]
151    pub const fn index(self) -> usize {
152        self.0 as usize
153    }
154}
155
156impl From<u32> for SchemaId {
157    #[inline]
158    fn from(id: u32) -> Self {
159        Self(id)
160    }
161}
162
163impl From<SchemaId> for u32 {
164    #[inline]
165    fn from(id: SchemaId) -> u32 {
166        id.0
167    }
168}
169
170impl From<SchemaId> for usize {
171    #[inline]
172    fn from(id: SchemaId) -> usize {
173        id.0 as usize
174    }
175}
176
177impl std::fmt::Display for SchemaId {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        write!(f, "schema#{}", self.0)
180    }
181}
182
183/// A stack slot index. Used for local variables and temporary values.
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
185#[repr(transparent)]
186pub struct StackSlotIdx(pub usize);
187
188impl StackSlotIdx {
189    /// Create a new StackSlotIdx.
190    #[inline]
191    pub const fn new(idx: usize) -> Self {
192        Self(idx)
193    }
194
195    /// Get the raw usize value.
196    #[inline]
197    pub const fn raw(self) -> usize {
198        self.0
199    }
200}
201
202impl From<usize> for StackSlotIdx {
203    #[inline]
204    fn from(idx: usize) -> Self {
205        Self(idx)
206    }
207}
208
209impl From<StackSlotIdx> for usize {
210    #[inline]
211    fn from(idx: StackSlotIdx) -> usize {
212        idx.0
213    }
214}
215
216impl std::fmt::Display for StackSlotIdx {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        write!(f, "slot#{}", self.0)
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_function_id_conversions() {
228        let id = FunctionId::new(42);
229        assert_eq!(id.raw(), 42u16);
230        assert_eq!(id.index(), 42usize);
231        assert_eq!(u16::from(id), 42u16);
232        assert_eq!(usize::from(id), 42usize);
233        assert_eq!(FunctionId::from(42u16), id);
234    }
235
236    #[test]
237    fn test_string_id_conversions() {
238        let id = StringId::new(100);
239        assert_eq!(id.raw(), 100u32);
240        assert_eq!(id.index(), 100usize);
241        assert_eq!(u32::from(id), 100u32);
242        assert_eq!(usize::from(id), 100usize);
243        assert_eq!(StringId::from(100u32), id);
244    }
245
246    #[test]
247    fn test_schema_id_conversions() {
248        let id = SchemaId::new(7);
249        assert_eq!(id.raw(), 7u32);
250        assert_eq!(id.index(), 7usize);
251        assert_eq!(u32::from(id), 7u32);
252        assert_eq!(SchemaId::from(7u32), id);
253    }
254
255    #[test]
256    fn test_display() {
257        assert_eq!(format!("{}", FunctionId::new(5)), "fn#5");
258        assert_eq!(format!("{}", StringId::new(10)), "str#10");
259        assert_eq!(format!("{}", SchemaId::new(3)), "schema#3");
260        assert_eq!(format!("{}", StackSlotIdx::new(0)), "slot#0");
261    }
262
263    #[test]
264    fn test_different_types_not_comparable() {
265        // This is a compile-time check — these should NOT compile:
266        // let _: FunctionId = StringId::new(1); // error: mismatched types
267        // let _: StringId = FunctionId::new(1); // error: mismatched types
268        // Just verify they're different types
269        let fn_id = FunctionId::new(1);
270        let str_id = StringId::new(1);
271        assert_ne!(
272            std::any::TypeId::of::<FunctionId>(),
273            std::any::TypeId::of::<StringId>()
274        );
275        // Can't accidentally mix them, even though the raw values are the same
276        let _ = (fn_id, str_id);
277    }
278}