plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
811569c
— Peter John
2026-10-25T16:57:29+05:30
docs(plans): Roc-style error handling design, plan and build log
docs/plans/2026-10-25-roc-errors.incidents.jsonl
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{"file": "plum-core/src/builtin_usage.rs", "line": 0, "context": "T0: change outside Owns blocked by commit_task; nothing committed", "agent": "commit_task", "kind": "exception", "resolution": "open", "time": "15:54:00"}
|
|
2
|
+
{"file": "plum-core/src/builtin_usage.rs", "line": 0, "context": "T1: change outside Owns blocked by commit_task; nothing committed", "agent": "commit_task", "kind": "exception", "resolution": "open", "time": "15:56:13"}
|
|
3
|
+
{"file": "plum-checker/src/lib.rs", "line": 1265, "context": "refutable payload pattern B(0) counted as covering tag B in exhaustiveness", "agent": "reviewer", "kind": "exception", "resolution": "fixed", "confidence": "high", "time": "16:02:03"}
|
|
4
|
+
{"file": "-", "line": 0, "context": "tag_literal_does_not_shadow_declared_enum_variant: failed", "agent": "run_tests", "kind": "test-failure", "resolution": "open", "time": "16:39:56"}
|
|
5
|
+
{"file": "-", "line": 0, "context": "unpinnedOkIsNotATagLiteral: failed", "agent": "run_tests", "kind": "test-failure", "resolution": "open", "time": "16:39:56"}
|
docs/plans/2026-10-25-roc-errors.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Roc-style error handling (Try + structural error tags)
|
|
2
|
+
|
|
3
|
+
**Goal:** Replace `Option[T]`/`Result[T,E]` with one `Try[T, E]` type whose errors are structural tag unions (`[NotFound, BadNum(Str)]`). `?` merges different error sets into the caller's declared union, `expr ? |e| Tag` maps an error inline, `??` gives a default, and `crash "msg"` stops the program.
|
|
4
|
+
**Architecture:** tree-sitter grammar → `plum-core` parser/AST → `plum-checker` (monomorphize, then check) → `plum-wasm-codegen` (wasm-gc) → hosts (`plum-cli`, `plum-runtime`).
|
|
5
|
+
**Tech stack:** Rust workspace, tree-sitter (regenerate with `tree-sitter generate --disable-optimizations`), wasm-gc via wasm-encoder, wasmtime hosts.
|
|
6
|
+
**Spec path:** this file, section "Design".
|
|
7
|
+
**Detail:** tests
|
|
8
|
+
**Target-min:** 30
|
|
9
|
+
|
|
10
|
+
## Design
|
|
11
|
+
|
|
12
|
+
What the user said: adopt Roc's error model (https://www.roc-lang.org/docs/main/langref/errors/) in full. Name the type `Try[T, E]`, replace `?:` with `??`, AI orchestrator.
|
|
13
|
+
|
|
14
|
+
1. **Try replaces Option and Result.** `enum Try[T, E] = | Ok(T) | Err(E)` lives in `plum-std/Try.plum`. `Option.plum`, `Result.plum` and `Err.plum` (an unused trait whose name clashes with the `Err` variant) are deleted. A missing value is an error tag, using Roc's names: `List.get`→`[OutOfBounds]`, `List.first`/`last`/`pop`→`[ListWasEmpty]`, `Map.get`→`[KeyNotFound]`, `parseInt`/`parseFloat`→`[BadNumStr]`, `Bool.parse`→`[BadBoolStr]`, Base64/Json decoding→`[InvalidBase64]`/`[InvalidJson(Str)]`.
|
|
15
|
+
2. **Tag union types.** The type syntax is `[A, B(Str)]`. Each tag carries zero or one payload, and the payload type must be non-generic. Ruling: tags have at most one payload and no generic payload types (because tag-union types travel through the monomorphizer as a single canonical name string, which keeps the change contained). The parser canonicalizes the union: tags sorted, no spaces, `[A,B(Str)]`, stored as `ast::Type{name, generics: []}`. The checker has `PlumType::TTags(BTreeMap<String, Option<Box<PlumType>>>)`, and its `Display` gives exactly that canonical string, so mangling and `plumTypeFromName` round-trip.
|
|
16
|
+
3. **Tag values.** An uppercase name that is not a declared type, variant or fn is a tag literal: `NotFound` has type `[NotFound]`, and `BadNum(s)` has type `[BadNum(Str)]`. Ruling: tags are never declared, as in Roc. Typos are caught by the declared return union.
|
|
17
|
+
4. **Subtyping.** `unify(expected, actual)` accepts a tag union `actual ⊆ expected` when every payload unifies. Two specializations of the same generic enum whose mangled names differ only in their tag-union segments (`Try$Int$[A]` and `Try$Int$[A,B]`) unify by comparing those segments pairwise in order. Ruling: E is covariant everywhere (because the runtime representation is identical, nothing can crash; aliasing a mutable List of Try values can only defeat exhaustiveness, which is an accepted gap).
|
|
18
|
+
5. **`?`.** `checkFn` binds the reserved env key `"$return"` to the declared return type, and closures remove it. `expr?` requires `expr: Try$..`. If `$return` is a Try, the inner E must unify into the outer E (covariantly), otherwise it's an error: `'?' error type [X] is not part of the function's error type [A,B]`. In codegen, the failure branch takes the payload and builds the **outer** function's `Err` variant, so Ok types may differ.
|
|
19
|
+
6. **`expr ? |e| T`** is parser sugar for `Expr::Try(expr.mapErr(|e| T))`. **`a ?? b`** replaces `?:` and keeps `Expr::Elvis`. **`crash expr`** is `ast::Stmt::Crash(Expr)` and checks that `expr` is `Str`. In codegen it calls the host import `plum::crash(Str)`, emitted only when used, followed by `unreachable`. `todo` stays as it is.
|
|
20
|
+
7. **Runtime representation.** There is one GC struct `$Tag {id: i32, payload: anyref (nullable)}`. Tag ids are interned in sorted order over every tag name in the program. Int, Float and Byte payloads are boxed in `$BoxI64`/`$BoxF64`/`$BoxI32`, and reference payloads are stored as-is. Codegen erases tag segments (`[...]`→`Tags`) when registering enum GC types, so specializations that differ only in their tags share wasm types.
|
|
21
|
+
8. **Matching.** On a tag-union subject, an arm `A =>` tests `id`, and `B(s) =>` tests `id`, then casts/unboxes the payload to the union's payload type. These arms also work nested (`Err(NotFound) =>`). A `match` on a tag union with no `_` must cover every tag (checker error `non-exhaustive match on [A,B]: missing B`).
|
|
22
|
+
9. **`==`** on tag unions compares `id`. It is only allowed when neither side's union has a payload tag.
|
|
23
|
+
10. **Out of scope** (YAGNI): inferring the error union of a function without an annotation, multi-payload tags, printing a tag (`toStr`), and `Try[Unit, E]` if it doesn't already work.
|
|
24
|
+
|
|
25
|
+
Testing: each compiler crate gets Rust unit tests; end-to-end behavior goes in `test` blocks in `plum-std/Try.plum` and `plum-examples/error_propagation.plum`, run with `cargo run -q -p plum-cli -- test plum-std plum-examples`.
|
|
26
|
+
|
|
27
|
+
## Global constraints
|
|
28
|
+
|
|
29
|
+
- Rust test command: `cargo test --workspace`. Plum tests: `cargo run -q -p plum-cli -- test plum-std plum-examples`.
|
|
30
|
+
- `touch` edited `.rs` files before trusting a build (stale cargo builds happen in this sandbox).
|
|
31
|
+
- Never run `plum format` on real files to "check" something: it rewrites them in place.
|
|
32
|
+
- After regenerating the grammar, rebuild the embedded grammar `.so` if the plum-cli build requires it (see Makefile.toml `grammar-so`).
|
|
33
|
+
- Canonical tag-union string: `[` + tags sorted by name, joined with `,`, each `Name` or `Name(PayloadType)` + `]`.
|
|
34
|
+
- Host import: module `plum`, name `crash`, signature identical to `plum::printLn`.
|
|
35
|
+
|
|
36
|
+
## Main diagram
|
|
37
|
+
|
|
38
|
+
```mermaid
|
|
39
|
+
flowchart LR
|
|
40
|
+
G["grammar.js + parser.rs"] -->|"ast::Type '[A,B(Str)]', Stmt::Crash, Elvis(??)"| C["checker: TTags, subset unify, ? vs $return"]
|
|
41
|
+
C -->|"monomorphized AST + types"| W["codegen: $Tag, boxing, ? rewrap, crash import"]
|
|
42
|
+
W -->|"wasm with plum::crash"| H["hosts: plum-cli, plum-runtime"]
|
|
43
|
+
W --> S["std: Try.plum migration"]
|
|
44
|
+
S --> E["examples, LSP, docs"]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## File structure
|
|
48
|
+
|
|
49
|
+
- `plum-tooling/tree-sitter-plum/{grammar.js,src/*,test/corpus/*,queries/*}`: syntax.
|
|
50
|
+
- `plum-core/src/{ast.rs,parser.rs,formatter.rs}`: AST, canonical tag type names, desugaring.
|
|
51
|
+
- `plum-checker/src/{types.rs,lib.rs,monomorphize.rs}`: TTags, unify, `?`, matching, crash.
|
|
52
|
+
- `plum-wasm-codegen/src/lib.rs`: runtime representation.
|
|
53
|
+
- `plum-cli/src/main.rs`, `plum-runtime/src/main.rs`, `plum-*/tests/examples_test.rs`: crash host import.
|
|
54
|
+
- `plum-std/*.plum`: library migration. `plum-examples/*.plum`, `plum-lsp/src/*`, `README.md`, `website/`: users and docs.
|
|
55
|
+
|
|
56
|
+
Ruling: sequential [all] - each compiler stage's tests parse and compile real Plum source through the earlier stages, so stubs can't stand in for them - cost: about 6 sequential tasks.
|
|
57
|
+
|
|
58
|
+
```mermaid
|
|
59
|
+
flowchart LR
|
|
60
|
+
T1[syntax]
|
|
61
|
+
T2[checker]
|
|
62
|
+
T3[codegen]
|
|
63
|
+
T4[hosts]
|
|
64
|
+
T5[std migration]
|
|
65
|
+
T6[examples docs lsp]
|
|
66
|
+
%% taskgraph
|
|
67
|
+
T1 --> T2
|
|
68
|
+
T2 --> T3
|
|
69
|
+
T3 --> T4
|
|
70
|
+
T4 --> T5
|
|
71
|
+
T5 --> T6
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
%% schedule
|
|
76
|
+
gantt
|
|
77
|
+
title Predicted schedule (mm:ss)
|
|
78
|
+
dateFormat X
|
|
79
|
+
axisFormat %M:%S
|
|
80
|
+
section lane 1
|
|
81
|
+
T1 syntax :t1, 0, 142s
|
|
82
|
+
T2 checker :t2, 142, 157s
|
|
83
|
+
T3 codegen :t3, 299, 160s
|
|
84
|
+
T4 hosts :t4, 459, 93s
|
|
85
|
+
T5 std migration :t5, 553, 189s
|
|
86
|
+
T6 examples LSP docs :t6, 742, 176s
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
critical path: T1 → T2 → T3 → T4 → T5 → T6 (15.3 min) · peak width 1 · predicted build 15.3 min · predicted total ≈ 18.8 min (target ✓)
|
|
90
|
+
|
|
91
|
+
## Package: compiler
|
|
92
|
+
|
|
93
|
+
**App type:** library / parser / evaluator. **Categories:** deep, numeric, encoding, state.
|
|
94
|
+
**Probe entry:** run=cargo run -q -p plum-cli -- test plum-std plum-examples
|
|
95
|
+
|
|
96
|
+
### T1 syntax · Complexity 5 · Est. context 60k
|
|
97
|
+
**Owns:** plum-tooling/tree-sitter-plum/grammar.js, plum-tooling/tree-sitter-plum/src/, plum-tooling/tree-sitter-plum/test/, plum-tooling/tree-sitter-plum/queries/, plum-core/src/ast.rs, plum-core/src/parser.rs, plum-core/src/formatter.rs, plum-core/tests/
|
|
98
|
+
**Components:** 2
|
|
99
|
+
**LOC owned:** 220
|
|
100
|
+
**Tests:** fast
|
|
101
|
+
**Model:** sonnet
|
|
102
|
+
**Est. time:** 4 min
|
|
103
|
+
**Consumes:** none
|
|
104
|
+
**Produces:** ast::Stmt::Crash(Expr) · parser canonical tag type name e.g. `x: [B, A(Str)]` -> ast::Type{name:"[A(Str),B]", generics:[]} · `a ?? b` -> Expr::Elvis · `f(x) ? |e| Bad` -> Expr::Try(MethodCall(f(x), "mapErr", [closure]))
|
|
105
|
+
**Review Focus:** `test_tag_type_canonical_sorted`: `fun f() -> Try[Int, [Zed, Abc(Str)]]` returns the Type with generics [Int, "[Abc(Str),Zed]"]; `test_slice_type_still_parses`: `[]Byte` still gives "[]Byte"; `test_elvis_token_removed`: `a ?: b` is a parse error; `test_ternary_unaffected`: `c ? 1 : 2` is still Ternary; `test_try_with_closure_desugars`.
|
|
106
|
+
**Test code:** Rust parser tests in plum-core/tests (follow existing file naming), with the assertions above plus `crash "boom"` → Stmt::Crash(String). Update the tree-sitter corpus fixtures for `?:`→`??`.
|
|
107
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 grammar edit + `tree-sitter generate --disable-optimizations` + `tree-sitter test`, parser.rs, 4 PASS
|
|
108
|
+
**Notes:** `try_expression` becomes `seq(primary, "?", optional(closure))`. Resolve the conflict with ternary using a declared conflict, not precedence alone. Add highlight captures for `crash` and `??`, and a formatter rule for `??` and tag-union types. Do not migrate any .plum files.
|
|
109
|
+
|
|
110
|
+
### T2 checker · Complexity 8 · Est. context 75k
|
|
111
|
+
**Owns:** plum-checker/src/types.rs, plum-checker/src/lib.rs, plum-checker/src/monomorphize.rs, plum-checker/tests/
|
|
112
|
+
**Components:** 2
|
|
113
|
+
**LOC owned:** 330
|
|
114
|
+
**Tests:** fast
|
|
115
|
+
**Model:** sonnet
|
|
116
|
+
**Est. time:** 5.5 min
|
|
117
|
+
**Consumes:** ast::Stmt::Crash(Expr) (T1)
|
|
118
|
+
**Produces:** PlumType::TTags(BTreeMap<String, Option<Box<PlumType>>>) · plum_checker::tagUnionSegmentsCompatible(expected: &str, actual: &str, ctx: &CheckCtx) -> bool · plum_checker::erasedEnumName(name: &str) -> String e.g. erasedEnumName("Try$Int$[A,B(Str)]") -> "Try$Int$Tags" · tryOperatorVariants(enum_name, ctx) now matches only Ok/Err
|
|
119
|
+
**Review Focus:** (deep) `nested_union_in_payload`: `[A(Try$Int$[X])]` segments split with brackets counted by depth; (state) `closure_question_mark_no_return_check`: `?` inside a closure is not checked against the outer `$return`; `question_mark_rejects_unlisted_tag`: `fun f() -> Try[Int,[A]] = { x := g()?; Ok(x) }` with g: `Try[Int,[B]]` reports an error naming `[B]`; `question_mark_widens`: g: `[A]` into `[A,B]` passes; `match_exhaustive_tags`; `eq_rejects_payload_tags`; `unknown_lowercase_still_error`: an unknown lowercase var is still an error, not a tag.
|
|
120
|
+
**Test code:** Rust tests in plum-checker/tests/checker_tests.rs. Embedded test sources must import `std/Try` (not Option/Result) and assert that parsing produced no errors. Migrate the existing tests in this crate that use Option/Result to Try plus tags.
|
|
121
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 implement, 4 PASS
|
|
122
|
+
**Notes:** The monomorphizer must walk `Stmt::Crash` everywhere it walks statements. A tag literal is anything not found in global_env, enum_variants or the class table. Treat `Display` for TTags as the canonical form (design item 2).
|
|
123
|
+
|
|
124
|
+
### T3 codegen · Complexity 8 · Est. context 78k
|
|
125
|
+
**Owns:** plum-wasm-codegen/src/lib.rs, plum-wasm-codegen/tests/codegen_tests.rs
|
|
126
|
+
**Components:** 2
|
|
127
|
+
**LOC owned:** 360
|
|
128
|
+
**Tests:** fast
|
|
129
|
+
**Model:** sonnet
|
|
130
|
+
**Est. time:** 5.8 min
|
|
131
|
+
**Consumes:** PlumType::TTags (T2) · plum_checker::erasedEnumName(name: &str) -> String (T2)
|
|
132
|
+
**Produces:** wasm import ("plum","crash") of printLn's type, emitted only when `crash` is used · $Tag GC struct
|
|
133
|
+
**Review Focus:** (numeric) `int_payload_roundtrip`: `Err(Big(9007199254740993))` matched back gives exactly 9007199254740993; `float_payload_roundtrip` with 0.1; (deep) `nested_err_tag_pattern`: `match r | Ok(v) => v | Err(NotFound) => 0 | Err(Bad(n)) => n`; (state) `question_mark_different_ok_types`: inner `Try[Int,[A]]` inside `fun f() -> Try[Str,[A,B]]` validates and returns Err(A); `tag_eq`: `A == A` is True, `A == B` is False; `specializations_share_gc_type`: `Try$Int$[A]` and `Try$Int$[A,B]` register one enum GC type.
|
|
134
|
+
**Test code:** Rust tests in codegen_tests.rs that compile and run through wasmtime the way existing tests do. Migrate the existing Option/Result tests in this file.
|
|
135
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 implement, 4 PASS
|
|
136
|
+
**Notes:** The `?` failure branch is struct.get the inner Err payload, then struct.new the outer function's Err variant, then return. Work out the outer variant from the current fn's return type in LocalCtx (add it if it's missing). The tag-id table is built from every tag literal plus every tag inside a TTags appearing in any signature.
|
|
137
|
+
|
|
138
|
+
### T4 hosts · Complexity 2 · Est. context 30k
|
|
139
|
+
**Owns:** plum-cli/src/main.rs, plum-runtime/src/main.rs, plum-wasm-codegen/tests/examples_test.rs, plum-checker/tests/examples_test.rs
|
|
140
|
+
**Components:** 1
|
|
141
|
+
**LOC owned:** 60
|
|
142
|
+
**Tests:** fast
|
|
143
|
+
**Kind:** mechanical
|
|
144
|
+
**Model:** haiku
|
|
145
|
+
**Est. time:** 1.5 min
|
|
146
|
+
**Consumes:** wasm import ("plum","crash") (T3)
|
|
147
|
+
**Produces:** each host links `plum::crash(Str)`: it prints `crash: <msg>` to stderr, then returns, and the wasm's `unreachable` traps
|
|
148
|
+
**Review Focus:** `crash_prints_message`: running `fun main() = crash "boom"` through plum-cli exits non-zero, and stderr contains `crash: boom`.
|
|
149
|
+
**Test code:** one Rust integration test in plum-cli that runs the binary on a temp file and asserts the two outcomes above.
|
|
150
|
+
**Steps:** 1 failing test, 2 FAIL, 3 copy the printLn linker arm to a crash arm in each host, 4 PASS
|
|
151
|
+
|
|
152
|
+
## Package: library + users
|
|
153
|
+
|
|
154
|
+
**App type:** library. **Categories:** deep, state.
|
|
155
|
+
**Probe entry:** run=cargo run -q -p plum-cli -- test plum-std plum-examples
|
|
156
|
+
|
|
157
|
+
### T5 std migration · Complexity 6 · Est. context 70k
|
|
158
|
+
**Owns:** plum-std/
|
|
159
|
+
**Components:** 2
|
|
160
|
+
**LOC owned:** 300
|
|
161
|
+
**Tests:** slow
|
|
162
|
+
**Model:** sonnet
|
|
163
|
+
**Est. time:** 5 min
|
|
164
|
+
**Consumes:** compiler from T1-T4
|
|
165
|
+
**Produces:** plum-std/Try.plum with `enum Try[T, E]` and methods isOk, isErr, unwrap (crash "unwrap called on Err"), unwrapErr, unwrapOr, unwrapOrElse, map, mapErr, andThen, toTryStr · Option.plum, Result.plum, Err.plum deleted · List/Map/Json/Base64/Testing/Str/Array/Number/Bool return Try with the tag names in Design item 1
|
|
166
|
+
**Review Focus:** (state) `list_get_out_of_bounds`: `List(1).get(5)` is `Err(OutOfBounds)`; `map_get_missing`: `Err(KeyNotFound)`; `parse_int_bad`: `parseInt("x")` is `Err(BadNumStr)`; (deep) `question_mark_merges_two_unions` in Try.plum: a fn with `-> Try[Int, [BadNumStr, OutOfBounds]]` uses `?` on parseInt and on List.get; `try_closure_maps_error`: `parseInt("x") ? |_| InvalidFirst` inside `-> Try[Int,[InvalidFirst]]`; `default_operator`: `List[Int]().first() ?? 7 == 7`.
|
|
167
|
+
**Test code:** `test` blocks in plum-std/Try.plum for each Review Focus name above, plus each migrated test from the old Option.plum/Result.plum. Testing.plum: drop expectSome/expectNone and keep expectOk/expectErr.
|
|
168
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 migrate, 4 PASS
|
|
169
|
+
**Notes:** List's internal `Option[Node[T]]` links become a private `enum Link[T] = | Next(Node[T]) | End`. Ruling: they are not error tags, so a domain enum fits better than Try. Update every `import std/Option`/`std/Result` across plum-std.
|
|
170
|
+
|
|
171
|
+
### T6 examples, LSP, docs · Complexity 3 · Est. context 50k
|
|
172
|
+
**Owns:** plum-examples/, plum-lsp/src/, README.md, website/src/, plum-tooling/vscode-plum/
|
|
173
|
+
**Components:** 2
|
|
174
|
+
**LOC owned:** 200
|
|
175
|
+
**Tests:** slow
|
|
176
|
+
**Model:** sonnet
|
|
177
|
+
**Est. time:** 3 min
|
|
178
|
+
**Consumes:** Try std API (T5)
|
|
179
|
+
**Produces:** examples use Try plus tags, `??`, `? |e|`, `crash`. error_propagation.plum shows two different unions merging. safe_navigation.plum is migrated to Try. LSP keyword completion gets `crash` and `??` and loses `?:`. README and website docs describe the new model.
|
|
180
|
+
**Review Focus:** `examples_all_pass`: the full `plum test plum-examples` passes; `no_option_left`: `git grep -nw "Option\[\|Some(\|Result\[" -- '*.plum' '*.md'` is empty apart from changelog/history.
|
|
181
|
+
**Test code:** `test` blocks in plum-examples/error_propagation.plum: `merges_unions`, `maps_error_inline`, `default_operator`.
|
|
182
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 migrate, 4 PASS
|
|
183
|
+
|
|
184
|
+
## Review Focus
|
|
185
|
+
|
|
186
|
+
Compiler: deep (nested tag patterns, nested brackets in mangled names), numeric (boxed i64/f64 payload precision), state (`?` widening across different Ok types, closure `$return` scoping). Library: Roc tag names returned exactly as in Design item 1.
|
|
187
|
+
|
|
188
|
+
## Self-review
|
|
189
|
+
|
|
190
|
+
All 10 design items map to tasks (1→T5, 2-4→T1/T2, 5→T2/T3, 6→T1/T3/T4, 7-9→T2/T3, 10 out of scope). There are no placeholders. The Consumes lines match T1-T3's Produces lines. The chain is sequential by ruling.
|
docs/plans/2026-10-25-roc-errors.progress.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
- start a473b02882bd4862c80377ffd7aa3a62e47a7224
|
|
2
|
+
- unblocked: dispatched T0 T1 at 15:51:57; waiting on fixes: none
|
|
3
|
+
- Ruling: intermediate tasks T1-T4 run only their own crate's unit tests; plum-std/examples suites (and examples_test.rs) are expected red until T5/T6 migrate .plum sources - cost: regressions in those suites are only caught at T5
|
|
4
|
+
- tests full passed=164 failed=0 skipped=0 seconds=88.0
|
|
5
|
+
- T1 dfe35c7 (manual commit: commit_task blocked on ruled compile-fix arms outside Owns)
|
|
6
|
+
- Ruling: T1 commit includes 10 lines of compile-only Stmt::Crash arms in plum-checker/plum-wasm-codegen/builtin_usage.rs - keeps every commit building - cost: none
|
|
7
|
+
- Ruling: single-letter tags alias generic->type_identifier in tag positions; generic tag payloads not rejected (render Name[Args]) - cost: generic payloads unsupported downstream, may miscompile
|
|
8
|
+
- T2 9c5f64a (manual commit incl. 2 compile-only codegen arms)
|
|
9
|
+
- review T1+T2: 1 fix f9467e7; open: generic tag payloads not rejected by parser (ruled out of scope, design item 2)
|
|
10
|
+
- T3 29ad747
|
|
11
|
+
- review T3: 1 fix 8336a15; residual: ? inside closure w/ differing Try types fails wasm validation (accepted)
|
|
12
|
+
- T4 0f2502c
|
|
13
|
+
- Ruling: T5/T6 may fix compiler bugs they hit (plum-core/checker/codegen) with regression tests, since nothing else runs concurrently - cost: larger T5 diff to review
|
|
14
|
+
- T5 45dada4
|
|
15
|
+
- Ruling: ?? binds tighter than comparison operators (Swift/Kotlin elvis), so a ?? 7 == 7 is (a ?? 7) == 7 - cost: differs from C#
|
|
16
|
+
- Ruling: Os/Http/Uuid keep Str error payloads (I/O messages) - cost: less Roc-like
|
|
17
|
+
- fix dc3b104: ?? precedence + |_| closures
|
|
18
|
+
- fix a3ab768: andThen bare-Ok closure; residual: |n| Err(Bad) with no typed context leaves U unresolved (same as Rust)
|
|
19
|
+
- T6 82b552d
|
|
20
|
+
- tests fast passed=1 failed=0 skipped=0 seconds=10.6
|
|
21
|
+
- tests full passed=101 failed=2 skipped=0 seconds=0.2
|
|
22
|
+
- tests full passed=218 failed=0 skipped=0 seconds=25.1
|
|
23
|
+
- fix f0eddab: Ok/Err direct args resolve from expected type; tag beats same-named variant when expected type is a tag union (#Name marker); README braces. residual: unpinned Ok in program w/o concrete Try reads as tag; keyword args matched positionally (pre-existing)
|
|
24
|
+
- tests full passed=218 failed=0 skipped=0 seconds=20.0
|
|
25
|
+
- final-review fixes d94bce0; residual: constructor sub-patterns inside a tag payload (generic payload) unsupported, ignored test documents it (design item 2 scope)
|
|
26
|
+
- tests full passed=226 failed=0 skipped=0 seconds=20.3
|