Skip to main content

shape_value/v2/
concrete_type.rs

1//! Concrete monomorphized type for v2 runtime.
2//!
3//! `ConcreteType` replaces `SlotKind` with richer type information that flows
4//! from type inference through the bytecode compiler, VM, and JIT. Every local,
5//! parameter, field, return value, and collection element has a `ConcreteType`
6//! at compile time — no unresolved type variables survive past compilation.
7//!
8//! This is the foundation for monomorphization: generic functions like
9//! `map<T, U>` are specialized per `ConcreteType` instantiation.
10
11use serde::{Deserialize, Serialize};
12
13/// Opaque ID into a registry of struct layouts (resolved at compile time).
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct StructLayoutId(pub u32);
16
17/// Opaque ID into a registry of enum layouts (resolved at compile time).
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct EnumLayoutId(pub u32);
20
21/// v0.3 WS-6 — the source-level type name carried alongside a
22/// `ConcreteType::Struct` / `ConcreteType::Enum`.
23///
24/// A struct's / enum's type identity is its *name*, not its layout id (the
25/// `StructLayoutId` / `EnumLayoutId` carriers are placeholders today — the
26/// schema-aware layout registry is a tracked follow-up). The monomorphizer
27/// uses this name for two load-bearing purposes:
28///
29///   1. The specialization cache key (`ConcreteType::mono_key`) — so
30///      `id<T>(P{..})` and `id<T>(Q{..})` produce *distinct* specializations.
31///   2. The substituted parameter / return annotation
32///      (`concrete_to_annotation`) — so the specialized body type-checks
33///      against the real user type rather than a synthetic placeholder.
34///
35/// `None` is the legacy/placeholder spelling: a struct/enum `ConcreteType`
36/// produced by a path that does not yet thread the name (e.g. the JIT-side
37/// MIR-shape inference at `helpers.rs`). Name-aware producers (the
38/// monomorphization call-site resolver) always populate it.
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
40pub struct NamedTypeId<Id> {
41    /// Layout-registry id (placeholder `0` until the schema-aware layout
42    /// registry lands).
43    pub layout: Id,
44    /// Source-level type name, when the producing site could thread it.
45    pub name: Option<std::sync::Arc<str>>,
46}
47
48impl<Id> NamedTypeId<Id> {
49    /// A named type id — the name is the load-bearing identity.
50    pub fn named(layout: Id, name: impl Into<std::sync::Arc<str>>) -> Self {
51        NamedTypeId { layout, name: Some(name.into()) }
52    }
53
54    /// A placeholder type id — no name threaded (legacy producers).
55    pub fn placeholder(layout: Id) -> Self {
56        NamedTypeId { layout, name: None }
57    }
58
59    /// The source-level name, if threaded.
60    pub fn name_str(&self) -> Option<&str> {
61        self.name.as_deref()
62    }
63}
64
65/// Opaque ID into a registry of closure capture layouts.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub struct ClosureTypeId(pub u32);
68
69/// Opaque ID into a registry of function signatures.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71pub struct FunctionTypeId(pub u32);
72
73/// Fully resolved, monomorphized type. No type variables, no generics.
74///
75/// Every expression and local slot in compiled bytecode has exactly one
76/// `ConcreteType`. The compiler resolves all `Type::Variable` and
77/// `Type::Generic` to `ConcreteType` after type inference.
78///
79/// The discriminant is stored as `u8` for compact bytecode encoding.
80#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
81pub enum ConcreteType {
82    /// f64 — the default `number` type.
83    F64,
84    /// f32 — 4-byte single-precision float. ADR-006 §2.7.5 amendment
85    /// (Round 19 S1.5, 2026-05-14): scalar concrete type introduced
86    /// alongside `NativeKind::Float32` for `Array<f32>` v2-raw
87    /// producer paths.
88    F32,
89    /// `char` — 4-byte Unicode scalar (UTF-32 subset of `u32`). ADR-006
90    /// §2.7.5 amendment (Round 19 S1.5, 2026-05-14): scalar concrete
91    /// type introduced alongside `NativeKind::Char` for `Array<char>`
92    /// v2-raw producer paths.
93    Char,
94    /// i64 — the default `int` type (i48 in NaN-boxed representation).
95    I64,
96    /// i32
97    I32,
98    /// i16
99    I16,
100    /// i8
101    I8,
102    /// u64
103    U64,
104    /// u32
105    U32,
106    /// u16
107    U16,
108    /// u8
109    U8,
110    /// bool
111    Bool,
112    /// Interned string (*const StringObj).
113    String,
114    /// Typed struct with compile-time field layout. Carries the source-level
115    /// type name (v0.3 WS-6) — see [`NamedTypeId`].
116    Struct(NamedTypeId<StructLayoutId>),
117    /// Homogeneous typed array with known element type.
118    /// `Array<number>` → `Array(Box::new(ConcreteType::F64))`.
119    Array(Box<ConcreteType>),
120    /// Typed hash map with known key and value types.
121    HashMap(Box<ConcreteType>, Box<ConcreteType>),
122    /// Nullable type — `T?` / `Option<T>`.
123    Option(Box<ConcreteType>),
124    /// Result type — `Result<T, E>`.
125    Result(Box<ConcreteType>, Box<ConcreteType>),
126    /// Typed enum with compile-time variant layouts. Carries the source-level
127    /// type name (v0.3 WS-6) — see [`NamedTypeId`].
128    Enum(NamedTypeId<EnumLayoutId>),
129    /// Closure with typed capture slots.
130    Closure(ClosureTypeId),
131    /// Function pointer with known signature.
132    Function(FunctionTypeId),
133    /// Raw typed pointer (for FFI / extern C).
134    Pointer(Box<ConcreteType>),
135    /// Tuple with known element types.
136    Tuple(Vec<ConcreteType>),
137    /// Void (unit) — no value.
138    Void,
139    /// Decimal (rust_decimal::Decimal).
140    Decimal,
141    /// BigInt (arbitrary precision integer).
142    BigInt,
143    /// DateTime.
144    DateTime,
145    // ── Phase 3 cluster-0 Round 11-trinity 11E (2026-05-13) ─────────────
146    //
147    // Collection-container and concurrency-primitive arms surfaced by
148    // Round 10's W12-jit-call-method-shell-rebuild close: the JIT-side
149    // §2.7.5 producing-site conduit cannot stamp parametric return kinds
150    // (`HashMap.get → Option<V>`, `Mutex.get → T`, `Lazy.force → T`) and
151    // the JIT-side `EnumStore` collection-ctor consumer (Round 10) needed
152    // a §2.7.5 in-band classifier rather than an out-of-band MIR-shape
153    // override at `mir_compiler/types.rs:467` — the audit's
154    // "W17-collection-concrete-types tracked follow-up" closing here.
155    //
156    // **Single-discriminator discipline (ADR-005 §1).** None of these
157    // arms project 1:1 to `HeapKind`. Each parametric arm wraps a
158    // `Box<ConcreteType>` for the inner kind (mirror of the existing
159    // `Array(Box<ConcreteType>)` / `HashMap(Box<_>, Box<_>)` shape).
160    // Nullary arms (`PriorityQueue`, `Atomic`) match a §2.7.18 / §2.7.25
161    // landing-time storage decision (i64-only payload) — the ConcreteType
162    // is genuinely nullary at landing, not "stripped to be a discriminator".
163    // When the typed-payload follow-up amendments land (Phase 2c per
164    // §2.7.18 / §2.7.25 "Out-of-scope" notes) these arms grow a
165    // `Box<ConcreteType>` parameter at that point.
166    //
167    /// HashSet with known element type. String-only at landing per
168    /// ADR-006 §2.7.15 (`HeapKind::HashSet`); the inner `ConcreteType`
169    /// is `String` at construction sites today, and the parametric arm
170    /// shape preserves room for the future typed-payload extension.
171    HashSet(Box<ConcreteType>),
172    /// Heterogeneous-element double-ended queue. Storage at the
173    /// `Arc<DequeData>` tier is `VecDeque<Arc<HeapValue>>` (§2.7.17
174    /// Q19 deferral); the ConcreteType inner element kind is the
175    /// per-element kind the producing-site classification stamps when
176    /// the bytecode compiler can prove it (e.g. `Deque<int>` literal
177    /// construction), else `ConcreteType::Void` placeholder.
178    Deque(Box<ConcreteType>),
179    /// i64-priority min-heap. i64-only at landing per ADR-006 §2.7.18
180    /// (`HeapKind::PriorityQueue`); no element-kind variance. Typed-
181    /// payload PriorityQueue is the Phase-2c amendment (§2.7.18
182    /// "Out-of-scope") tracked separately.
183    PriorityQueue,
184    /// MPSC-style channel with typed payload kind. Storage at the
185    /// `Arc<ChannelData>` tier holds `KindedSlot` elements (§2.7.20);
186    /// the ConcreteType inner kind is the element kind for `Channel<T>`
187    /// landings. `ConcreteType::Void` placeholder when the producing
188    /// site can't prove the element kind.
189    Channel(Box<ConcreteType>),
190    /// `Mutex<T>` concurrency primitive — single typed payload protected
191    /// by `Mutex<MutexInner>` per ADR-006 §2.7.25. Inner `ConcreteType`
192    /// is the wrapped value's kind (`Mutex(int)` → `Mutex(I64)`); the
193    /// payload is mutable at runtime and the parametric arm captures the
194    /// declared inner kind for the `m.get() → T` parametric-return
195    /// classifier at the §2.7.5 conduit.
196    Mutex(Box<ConcreteType>),
197    /// `Atomic<i64>` concurrency primitive — wraps
198    /// `std::sync::atomic::AtomicI64` per ADR-006 §2.7.25. i64-only at
199    /// landing per the "typed-payload deferral" precedent
200    /// (W15-priority-queue i64-only, W13-hashset string-only); typed-
201    /// payload `Atomic<T>` is the Phase-2c amendment tracked separately.
202    Atomic,
203    /// `Lazy<T>` initialize-once carrier — wraps an initializer closure
204    /// + cached value slot per ADR-006 §2.7.25. Inner `ConcreteType` is
205    /// the cached value's kind (the closure's return type), enabling the
206    /// `l.get() → T` parametric-return classifier at the §2.7.5 conduit.
207    Lazy(Box<ConcreteType>),
208}
209
210impl ConcreteType {
211    /// v0.3 WS-6 — a named struct `ConcreteType`. The `name` is the
212    /// load-bearing type identity (cache key + substituted annotation); the
213    /// `layout` id is a placeholder until the schema-aware layout registry
214    /// lands.
215    pub fn named_struct(name: impl Into<std::sync::Arc<str>>, layout: StructLayoutId) -> Self {
216        ConcreteType::Struct(NamedTypeId::named(layout, name))
217    }
218
219    /// v0.3 WS-6 — a named enum `ConcreteType`. See [`Self::named_struct`].
220    pub fn named_enum(name: impl Into<std::sync::Arc<str>>, layout: EnumLayoutId) -> Self {
221        ConcreteType::Enum(NamedTypeId::named(layout, name))
222    }
223
224    /// A struct `ConcreteType` with no threaded name (legacy/placeholder
225    /// producers — see [`NamedTypeId`]).
226    pub fn placeholder_struct(layout: StructLayoutId) -> Self {
227        ConcreteType::Struct(NamedTypeId::placeholder(layout))
228    }
229
230    /// An enum `ConcreteType` with no threaded name (legacy/placeholder
231    /// producers — see [`NamedTypeId`]).
232    pub fn placeholder_enum(layout: EnumLayoutId) -> Self {
233        ConcreteType::Enum(NamedTypeId::placeholder(layout))
234    }
235
236    /// Size in bytes for stack storage (all values stored as 8-byte slots).
237    #[inline]
238    pub fn stack_size(&self) -> usize {
239        8 // All values occupy one 8-byte stack slot
240    }
241
242    /// Natural alignment for this type when stored in a struct.
243    #[inline]
244    pub fn alignment(&self) -> usize {
245        match self {
246            ConcreteType::I8 | ConcreteType::U8 | ConcreteType::Bool => 1,
247            ConcreteType::I16 | ConcreteType::U16 => 2,
248            // Round 19 S1.5 (2026-05-14): F32 and Char are 4-byte
249            // scalars per the §2.7.5 amendment.
250            ConcreteType::I32
251            | ConcreteType::U32
252            | ConcreteType::F32
253            | ConcreteType::Char => 4,
254            _ => 8, // f64, i64, u64, pointers, etc.
255        }
256    }
257
258    /// Size in bytes when stored in a struct field (not on stack).
259    #[inline]
260    pub fn field_size(&self) -> usize {
261        match self {
262            ConcreteType::I8 | ConcreteType::U8 | ConcreteType::Bool => 1,
263            ConcreteType::I16 | ConcreteType::U16 => 2,
264            // Round 19 S1.5 (2026-05-14): F32 and Char are 4-byte
265            // scalars per the §2.7.5 amendment.
266            ConcreteType::I32
267            | ConcreteType::U32
268            | ConcreteType::F32
269            | ConcreteType::Char => 4,
270            _ => 8,
271        }
272    }
273
274    /// Whether this type is a numeric type (integer or float).
275    #[inline]
276    pub fn is_numeric(&self) -> bool {
277        matches!(
278            self,
279            ConcreteType::F64
280                | ConcreteType::F32
281                | ConcreteType::I64
282                | ConcreteType::I32
283                | ConcreteType::I16
284                | ConcreteType::I8
285                | ConcreteType::U64
286                | ConcreteType::U32
287                | ConcreteType::U16
288                | ConcreteType::U8
289                | ConcreteType::Decimal
290                | ConcreteType::BigInt
291        )
292    }
293
294    /// Whether this type is an integer type.
295    #[inline]
296    pub fn is_integer(&self) -> bool {
297        matches!(
298            self,
299            ConcreteType::I64
300                | ConcreteType::I32
301                | ConcreteType::I16
302                | ConcreteType::I8
303                | ConcreteType::U64
304                | ConcreteType::U32
305                | ConcreteType::U16
306                | ConcreteType::U8
307        )
308    }
309
310    /// Whether this type is a heap-allocated reference type.
311    #[inline]
312    pub fn is_heap(&self) -> bool {
313        matches!(
314            self,
315            ConcreteType::String
316                | ConcreteType::Struct(_)
317                | ConcreteType::Array(_)
318                | ConcreteType::HashMap(_, _)
319                | ConcreteType::Enum(_)
320                | ConcreteType::Closure(_)
321                | ConcreteType::Pointer(_)
322                | ConcreteType::BigInt
323                | ConcreteType::Decimal
324                | ConcreteType::DateTime
325                // ── Phase 3 cluster-0 Round 11-trinity 11E ─────────────
326                // All Round-11 collection / concurrency carriers are
327                // heap-allocated (typed Arc<XData> per §2.7.15 /
328                // §2.7.17-§2.7.20 / §2.7.25). Mirror of the existing
329                // Array / HashMap heap classification.
330                | ConcreteType::HashSet(_)
331                | ConcreteType::Deque(_)
332                | ConcreteType::PriorityQueue
333                | ConcreteType::Channel(_)
334                | ConcreteType::Mutex(_)
335                | ConcreteType::Atomic
336                | ConcreteType::Lazy(_)
337        )
338    }
339
340    /// Whether this is a primitive scalar that fits in a register.
341    #[inline]
342    pub fn is_scalar(&self) -> bool {
343        matches!(
344            self,
345            ConcreteType::F64
346                | ConcreteType::F32
347                | ConcreteType::I64
348                | ConcreteType::I32
349                | ConcreteType::I16
350                | ConcreteType::I8
351                | ConcreteType::U64
352                | ConcreteType::U32
353                | ConcreteType::U16
354                | ConcreteType::U8
355                | ConcreteType::Bool
356                | ConcreteType::Char
357        )
358    }
359
360    /// Convert to the corresponding `FieldKind` for struct layout computation.
361    pub fn to_field_kind(&self) -> super::struct_layout::FieldKind {
362        use super::struct_layout::FieldKind;
363        match self {
364            ConcreteType::F64 => FieldKind::F64,
365            ConcreteType::I64 => FieldKind::I64,
366            ConcreteType::I32 => FieldKind::I32,
367            ConcreteType::I16 => FieldKind::I16,
368            ConcreteType::I8 => FieldKind::I8,
369            ConcreteType::U64 => FieldKind::U64,
370            ConcreteType::U32 => FieldKind::U32,
371            ConcreteType::U16 => FieldKind::U16,
372            ConcreteType::U8 => FieldKind::U8,
373            ConcreteType::Bool => FieldKind::Bool,
374            // Round 19 S1.5 (2026-05-14): F32 and Char are 4-byte
375            // scalars per the §2.7.5 amendment. FieldKind has no
376            // dedicated F32 / Char variants, so the bit-equivalent
377            // 4-byte FieldKind::U32 is the struct-layout carrier (size
378            // + alignment + load/store width all match). Semantic
379            // float-vs-bits / codepoint-vs-bits distinction is preserved
380            // at the NativeKind layer, NOT the struct-layout layer.
381            // FieldKind cardinality extension is a follow-up sub-cluster
382            // (cluster-1 hardening) if struct-field-layout typing of F32
383            // / Char becomes load-bearing.
384            ConcreteType::F32 | ConcreteType::Char => FieldKind::U32,
385            // All reference/heap types are pointer-sized
386            _ => FieldKind::Ptr,
387        }
388    }
389
390    /// Generate a monomorphization key string for specialization caching.
391    /// e.g., `"f64"`, `"array_i64"`, `"hashmap_string_f64"`
392    pub fn mono_key(&self) -> String {
393        match self {
394            ConcreteType::F64 => "f64".into(),
395            ConcreteType::F32 => "f32".into(),
396            ConcreteType::Char => "char".into(),
397            ConcreteType::I64 => "i64".into(),
398            ConcreteType::I32 => "i32".into(),
399            ConcreteType::I16 => "i16".into(),
400            ConcreteType::I8 => "i8".into(),
401            ConcreteType::U64 => "u64".into(),
402            ConcreteType::U32 => "u32".into(),
403            ConcreteType::U16 => "u16".into(),
404            ConcreteType::U8 => "u8".into(),
405            ConcreteType::Bool => "bool".into(),
406            ConcreteType::String => "string".into(),
407            // v0.3 WS-6: a struct's specialization-cache identity is its
408            // source-level name when threaded — so `id<T>(P{..})` and
409            // `id<T>(Q{..})` produce *distinct* mono keys. When the name is
410            // absent (legacy/placeholder producers) the layout id is the
411            // fallback, preserving the pre-WS-6 `struct_<id>` spelling.
412            ConcreteType::Struct(id) => match id.name_str() {
413                Some(name) => format!("struct_{}", name),
414                None => format!("struct_{}", id.layout.0),
415            },
416            ConcreteType::Array(elem) => format!("array_{}", elem.mono_key()),
417            ConcreteType::HashMap(k, v) => {
418                format!("hashmap_{}_{}", k.mono_key(), v.mono_key())
419            }
420            ConcreteType::Option(inner) => format!("option_{}", inner.mono_key()),
421            ConcreteType::Result(ok, err) => {
422                format!("result_{}_{}", ok.mono_key(), err.mono_key())
423            }
424            // v0.3 WS-6: see the `Struct` arm above — name-keyed when
425            // threaded, layout-id fallback otherwise.
426            ConcreteType::Enum(id) => match id.name_str() {
427                Some(name) => format!("enum_{}", name),
428                None => format!("enum_{}", id.layout.0),
429            },
430            ConcreteType::Closure(id) => format!("closure_{}", id.0),
431            ConcreteType::Function(id) => format!("fn_{}", id.0),
432            ConcreteType::Pointer(inner) => format!("ptr_{}", inner.mono_key()),
433            ConcreteType::Tuple(elems) => {
434                let parts: Vec<_> = elems.iter().map(|e| e.mono_key()).collect();
435                format!("tuple_{}", parts.join("_"))
436            }
437            ConcreteType::Void => "void".into(),
438            ConcreteType::Decimal => "decimal".into(),
439            ConcreteType::BigInt => "bigint".into(),
440            ConcreteType::DateTime => "datetime".into(),
441            // ── Phase 3 cluster-0 Round 11-trinity 11E ─────────────────
442            ConcreteType::HashSet(elem) => format!("hashset_{}", elem.mono_key()),
443            ConcreteType::Deque(elem) => format!("deque_{}", elem.mono_key()),
444            ConcreteType::PriorityQueue => "priority_queue".into(),
445            ConcreteType::Channel(elem) => format!("channel_{}", elem.mono_key()),
446            ConcreteType::Mutex(inner) => format!("mutex_{}", inner.mono_key()),
447            ConcreteType::Atomic => "atomic".into(),
448            ConcreteType::Lazy(inner) => format!("lazy_{}", inner.mono_key()),
449        }
450    }
451
452    /// Compact type tag for bytecode encoding (single byte).
453    pub fn type_tag(&self) -> u8 {
454        match self {
455            ConcreteType::F64 => 0,
456            ConcreteType::I64 => 1,
457            ConcreteType::I32 => 2,
458            ConcreteType::I16 => 3,
459            ConcreteType::I8 => 4,
460            ConcreteType::U64 => 5,
461            ConcreteType::U32 => 6,
462            ConcreteType::U16 => 7,
463            ConcreteType::U8 => 8,
464            ConcreteType::Bool => 9,
465            ConcreteType::String => 10,
466            ConcreteType::Struct(_) => 11,
467            ConcreteType::Array(_) => 12,
468            ConcreteType::HashMap(_, _) => 13,
469            ConcreteType::Option(_) => 14,
470            ConcreteType::Result(_, _) => 15,
471            ConcreteType::Enum(_) => 16,
472            ConcreteType::Closure(_) => 17,
473            ConcreteType::Function(_) => 18,
474            ConcreteType::Pointer(_) => 19,
475            ConcreteType::Tuple(_) => 20,
476            ConcreteType::Void => 21,
477            ConcreteType::Decimal => 22,
478            ConcreteType::BigInt => 23,
479            ConcreteType::DateTime => 24,
480            // ── Phase 3 cluster-0 Round 11-trinity 11E ─────────────────
481            ConcreteType::HashSet(_) => 25,
482            ConcreteType::Deque(_) => 26,
483            ConcreteType::PriorityQueue => 27,
484            ConcreteType::Channel(_) => 28,
485            ConcreteType::Mutex(_) => 29,
486            ConcreteType::Atomic => 30,
487            ConcreteType::Lazy(_) => 31,
488            // ── Round 19 S1.5 W12-nativekind-scalar-additions ──────────
489            // (2026-05-14) — ADR-006 §2.7.5 amendment adds F32 + Char
490            // as 4-byte scalar concrete types. Tags 32 and 33 allocated
491            // contiguously after the Round-11 collection/concurrency arms.
492            ConcreteType::F32 => 32,
493            ConcreteType::Char => 33,
494        }
495    }
496}
497
498/// Convert from `FieldKind` (struct layout) to `ConcreteType`.
499impl From<super::struct_layout::FieldKind> for ConcreteType {
500    fn from(fk: super::struct_layout::FieldKind) -> Self {
501        use super::struct_layout::FieldKind;
502        match fk {
503            FieldKind::F64 => ConcreteType::F64,
504            FieldKind::I64 => ConcreteType::I64,
505            FieldKind::I32 => ConcreteType::I32,
506            FieldKind::I16 => ConcreteType::I16,
507            FieldKind::I8 => ConcreteType::I8,
508            FieldKind::U64 => ConcreteType::U64,
509            FieldKind::U32 => ConcreteType::U32,
510            FieldKind::U16 => ConcreteType::U16,
511            FieldKind::U8 => ConcreteType::U8,
512            FieldKind::Bool => ConcreteType::Bool,
513            // Ptr is an opaque pointer — caller must know the pointed-to type
514            FieldKind::Ptr => ConcreteType::Pointer(Box::new(ConcreteType::Void)),
515        }
516    }
517}
518
519impl std::fmt::Display for ConcreteType {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        match self {
522            ConcreteType::F64 => write!(f, "number"),
523            ConcreteType::F32 => write!(f, "f32"),
524            ConcreteType::Char => write!(f, "char"),
525            ConcreteType::I64 => write!(f, "int"),
526            ConcreteType::I32 => write!(f, "i32"),
527            ConcreteType::I16 => write!(f, "i16"),
528            ConcreteType::I8 => write!(f, "i8"),
529            ConcreteType::U64 => write!(f, "u64"),
530            ConcreteType::U32 => write!(f, "u32"),
531            ConcreteType::U16 => write!(f, "u16"),
532            ConcreteType::U8 => write!(f, "u8"),
533            ConcreteType::Bool => write!(f, "bool"),
534            ConcreteType::String => write!(f, "string"),
535            ConcreteType::Struct(id) => match id.name_str() {
536                Some(name) => write!(f, "{}", name),
537                None => write!(f, "Struct#{}", id.layout.0),
538            },
539            ConcreteType::Array(elem) => write!(f, "Array<{elem}>"),
540            ConcreteType::HashMap(k, v) => write!(f, "HashMap<{k}, {v}>"),
541            ConcreteType::Option(inner) => write!(f, "{inner}?"),
542            ConcreteType::Result(ok, err) => write!(f, "Result<{ok}, {err}>"),
543            ConcreteType::Enum(id) => match id.name_str() {
544                Some(name) => write!(f, "{}", name),
545                None => write!(f, "Enum#{}", id.layout.0),
546            },
547            ConcreteType::Closure(id) => write!(f, "Closure#{}", id.0),
548            ConcreteType::Function(id) => write!(f, "Function#{}", id.0),
549            ConcreteType::Pointer(inner) => write!(f, "ptr<{inner}>"),
550            ConcreteType::Tuple(elems) => {
551                write!(f, "(")?;
552                for (i, e) in elems.iter().enumerate() {
553                    if i > 0 {
554                        write!(f, ", ")?;
555                    }
556                    write!(f, "{e}")?;
557                }
558                write!(f, ")")
559            }
560            ConcreteType::Void => write!(f, "void"),
561            ConcreteType::Decimal => write!(f, "decimal"),
562            ConcreteType::BigInt => write!(f, "bigint"),
563            ConcreteType::DateTime => write!(f, "DateTime"),
564            // ── Phase 3 cluster-0 Round 11-trinity 11E ─────────────────
565            ConcreteType::HashSet(elem) => write!(f, "HashSet<{elem}>"),
566            ConcreteType::Deque(elem) => write!(f, "Deque<{elem}>"),
567            ConcreteType::PriorityQueue => write!(f, "PriorityQueue"),
568            ConcreteType::Channel(elem) => write!(f, "Channel<{elem}>"),
569            ConcreteType::Mutex(inner) => write!(f, "Mutex<{inner}>"),
570            ConcreteType::Atomic => write!(f, "Atomic"),
571            ConcreteType::Lazy(inner) => write!(f, "Lazy<{inner}>"),
572        }
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579
580    #[test]
581    fn test_mono_key_primitives() {
582        assert_eq!(ConcreteType::F64.mono_key(), "f64");
583        assert_eq!(ConcreteType::I64.mono_key(), "i64");
584        assert_eq!(ConcreteType::Bool.mono_key(), "bool");
585        assert_eq!(ConcreteType::String.mono_key(), "string");
586    }
587
588    #[test]
589    fn test_mono_key_composites() {
590        let arr_f64 = ConcreteType::Array(Box::new(ConcreteType::F64));
591        assert_eq!(arr_f64.mono_key(), "array_f64");
592
593        let map = ConcreteType::HashMap(
594            Box::new(ConcreteType::String),
595            Box::new(ConcreteType::I64),
596        );
597        assert_eq!(map.mono_key(), "hashmap_string_i64");
598
599        let nested = ConcreteType::Array(Box::new(ConcreteType::Array(Box::new(
600            ConcreteType::I32,
601        ))));
602        assert_eq!(nested.mono_key(), "array_array_i32");
603    }
604
605    #[test]
606    fn ws6_named_struct_mono_key_uses_name() {
607        // v0.3 WS-6: a named struct's mono key is keyed on the source-level
608        // type name — so a generic `id<T>` specialized on two different
609        // structs produces two distinct cache keys / specializations.
610        let p = ConcreteType::named_struct("P", StructLayoutId(0));
611        let q = ConcreteType::named_struct("Q", StructLayoutId(0));
612        assert_eq!(p.mono_key(), "struct_P");
613        assert_eq!(q.mono_key(), "struct_Q");
614        assert_ne!(
615            p.mono_key(),
616            q.mono_key(),
617            "distinct structs must produce distinct mono keys"
618        );
619        assert_ne!(p, q, "named structs with distinct names are not equal");
620    }
621
622    #[test]
623    fn ws6_named_enum_mono_key_uses_name() {
624        let c = ConcreteType::named_enum("Color", EnumLayoutId(0));
625        assert_eq!(c.mono_key(), "enum_Color");
626    }
627
628    #[test]
629    fn ws6_placeholder_struct_falls_back_to_layout_id() {
630        // A struct ConcreteType produced by a name-blind path (legacy
631        // producer) keeps the pre-WS-6 `struct_<id>` mono-key spelling.
632        let s = ConcreteType::placeholder_struct(StructLayoutId(0));
633        assert_eq!(s.mono_key(), "struct_0");
634        assert!(matches!(s, ConcreteType::Struct(ref id) if id.name_str().is_none()));
635    }
636
637    #[test]
638    fn test_type_tags_unique() {
639        let types = vec![
640            ConcreteType::F64,
641            ConcreteType::I64,
642            ConcreteType::I32,
643            ConcreteType::I16,
644            ConcreteType::I8,
645            ConcreteType::U64,
646            ConcreteType::U32,
647            ConcreteType::U16,
648            ConcreteType::U8,
649            ConcreteType::Bool,
650            ConcreteType::String,
651            ConcreteType::placeholder_struct(StructLayoutId(0)),
652            ConcreteType::Array(Box::new(ConcreteType::F64)),
653            ConcreteType::HashMap(Box::new(ConcreteType::String), Box::new(ConcreteType::F64)),
654            ConcreteType::Option(Box::new(ConcreteType::I64)),
655            ConcreteType::Result(Box::new(ConcreteType::I64), Box::new(ConcreteType::String)),
656            ConcreteType::placeholder_enum(EnumLayoutId(0)),
657            ConcreteType::Closure(ClosureTypeId(0)),
658            ConcreteType::Function(FunctionTypeId(0)),
659            ConcreteType::Pointer(Box::new(ConcreteType::U8)),
660            ConcreteType::Tuple(vec![ConcreteType::I64, ConcreteType::F64]),
661            ConcreteType::Void,
662            ConcreteType::Decimal,
663            ConcreteType::BigInt,
664            ConcreteType::DateTime,
665            // ── Phase 3 cluster-0 Round 11-trinity 11E ─────────────────
666            ConcreteType::HashSet(Box::new(ConcreteType::String)),
667            ConcreteType::Deque(Box::new(ConcreteType::I64)),
668            ConcreteType::PriorityQueue,
669            ConcreteType::Channel(Box::new(ConcreteType::I64)),
670            ConcreteType::Mutex(Box::new(ConcreteType::I64)),
671            ConcreteType::Atomic,
672            ConcreteType::Lazy(Box::new(ConcreteType::I64)),
673            // ── Round 19 S1.5 W12-nativekind-scalar-additions ─────────
674            ConcreteType::F32,
675            ConcreteType::Char,
676        ];
677        let tags: Vec<u8> = types.iter().map(|t| t.type_tag()).collect();
678        let unique: std::collections::HashSet<u8> = tags.iter().copied().collect();
679        assert_eq!(tags.len(), unique.len(), "type tags must be unique");
680    }
681
682    /// Round 19 S1.5 (2026-05-14): F32 + Char additions ride the same
683    /// scalar dispatch shape as the existing 4-byte scalars (I32 / U32).
684    #[test]
685    fn test_round_19_f32_char_scalars() {
686        assert_eq!(ConcreteType::F32.mono_key(), "f32");
687        assert_eq!(ConcreteType::Char.mono_key(), "char");
688        assert_eq!(format!("{}", ConcreteType::F32), "f32");
689        assert_eq!(format!("{}", ConcreteType::Char), "char");
690        // 4-byte alignment + field-size match I32 / U32.
691        assert_eq!(ConcreteType::F32.alignment(), 4);
692        assert_eq!(ConcreteType::F32.field_size(), 4);
693        assert_eq!(ConcreteType::Char.alignment(), 4);
694        assert_eq!(ConcreteType::Char.field_size(), 4);
695        // Both are scalar; F32 is numeric (float-family), Char is NOT
696        // numeric (UTF-32 codepoint, not a numeric type).
697        assert!(ConcreteType::F32.is_scalar());
698        assert!(ConcreteType::Char.is_scalar());
699        assert!(ConcreteType::F32.is_numeric());
700        assert!(!ConcreteType::Char.is_numeric());
701        assert!(!ConcreteType::F32.is_integer());
702        assert!(!ConcreteType::Char.is_integer());
703        // Neither is heap-allocated.
704        assert!(!ConcreteType::F32.is_heap());
705        assert!(!ConcreteType::Char.is_heap());
706    }
707
708    #[test]
709    fn test_round_11_collection_concurrency_arms_mono_key_and_display() {
710        // Phase 3 cluster-0 Round 11-trinity 11E: collection/concurrency
711        // arms round-trip through mono_key and Display.
712        let hs = ConcreteType::HashSet(Box::new(ConcreteType::String));
713        assert_eq!(hs.mono_key(), "hashset_string");
714        assert_eq!(format!("{hs}"), "HashSet<string>");
715
716        let dq = ConcreteType::Deque(Box::new(ConcreteType::I64));
717        assert_eq!(dq.mono_key(), "deque_i64");
718        assert_eq!(format!("{dq}"), "Deque<int>");
719
720        let pq = ConcreteType::PriorityQueue;
721        assert_eq!(pq.mono_key(), "priority_queue");
722        assert_eq!(format!("{pq}"), "PriorityQueue");
723
724        let ch = ConcreteType::Channel(Box::new(ConcreteType::I64));
725        assert_eq!(ch.mono_key(), "channel_i64");
726        assert_eq!(format!("{ch}"), "Channel<int>");
727
728        let mx = ConcreteType::Mutex(Box::new(ConcreteType::I64));
729        assert_eq!(mx.mono_key(), "mutex_i64");
730        assert_eq!(format!("{mx}"), "Mutex<int>");
731
732        let at = ConcreteType::Atomic;
733        assert_eq!(at.mono_key(), "atomic");
734        assert_eq!(format!("{at}"), "Atomic");
735
736        let lz = ConcreteType::Lazy(Box::new(ConcreteType::Bool));
737        assert_eq!(lz.mono_key(), "lazy_bool");
738        assert_eq!(format!("{lz}"), "Lazy<bool>");
739    }
740
741    #[test]
742    fn test_round_11_arms_are_heap() {
743        // All 7 new arms are heap-allocated typed-Arc carriers per
744        // ADR-006 §2.7.15 / §2.7.17-§2.7.20 / §2.7.25.
745        assert!(ConcreteType::HashSet(Box::new(ConcreteType::String)).is_heap());
746        assert!(ConcreteType::Deque(Box::new(ConcreteType::I64)).is_heap());
747        assert!(ConcreteType::PriorityQueue.is_heap());
748        assert!(ConcreteType::Channel(Box::new(ConcreteType::I64)).is_heap());
749        assert!(ConcreteType::Mutex(Box::new(ConcreteType::I64)).is_heap());
750        assert!(ConcreteType::Atomic.is_heap());
751        assert!(ConcreteType::Lazy(Box::new(ConcreteType::I64)).is_heap());
752
753        // None are scalar / numeric / integer.
754        assert!(!ConcreteType::HashSet(Box::new(ConcreteType::String)).is_scalar());
755        assert!(!ConcreteType::Mutex(Box::new(ConcreteType::I64)).is_numeric());
756        assert!(!ConcreteType::Atomic.is_integer());
757    }
758
759    #[test]
760    fn test_field_kind_roundtrip() {
761        use super::super::struct_layout::FieldKind;
762        let kinds = [
763            FieldKind::F64,
764            FieldKind::I64,
765            FieldKind::I32,
766            FieldKind::I16,
767            FieldKind::I8,
768            FieldKind::U64,
769            FieldKind::U32,
770            FieldKind::U16,
771            FieldKind::U8,
772            FieldKind::Bool,
773        ];
774        for kind in kinds {
775            let ct = ConcreteType::from(kind);
776            let back = ct.to_field_kind();
777            assert_eq!(kind, back);
778        }
779    }
780
781    #[test]
782    fn test_is_numeric() {
783        assert!(ConcreteType::F64.is_numeric());
784        assert!(ConcreteType::I64.is_numeric());
785        assert!(ConcreteType::U8.is_numeric());
786        assert!(ConcreteType::Decimal.is_numeric());
787        assert!(!ConcreteType::Bool.is_numeric());
788        assert!(!ConcreteType::String.is_numeric());
789    }
790
791    #[test]
792    fn test_is_heap() {
793        assert!(ConcreteType::String.is_heap());
794        assert!(ConcreteType::Array(Box::new(ConcreteType::F64)).is_heap());
795        assert!(!ConcreteType::F64.is_heap());
796        assert!(!ConcreteType::Bool.is_heap());
797    }
798
799    #[test]
800    fn test_display() {
801        assert_eq!(format!("{}", ConcreteType::F64), "number");
802        assert_eq!(format!("{}", ConcreteType::I64), "int");
803        assert_eq!(
804            format!("{}", ConcreteType::Array(Box::new(ConcreteType::F64))),
805            "Array<number>"
806        );
807        assert_eq!(
808            format!("{}", ConcreteType::Option(Box::new(ConcreteType::I64))),
809            "int?"
810        );
811    }
812}