Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/value.c
Line
Count
Source
1
#include "value.h"
2
#include "env.h"
3
#include "memory.h"
4
#include "channel.h"
5
#include <stdlib.h>
6
#include <string.h>
7
#include <stdio.h>
8
9
_Static_assert(sizeof(LatValue) <= LAT_MAP_INLINE_MAX,
10
               "LatValue must fit in LAT_MAP_INLINE_MAX bytes for inline hashmap storage");
11
12
/* ── Heap-tracked allocation wrappers ── */
13
14
#ifdef __EMSCRIPTEN__
15
static DualHeap *g_heap = NULL;
16
static CrystalRegion *g_arena = NULL;
17
#else
18
static _Thread_local DualHeap *g_heap = NULL;
19
static _Thread_local CrystalRegion *g_arena = NULL;
20
#endif
21
22
3.46k
void value_set_heap(DualHeap *heap) { g_heap = heap; }
23
2.41k
void value_set_arena(CrystalRegion *region) { g_arena = region; }
24
9.45k
CrystalRegion *value_get_arena(void) { return g_arena; }
25
26
198k
static void *lat_alloc(size_t size) {
27
198k
    if (g_arena) return arena_alloc(g_arena, size);
28
197k
    if (g_heap) return fluid_alloc(g_heap->fluid, size);
29
140k
    return malloc(size);
30
197k
}
31
32
84
static void *lat_calloc(size_t count, size_t size) {
33
84
    if (count > 0 && size > SIZE_MAX / count) return NULL;
34
84
    if (g_arena) return arena_calloc(g_arena, count, size);
35
24
    if (g_heap) {
36
8
        size_t total = count * size;
37
8
        void *ptr = fluid_alloc(g_heap->fluid, total);
38
8
        memset(ptr, 0, total);
39
8
        return ptr;
40
8
    }
41
16
    return calloc(count, size);
42
24
}
43
44
146k
static char *lat_strdup(const char *s) {
45
146k
    if (g_arena) return arena_strdup(g_arena, s);
46
146k
    size_t len = strlen(s) + 1;
47
146k
    char *p = lat_alloc(len);
48
146k
    memcpy(p, s, len);
49
146k
    return p;
50
146k
}
51
52
219k
static void lat_free(void *ptr) {
53
219k
    if (!ptr) return;
54
219k
    if (g_arena) return;  /* no-op during arena clone */
55
219k
    if (g_heap && fluid_dealloc(g_heap->fluid, ptr)) return;
56
162k
    free(ptr);
57
162k
}
58
59
/* ── Arena-routed allocation (public, for env.c) ── */
60
61
26
void *lat_alloc_routed(size_t size) { return lat_alloc(size); }
62
38
void *lat_calloc_routed(size_t count, size_t size) { return lat_calloc(count, size); }
63
19
char *lat_strdup_routed(const char *s) { return lat_strdup(s); }
64
65
/* ── Constructors ── */
66
67
34.4k
LatValue value_int(int64_t v) {
68
34.4k
    LatValue val = { .type = VAL_INT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
69
34.4k
    val.as.int_val = v;
70
34.4k
    return val;
71
34.4k
}
72
73
431
LatValue value_float(double v) {
74
431
    LatValue val = { .type = VAL_FLOAT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
75
431
    val.as.float_val = v;
76
431
    return val;
77
431
}
78
79
9.60k
LatValue value_bool(bool v) {
80
9.60k
    LatValue val = { .type = VAL_BOOL, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
81
9.60k
    val.as.bool_val = v;
82
9.60k
    return val;
83
9.60k
}
84
85
72.0k
LatValue value_string(const char *s) {
86
72.0k
    LatValue val = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
87
72.0k
    val.as.str_val = lat_strdup(s);
88
72.0k
    return val;
89
72.0k
}
90
91
1.35k
LatValue value_string_owned(char *s) {
92
1.35k
    LatValue val = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
93
1.35k
    val.as.str_val = s;
94
1.35k
    return val;
95
1.35k
}
96
97
4.82k
LatValue value_array(LatValue *elems, size_t len) {
98
4.82k
    LatValue val = { .type = VAL_ARRAY, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
99
4.82k
    size_t cap = len < 4 ? 4 : len;
100
4.82k
    val.as.array.elems = lat_alloc(cap * sizeof(LatValue));
101
4.82k
    if (len > 0) memcpy(val.as.array.elems, elems, len * sizeof(LatValue));
102
4.82k
    val.as.array.len = len;
103
4.82k
    val.as.array.cap = cap;
104
4.82k
    return val;
105
4.82k
}
106
107
406
LatValue value_struct(const char *name, char **field_names, LatValue *field_values, size_t count) {
108
406
    LatValue val = { .type = VAL_STRUCT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
109
406
    val.as.strct.name = lat_strdup(name);
110
406
    val.as.strct.field_names = lat_alloc(count * sizeof(char *));
111
406
    val.as.strct.field_values = lat_alloc(count * sizeof(LatValue));
112
406
    val.as.strct.field_phases = NULL;  /* lazy-allocated on first field freeze */
113
1.21k
    for (size_t i = 0; i < count; i++) {
114
812
        val.as.strct.field_names[i] = lat_strdup(field_names[i]);
115
812
        val.as.strct.field_values[i] = field_values[i];
116
812
    }
117
406
    val.as.strct.field_count = count;
118
406
    return val;
119
406
}
120
121
194
LatValue value_struct_vm(const char *name, const char **field_names, LatValue *field_values, size_t count) {
122
194
    LatValue val = { .type = VAL_STRUCT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
123
194
    val.as.strct.name = lat_strdup(name);
124
194
    val.as.strct.field_names = lat_alloc(count * sizeof(char *));
125
194
    val.as.strct.field_values = lat_alloc(count * sizeof(LatValue));
126
194
    val.as.strct.field_phases = NULL;
127
578
    for (size_t i = 0; i < count; i++) {
128
384
        val.as.strct.field_names[i] = lat_strdup(field_names[i]);
129
384
        val.as.strct.field_values[i] = field_values[i];
130
384
    }
131
194
    val.as.strct.field_count = count;
132
194
    return val;
133
194
}
134
135
LatValue value_closure(char **param_names, size_t param_count, struct Expr *body, Env *captured,
136
2.16k
                       struct Expr **default_values, bool has_variadic) {
137
2.16k
    LatValue val = { .type = VAL_CLOSURE, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
138
2.16k
    val.as.closure.param_names = lat_alloc(param_count * sizeof(char *));
139
5.41k
    for (size_t i = 0; i < param_count; i++) {
140
3.24k
        val.as.closure.param_names[i] = lat_strdup(param_names[i]);
141
3.24k
    }
142
2.16k
    val.as.closure.param_count = param_count;
143
2.16k
    val.as.closure.body = body;       /* borrowed reference */
144
2.16k
    val.as.closure.captured_env = captured;
145
2.16k
    val.as.closure.default_values = default_values;  /* borrowed from AST */
146
2.16k
    val.as.closure.has_variadic = has_variadic;
147
2.16k
    return val;
148
2.16k
}
149
150
39.5k
LatValue value_unit(void) {
151
39.5k
    return (LatValue){ .type = VAL_UNIT, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
152
39.5k
}
153
154
14.3M
LatValue value_nil(void) {
155
14.3M
    return (LatValue){ .type = VAL_NIL, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
156
14.3M
}
157
158
48
LatValue value_range(int64_t start, int64_t end) {
159
48
    LatValue val = { .type = VAL_RANGE, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
160
48
    val.as.range.start = start;
161
48
    val.as.range.end = end;
162
48
    return val;
163
48
}
164
165
2.44k
LatValue value_map_new(void) {
166
2.44k
    LatValue val = { .type = VAL_MAP, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
167
2.44k
    val.as.map.map = lat_alloc(sizeof(LatMap));
168
2.44k
    *val.as.map.map = lat_map_new(sizeof(LatValue));
169
2.44k
    val.as.map.key_phases = NULL;  /* lazy-allocated on first key freeze */
170
2.44k
    return val;
171
2.44k
}
172
173
42
LatValue value_channel(struct LatChannel *ch) {
174
42
    LatValue val = { .type = VAL_CHANNEL, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
175
42
    channel_retain(ch);
176
42
    val.as.channel.ch = ch;
177
42
    return val;
178
42
}
179
180
LatValue value_enum(const char *enum_name, const char *variant_name,
181
42
                    LatValue *payload, size_t count) {
182
42
    LatValue val = { .type = VAL_ENUM, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
183
42
    val.as.enm.enum_name = lat_strdup(enum_name);
184
42
    val.as.enm.variant_name = lat_strdup(variant_name);
185
42
    if (count > 0 && payload) {
186
12
        val.as.enm.payload = lat_alloc(count * sizeof(LatValue));
187
30
        for (size_t i = 0; i < count; i++)
188
18
            val.as.enm.payload[i] = value_deep_clone(&payload[i]);
189
12
        val.as.enm.payload_count = count;
190
30
    } else {
191
30
        val.as.enm.payload = NULL;
192
30
        val.as.enm.payload_count = 0;
193
30
    }
194
42
    return val;
195
42
}
196
197
57
LatValue value_set_new(void) {
198
57
    LatValue val = { .type = VAL_SET, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
199
57
    val.as.set.map = lat_alloc(sizeof(LatMap));
200
57
    *val.as.set.map = lat_map_new(sizeof(LatValue));
201
57
    return val;
202
57
}
203
204
22
LatValue value_tuple(LatValue *elems, size_t len) {
205
22
    LatValue val = { .type = VAL_TUPLE, .phase = VTAG_CRYSTAL, .region_id = (size_t)-1 };
206
22
    val.as.tuple.elems = lat_alloc(len * sizeof(LatValue));
207
84
    for (size_t i = 0; i < len; i++) {
208
62
        val.as.tuple.elems[i] = value_deep_clone(&elems[i]);
209
62
    }
210
22
    val.as.tuple.len = len;
211
22
    return val;
212
22
}
213
214
33
LatValue value_buffer(const uint8_t *data, size_t len) {
215
33
    LatValue val = { .type = VAL_BUFFER, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
216
33
    size_t cap = len < 8 ? 8 : len;
217
33
    val.as.buffer.data = lat_alloc(cap);
218
33
    if (len > 0 && data) memcpy(val.as.buffer.data, data, len);
219
33
    val.as.buffer.len = len;
220
33
    val.as.buffer.cap = cap;
221
33
    return val;
222
33
}
223
224
24
LatValue value_buffer_alloc(size_t size) {
225
24
    LatValue val = { .type = VAL_BUFFER, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
226
24
    size_t cap = size < 8 ? 8 : size;
227
24
    val.as.buffer.data = lat_calloc(cap, 1);
228
24
    val.as.buffer.len = size;
229
24
    val.as.buffer.cap = cap;
230
24
    return val;
231
24
}
232
233
45
LatValue value_ref(LatValue inner) {
234
45
    LatValue val = { .type = VAL_REF, .phase = VTAG_UNPHASED, .region_id = (size_t)-1 };
235
45
    LatRef *r = malloc(sizeof(LatRef));
236
45
    r->value = value_deep_clone(&inner);
237
45
    r->refcount = 1;
238
45
    val.as.ref.ref = r;
239
45
    return val;
240
45
}
241
242
140
void ref_retain(LatRef *r) {
243
140
    if (r) r->refcount++;
244
140
}
245
246
163
void ref_release(LatRef *r) {
247
163
    if (!r) return;
248
163
    if (--r->refcount == 0) {
249
33
        value_free(&r->value);
250
33
        free(r);
251
33
    }
252
163
}
253
254
/* ── Phase helpers ── */
255
256
0
bool value_is_fluid(const LatValue *v) { return v->phase == VTAG_FLUID; }
257
304
bool value_is_crystal(const LatValue *v) { return v->phase == VTAG_CRYSTAL; }
258
259
/* ── Deep clone ── */
260
261
114k
LatValue value_deep_clone(const LatValue *v) {
262
114k
    LatValue out = { .type = v->type, .phase = v->phase, .region_id = (size_t)-1 };
263
264
114k
    switch (v->type) {
265
40.1k
        case VAL_INT:   out.as.int_val = v->as.int_val; break;
266
387
        case VAL_FLOAT: out.as.float_val = v->as.float_val; break;
267
5.04k
        case VAL_BOOL:  out.as.bool_val = v->as.bool_val; break;
268
21.3k
        case VAL_STR:   out.as.str_val = lat_strdup(v->as.str_val); break;
269
3.30k
        case VAL_ARRAY: {
270
3.30k
            size_t len = v->as.array.len;
271
3.30k
            size_t cap = v->as.array.cap;
272
3.30k
            out.as.array.elems = lat_alloc(cap * sizeof(LatValue));
273
22.9k
            for (size_t i = 0; i < len; i++) {
274
19.5k
                out.as.array.elems[i] = value_deep_clone(&v->as.array.elems[i]);
275
19.5k
            }
276
3.30k
            out.as.array.len = len;
277
3.30k
            out.as.array.cap = cap;
278
3.30k
            break;
279
0
        }
280
3.05k
        case VAL_STRUCT: {
281
3.05k
            size_t fc = v->as.strct.field_count;
282
3.05k
            out.as.strct.name = lat_strdup(v->as.strct.name);
283
3.05k
            out.as.strct.field_names = lat_alloc(fc * sizeof(char *));
284
3.05k
            out.as.strct.field_values = lat_alloc(fc * sizeof(LatValue));
285
9.19k
            for (size_t i = 0; i < fc; i++) {
286
6.13k
                out.as.strct.field_names[i] = lat_strdup(v->as.strct.field_names[i]);
287
6.13k
                out.as.strct.field_values[i] = value_deep_clone(&v->as.strct.field_values[i]);
288
6.13k
            }
289
3.05k
            out.as.strct.field_count = fc;
290
3.05k
            if (v->as.strct.field_phases) {
291
20
                out.as.strct.field_phases = lat_alloc(fc * sizeof(PhaseTag));
292
20
                memcpy(out.as.strct.field_phases, v->as.strct.field_phases, fc * sizeof(PhaseTag));
293
3.03k
            } else {
294
3.03k
                out.as.strct.field_phases = NULL;
295
3.03k
            }
296
3.05k
            break;
297
0
        }
298
30.2k
        case VAL_CLOSURE: {
299
30.2k
            size_t pc = v->as.closure.param_count;
300
30.2k
            if (v->as.closure.param_names) {
301
24.4k
                out.as.closure.param_names = lat_alloc(pc * sizeof(char *));
302
63.2k
                for (size_t i = 0; i < pc; i++) {
303
38.7k
                    out.as.closure.param_names[i] = lat_strdup(v->as.closure.param_names[i]);
304
38.7k
                }
305
24.4k
            } else {
306
5.88k
                out.as.closure.param_names = NULL;
307
5.88k
            }
308
30.2k
            out.as.closure.param_count = pc;
309
30.2k
            out.as.closure.body = v->as.closure.body;  /* borrowed */
310
            /* Compiled bytecode closures store ObjUpvalue** in captured_env (not Env*).
311
             * Shallow-copy the pointer; the VM manages upvalue lifetime. */
312
30.2k
            if (v->as.closure.body == NULL && v->as.closure.native_fn != NULL) {
313
22.7k
                out.as.closure.captured_env = v->as.closure.captured_env;
314
22.7k
            } else {
315
                /* Tree-walk closures: share the env (refcounted) so mutations
316
                 * inside the closure persist across calls.
317
                 * Exception: when an arena is active (during freeze), we must
318
                 * clone the env into the arena so GC can safely skip crystal
319
                 * values without traversing their fluid-heap env pointers. */
320
7.53k
                if (value_get_arena()) {
321
13
                    out.as.closure.captured_env = env_clone(v->as.closure.captured_env);
322
7.52k
                } else {
323
7.52k
                    out.as.closure.captured_env = v->as.closure.captured_env;
324
7.52k
                    env_retain(v->as.closure.captured_env);
325
7.52k
                }
326
7.53k
            }
327
30.2k
            out.as.closure.default_values = v->as.closure.default_values;  /* borrowed */
328
30.2k
            out.as.closure.has_variadic = v->as.closure.has_variadic;
329
30.2k
            out.as.closure.native_fn = v->as.closure.native_fn;  /* shared, not owned */
330
            /* Compiled bytecode closures store upvalue count in region_id */
331
30.2k
            if (v->as.closure.body == NULL && v->as.closure.native_fn != NULL)
332
22.7k
                out.region_id = v->region_id;
333
30.2k
            break;
334
0
        }
335
110
        case VAL_UNIT: break;
336
2.79k
        case VAL_NIL: break;
337
0
        case VAL_RANGE:
338
0
            out.as.range.start = v->as.range.start;
339
0
            out.as.range.end = v->as.range.end;
340
0
            break;
341
119
        case VAL_CHANNEL:
342
119
            channel_retain(v->as.channel.ch);
343
119
            out.as.channel.ch = v->as.channel.ch;
344
119
            break;
345
52
        case VAL_ENUM: {
346
52
            out.as.enm.enum_name = lat_strdup(v->as.enm.enum_name);
347
52
            out.as.enm.variant_name = lat_strdup(v->as.enm.variant_name);
348
52
            if (v->as.enm.payload_count > 0) {
349
8
                out.as.enm.payload = lat_alloc(v->as.enm.payload_count * sizeof(LatValue));
350
20
                for (size_t i = 0; i < v->as.enm.payload_count; i++)
351
12
                    out.as.enm.payload[i] = value_deep_clone(&v->as.enm.payload[i]);
352
8
                out.as.enm.payload_count = v->as.enm.payload_count;
353
44
            } else {
354
44
                out.as.enm.payload = NULL;
355
44
                out.as.enm.payload_count = 0;
356
44
            }
357
52
            break;
358
0
        }
359
7.41k
        case VAL_MAP: {
360
7.41k
            LatMap *src = v->as.map.map;
361
7.41k
            if (g_arena) {
362
                /* Arena mode: build map internals through lat_alloc/lat_calloc
363
                 * so everything goes into the arena. No rehashing possible. */
364
22
                LatMap *dst = lat_alloc(sizeof(LatMap));
365
22
                dst->value_size = src->value_size;
366
22
                dst->cap = src->cap;
367
22
                dst->count = src->count;  /* preserve tombstone count for probe chains */
368
22
                dst->live = src->live;
369
22
                dst->entries = lat_calloc(src->cap, sizeof(LatMapEntry));
370
374
                for (size_t i = 0; i < src->cap; i++) {
371
352
                    dst->entries[i].value = dst->entries[i]._ibuf;
372
352
                    if (src->entries[i].state == MAP_OCCUPIED) {
373
15
                        dst->entries[i].state = MAP_OCCUPIED;
374
15
                        dst->entries[i].key = lat_strdup(src->entries[i].key);
375
15
                        LatValue *sv = (LatValue *)src->entries[i].value;
376
15
                        LatValue cloned = value_deep_clone(sv);
377
15
                        *(LatValue *)dst->entries[i].value = cloned;
378
337
                    } else if (src->entries[i].state == MAP_TOMBSTONE) {
379
0
                        dst->entries[i].state = MAP_TOMBSTONE;
380
0
                    }
381
352
                }
382
22
                out.as.map.map = dst;
383
7.39k
            } else {
384
                /* Normal path: lat_map_new + lat_map_set */
385
7.39k
                out.as.map.map = lat_alloc(sizeof(LatMap));
386
7.39k
                *out.as.map.map = lat_map_new(sizeof(LatValue));
387
143k
                for (size_t i = 0; i < src->cap; i++) {
388
135k
                    if (src->entries[i].state == MAP_OCCUPIED) {
389
32.5k
                        LatValue *sv = (LatValue *)src->entries[i].value;
390
32.5k
                        LatValue cloned = value_deep_clone(sv);
391
32.5k
                        lat_map_set(out.as.map.map, src->entries[i].key, &cloned);
392
32.5k
                    }
393
135k
                }
394
7.39k
            }
395
            /* Clone per-key phases if present */
396
7.41k
            if (v->as.map.key_phases) {
397
10
                LatMap *ksrc = v->as.map.key_phases;
398
10
                out.as.map.key_phases = lat_alloc(sizeof(LatMap));
399
10
                *out.as.map.key_phases = lat_map_new(sizeof(PhaseTag));
400
170
                for (size_t i = 0; i < ksrc->cap; i++) {
401
160
                    if (ksrc->entries[i].state == MAP_OCCUPIED) {
402
18
                        lat_map_set(out.as.map.key_phases, ksrc->entries[i].key,
403
18
                                    ksrc->entries[i].value);
404
18
                    }
405
160
                }
406
7.40k
            } else {
407
7.40k
                out.as.map.key_phases = NULL;
408
7.40k
            }
409
7.41k
            break;
410
0
        }
411
77
        case VAL_SET: {
412
77
            LatMap *src = v->as.set.map;
413
77
            if (g_arena) {
414
0
                LatMap *dst = lat_alloc(sizeof(LatMap));
415
0
                dst->value_size = src->value_size;
416
0
                dst->cap = src->cap;
417
0
                dst->count = src->count;  /* preserve tombstone count for probe chains */
418
0
                dst->live = src->live;
419
0
                dst->entries = lat_calloc(src->cap, sizeof(LatMapEntry));
420
0
                for (size_t i = 0; i < src->cap; i++) {
421
0
                    dst->entries[i].value = dst->entries[i]._ibuf;
422
0
                    if (src->entries[i].state == MAP_OCCUPIED) {
423
0
                        dst->entries[i].state = MAP_OCCUPIED;
424
0
                        dst->entries[i].key = lat_strdup(src->entries[i].key);
425
0
                        LatValue *sv = (LatValue *)src->entries[i].value;
426
0
                        LatValue cloned = value_deep_clone(sv);
427
0
                        *(LatValue *)dst->entries[i].value = cloned;
428
0
                    } else if (src->entries[i].state == MAP_TOMBSTONE) {
429
0
                        dst->entries[i].state = MAP_TOMBSTONE;
430
0
                    }
431
0
                }
432
0
                out.as.set.map = dst;
433
77
            } else {
434
77
                out.as.set.map = lat_alloc(sizeof(LatMap));
435
77
                *out.as.set.map = lat_map_new(sizeof(LatValue));
436
1.30k
                for (size_t i = 0; i < src->cap; i++) {
437
1.23k
                    if (src->entries[i].state == MAP_OCCUPIED) {
438
140
                        LatValue *sv = (LatValue *)src->entries[i].value;
439
140
                        LatValue cloned = value_deep_clone(sv);
440
140
                        lat_map_set(out.as.set.map, src->entries[i].key, &cloned);
441
140
                    }
442
1.23k
                }
443
77
            }
444
77
            break;
445
0
        }
446
33
        case VAL_TUPLE: {
447
33
            out.as.tuple.elems = lat_alloc(v->as.tuple.len * sizeof(LatValue));
448
118
            for (size_t i = 0; i < v->as.tuple.len; i++) {
449
85
                out.as.tuple.elems[i] = value_deep_clone(&v->as.tuple.elems[i]);
450
85
            }
451
33
            out.as.tuple.len = v->as.tuple.len;
452
33
            break;
453
0
        }
454
185
        case VAL_BUFFER: {
455
185
            size_t cap = v->as.buffer.cap;
456
185
            out.as.buffer.data = lat_alloc(cap);
457
185
            memcpy(out.as.buffer.data, v->as.buffer.data, v->as.buffer.len);
458
185
            out.as.buffer.len = v->as.buffer.len;
459
185
            out.as.buffer.cap = cap;
460
185
            break;
461
0
        }
462
114
        case VAL_REF:
463
114
            ref_retain(v->as.ref.ref);
464
114
            out.as.ref.ref = v->as.ref.ref;
465
114
            break;
466
114k
    }
467
114k
    return out;
468
114k
}
469
470
/* ── Freeze ── */
471
472
3.84k
static void set_phase_recursive(LatValue *v, PhaseTag phase) {
473
3.84k
    v->phase = phase;
474
3.84k
    if (v->type == VAL_ARRAY) {
475
638
        for (size_t i = 0; i < v->as.array.len; i++) {
476
468
            set_phase_recursive(&v->as.array.elems[i], phase);
477
468
        }
478
3.67k
    } else if (v->type == VAL_STRUCT) {
479
2.80k
        for (size_t i = 0; i < v->as.strct.field_count; i++) {
480
1.87k
            set_phase_recursive(&v->as.strct.field_values[i], phase);
481
1.87k
        }
482
        /* Update field-level phases */
483
932
        if (v->as.strct.field_phases) {
484
0
            if (phase == VTAG_CRYSTAL) {
485
0
                for (size_t i = 0; i < v->as.strct.field_count; i++)
486
0
                    v->as.strct.field_phases[i] = VTAG_CRYSTAL;
487
0
            } else {
488
                /* Thawing: clear per-field phases */
489
0
                lat_free(v->as.strct.field_phases);
490
0
                v->as.strct.field_phases = NULL;
491
0
            }
492
0
        }
493
2.74k
    } else if (v->type == VAL_MAP) {
494
1.34k
        for (size_t i = 0; i < v->as.map.map->cap; i++) {
495
1.26k
            if (v->as.map.map->entries[i].state == MAP_OCCUPIED) {
496
63
                LatValue *mv = (LatValue *)v->as.map.map->entries[i].value;
497
63
                set_phase_recursive(mv, phase);
498
63
            }
499
1.26k
        }
500
2.66k
    } else if (v->type == VAL_ENUM) {
501
0
        for (size_t i = 0; i < v->as.enm.payload_count; i++)
502
0
            set_phase_recursive(&v->as.enm.payload[i], phase);
503
2.66k
    } else if (v->type == VAL_SET) {
504
0
        for (size_t i = 0; i < v->as.set.map->cap; i++) {
505
0
            if (v->as.set.map->entries[i].state == MAP_OCCUPIED) {
506
0
                LatValue *sv = (LatValue *)v->as.set.map->entries[i].value;
507
0
                set_phase_recursive(sv, phase);
508
0
            }
509
0
        }
510
2.66k
    } else if (v->type == VAL_TUPLE) {
511
0
        for (size_t i = 0; i < v->as.tuple.len; i++) {
512
0
            set_phase_recursive(&v->as.tuple.elems[i], phase);
513
0
        }
514
0
    }
515
    /* VAL_BUFFER: just set phase tag (no nested values) */
516
2.66k
    else if (v->type == VAL_REF) {
517
3
        set_phase_recursive(&v->as.ref.ref->value, phase);
518
3
    }
519
3.84k
}
520
521
894
LatValue value_freeze(LatValue v) {
522
894
    set_phase_recursive(&v, VTAG_CRYSTAL);
523
894
    return v;
524
894
}
525
526
/* ── Thaw ── */
527
528
543
LatValue value_thaw(const LatValue *v) {
529
543
    if (v->type == VAL_REF) {
530
        /* Thaw breaks sharing: new LatRef with deep-cloned inner */
531
0
        LatValue inner_clone = value_deep_clone(&v->as.ref.ref->value);
532
0
        set_phase_recursive(&inner_clone, VTAG_FLUID);
533
0
        LatValue result = value_ref(inner_clone);
534
0
        value_free(&inner_clone);
535
0
        result.phase = VTAG_FLUID;
536
0
        return result;
537
0
    }
538
543
    LatValue cloned = value_deep_clone(v);
539
543
    set_phase_recursive(&cloned, VTAG_FLUID);
540
543
    return cloned;
541
543
}
542
543
/* ── Display ── */
544
545
1.00k
void value_print(const LatValue *v, FILE *out) {
546
1.00k
    char *s = value_display(v);
547
1.00k
    fprintf(out, "%s", s);
548
1.00k
    free(s);
549
1.00k
}
550
551
4.49k
char *value_display(const LatValue *v) {
552
4.49k
    char *buf = NULL;
553
4.49k
    switch (v->type) {
554
2.40k
        case VAL_INT:
555
2.40k
            (void)asprintf(&buf, "%lld", (long long)v->as.int_val);
556
2.40k
            break;
557
147
        case VAL_FLOAT: {
558
147
            (void)asprintf(&buf, "%g", v->as.float_val);
559
147
            break;
560
0
        }
561
471
        case VAL_BOOL:
562
471
            buf = strdup(v->as.bool_val ? "true" : "false");
563
471
            break;
564
1.08k
        case VAL_STR:
565
1.08k
            buf = strdup(v->as.str_val);
566
1.08k
            break;
567
306
        case VAL_ARRAY: {
568
306
            size_t cap = 64;
569
306
            buf = malloc(cap);
570
306
            size_t pos = 0;
571
306
            buf[pos++] = '[';
572
1.14k
            for (size_t i = 0; i < v->as.array.len; i++) {
573
834
                if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; }
574
834
                char *elem = value_display(&v->as.array.elems[i]);
575
834
                size_t elen = strlen(elem);
576
834
                while (pos + elen + 4 > cap) { cap *= 2; buf = realloc(buf, cap); }
577
834
                memcpy(buf + pos, elem, elen);
578
834
                pos += elen;
579
834
                free(elem);
580
834
            }
581
306
            buf[pos++] = ']';
582
306
            buf[pos] = '\0';
583
306
            break;
584
0
        }
585
6
        case VAL_STRUCT: {
586
6
            size_t cap = 64;
587
6
            buf = malloc(cap);
588
6
            size_t pos = 0;
589
6
            size_t nlen = strlen(v->as.strct.name);
590
6
            while (pos + nlen + 8 > cap) { cap *= 2; buf = realloc(buf, cap); }
591
6
            memcpy(buf + pos, v->as.strct.name, nlen);
592
6
            pos += nlen;
593
6
            buf[pos++] = ' ';
594
6
            buf[pos++] = '{';
595
6
            buf[pos++] = ' ';
596
18
            for (size_t i = 0; i < v->as.strct.field_count; i++) {
597
12
                if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; }
598
12
                const char *fname = v->as.strct.field_names[i];
599
12
                size_t flen = strlen(fname);
600
12
                char *fval = value_display(&v->as.strct.field_values[i]);
601
12
                size_t vlen = strlen(fval);
602
12
                while (pos + flen + vlen + 8 > cap) { cap *= 2; buf = realloc(buf, cap); }
603
12
                memcpy(buf + pos, fname, flen);
604
12
                pos += flen;
605
12
                buf[pos++] = ':';
606
12
                buf[pos++] = ' ';
607
12
                memcpy(buf + pos, fval, vlen);
608
12
                pos += vlen;
609
12
                free(fval);
610
12
            }
611
6
            buf[pos++] = ' ';
612
6
            buf[pos++] = '}';
613
6
            buf[pos] = '\0';
614
6
            break;
615
0
        }
616
3
        case VAL_CLOSURE: {
617
3
            size_t cap = 64;
618
3
            buf = malloc(cap);
619
3
            size_t pos = 0;
620
3
            const char *prefix = "<closure|";
621
3
            size_t plen = strlen(prefix);
622
3
            memcpy(buf, prefix, plen);
623
3
            pos = plen;
624
3
            if (v->as.closure.param_names) {
625
6
                for (size_t i = 0; i < v->as.closure.param_count; i++) {
626
3
                    if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; }
627
3
                    const char *pn = v->as.closure.param_names[i];
628
3
                    size_t nl = strlen(pn);
629
3
                    while (pos + nl + 4 > cap) { cap *= 2; buf = realloc(buf, cap); }
630
3
                    memcpy(buf + pos, pn, nl);
631
3
                    pos += nl;
632
3
                }
633
3
            }
634
3
            buf[pos++] = '|';
635
3
            buf[pos++] = '>';
636
3
            buf[pos] = '\0';
637
3
            break;
638
0
        }
639
15
        case VAL_UNIT:
640
15
            buf = strdup("()");
641
15
            break;
642
27
        case VAL_NIL:
643
27
            buf = strdup("nil");
644
27
            break;
645
0
        case VAL_RANGE:
646
0
            (void)asprintf(&buf, "%lld..%lld", (long long)v->as.range.start, (long long)v->as.range.end);
647
0
            break;
648
0
        case VAL_CHANNEL:
649
0
            buf = strdup("<Channel>");
650
0
            break;
651
9
        case VAL_ENUM: {
652
9
            if (v->as.enm.payload_count == 0) {
653
3
                (void)asprintf(&buf, "%s::%s", v->as.enm.enum_name, v->as.enm.variant_name);
654
6
            } else {
655
6
                size_t ecap = 64;
656
6
                buf = malloc(ecap);
657
6
                size_t epos = 0;
658
6
                size_t enlen = strlen(v->as.enm.enum_name);
659
6
                size_t vnlen = strlen(v->as.enm.variant_name);
660
6
                while (epos + enlen + vnlen + 8 > ecap) { ecap *= 2; buf = realloc(buf, ecap); }
661
6
                memcpy(buf + epos, v->as.enm.enum_name, enlen); epos += enlen;
662
6
                buf[epos++] = ':'; buf[epos++] = ':';
663
6
                memcpy(buf + epos, v->as.enm.variant_name, vnlen); epos += vnlen;
664
6
                buf[epos++] = '(';
665
15
                for (size_t i = 0; i < v->as.enm.payload_count; i++) {
666
9
                    if (i > 0) { buf[epos++] = ','; buf[epos++] = ' '; }
667
9
                    char *elem = value_display(&v->as.enm.payload[i]);
668
9
                    size_t elen = strlen(elem);
669
9
                    while (epos + elen + 4 > ecap) { ecap *= 2; buf = realloc(buf, ecap); }
670
9
                    memcpy(buf + epos, elem, elen); epos += elen;
671
9
                    free(elem);
672
9
                }
673
6
                buf[epos++] = ')';
674
6
                buf[epos] = '\0';
675
6
            }
676
9
            break;
677
0
        }
678
0
        case VAL_SET: {
679
0
            size_t cap2 = 64;
680
0
            buf = malloc(cap2);
681
0
            size_t pos2 = 0;
682
0
            memcpy(buf, "Set{", 4); pos2 = 4;
683
0
            bool sfirst = true;
684
0
            for (size_t i = 0; i < v->as.set.map->cap; i++) {
685
0
                if (v->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
686
0
                if (!sfirst) { while (pos2 + 3 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); } buf[pos2++] = ','; buf[pos2++] = ' '; }
687
0
                sfirst = false;
688
0
                LatValue *sv = (LatValue *)v->as.set.map->entries[i].value;
689
0
                char *elem = value_display(sv);
690
0
                size_t elen = strlen(elem);
691
0
                while (pos2 + elen + 4 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); }
692
0
                memcpy(buf + pos2, elem, elen); pos2 += elen;
693
0
                free(elem);
694
0
            }
695
0
            while (pos2 + 2 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); }
696
0
            buf[pos2++] = '}';
697
0
            buf[pos2] = '\0';
698
0
            break;
699
0
        }
700
6
        case VAL_TUPLE: {
701
6
            size_t tcap = 64;
702
6
            buf = malloc(tcap);
703
6
            size_t tpos = 0;
704
6
            buf[tpos++] = '(';
705
18
            for (size_t i = 0; i < v->as.tuple.len; i++) {
706
12
                if (i > 0) {
707
6
                    while (tpos + 3 > tcap) { tcap *= 2; buf = realloc(buf, tcap); }
708
6
                    buf[tpos++] = ','; buf[tpos++] = ' ';
709
6
                }
710
12
                char *elem = value_display(&v->as.tuple.elems[i]);
711
12
                size_t elen = strlen(elem);
712
12
                while (tpos + elen + 4 > tcap) { tcap *= 2; buf = realloc(buf, tcap); }
713
12
                memcpy(buf + tpos, elem, elen); tpos += elen;
714
12
                free(elem);
715
12
            }
716
6
            if (v->as.tuple.len == 1) {
717
3
                while (tpos + 3 > tcap) { tcap *= 2; buf = realloc(buf, tcap); }
718
3
                buf[tpos++] = ',';
719
3
            }
720
6
            while (tpos + 2 > tcap) { tcap *= 2; buf = realloc(buf, tcap); }
721
6
            buf[tpos++] = ')';
722
6
            buf[tpos] = '\0';
723
6
            break;
724
0
        }
725
0
        case VAL_BUFFER:
726
0
            (void)asprintf(&buf, "Buffer<%zu bytes>", v->as.buffer.len);
727
0
            break;
728
6
        case VAL_REF:
729
6
            (void)asprintf(&buf, "Ref<%s>", value_type_name(&v->as.ref.ref->value));
730
6
            break;
731
3
        case VAL_MAP: {
732
3
            size_t cap2 = 64;
733
3
            buf = malloc(cap2);
734
3
            size_t pos2 = 0;
735
3
            buf[pos2++] = '{';
736
3
            bool first = true;
737
51
            for (size_t i = 0; i < v->as.map.map->cap; i++) {
738
48
                if (v->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
739
3
                if (!first) { buf[pos2++] = ','; buf[pos2++] = ' '; }
740
3
                first = false;
741
3
                const char *key = v->as.map.map->entries[i].key;
742
3
                LatValue *mval = (LatValue *)v->as.map.map->entries[i].value;
743
3
                char *vstr = value_display(mval);
744
3
                size_t klen = strlen(key);
745
3
                size_t vlen = strlen(vstr);
746
3
                while (pos2 + klen + vlen + 8 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); }
747
3
                buf[pos2++] = '"';
748
3
                memcpy(buf + pos2, key, klen); pos2 += klen;
749
3
                buf[pos2++] = '"';
750
3
                buf[pos2++] = ':';
751
3
                buf[pos2++] = ' ';
752
3
                memcpy(buf + pos2, vstr, vlen); pos2 += vlen;
753
3
                free(vstr);
754
3
            }
755
3
            while (pos2 + 2 > cap2) { cap2 *= 2; buf = realloc(buf, cap2); }
756
3
            buf[pos2++] = '}';
757
3
            buf[pos2] = '\0';
758
3
            break;
759
0
        }
760
4.49k
    }
761
4.49k
    return buf;
762
4.49k
}
763
764
665
char *value_repr(const LatValue *v) {
765
665
    if (v->type == VAL_STR) {
766
        /* Wrap strings in double quotes */
767
3
        size_t slen = strlen(v->as.str_val);
768
3
        char *buf = malloc(slen + 3);
769
3
        buf[0] = '"';
770
3
        memcpy(buf + 1, v->as.str_val, slen);
771
3
        buf[slen + 1] = '"';
772
3
        buf[slen + 2] = '\0';
773
3
        return buf;
774
3
    }
775
662
    if (v->type == VAL_BUFFER) {
776
        /* Buffer<N bytes: XX XX XX ...> (show first 8 bytes hex) */
777
0
        size_t show = v->as.buffer.len < 8 ? v->as.buffer.len : 8;
778
0
        size_t cap = 64 + show * 3;
779
0
        char *buf = malloc(cap);
780
0
        int pos = snprintf(buf, cap, "Buffer<%zu bytes:", v->as.buffer.len);
781
0
        for (size_t i = 0; i < show; i++)
782
0
            pos += snprintf(buf + pos, cap - (size_t)pos, " %02x", v->as.buffer.data[i]);
783
0
        if (v->as.buffer.len > 8)
784
0
            pos += snprintf(buf + pos, cap - (size_t)pos, " ...");
785
0
        snprintf(buf + pos, cap - (size_t)pos, ">");
786
0
        return buf;
787
0
    }
788
    /* Everything else uses standard display */
789
662
    return value_display(v);
790
662
}
791
792
/* ── Type name ── */
793
794
19
const char *value_type_name(const LatValue *v) {
795
19
    switch (v->type) {
796
7
        case VAL_INT:     return "Int";
797
0
        case VAL_FLOAT:   return "Float";
798
0
        case VAL_BOOL:    return "Bool";
799
3
        case VAL_STR:     return "String";
800
0
        case VAL_ARRAY:   return "Array";
801
0
        case VAL_STRUCT:  return "Struct";
802
0
        case VAL_CLOSURE: return "Closure";
803
0
        case VAL_UNIT:    return "Unit";
804
3
        case VAL_NIL:     return "Nil";
805
0
        case VAL_RANGE:   return "Range";
806
6
        case VAL_MAP:     return "Map";
807
0
        case VAL_CHANNEL: return "Channel";
808
0
        case VAL_ENUM:    return "Enum";
809
0
        case VAL_SET:     return "Set";
810
0
        case VAL_TUPLE:   return "Tuple";
811
0
        case VAL_BUFFER:  return "Buffer";
812
0
        case VAL_REF:     return "Ref";
813
19
    }
814
0
    return "?";
815
19
}
816
817
/* ── Equality ── */
818
819
9.23k
bool value_eq(const LatValue *a, const LatValue *b) {
820
9.23k
    if (a->type != b->type) return false;
821
9.15k
    switch (a->type) {
822
519
        case VAL_INT:   return a->as.int_val == b->as.int_val;
823
0
        case VAL_FLOAT: return a->as.float_val == b->as.float_val;
824
12
        case VAL_BOOL:  return a->as.bool_val == b->as.bool_val;
825
8.56k
        case VAL_STR:   return strcmp(a->as.str_val, b->as.str_val) == 0;
826
0
        case VAL_UNIT:  return true;
827
26
        case VAL_NIL:   return true;
828
0
        case VAL_RANGE: return a->as.range.start == b->as.range.start &&
829
0
                               a->as.range.end == b->as.range.end;
830
0
        case VAL_ARRAY:
831
0
            if (a->as.array.len != b->as.array.len) return false;
832
0
            for (size_t i = 0; i < a->as.array.len; i++) {
833
0
                if (!value_eq(&a->as.array.elems[i], &b->as.array.elems[i]))
834
0
                    return false;
835
0
            }
836
0
            return true;
837
0
        case VAL_STRUCT:
838
0
            if (strcmp(a->as.strct.name, b->as.strct.name) != 0) return false;
839
0
            if (a->as.strct.field_count != b->as.strct.field_count) return false;
840
0
            for (size_t i = 0; i < a->as.strct.field_count; i++) {
841
0
                if (strcmp(a->as.strct.field_names[i], b->as.strct.field_names[i]) != 0)
842
0
                    return false;
843
0
                if (!value_eq(&a->as.strct.field_values[i], &b->as.strct.field_values[i]))
844
0
                    return false;
845
0
            }
846
0
            return true;
847
0
        case VAL_CLOSURE: return false;
848
0
        case VAL_CHANNEL:
849
0
            return a->as.channel.ch == b->as.channel.ch;
850
12
        case VAL_ENUM:
851
12
            if (strcmp(a->as.enm.enum_name, b->as.enm.enum_name) != 0) return false;
852
12
            if (strcmp(a->as.enm.variant_name, b->as.enm.variant_name) != 0) return false;
853
6
            if (a->as.enm.payload_count != b->as.enm.payload_count) return false;
854
6
            for (size_t i = 0; i < a->as.enm.payload_count; i++) {
855
0
                if (!value_eq(&a->as.enm.payload[i], &b->as.enm.payload[i])) return false;
856
0
            }
857
6
            return true;
858
0
        case VAL_MAP: {
859
0
            if (lat_map_len(a->as.map.map) != lat_map_len(b->as.map.map)) return false;
860
0
            for (size_t i = 0; i < a->as.map.map->cap; i++) {
861
0
                if (a->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
862
0
                const char *key = a->as.map.map->entries[i].key;
863
0
                LatValue *av = (LatValue *)a->as.map.map->entries[i].value;
864
0
                LatValue *bv = (LatValue *)lat_map_get(b->as.map.map, key);
865
0
                if (!bv || !value_eq(av, bv)) return false;
866
0
            }
867
0
            return true;
868
0
        }
869
0
        case VAL_SET: {
870
0
            if (lat_map_len(a->as.set.map) != lat_map_len(b->as.set.map)) return false;
871
0
            for (size_t i = 0; i < a->as.set.map->cap; i++) {
872
0
                if (a->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
873
0
                const char *key = a->as.set.map->entries[i].key;
874
0
                if (!lat_map_contains(b->as.set.map, key)) return false;
875
0
            }
876
0
            return true;
877
0
        }
878
6
        case VAL_TUPLE:
879
6
            if (a->as.tuple.len != b->as.tuple.len) return false;
880
21
            for (size_t i = 0; i < a->as.tuple.len; i++) {
881
18
                if (!value_eq(&a->as.tuple.elems[i], &b->as.tuple.elems[i]))
882
3
                    return false;
883
18
            }
884
3
            return true;
885
6
        case VAL_BUFFER:
886
6
            if (a->as.buffer.len != b->as.buffer.len) return false;
887
6
            return memcmp(a->as.buffer.data, b->as.buffer.data, a->as.buffer.len) == 0;
888
6
        case VAL_REF:
889
6
            return a->as.ref.ref == b->as.ref.ref;
890
9.15k
    }
891
0
    return false;
892
9.15k
}
893
894
/* ── Free ── */
895
896
223k
static void val_dealloc(LatValue *v, void *ptr) {
897
223k
    if (!ptr) return;
898
219k
    if (v->region_id != (size_t)-1) return;  /* arena-backed: no-op */
899
219k
    lat_free(ptr);
900
219k
}
901
902
577k
void value_free(LatValue *v) {
903
577k
    if (v->region_id != (size_t)-1) {
904
17.1k
        memset(v, 0, sizeof(*v));  /* arena owns everything */
905
17.1k
        return;
906
17.1k
    }
907
560k
    switch (v->type) {
908
116k
        case VAL_STR:
909
116k
            val_dealloc(v, v->as.str_val);
910
116k
            break;
911
9.67k
        case VAL_ARRAY:
912
59.0k
            for (size_t i = 0; i < v->as.array.len; i++)
913
49.4k
                value_free(&v->as.array.elems[i]);
914
9.67k
            val_dealloc(v, v->as.array.elems);
915
9.67k
            break;
916
3.93k
        case VAL_STRUCT:
917
3.93k
            val_dealloc(v, v->as.strct.name);
918
11.8k
            for (size_t i = 0; i < v->as.strct.field_count; i++) {
919
7.89k
                val_dealloc(v, v->as.strct.field_names[i]);
920
7.89k
                value_free(&v->as.strct.field_values[i]);
921
7.89k
            }
922
3.93k
            val_dealloc(v, v->as.strct.field_names);
923
3.93k
            val_dealloc(v, v->as.strct.field_values);
924
3.93k
            val_dealloc(v, v->as.strct.field_phases);
925
3.93k
            break;
926
309k
        case VAL_CLOSURE:
927
309k
            if (v->as.closure.param_names) {
928
62.7k
                for (size_t i = 0; i < v->as.closure.param_count; i++)
929
38.3k
                    val_dealloc(v, v->as.closure.param_names[i]);
930
24.4k
                val_dealloc(v, v->as.closure.param_names);
931
24.4k
            }
932
            /* Don't free compiled bytecode closures' env — they store ObjUpvalue**,
933
             * not Env*. The VM manages upvalue lifetime. */
934
309k
            if (v->as.closure.captured_env &&
935
309k
                !(v->as.closure.body == NULL && v->as.closure.native_fn != NULL))
936
9.42k
                env_release(v->as.closure.captured_env);
937
309k
            break;
938
9.82k
        case VAL_MAP:
939
9.82k
            if (v->as.map.map) {
940
                /* Free each stored LatValue before freeing the map */
941
190k
                for (size_t i = 0; i < v->as.map.map->cap; i++) {
942
181k
                    if (v->as.map.map->entries[i].state == MAP_OCCUPIED) {
943
43.8k
                        LatValue *mv = (LatValue *)v->as.map.map->entries[i].value;
944
43.8k
                        value_free(mv);
945
43.8k
                    }
946
181k
                }
947
9.82k
                lat_map_free(v->as.map.map);
948
9.82k
                val_dealloc(v, v->as.map.map);
949
9.82k
            }
950
9.82k
            if (v->as.map.key_phases) {
951
21
                lat_map_free(v->as.map.key_phases);
952
21
                val_dealloc(v, v->as.map.key_phases);
953
21
            }
954
9.82k
            break;
955
155
        case VAL_CHANNEL:
956
155
            if (v->as.channel.ch)
957
155
                channel_release(v->as.channel.ch);
958
155
            break;
959
94
        case VAL_ENUM:
960
94
            val_dealloc(v, v->as.enm.enum_name);
961
94
            val_dealloc(v, v->as.enm.variant_name);
962
94
            if (v->as.enm.payload) {
963
50
                for (size_t i = 0; i < v->as.enm.payload_count; i++)
964
30
                    value_free(&v->as.enm.payload[i]);
965
20
                val_dealloc(v, v->as.enm.payload);
966
20
            }
967
94
            break;
968
134
        case VAL_SET:
969
134
            if (v->as.set.map) {
970
2.27k
                for (size_t i = 0; i < v->as.set.map->cap; i++) {
971
2.14k
                    if (v->as.set.map->entries[i].state == MAP_OCCUPIED) {
972
251
                        LatValue *sv = (LatValue *)v->as.set.map->entries[i].value;
973
251
                        value_free(sv);
974
251
                    }
975
2.14k
                }
976
134
                lat_map_free(v->as.set.map);
977
134
                val_dealloc(v, v->as.set.map);
978
134
            }
979
134
            break;
980
79
        case VAL_TUPLE:
981
292
            for (size_t i = 0; i < v->as.tuple.len; i++)
982
213
                value_free(&v->as.tuple.elems[i]);
983
79
            val_dealloc(v, v->as.tuple.elems);
984
79
            break;
985
243
        case VAL_BUFFER:
986
243
            val_dealloc(v, v->as.buffer.data);
987
243
            break;
988
163
        case VAL_REF:
989
163
            ref_release(v->as.ref.ref);
990
163
            break;
991
109k
        default:
992
109k
            break;
993
560k
    }
994
560k
    memset(v, 0, sizeof(*v));
995
560k
}
996
997
/* ── Truthiness ── */
998
999
6.63k
bool value_is_truthy(const LatValue *v) {
1000
6.63k
    switch (v->type) {
1001
6.62k
        case VAL_BOOL:  return v->as.bool_val;
1002
0
        case VAL_INT:   return v->as.int_val != 0;
1003
0
        case VAL_FLOAT: return v->as.float_val != 0.0;
1004
0
        case VAL_STR:   return v->as.str_val[0] != '\0';
1005
0
        case VAL_UNIT:  return false;
1006
8
        case VAL_NIL:   return false;
1007
0
        case VAL_MAP:     return lat_map_len(v->as.map.map) > 0;
1008
0
        case VAL_SET:     return lat_map_len(v->as.set.map) > 0;
1009
0
        case VAL_TUPLE:   return v->as.tuple.len > 0;
1010
0
        case VAL_CHANNEL: return true;
1011
0
        case VAL_BUFFER:  return v->as.buffer.len > 0;
1012
0
        case VAL_REF:     return true;
1013
3
        default:          return true;
1014
6.63k
    }
1015
6.63k
}