praxis_runtime/repr.rs
1//! What a live value records about its own type.
2//!
3//! A `GcRef`'s header names a [`TypeDescriptor`], and for most types that is the
4//! whole story: an `Int` object is an `Int`. For the parameterized types it is
5//! not — one `VEC` descriptor serves every `Vec[T]`, and the element type lives
6//! in the *payload* (§11.2). Recovering a static `Type` from a value therefore
7//! means reading per-instance descriptors out of payloads, which is a raw-pointer
8//! operation this module performs once, safely, so the
9//! [`praxis-repr`](https://docs.rs/praxis-repr) bridge never has to.
10//!
11//! The dispatch is a total match over [`BuiltinTypeId`]: a new built-in is a
12//! compile error here, not a silent wrong answer. That is the same discipline
13//! that makes the *forward* map total, and the two are inverses only because
14//! both are exhaustive.
15
16use crate::GcRef;
17use crate::collections::nullable;
18use crate::descriptor::{BuiltinTypeId, TypeDescriptor};
19
20/// One per-instance type argument a value records.
21///
22/// `descriptor` is what the payload stores — `None` when the collection was
23/// never told its element type (a null element descriptor, which every
24/// `praxis_*_new` wrapper accepts as "unknown"). `sample` is a live value that
25/// argument describes, when the payload holds one; recursing into it recovers
26/// nested arguments a descriptor alone cannot (`Vec[Vec[Int]]`).
27#[derive(Clone, Copy)]
28pub struct InstanceArg {
29 /// The descriptor the payload records for this argument position.
30 pub descriptor: Option<&'static TypeDescriptor>,
31 /// A live value of this argument's type, if the value holds one.
32 pub sample: Option<GcRef>,
33}
34
35/// How much of a value's type its own payload determines.
36pub enum InstanceRepr {
37 /// The descriptor is the whole answer: every scalar, `Unit`, `BitSet`.
38 Complete,
39 /// The type takes arguments, and these are what the payload records, in the
40 /// order the type spells them (`Map[K, V]` yields key then value).
41 Args(Vec<InstanceArg>),
42 /// The value's type is not recoverable from the value. The string says why,
43 /// and is what a `NoRuntimeRepr` reports.
44 Unrecorded(&'static str),
45}
46
47/// Read what `value` records about its own type.
48///
49/// # Safety
50/// `value` must be a live `GcRef` whose payload matches its descriptor.
51#[must_use]
52pub unsafe fn instance_repr(value: GcRef) -> InstanceRepr {
53 let Some(builtin) = value.descriptor().as_builtin() else {
54 return InstanceRepr::Unrecorded("not a built-in type");
55 };
56 // SAFETY: forwarded from this function's contract — each arm reads the
57 // payload its descriptor names.
58 unsafe {
59 match builtin {
60 // Scalars and the two nullary collections are their own answer: a
61 // `BitSet` holds `Int`s and a `Range` yields them, so neither has an
62 // element descriptor to recover.
63 BuiltinTypeId::Unit
64 | BuiltinTypeId::Bool
65 | BuiltinTypeId::Int
66 | BuiltinTypeId::Byte
67 | BuiltinTypeId::Char
68 | BuiltinTypeId::Float
69 | BuiltinTypeId::Text
70 | BuiltinTypeId::BitSet
71 | BuiltinTypeId::Range => InstanceRepr::Complete,
72
73 BuiltinTypeId::Vec => {
74 let p = &*value.payload::<crate::collections::VecPayload>();
75 InstanceRepr::Args(vec![InstanceArg {
76 descriptor: nullable(p.element_descriptor),
77 sample: p.items.first().copied(),
78 }])
79 }
80 BuiltinTypeId::Deque => {
81 let p = &*value.payload::<crate::collections::DequePayload>();
82 InstanceRepr::Args(vec![InstanceArg {
83 descriptor: nullable(p.element_descriptor),
84 sample: p.items.front().copied(),
85 }])
86 }
87 BuiltinTypeId::Grid => {
88 let p = &*value.payload::<crate::collections::GridPayload>();
89 InstanceRepr::Args(vec![InstanceArg {
90 descriptor: nullable(p.element_descriptor),
91 sample: p.items.first().copied(),
92 }])
93 }
94 BuiltinTypeId::Set => {
95 let p = &*value.payload::<crate::maps::SetPayload>();
96 InstanceRepr::Args(vec![InstanceArg {
97 descriptor: nullable(p.element_descriptor),
98 sample: p.entries.iter().next().map(|k| k.value()),
99 }])
100 }
101 BuiltinTypeId::MinHeap => {
102 let p = &*value.payload::<crate::heaps::MinHeapPayload>();
103 InstanceRepr::Args(vec![InstanceArg {
104 descriptor: nullable(p.element_descriptor),
105 sample: p.items.peek().map(|e| e.0.value),
106 }])
107 }
108 BuiltinTypeId::MaxHeap => {
109 let p = &*value.payload::<crate::heaps::MaxHeapPayload>();
110 InstanceRepr::Args(vec![InstanceArg {
111 descriptor: nullable(p.element_descriptor),
112 sample: p.items.peek().map(|e| e.value),
113 }])
114 }
115 BuiltinTypeId::Map => {
116 let p = &*value.payload::<crate::maps::MapPayload>();
117 let entry = p.entries.iter().next();
118 InstanceRepr::Args(vec![
119 InstanceArg {
120 descriptor: nullable(p.key_descriptor),
121 sample: entry.map(|(k, _)| k.value()),
122 },
123 InstanceArg {
124 descriptor: nullable(p.value_descriptor),
125 sample: entry.map(|(_, v)| *v),
126 },
127 ])
128 }
129 // `Counter[T]` is unary: its values are always `Int` (§6.2).
130 BuiltinTypeId::Counter => {
131 let p = &*value.payload::<crate::maps::CounterPayload>();
132 InstanceRepr::Args(vec![InstanceArg {
133 descriptor: nullable(p.key_descriptor),
134 sample: p.entries.keys().next().map(|k| k.value()),
135 }])
136 }
137 BuiltinTypeId::Tuple => {
138 let p = &*value.payload::<crate::tuples::TuplePayload>();
139 if p.schema.is_null() {
140 return InstanceRepr::Unrecorded("tuple has no schema");
141 }
142 let schema = &*p.schema;
143 InstanceRepr::Args(
144 schema
145 .descriptors
146 .iter()
147 .enumerate()
148 .map(|(i, d)| InstanceArg {
149 descriptor: nullable(*d),
150 sample: p.items.get(i).copied(),
151 })
152 .collect(),
153 )
154 }
155 // A record/enum object carries its *field* schema, not which named
156 // type it is: two records with the same field descriptors are
157 // indistinguishable here.
158 BuiltinTypeId::Record => {
159 InstanceRepr::Unrecorded("a record value does not record its nominal identity")
160 }
161 BuiltinTypeId::Enum => {
162 InstanceRepr::Unrecorded("an enum value does not record its nominal identity")
163 }
164 BuiltinTypeId::Closure => {
165 InstanceRepr::Unrecorded("a closure records no parameter or result types")
166 }
167 // A `VarCell` is the compiler's mutable slot, not a source type.
168 BuiltinTypeId::VarCell => {
169 InstanceRepr::Unrecorded("a VarCell is a compiler-internal slot, not a source type")
170 }
171 }
172 }
173}