plum

#treesitter#compiler#wasm

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

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


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