plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
6a8cfe6
— Peter John
2026-07-19T22:25:02+05:30
docs: add design spec for general enum support in checker/codegen
docs/superpowers/specs/2026-07-19-general-enum-support-design.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# General enum support (checker + codegen)
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
Today only `Bool`'s built-in `True`/`False` variants have a runtime representation
|
|
6
|
+
(`i32` 1/0). Any user-declared `enum` — payload-free (`Color = Red | Green | Blue`)
|
|
7
|
+
or payload-carrying (`Option = Some(a) | None`) — type-checks permissively but
|
|
8
|
+
fails to compile: `plum-wasm-codegen` hits the hardcoded
|
|
9
|
+
`"codegen: enum variant pattern '{}' is not yet supported (only True/False)"` error
|
|
10
|
+
(`plum-wasm-codegen/src/lib.rs:915`) for any non-Bool tag or constructor pattern in
|
|
11
|
+
`match`, and there's no construction path for payload variants like `Some(v)` at all.
|
|
12
|
+
|
|
13
|
+
This is scoped as a standalone, tractable slice of the larger "implement the
|
|
14
|
+
standard library" ambition. **It does not attempt generics monomorphization.**
|
|
15
|
+
`libs/std/option.plum` and `result.plum` use generic type params (`a`, `b`) and
|
|
16
|
+
will remain blocked on the separate, larger generics-monomorphization gap. This
|
|
17
|
+
work targets *concrete* user-declared enums only (e.g. a non-generic `Option`-shaped
|
|
18
|
+
enum over a specific type, or `Color`, or `Pair(Int, Str)`).
|
|
19
|
+
|
|
20
|
+
## Runtime representation
|
|
21
|
+
|
|
22
|
+
Every enum value is a single `i32`, matching the existing `Bool` scheme:
|
|
23
|
+
|
|
24
|
+
- **Payload-free variant** (`Red`, `None`, `True`, `False`): the `i32` is the
|
|
25
|
+
variant's tag number directly — a small int.
|
|
26
|
+
- **Payload variant** (`Some(a)`, `Ok(a)`, `Pair(a, b)`): the `i32` is a pointer
|
|
27
|
+
into the bump heap (which starts at address 65536, the second 64KiB page) to a
|
|
28
|
+
struct `[tag: i32][field0][field1]...`, using the exact bump-alloc/store/load
|
|
29
|
+
mechanism already used for class instance fields.
|
|
30
|
+
|
|
31
|
+
Tags are assigned per-enum, 0-based in declaration order across *all* variants
|
|
32
|
+
(payload and non-payload share one numbering) — e.g. `Option`: `Some`=0, `None`=1.
|
|
33
|
+
|
|
34
|
+
Because heap pointers are always ≥ 65536 and per-enum tags are small ints, a
|
|
35
|
+
payload-free tag value can never collide with a payload variant's pointer value,
|
|
36
|
+
so no separate discriminant bit is needed — matching on a bare tag pattern is a
|
|
37
|
+
direct `i32` comparison regardless of whether the enum has other payload variants.
|
|
38
|
+
|
|
39
|
+
## Checker changes (`plum-checker`)
|
|
40
|
+
|
|
41
|
+
`EnumVariants` (currently `BTreeMap<variant_name, enum_name>`) is extended to also
|
|
42
|
+
carry, per variant: its 0-based tag index and its field type names (parallel to
|
|
43
|
+
`EnumVariant.fields: Vec<String>` in the AST). This is purely additive — existing
|
|
44
|
+
lookups (`contains_key`, enum-name-of) are unaffected, and no currently-accepted or
|
|
45
|
+
currently-rejected program changes behavior. Type-checking of enum construction and
|
|
46
|
+
matching already works today per the README; this just carries enough data through
|
|
47
|
+
for codegen to compute struct layouts and payload types without re-deriving them
|
|
48
|
+
from the AST.
|
|
49
|
+
|
|
50
|
+
## Codegen — construction
|
|
51
|
+
|
|
52
|
+
A capitalized call expression (`Some(v)`, `Pair(a, b)`) currently only has meaning
|
|
53
|
+
for class instantiation. It gets a parallel variant-construction path, chosen by
|
|
54
|
+
looking up the callee name in the (extended) enum variants table:
|
|
55
|
+
|
|
56
|
+
- **No-payload variant used as a bare value** (`None`, `Red`, and `True`/`False`
|
|
57
|
+
unchanged): compiles to `i32.const <tag>`.
|
|
58
|
+
- **Payload variant call**: bump-allocate `4 * (1 + field_count)` bytes, store the
|
|
59
|
+
tag at offset 0, store each argument at its field offset using the same
|
|
60
|
+
width-dependent store logic (i32/i64/f64) already used for class fields, leave
|
|
61
|
+
the base pointer on the stack as the resulting value.
|
|
62
|
+
|
|
63
|
+
## Codegen — match lowering
|
|
64
|
+
|
|
65
|
+
Replace the current hardcoded-to-Bool error path
|
|
66
|
+
(`plum-wasm-codegen/src/lib.rs:901-918`, `compile_variant_eq_arm`) with a general
|
|
67
|
+
variant-tag test:
|
|
68
|
+
|
|
69
|
+
- **Bare tag pattern** (`Red`, `None`, `True`, `False`): `subject == i32.const <tag>`.
|
|
70
|
+
- **Constructor pattern** (`Some(v)`, `Pair(a, b)`): test that `subject`'s stored
|
|
71
|
+
tag word (load `i32` at offset 0 of the pointer) equals the variant's tag, then
|
|
72
|
+
for each pattern-bound name, load the corresponding field offset from the
|
|
73
|
+
pointer and bind it as a local before evaluating the arm body.
|
|
74
|
+
|
|
75
|
+
Multi-subject `match` and non-constructor/non-tag patterns (already-known gaps)
|
|
76
|
+
remain out of scope.
|
|
77
|
+
|
|
78
|
+
## Testing plan
|
|
79
|
+
|
|
80
|
+
- **Checker tests** (`plum-checker/tests/checker_tests.rs`): payload-free enum
|
|
81
|
+
(`Color`), payload enum (concrete, non-generic `Option`-shaped enum), and
|
|
82
|
+
constructor-pattern matches — confirm the extended `EnumVariants` table changes
|
|
83
|
+
no pass/fail outcome versus today.
|
|
84
|
+
- **Codegen tests** (`plum-wasm-codegen/tests/codegen_tests.rs`, compiled and run
|
|
85
|
+
via wasmtime matching existing style): construct + match a payload-free variant;
|
|
86
|
+
construct + destructure a payload variant in `match`; a multi-field variant
|
|
87
|
+
(`Pair(a, b)`); a variant used as a class field/method to confirm interop with
|
|
88
|
+
existing class codegen.
|
|
89
|
+
- **Examples**: extend `examples/match.plum` (or add a new example) with a
|
|
90
|
+
concrete user-declared enum exercised end-to-end; update the README's Known
|
|
91
|
+
Gaps section to drop the now-solved bullet (non-Bool enum-tag and constructor
|
|
92
|
+
patterns), while keeping the user-defined-generics bullet as-is.
|
|
93
|
+
|
|
94
|
+
## Out of scope
|
|
95
|
+
|
|
96
|
+
- Generics monomorphization (separate, larger effort — blocks `libs/std`'s actual
|
|
97
|
+
`Option`/`Result`/`List`/`Map`).
|
|
98
|
+
- Multi-subject `match`.
|
|
99
|
+
- Any pattern kind beyond bare tag / constructor (literal, binding, wildcard
|
|
100
|
+
already work and are untouched).
|