plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
60b292c
— Peter John
2026-11-04T09:50:09+05:30
docs(plans): language restrictions: Io capability instead of io fun
docs/plans/2026-11-03-language-restrictions.review.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Language restrictions for a simpler supercompiler: review plan
|
|
2
2
|
|
|
3
|
-
**Builds on:** master
|
|
3
|
+
**Builds on:** master b3aeaba (supercompiler merged; `--no-fusion` / `--explain-fusion` flags).
|
|
4
4
|
**Goal (user, 2026-11-03):** make the language stricter where that removes whole classes of supercompiler work and bugs. All five restrictions are accepted. Mutation stays available through an explicit `mut record`.
|
|
5
5
|
|
|
6
6
|
## Summary
|
|
@@ -9,7 +9,7 @@ The supercompiler build showed where analysis gets hard. Each restriction below
|
|
|
9
9
|
| # | Restriction | What it removes |
|
|
10
10
|
|---|---|---|
|
|
11
11
|
| 1 | Records are immutable; `mut record` (and `mut trait`) opt in to field assignment | `own`, the ownership checker, alias analysis for in-place field writes |
|
|
12
|
-
| 2 | Effects
|
|
12
|
+
| 2 | Effects go through an `Io` capability value (Zig `std.Io` style) | inferred purity and its mistakes, with no function colouring |
|
|
13
13
|
| 3 | Fusion runs before union lowering | drive/simplify having to see through `List__Cons(Cons(..))` wrappers |
|
|
14
14
|
| 4 | Only expression-bodied functions (`= ...`, incl. `= match`) are fusion candidates | handling early `return`s, statement matches, wrappers in drive |
|
|
15
15
|
| 5 | In patterns, Capitalised names are tags and lowercase names are binders, enforced | the tag-read-as-binder class of bug (the 25-vs-26 miscompile) |
|
|
@@ -22,12 +22,13 @@ The supercompiler build showed where analysis gets hard. Each restriction below
|
|
|
22
22
|
- Structs stay plain values: assigning a field of a local struct is a local update, as today.
|
|
23
23
|
- Std collections (`Slice`, `Array`, `Map`, `Buffer`) keep their mutating methods: they become `mut` types.
|
|
24
24
|
- **`own` is removed** (keyword, `own record`/`own trait`/`own` params, plum-checker/src/ownership.rs) once the migrated pure code is proven in place by uniqueness. The `own` report from the supercompiler (docs/plans/2026-11-03-supercompiler.own-report.md) becomes the acceptance check: every former `own` in-place site is either an in-place spread proved unique, or now lives in a `mut` type.
|
|
25
|
-
2. **
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
25
|
+
2. **Effects through an `Io` capability (user decision 2026-11-04: Zig `std.Io` style, no function colouring).**
|
|
26
|
+
- Host I/O is reachable only through a value of the std type `Io`: the host API becomes methods on it (`io.drawRect(..)`, `io.isKeyDown(Key.Left)`, `io.printLn(..)`, `io.soundPlay(..)`, `io.time()`, `io.random()` …). Raw `extern fun`s are only callable from std's `Io` implementation.
|
|
27
|
+
- `export fun init(io: Io)`, `update(io: Io, w: World, dt: Float)`, `draw(io: Io, w: World)`, `main(io: Io)` and `test` blocks receive an `Io` from the host.
|
|
28
|
+
- No keyword and no split between kinds of function: `Io` is an ordinary value any function, generic or closure can take or capture.
|
|
29
|
+
- Purity is a type fact: a function is pure if no `Io` is reachable from its parameters (including inside records/closures passed in) or its captures. Fusion uses this directly: no inference.
|
|
30
|
+
- Tests and the queued deterministic-replay plan can pass a fake/recording `Io` (the host-parity native backend already has the shape).
|
|
31
|
+
- `Io` itself is a `mut` type (it wraps host state).
|
|
31
32
|
3. **Fusion before union lowering.** The fusion stage moves to the checked, not-yet-lowered program; lowering runs after on the fused result. No language change.
|
|
32
33
|
4. **Fusion candidates are expression-bodied.** A producer or consumer is considered only if its body is a single expression (`fun f(..) -> T = match ...` / `= if ...` / `= Ctor(..)`). Block-bodied functions are compiled as today and get an explain note "not expression-bodied". `Picture` and std `List` are already (or get rewritten to be) expression-bodied.
|
|
33
34
|
5. **Pattern casing.**
|
|
@@ -41,11 +42,11 @@ The supercompiler build showed where analysis gets hard. Each restriction below
|
|
|
41
42
|
| Casing rule (5) | checker errors + tests; simplify/drive use the rule | S |
|
|
42
43
|
| Fusion before lowering (3) | move the fusion stage in typedSource; drop wrapper handling in drive/simplify | M |
|
|
43
44
|
| Expression-bodied candidates (4) | eligibility check + explain note; std List/Picture checked or rewritten | S–M |
|
|
44
|
-
| `
|
|
45
|
+
| `Io` capability (2) | std `Io` type wrapping the host externs; externs callable only from it; export/test entry points receive `io`; purity = no reachable `Io`; migrate std/examples/games/fixtures and plum-game host bindings | L |
|
|
45
46
|
| `mut record` / `mut trait` (1a) | grammar, checker error on field assignment of immutable types, `mut` std collections; migrate std/examples/games | L |
|
|
46
47
|
| Spread in place without `own` (1b) | uniqueness covers all migrated pure code; own report re-run must be all proved | M |
|
|
47
48
|
| Remove `own` (1c) | grammar, AST, checker, ownership.rs, codegen own paths, docs; migrate remaining uses | M |
|
|
48
|
-
| Docs | README: immutability, `mut`, `
|
|
49
|
+
| Docs | README: immutability, `mut`, the `Io` capability, pattern casing, fusion eligibility | S |
|
|
49
50
|
|
|
50
51
|
## How it fits together
|
|
51
52
|
```mermaid
|
|
@@ -53,14 +54,14 @@ flowchart LR
|
|
|
53
54
|
C5["5 casing rule"] --> F["supercompiler passes simpler"]
|
|
54
55
|
L3["3 fuse before lowering"] --> F
|
|
55
56
|
E4["4 expression-bodied candidates"] --> F
|
|
56
|
-
IO["2
|
|
57
|
+
IO["2 Io capability"] --> PU["purity = no Io reachable"] --> F
|
|
57
58
|
M1["1a immutable + mut record"] --> U["1b uniqueness covers in-place"] --> R["1c remove own"]
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
## Decisions you can change
|
|
61
|
-
- **
|
|
62
|
+
- **Effects.**
|
|
62
|
-
-
|
|
63
|
+
- Chosen: `Io` capability parameter (user, 2026-11-04).
|
|
63
|
-
-
|
|
64
|
+
- Rejected: `io fun` keyword and `fun!` markers (function colouring), implicit inference (hidden).
|
|
64
65
|
- **Mutable keyword.**
|
|
65
66
|
- Default: `mut record` / `mut trait`.
|
|
66
67
|
- Alternative: `class` for mutable reference types.
|
|
@@ -79,7 +80,7 @@ flowchart LR
|
|
|
79
80
|
- migration runs per folder;
|
|
80
81
|
- suites stay green at every step.
|
|
81
82
|
- **R2: performance after removing `own`.** In-place updates now depend on the uniqueness proof. The benchmark is re-run (pure scene, state/view, mutable), and any update that falls back to a copy is listed with its reason.
|
|
82
|
-
- **R3: `io`
|
|
83
|
+
- **R3: threading `io`.** Call chains that do I/O pass `io` along (`Picture.draw(io, node)`); pure code (view, step, rules) never sees it. Std higher-order functions stay generic: a closure capturing `io` is simply impure and won't fuse.
|
|
83
84
|
- **R4: `mut` creep.** If most records end up `mut`, the benefit shrinks. The plan reports how many types became `mut` and why.
|
|
84
85
|
|
|
85
86
|
## Out of scope
|