plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


6a8cfe6Peter 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).