plum

#treesitter#compiler#wasm

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

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


78b9e89Peter John 2026-07-19T18:52:54+05:30
docs: add wasm codegen + type checker design spec
docs/superpowers/specs/2026-07-19-wasm-codegen-typechecker-design.md ADDED
@@ -0,0 +1,201 @@
1
+ # Plum WASM Codegen + Type Checker — Design Spec
2
+
3
+ **Date:** 2026-07-19
4
+ **Status:** Approved
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Add a strict type checker and WASM code generator to the plum language compiler, following the same architecture as the sibling `hica` compiler (already present in `plum/hica/`).
11
+
12
+ Pipeline: `parse (plum-core) → check (plum-checker) → codegen (plum-wasm-codegen)`
13
+
14
+ ---
15
+
16
+ ## Crate Structure
17
+
18
+ Two new crates added to the workspace in `Cargo.toml`:
19
+
20
+ - `plum-checker` — type inference and checking
21
+ - `plum-wasm-codegen` — lowers checked AST to `.wasm` binary via `wasm_encoder`
22
+
23
+ Both depend on `plum-core` for the shared AST types.
24
+
25
+ ---
26
+
27
+ ## Type Checker (`plum-checker`)
28
+
29
+ ### Core Types
30
+
31
+ ```rust
32
+ pub type TypeEnv = BTreeMap<String, TypeScheme>;
33
+
34
+ pub struct TypeScheme {
35
+ pub vars: Vec<String>, // generic type params
36
+ pub body: Box<PlumType>,
37
+ }
38
+
39
+ pub enum PlumType {
40
+ TInt,
41
+ TFloat,
42
+ TBool,
43
+ TStr,
44
+ TUnit,
45
+ TVar(String), // fresh inference variable
46
+ TFun(Vec<PlumType>, Box<PlumType>),
47
+ TNamed(String), // user-defined class/enum/trait name (v1: opaque)
48
+ }
49
+
50
+ pub struct InferState {
51
+ pub counter: u64, // fresh variable counter
52
+ }
53
+ ```
54
+
55
+ ### Error Reporting
56
+
57
+ ```rust
58
+ pub struct CheckError {
59
+ pub message: String,
60
+ }
61
+ pub type CheckResult<T> = Result<T, Vec<CheckError>>;
62
+ ```
63
+
64
+ A non-empty error list halts compilation entirely (strict mode). Errors accumulate per-function so multiple errors are reported in one pass.
65
+
66
+ ### Primitive Type Mapping
67
+
68
+ | Plum type | Internal |
69
+ |-----------|-----------|
70
+ | `Int` | `TInt` |
71
+ | `Float` | `TFloat` |
72
+ | `Bool` | `TBool` |
73
+ | `Str` | `TStr` |
74
+ | `Unit` | `TUnit` |
75
+
76
+ ### Checks in v1 (minimal core)
77
+
78
+ - **Variable scope**: `Assign` targets added to env; undeclared variable reference → error
79
+ - **Function signatures**: params added to local env; return expression type must match declared return type
80
+ - **Binary ops**: both operands must be same numeric type; result type = operand type
81
+ - **Boolean ops** (`&&`, `||`, `!`): operands must be `TBool`
82
+ - **Compare ops**: both operands same type; result = `TBool`
83
+ - **`if`/`else`**: all branches must return the same type (or `TUnit` for statement-style)
84
+ - **`for` range**: range operands must be `TInt`; body may be `TUnit`
85
+ - **`while`**: condition must be `TBool`
86
+ - **`FnCall`**: arity and argument types must match declared function signature
87
+ - **`return`**: type must match enclosing function's declared return type
88
+
89
+ ### Out of scope for v1
90
+
91
+ Traits, classes, enums, and generic dispatch are **recognized** in the AST but produce an "unsupported in v1" error if their methods or constructors are invoked in checked code. Top-level `class`/`trait`/`enum` declarations are accepted without deep checking.
92
+
93
+ ### Public API
94
+
95
+ ```rust
96
+ pub fn check_source(source: &plum_core::ast::Source) -> CheckResult<()>;
97
+ ```
98
+
99
+ ---
100
+
101
+ ## WASM Codegen (`plum-wasm-codegen`)
102
+
103
+ ### WasmModule helper
104
+
105
+ Mirrors `hica-wasm-codegen`'s `WasmModule` struct — a thin builder over `wasm_encoder` sections (types, imports, functions, exports, memories, globals, data segments, tables, elements).
106
+
107
+ ### CompileCtx
108
+
109
+ ```rust
110
+ pub struct CompileCtx {
111
+ pub module: WasmModule,
112
+ pub func_ids: HashMap<String, u32>,
113
+ pub func_sigs: HashMap<String, FuncSig>,
114
+ pub current_locals: HashMap<String, u32>,
115
+ pub label_count: u32,
116
+ pub bump_offset: u32, // linear memory bump allocator for strings
117
+ control_stack: Vec<ControlFrame>,
118
+ }
119
+ ```
120
+
121
+ ### Type Mapping (Plum → WASM)
122
+
123
+ | Plum type | WASM ValType |
124
+ |-----------|-------------|
125
+ | `Int` | `i64` |
126
+ | `Float` | `f64` |
127
+ | `Bool` | `i32` |
128
+ | `Str` | `i32` (ptr) |
129
+ | `Unit` | (no value) |
130
+
131
+ ### Memory Layout
132
+
133
+ One linear memory (1 page = 64 KiB min). String literals are emitted into a data segment; a bump allocator pointer (global `i32`) tracks the next free byte for runtime string allocation.
134
+
135
+ ### Codegen Scope (v1)
136
+
137
+ | Plum construct | WASM output |
138
+ |---|---|
139
+ | Top-level `fn` | `function` with matching type |
140
+ | `Int` / `Float` / `Bool` literals | `i64.const` / `f64.const` / `i32.const` |
141
+ | Arithmetic `BinOp` | `i64.add`, `i64.sub`, `f64.mul`, etc. |
142
+ | `BoolOp` | `i32.and`, `i32.or` |
143
+ | `CompareOp` | `i64.lt_s`, `f64.eq`, etc. |
144
+ | `if` / `else if` / `else` | `block` + `if` instructions |
145
+ | `for` (range `a..b`) | `loop` + `br_if` |
146
+ | `while` | `loop` + `br_if` |
147
+ | `Assign` (local) | `local.set` + `local.get` |
148
+ | `FnCall` | `call` |
149
+ | `return` | `return` |
150
+ | `Const` (top-level) | WASM `global` with constant initializer |
151
+ | `main()` | exported as `"main"` |
152
+
153
+ ### Public API
154
+
155
+ ```rust
156
+ pub fn compile_source(source: &plum_core::ast::Source) -> Result<Vec<u8>, String>;
157
+ ```
158
+
159
+ Returns the raw `.wasm` bytes.
160
+
161
+ ---
162
+
163
+ ## CLI Integration (`plum-cli`)
164
+
165
+ New `compile` subcommand:
166
+
167
+ ```
168
+ plum compile <file.plum> [-o output.wasm]
169
+ ```
170
+
171
+ Steps:
172
+ 1. Parse with `plum-core::parse_source`
173
+ 2. Type-check with `plum-checker::check_source` — errors printed to stderr, exit 1
174
+ 3. Codegen with `plum-wasm-codegen::compile_source`
175
+ 4. Write `.wasm` to output path (default: input path with `.wasm` extension)
176
+
177
+ ---
178
+
179
+ ## Testing
180
+
181
+ ### `plum-checker/tests/`
182
+
183
+ Unit tests per rule:
184
+ - Undeclared variable → error
185
+ - Wrong return type → error
186
+ - Binary op type mismatch → error
187
+ - Valid function → no errors
188
+
189
+ ### `plum-wasm-codegen/tests/`
190
+
191
+ Integration tests:
192
+ - Compile `test/add.plum` → validate `.wasm` bytes with `wasmparser`
193
+ - Compile factorial function → run with `wasmtime` crate, assert result
194
+
195
+ ---
196
+
197
+ ## Reference
198
+
199
+ - hica type checker: `hica/crates/hica-checker/src/lib.rs`
200
+ - hica WASM codegen: `hica/crates/hica-wasm-codegen/src/lib.rs`
201
+ - plum AST: `plum-core/src/ast.rs`