plum

#treesitter#compiler#wasm

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

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


949dabf — Peter John 2026-11-05T16:34:55+05:30
Build speed: one test binary per crate, editor assets only for dist builds
Files changed (95) hide show
  1. AGENTS.md +1 -1
  2. Cargo.toml +3 -0
  3. Makefile.toml +5 -0
  4. docs/backlog.md +3 -0
  5. docs/plans/2026-11-05-build-speed.md +92 -0
  6. docs/plans/2026-11-05-build-speed.results.md +21 -0
  7. docs/plans/2026-11-05-language-simplification-d3.progress.md +11 -0
  8. plum-checker/tests/{checker_tests.rs → checker/checker_tests.rs} +0 -0
  9. plum-checker/tests/{closure_hint_test.rs → checker/closure_hint_test.rs} +0 -0
  10. plum-checker/tests/{features → checker/features}/closure_types.rs +0 -0
  11. plum-checker/tests/{features → checker/features}/io_capability.rs +0 -0
  12. plum-checker/tests/{features/main.rs → checker/features/mod.rs} +0 -0
  13. plum-checker/tests/{features → checker/features}/mutability.rs +0 -0
  14. plum-checker/tests/{features → checker/features}/optional_unpinned.rs +0 -0
  15. plum-checker/tests/{features → checker/features}/pattern_casing.rs +0 -0
  16. plum-checker/tests/{features → checker/features}/primitive_generics.rs +0 -0
  17. plum-checker/tests/{features → checker/features}/types.rs +0 -0
  18. plum-checker/tests/{features → checker/features}/variant_pattern.rs +0 -0
  19. plum-checker/tests/{fusion_pipeline_test.rs → checker/fusion_pipeline_test.rs} +0 -0
  20. plum-checker/tests/{inline_variants_tests.rs → checker/inline_variants_tests.rs} +0 -0
  21. plum-checker/tests/checker/main.rs +9 -0
  22. plum-checker/tests/{question_tests.rs → checker/question_tests.rs} +0 -0
  23. plum-checker/tests/{subtyping_tests.rs → checker/subtyping_tests.rs} +0 -0
  24. plum-cli/build.rs +17 -0
  25. plum-cli/src/editor.rs +6 -1
  26. plum-cli/tests/{assert_once_test.rs → cli/assert_once_test.rs} +0 -0
  27. plum-cli/tests/{compile_tests.rs → cli/compile_tests.rs} +0 -0
  28. plum-cli/tests/{crash_test.rs → cli/crash_test.rs} +0 -0
  29. plum-cli/tests/{explain_layout_test.rs → cli/explain_layout_test.rs} +0 -0
  30. plum-cli/tests/{game_web_test.rs → cli/game_web_test.rs} +0 -0
  31. plum-cli/tests/{host_stub_test.rs → cli/host_stub_test.rs} +0 -0
  32. plum-cli/tests/cli/main.rs +9 -0
  33. plum-cli/tests/{test_scope_test.rs → cli/test_scope_test.rs} +0 -0
  34. plum-core/tests/{closure_escape_test.rs → core/closure_escape_test.rs} +0 -0
  35. plum-core/tests/{closure_spec_test.rs → core/closure_spec_test.rs} +0 -0
  36. plum-core/tests/{closure_types_parse_test.rs → core/closure_types_parse_test.rs} +0 -0
  37. plum-core/tests/{drive_test.rs → core/drive_test.rs} +0 -0
  38. plum-core/tests/{for_next_test.rs → core/for_next_test.rs} +0 -0
  39. plum-core/tests/{formatter_test.rs → core/formatter_test.rs} +0 -0
  40. plum-core/tests/{forms_list_range_test.rs → core/forms_list_range_test.rs} +0 -0
  41. plum-core/tests/{fused_closure_generic_test.rs → core/fused_closure_generic_test.rs} +0 -0
  42. plum-core/tests/{generics_test.rs → core/generics_test.rs} +0 -0
  43. plum-core/tests/{inline_variants_test.rs → core/inline_variants_test.rs} +0 -0
  44. plum-core/tests/{layout_test.rs → core/layout_test.rs} +0 -0
  45. plum-core/tests/{list_literal_test.rs → core/list_literal_test.rs} +0 -0
  46. plum-core/tests/{loader_test.rs → core/loader_test.rs} +0 -0
  47. plum-core/tests/{lower_test.rs → core/lower_test.rs} +0 -0
  48. plum-core/tests/core/main.rs +33 -0
  49. plum-core/tests/{match_body_parse_test.rs → core/match_body_parse_test.rs} +0 -0
  50. plum-core/tests/{override_parse_test.rs → core/override_parse_test.rs} +0 -0
  51. plum-core/tests/{parser_test.rs → core/parser_test.rs} +0 -0
  52. plum-core/tests/{purity_test.rs → core/purity_test.rs} +0 -0
  53. plum-core/tests/{question_parse_test.rs → core/question_parse_test.rs} +0 -0
  54. plum-core/tests/{record_embed_test.rs → core/record_embed_test.rs} +0 -0
  55. plum-core/tests/{record_init_parse_test.rs → core/record_init_parse_test.rs} +0 -0
  56. plum-core/tests/{record_init_test.rs → core/record_init_test.rs} +0 -0
  57. plum-core/tests/{rename_test.rs → core/rename_test.rs} +0 -0
  58. plum-core/tests/{session_test.rs → core/session_test.rs} +0 -0
  59. plum-core/tests/{simplify_test.rs → core/simplify_test.rs} +0 -0
  60. plum-core/tests/{subtyping_test.rs → core/subtyping_test.rs} +0 -0
  61. plum-core/tests/{tail_test.rs → core/tail_test.rs} +0 -0
  62. plum-core/tests/{tests_lower_test.rs → core/tests_lower_test.rs} +0 -0
  63. plum-core/tests/{trmc_test.rs → core/trmc_test.rs} +0 -0
  64. plum-core/tests/{unions_test.rs → core/unions_test.rs} +0 -0
  65. plum-core/tests/{uniqueness_test.rs → core/uniqueness_test.rs} +0 -0
  66. plum-core/tests/{visit_test.rs → core/visit_test.rs} +0 -0
  67. plum-core/tests/{whistle_test.rs → core/whistle_test.rs} +0 -0
  68. plum-wasm-codegen/tests/{codegen_tests.rs → wasm_codegen/codegen_tests.rs} +0 -0
  69. plum-wasm-codegen/tests/{e2e_mem/main.rs → wasm_codegen/e2e_mem/mod.rs} +0 -0
  70. plum-wasm-codegen/tests/{e2e_mem → wasm_codegen/e2e_mem}/reuse.rs +0 -0
  71. plum-wasm-codegen/tests/{e2e_mem → wasm_codegen/e2e_mem}/unique_inplace.rs +0 -0
  72. plum-wasm-codegen/tests/{e2e_mem → wasm_codegen/e2e_mem}/unique_spread.rs +0 -0
  73. plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/empty_record_embed.rs +0 -0
  74. plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/enum_init.rs +0 -0
  75. plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/inline_variants.rs +0 -0
  76. plum-wasm-codegen/tests/{e2e_records/main.rs → wasm_codegen/e2e_records/mod.rs} +0 -0
  77. plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/record_embed.rs +0 -0
  78. plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/record_init.rs +0 -0
  79. plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/unboxed_variants.rs +0 -0
  80. plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/closure_tail.rs +0 -0
  81. plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/error_tail.rs +0 -0
  82. plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/loop_break.rs +0 -0
  83. plum-wasm-codegen/tests/{e2e_tail/main.rs → wasm_codegen/e2e_tail/mod.rs} +0 -0
  84. plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/tail.rs +0 -0
  85. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/closure_optional.rs +0 -0
  86. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/closure_types.rs +0 -0
  87. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/io_entry.rs +0 -0
  88. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/list_and_shadow.rs +0 -0
  89. plum-wasm-codegen/tests/{e2e_types/main.rs → wasm_codegen/e2e_types/mod.rs} +0 -0
  90. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/question.rs +0 -0
  91. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/subtyping.rs +0 -0
  92. plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/type_param_static.rs +0 -0
  93. plum-wasm-codegen/tests/{examples_test.rs → wasm_codegen/examples_test.rs} +0 -0
  94. plum-wasm-codegen/tests/{fusion_alloc_test.rs → wasm_codegen/fusion_alloc_test.rs} +0 -0
  95. plum-wasm-codegen/tests/wasm_codegen/main.rs +9 -0
AGENTS.md CHANGED
@@ -6,7 +6,7 @@ Do not use git worktrees or feature branches. Work directly on `master` in the m
6
6
 
7
7
  ## Disk space
8
8
 
9
- The SSD is nearly full. After a plan or phase is finished and merged to `master`, run:
9
+ The SSD is nearly full. After a plan or phase is finished and merged to `master`, check `df -h /` and, only when under ~20 GB is free, run:
10
10
 
11
11
  ```
12
12
  cargo clean-all -y
Cargo.toml CHANGED
@@ -2,6 +2,9 @@
2
2
  members = ["plum-core", "plum-cli", "plum-checker", "plum-wasm-codegen", "plum-lsp", "plum-test-support", "plum-host"]
3
3
  resolver = "2"
4
4
 
5
+ [profile.dev]
6
+ debug = "line-tables-only"
7
+
5
8
  [profile.dev.package.wasmtime]
6
9
  opt-level = 3
7
10
 
Makefile.toml CHANGED
@@ -101,6 +101,8 @@ fi
101
101
  # everything to R2. Version comes from plum-cli/Cargo.toml — bump that first.
102
102
 
103
103
  [env]
104
+ # Distributed binaries embed the editor assets (dev builds skip them).
105
+ PLUM_EMBED_EDITOR = "1"
104
106
  PLUM_VERSION = { script = ["grep -m1 '^version' plum-cli/Cargo.toml | sed -E 's/version = \"(.*)\"/\\1/'"] }
105
107
 
106
108
  [tasks.dist-macos]
@@ -108,6 +110,7 @@ description = "Build a release plum-cli binary for this Mac into dist/plum-cli/"
108
110
  script_runner = "@shell"
109
111
  script = '''
110
112
  set -e
113
+ export PLUM_EMBED_EDITOR=1
111
114
  cargo build --release -p plum-cli
112
115
  mkdir -p dist/plum-cli
113
116
  ARCH=$(uname -m)
@@ -125,6 +128,7 @@ description = "Cross-build a release plum-cli binary for Linux x86_64 (via `cros
125
128
  script_runner = "@shell"
126
129
  script = '''
127
130
  set -e
131
+ export PLUM_EMBED_EDITOR=1
128
132
  cross build --release -p plum-cli --target x86_64-unknown-linux-gnu
129
133
  mkdir -p dist/plum-cli
130
134
  cp target/x86_64-unknown-linux-gnu/release/plum dist/plum-cli/plum-linux-x86_64
@@ -136,6 +140,7 @@ description = "Cross-build a release plum-cli binary for Windows x86_64 (via `cr
136
140
  script_runner = "@shell"
137
141
  script = '''
138
142
  set -e
143
+ export PLUM_EMBED_EDITOR=1
139
144
  cross build --release -p plum-cli --target x86_64-pc-windows-gnu
140
145
  mkdir -p dist/plum-cli
141
146
  cp target/x86_64-pc-windows-gnu/release/plum.exe dist/plum-cli/plum-windows-x86_64.exe
docs/backlog.md CHANGED
@@ -10,6 +10,9 @@ Open work, in the order the user queued it, plus smaller items found during buil
10
10
  - **Move the Rust code into `compiler/`** (user 2026-11-04: once all the refactoring is done) — every Rust crate (plum-core, plum-checker, plum-wasm-codegen, plum-cli, plum-lsp, plum-host, plum-test-support, plum-runtime, plum-game) goes under `compiler/`; `plum-std` and `plum-examples` stay at the top level. Mechanical but touches hardcoded paths: workspace `Cargo.toml` members, every `CARGO_MANIFEST_DIR/..` repoRoot (test-support, tests, cli), the loader's std lookup, the checker's `isStdFile`/`isIoFile` path tests, `cmdBuild`'s runtime manifest path, plum-game web-test fixtures, scripts/, Makefile.toml, plum-tooling (vscode/tree-sitter paths), README and docs links. Open question for then: keep the `plum-` prefixes inside `compiler/` or drop them (`compiler/core`, …).
11
11
  2. **Simplify the language: no traits, no union enums, native inline variants** (decided by the user 2026-11-04; runs right after compiler-cleanup phase 3a, before explicit generics and 3b; decision record §13). Traits are removed as a language feature and replaced by records embedding records with subtyping + `override` dispatch (a `Ship` embedding `Node` is accepted wherever a `Node` is, and calls dispatch to Ship's overrides — the scene graph's `trait Actor` becomes a base record). Union enums (`enum X = A | B` of records) are removed: `JsonParse` and the union examples become inline-variant enums, union syntax becomes an error. Inline variants compile natively as struct subtypes of their enum, deleting `hoistInlineVariants`, the `List__Cons(Cons(..))` wrappers, union forwarders, `union_lower` and the checker's union plan. `unboxed_variants.plum` is a compiler probe: move it to a codegen test. Same plan also covers (user 2026-11-04): `=` reassign-only, no generators (`fun*`/`yield`; Seq deleted, one loop form `for x := range v` over Int or any `next(self) -> Option[Pair[T, Self]]`), no variadics (`...T`; `[..]` literal → `Cons`/`Nil`, `[k => v]` → `Map.empty().with(..)`), no value enums, `record` merged into `struct` now, and **run D: error sets / `T!E` / `T?` / `null` replaced by std `Option[T]` and `Result[T, E]` enums**, with postfix `?` as the ONLY sugar (revised 2026-11-05: `try`, `?:`, `?.`, `catch`, `T?`, `E!T`, `null` all removed; runs right after run C; decision record §13 item 1). Runs A (in progress), B (struct), C (traits/unions), D (Option/Result).
12
12
  3. **Benchmark** before FBIP (user: FBIP waits for it).
13
+ - **Unbox iterators / niche Option layout** (user 2026-11-05: later, here, not before generics). Run D made `for` loops slower: for-over-List 1M 28 → 49 ms (+75%), for-over-Range 6 → 8 ms (+33%), Map.get +17% (docs/plans/2026-11-05-language-simplification-d.benchmark.md). Cause: `next() -> Option[Pair[T, Self]]` allocates a `Some` cell per step on top of the Pair (the old `Pair?` was a nullable ref). Options: `Option[R]` for a GC ref R as a nullable ref (niche), and/or fusion/drive unboxing the `Some(step) := it.next()` loop. Bisect 961bd21 vs d7658cc back-to-back first.
14
+ - **Run C game A/B** still owed (stress_scene/stress_world vs pre-run-C). D2 found closure-spec hints were empty since run C (fixed), which may explain part of it.
15
+ - **Build-speed** done separately right after run D (2026-11-05).
13
16
  4. **FBIP: no heap mutation** — persistent std collections updated in place when unique, Koka-style `fip` functions as a checked in-place guarantee, a reference count used only as a uniqueness check if static proof falls short. Worklist = the `mut` fields left today (see below). (Decision record §9.)
14
17
  5. **One product type, `struct`** — after FBIP, merge `record` into an immutable `struct` with compiler-chosen boxing (optional hint, shown by `--explain`). (Decision record §9b.)
15
18
  - **Decide: do we still need traits?** (user 2026-11-04, decide with this item and item 2, since traits are lowered through the same hidden-union machinery). Today traits are used only by `std/game/imperative/Scene` (`trait Actor`, 14 fields), `std/game/pure/PureScene` (field-less `trait Actor` for `Custom` nodes) and three examples. The functional scene, the kids' games plan and `../kam_plum` use none. Their unique job is letting std hold user-defined kinds (a scene tree of the game's own `Ship`/`Orb`) with static dispatch.
docs/plans/2026-11-05-build-speed.md ADDED
@@ -0,0 +1,92 @@
1
+ # Build speed: fewer test binaries, no nested release build in dev
2
+
3
+ **Goal:** an edit to a compiler crate followed by `cargo build --workspace --tests` (or `cargo build -p plum-cli`) takes far less time. Today ~63 integration-test binaries each link wasmtime, and `plum-cli/build.rs` runs a nested `cargo build --release -p plum-lsp`, an `-O2` compile of the ~10 MB tree-sitter `parser.c`, and an npm build of the VS Code client on every plum-cli build whose inputs changed.
4
+ **Architecture:** Cargo workspace (plum-core, plum-checker, plum-wasm-codegen, plum-cli, ...), plum-cli/build.rs, Makefile.toml.
5
+ **Tech stack:** Rust 2021, cargo, cargo-make.
6
+ **Detail:** contracts
7
+ **Target-min:** 15
8
+
9
+ ## Global constraints
10
+ - Work on `master` in the main checkout, default shared `target/` (no CARGO_TARGET_DIR). Don't commit. Never run `plum format` on real files. No `sed -i -E` (macOS backups).
11
+ - No `cargo test` (hook): `cargo build --tests -p <crate>`, run binaries in `target/debug/deps` (newest by mtime, `/bin/ls -t`; `ls` is aliased).
12
+ - No test is deleted or weakened: merging only moves files. Test counts per crate stay the same (record before/after).
13
+ - Moving a test file one directory deeper breaks relative `include_str!`/`include_bytes!`/fixture paths: use `env!("CARGO_MANIFEST_DIR")`-based paths or fix the relative path.
14
+ - Each merged crate ends with ONE integration-test binary: `tests/<name>/main.rs` with `mod <file>;` per former file (former `dir/main.rs` → `dir/mod.rs` as a nested module). Keep file names.
15
+
16
+ ## Task graph
17
+ ```mermaid
18
+ flowchart LR
19
+ T1[build.rs gating + dev profile + AGENTS.md]
20
+ T2[merge plum-core tests]
21
+ T3[merge plum-checker + plum-cli tests]
22
+ T4[merge plum-wasm-codegen tests]
23
+ T5[measure + results]
24
+ %% taskgraph
25
+ T1 --> T5
26
+ T2 --> T5
27
+ T3 --> T5
28
+ T4 --> T5
29
+ ```
30
+
31
+ ## Package A: build
32
+ **Probe entry:** run=cargo build --workspace --tests
33
+
34
+ ### T1 build.rs gating, dev profile, AGENTS.md · Complexity 3 · Est. context 30k
35
+ **Owns:** plum-cli/build.rs, Makefile.toml, Cargo.toml, AGENTS.md, plum-cli/src/editor*.rs
36
+ **Components:** 1
37
+ **LOC owned:** 60
38
+ **Tests:** fast
39
+ **Model:** sonnet
40
+ **Produces:** plum-cli dev builds skip editor assets unless `PLUM_EMBED_EDITOR=1`
41
+ e.g. `cargo build -p plum-cli` -> no nested plum-lsp release build, no parser.c .so, no npm; `PLUM_EMBED_EDITOR=1 cargo build -p plum-cli` -> all three as today
42
+ **Notes:** reuse the existing graceful placeholders (each asset already degrades when its toolchain is missing); emit `cargo:rerun-if-env-changed=PLUM_EMBED_EDITOR`. Set `PLUM_EMBED_EDITOR=1` in every Makefile.toml dist task. `plum editor ...` with a placeholder asset must print a clear "this plum was built without editor assets; rebuild with PLUM_EMBED_EDITOR=1" (check plum-cli/src for where the embedded bins are used). Workspace `Cargo.toml`: `[profile.dev] debug = "line-tables-only"` (keep the wasmtime/cranelift opt-level overrides). AGENTS.md "Disk space": after a merged plan/phase check `df -h /` and run `cargo clean-all -y` only when under ~20 GB free (user 2026-11-05).
43
+ **Test code:** `cargo build -p plum-cli` succeeds without npm/cc being invoked (check build output / `-vv`); plum-cli test binaries green; `target/debug/plum test plum-std/Option.plum` green.
44
+
45
+ ## Package B: test binaries
46
+ **Probe entry:** run=cargo build --workspace --tests
47
+
48
+ ### T2 merge plum-core tests · Complexity 2 · Est. context 30k
49
+ **Owns:** plum-core/tests/**
50
+ **Components:** 1
51
+ **LOC owned:** 60
52
+ **Tests:** fast
53
+ **Model:** sonnet
54
+ **Produces:** plum-core has one integration-test binary `core`
55
+ e.g. `cargo build --tests -p plum-core` -> deps has `core-<hash>` (plus the lib unit-test binary), not 33
56
+ **Test code:** total passed count of plum-core integration tests before == after (record both); all green.
57
+
58
+ ### T3 merge plum-checker and plum-cli tests · Complexity 2 · Est. context 30k
59
+ **Owns:** plum-checker/tests/**, plum-cli/tests/**
60
+ **Components:** 1
61
+ **LOC owned:** 50
62
+ **Tests:** fast
63
+ **Model:** sonnet
64
+ **Produces:** plum-checker has one binary `checker`, plum-cli one binary `cli`
65
+ e.g. `features/main.rs` -> `checker/features/mod.rs`
66
+ **Notes:** plum-cli tests may spawn `target/debug/plum` or use `env!("CARGO_BIN_EXE_plum")`; keep that working. Fixture dirs (`plum-checker/tests/fixtures`) stay where they are unless paths are relative.
67
+ **Test code:** passed counts per crate before == after; all green.
68
+
69
+ ### T4 merge plum-wasm-codegen tests · Complexity 2 · Est. context 30k
70
+ **Owns:** plum-wasm-codegen/tests/**
71
+ **Components:** 1
72
+ **LOC owned:** 50
73
+ **Tests:** fast
74
+ **Model:** sonnet
75
+ **Produces:** plum-wasm-codegen has one binary `wasm_codegen` (`codegen` is git-ignored)
76
+ e.g. `e2e_types/main.rs` -> `wasm_codegen/e2e_types/mod.rs`; codegen_tests.rs, examples_test.rs, fusion_alloc_test.rs become modules
77
+ **Notes:** shared helpers duplicated across the e2e_* mains may now collide — keep them per module (`super::` paths) rather than merging.
78
+ **Test code:** passed count before == after; all green.
79
+
80
+ ### T5 measure and results · Complexity 2 · Est. context 20k
81
+ **Owns:** docs/plans/2026-11-05-build-speed.results.md
82
+ **Components:** 1
83
+ **LOC owned:** 30
84
+ **Tests:** slow
85
+ **Model:** sonnet
86
+ **Consumes:** plum-cli dev builds skip editor assets unless `PLUM_EMBED_EDITOR=1` (T1) · plum-core has one integration-test binary `core` (T2) · plum-checker has one binary `checker`, plum-cli one binary `cli` (T3) · plum-wasm-codegen has one binary `wasm_codegen` (T4)
87
+ **Produces:** before/after build timings
88
+ **Notes:** baseline (before T1-T4) is in the coordinator's notes and goes in the doc as given. After: warm build, then `touch plum-checker/src/lib.rs` → time `cargo build --workspace --tests`; `touch plum-wasm-codegen/src/lib.rs` → same; `touch plum-core/src/ast.rs` → time `cargo build -p plum-cli`; count test executables in target/debug/deps; `uptime`. Also run every test binary and `target/debug/plum test plum-std plum-examples` (662).
89
+ **Test code:** results table before/after; all green.
90
+
91
+ ## Review Focus
92
+ - B: state — no test lost (counts before == after per crate); fixture paths still resolve.
docs/plans/2026-11-05-build-speed.results.md ADDED
@@ -0,0 +1,21 @@
1
+ # Build speed results (2026-11-05)
2
+
3
+ | Edit, then build | Before | After |
4
+ |---|---|---|
5
+ | `touch plum-checker/src/lib.rs` → `cargo build --workspace --tests` | 17 s | 3 s |
6
+ | `touch plum-wasm-codegen/src/lib.rs` → `cargo build --workspace --tests` | 9 s | 4 s |
7
+ | `touch plum-core/src/ast.rs` → `cargo build -p plum-cli` | 13 s | 2 s |
8
+ | integration-test binaries | ~63 | 13 (one per crate + lib unit tests) |
9
+
10
+ Before at load ~9, after at load ~15 (so the after numbers are if anything pessimistic). Cold builds after `cargo clean-all` are unchanged by this work; they are now rare (AGENTS.md: clean only when under ~20 GB free).
11
+
12
+ ## Changes
13
+ - `plum-cli/build.rs`: the nested `cargo build --release -p plum-lsp`, the `-O2` tree-sitter grammar `.so` and the npm VS Code client build run only with `PLUM_EMBED_EDITOR=1` (set by the Makefile.toml dist tasks); otherwise 0-byte placeholders, and `plum editor` says "this plum was built without editor assets; rebuild with PLUM_EMBED_EDITOR=1".
14
+ - Workspace `[profile.dev] debug = "line-tables-only"`.
15
+ - One integration-test binary per crate: `plum-core/tests/core` (381 tests), `plum-checker/tests/checker` (130), `plum-cli/tests/cli` (15), `plum-wasm-codegen/tests/wasm_codegen` (206; not `codegen`, which `.gitignore` excludes). Counts identical before and after.
16
+ - AGENTS.md disk rule: `cargo clean-all -y` only when `df -h /` shows under ~20 GB free.
17
+
18
+ ## Sweep
19
+ All test binaries green; `plum test plum-std plum-examples` 662 passed, 65/65 files.
20
+
21
+ Running one test file now: `target/debug/deps/core-<hash> parser_test::` (filter by module path).
docs/plans/2026-11-05-language-simplification-d3.progress.md ADDED
@@ -0,0 +1,11 @@
1
+ - start d7658cc
2
+ Ruling: skip Wave 0 skeleton - removal run, no new interfaces - cost: none
3
+ Ruling: accept predicted 45.6 min - removal chain is forced (layer order) - cost: longer run
4
+ - unblocked: dispatched T1 T2 at Ruling: README/docs part of T7 pulled forward as fixer F-D3-1 (only README/docs, no overlap with T4-T6) - keeps lsp test green between tasks - cost: none
5
+ Ruling: README/docs part of T7 pulled forward as fixer F-D3-1 - keeps lsp test green - cost: none
6
+ Ruling: T3 touched loader.rs, scanner.c, arrays.plum, vscode-plum/* outside Owns - needed for the grammar removal to stay green - cost: none
7
+ Ruling: D3 loop regression (for-over-List +75%, Range +33%; cause: Some cell per next() step) - niche Option layout moves up to right after the build-speed task, ahead of explicit fn generics - core loops matter more than queue order - cost: generics slip one item
8
+ Ruling: final-review vscode/progress "blocked, nothing saved" - checkpoint refusals are bookkeeping; edits are in the working tree (git diff shows them); real leftover (?: and ?. highlight rules) sent to F-D3-2 - cost: none
9
+ Ruling: final-review error_union_tests coverage - 12 deleted tests were error-set/catch/inferred-set only except try_on_type_param_static (live `?` on T.static) -> twin in F-D3-2 - cost: none
10
+ Ruling: final-review "no test summary" - T8 ran all 62 binaries (777 passed) + plum sweep 662 + corpus green - cost: none
11
+ Ruling: final-review loop regression - accepted for D3 commit; fixed next (niche Option layout after build-speed task) - cost: loops slower until then
plum-checker/tests/{checker_tests.rs → checker/checker_tests.rs} RENAMED
File without changes
plum-checker/tests/{closure_hint_test.rs → checker/closure_hint_test.rs} RENAMED
File without changes
plum-checker/tests/{features → checker/features}/closure_types.rs RENAMED
File without changes
plum-checker/tests/{features → checker/features}/io_capability.rs RENAMED
File without changes
plum-checker/tests/{features/main.rs → checker/features/mod.rs} RENAMED
File without changes
plum-checker/tests/{features → checker/features}/mutability.rs RENAMED
File without changes
plum-checker/tests/{features → checker/features}/optional_unpinned.rs RENAMED
File without changes
plum-checker/tests/{features → checker/features}/pattern_casing.rs RENAMED
File without changes
plum-checker/tests/{features → checker/features}/primitive_generics.rs RENAMED
File without changes
plum-checker/tests/{features → checker/features}/types.rs RENAMED
File without changes
plum-checker/tests/{features → checker/features}/variant_pattern.rs RENAMED
File without changes
plum-checker/tests/{fusion_pipeline_test.rs → checker/fusion_pipeline_test.rs} RENAMED
File without changes
plum-checker/tests/{inline_variants_tests.rs → checker/inline_variants_tests.rs} RENAMED
File without changes
plum-checker/tests/checker/main.rs ADDED
@@ -0,0 +1,9 @@
1
+ #![allow(non_snake_case)]
2
+ //! Single integration-test binary for plum-checker (one link instead of one per file).
3
+ mod checker_tests;
4
+ mod closure_hint_test;
5
+ mod features;
6
+ mod fusion_pipeline_test;
7
+ mod inline_variants_tests;
8
+ mod question_tests;
9
+ mod subtyping_tests;
plum-checker/tests/{question_tests.rs → checker/question_tests.rs} RENAMED
File without changes
plum-checker/tests/{subtyping_tests.rs → checker/subtyping_tests.rs} RENAMED
File without changes
plum-cli/build.rs CHANGED
@@ -32,6 +32,23 @@ fn main() {
32
32
  let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
33
33
  let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
34
34
 
35
+ println!("cargo:rerun-if-env-changed=PLUM_EMBED_EDITOR");
36
+ // Editor assets (nested plum-lsp release build, parser.c -O2, npm) are slow
37
+ // and only matter for distributed binaries: dev builds embed empty
38
+ // placeholders unless PLUM_EMBED_EDITOR=1.
39
+ if env::var("PLUM_EMBED_EDITOR").as_deref() != Ok("1") {
40
+ for (name, file) in [
41
+ ("PLUM_GRAMMAR_SO", "plum-grammar.so"),
42
+ ("PLUM_LSP_BIN", if cfg!(windows) { "plum-lsp.exe" } else { "plum-lsp" }),
43
+ ("PLUM_VSCODE_EXTENSION_JS", "vscode-extension.js"),
44
+ ] {
45
+ let path = out_dir.join(file);
46
+ std::fs::write(&path, []).expect("failed to write placeholder editor asset");
47
+ println!("cargo:rustc-env={name}={}", path.display());
48
+ }
49
+ return;
50
+ }
51
+
35
52
  buildGrammarSo(&manifest_dir, &out_dir);
36
53
  buildPlumLsp(&manifest_dir, &out_dir);
37
54
  buildVscodeExtensionClient(&manifest_dir, &out_dir);
plum-cli/src/editor.rs CHANGED
@@ -123,7 +123,12 @@ fn writeExecutable(path: &Path, bytes: &[u8]) -> Result<()> {
123
123
  Ok(())
124
124
  }
125
125
 
126
+ const NO_EDITOR_ASSETS: &str = "this plum was built without editor assets; rebuild with PLUM_EMBED_EDITOR=1 for the language server and precompiled grammar";
127
+
126
128
  fn installHelix() -> Result<()> {
129
+ if PLUM_LSP_BIN.is_empty() {
130
+ eprintln!("note: {NO_EDITOR_ASSETS}");
131
+ }
127
132
  let config_dir = helixConfigDir()?;
128
133
  fs::create_dir_all(&config_dir)
129
134
  .with_context(|| format!("failed to create {}", config_dir.display()))?;
@@ -447,7 +452,7 @@ fn installVscode(insiders: bool) -> Result<()> {
447
452
  writeExecutable(&server_dir.join(bin_name), PLUM_LSP_BIN)?;
448
453
  println!("installed plum-lsp language server support");
449
454
  } else {
450
- println!("plum-lsp/extension bundle not available in this `plum` build; installed syntax highlighting only");
455
+ println!("note: {NO_EDITOR_ASSETS}; installed syntax highlighting only");
451
456
  }
452
457
 
453
458
  println!(
plum-cli/tests/{assert_once_test.rs → cli/assert_once_test.rs} RENAMED
File without changes
plum-cli/tests/{compile_tests.rs → cli/compile_tests.rs} RENAMED
File without changes
plum-cli/tests/{crash_test.rs → cli/crash_test.rs} RENAMED
File without changes
plum-cli/tests/{explain_layout_test.rs → cli/explain_layout_test.rs} RENAMED
File without changes
plum-cli/tests/{game_web_test.rs → cli/game_web_test.rs} RENAMED
File without changes
plum-cli/tests/{host_stub_test.rs → cli/host_stub_test.rs} RENAMED
File without changes
plum-cli/tests/cli/main.rs ADDED
@@ -0,0 +1,9 @@
1
+ #![allow(non_snake_case)]
2
+ //! Single integration-test binary for plum-cli (one link instead of one per file).
3
+ mod assert_once_test;
4
+ mod compile_tests;
5
+ mod crash_test;
6
+ mod explain_layout_test;
7
+ mod game_web_test;
8
+ mod host_stub_test;
9
+ mod test_scope_test;
plum-cli/tests/{test_scope_test.rs → cli/test_scope_test.rs} RENAMED
File without changes
plum-core/tests/{closure_escape_test.rs → core/closure_escape_test.rs} RENAMED
File without changes
plum-core/tests/{closure_spec_test.rs → core/closure_spec_test.rs} RENAMED
File without changes
plum-core/tests/{closure_types_parse_test.rs → core/closure_types_parse_test.rs} RENAMED
File without changes
plum-core/tests/{drive_test.rs → core/drive_test.rs} RENAMED
File without changes
plum-core/tests/{for_next_test.rs → core/for_next_test.rs} RENAMED
File without changes
plum-core/tests/{formatter_test.rs → core/formatter_test.rs} RENAMED
File without changes
plum-core/tests/{forms_list_range_test.rs → core/forms_list_range_test.rs} RENAMED
File without changes
plum-core/tests/{fused_closure_generic_test.rs → core/fused_closure_generic_test.rs} RENAMED
File without changes
plum-core/tests/{generics_test.rs → core/generics_test.rs} RENAMED
File without changes
plum-core/tests/{inline_variants_test.rs → core/inline_variants_test.rs} RENAMED
File without changes
plum-core/tests/{layout_test.rs → core/layout_test.rs} RENAMED
File without changes
plum-core/tests/{list_literal_test.rs → core/list_literal_test.rs} RENAMED
File without changes
plum-core/tests/{loader_test.rs → core/loader_test.rs} RENAMED
File without changes
plum-core/tests/{lower_test.rs → core/lower_test.rs} RENAMED
File without changes
plum-core/tests/core/main.rs ADDED
@@ -0,0 +1,33 @@
1
+ mod closure_escape_test;
2
+ mod closure_spec_test;
3
+ mod closure_types_parse_test;
4
+ mod drive_test;
5
+ mod for_next_test;
6
+ mod formatter_test;
7
+ mod forms_list_range_test;
8
+ mod fused_closure_generic_test;
9
+ mod generics_test;
10
+ mod inline_variants_test;
11
+ mod layout_test;
12
+ mod list_literal_test;
13
+ mod loader_test;
14
+ mod lower_test;
15
+ mod match_body_parse_test;
16
+ mod override_parse_test;
17
+ mod parser_test;
18
+ mod purity_test;
19
+ mod question_parse_test;
20
+ mod record_embed_test;
21
+ mod record_init_parse_test;
22
+ mod record_init_test;
23
+ mod rename_test;
24
+ mod session_test;
25
+ mod simplify_test;
26
+ mod subtyping_test;
27
+ mod tail_test;
28
+ mod tests_lower_test;
29
+ mod trmc_test;
30
+ mod unions_test;
31
+ mod uniqueness_test;
32
+ mod visit_test;
33
+ mod whistle_test;
plum-core/tests/{match_body_parse_test.rs → core/match_body_parse_test.rs} RENAMED
File without changes
plum-core/tests/{override_parse_test.rs → core/override_parse_test.rs} RENAMED
File without changes
plum-core/tests/{parser_test.rs → core/parser_test.rs} RENAMED
File without changes
plum-core/tests/{purity_test.rs → core/purity_test.rs} RENAMED
File without changes
plum-core/tests/{question_parse_test.rs → core/question_parse_test.rs} RENAMED
File without changes
plum-core/tests/{record_embed_test.rs → core/record_embed_test.rs} RENAMED
File without changes
plum-core/tests/{record_init_parse_test.rs → core/record_init_parse_test.rs} RENAMED
File without changes
plum-core/tests/{record_init_test.rs → core/record_init_test.rs} RENAMED
File without changes
plum-core/tests/{rename_test.rs → core/rename_test.rs} RENAMED
File without changes
plum-core/tests/{session_test.rs → core/session_test.rs} RENAMED
File without changes
plum-core/tests/{simplify_test.rs → core/simplify_test.rs} RENAMED
File without changes
plum-core/tests/{subtyping_test.rs → core/subtyping_test.rs} RENAMED
File without changes
plum-core/tests/{tail_test.rs → core/tail_test.rs} RENAMED
File without changes
plum-core/tests/{tests_lower_test.rs → core/tests_lower_test.rs} RENAMED
File without changes
plum-core/tests/{trmc_test.rs → core/trmc_test.rs} RENAMED
File without changes
plum-core/tests/{unions_test.rs → core/unions_test.rs} RENAMED
File without changes
plum-core/tests/{uniqueness_test.rs → core/uniqueness_test.rs} RENAMED
File without changes
plum-core/tests/{visit_test.rs → core/visit_test.rs} RENAMED
File without changes
plum-core/tests/{whistle_test.rs → core/whistle_test.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{codegen_tests.rs → wasm_codegen/codegen_tests.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_mem/main.rs → wasm_codegen/e2e_mem/mod.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_mem → wasm_codegen/e2e_mem}/reuse.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_mem → wasm_codegen/e2e_mem}/unique_inplace.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_mem → wasm_codegen/e2e_mem}/unique_spread.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/empty_record_embed.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/enum_init.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/inline_variants.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records/main.rs → wasm_codegen/e2e_records/mod.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/record_embed.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/record_init.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_records → wasm_codegen/e2e_records}/unboxed_variants.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/closure_tail.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/error_tail.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/loop_break.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_tail/main.rs → wasm_codegen/e2e_tail/mod.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_tail → wasm_codegen/e2e_tail}/tail.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/closure_optional.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/closure_types.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/io_entry.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/list_and_shadow.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types/main.rs → wasm_codegen/e2e_types/mod.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/question.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/subtyping.rs RENAMED
File without changes
plum-wasm-codegen/tests/{e2e_types → wasm_codegen/e2e_types}/type_param_static.rs RENAMED
File without changes
plum-wasm-codegen/tests/{examples_test.rs → wasm_codegen/examples_test.rs} RENAMED
File without changes
plum-wasm-codegen/tests/{fusion_alloc_test.rs → wasm_codegen/fusion_alloc_test.rs} RENAMED
File without changes
plum-wasm-codegen/tests/wasm_codegen/main.rs ADDED
@@ -0,0 +1,9 @@
1
+ #![allow(non_snake_case)]
2
+ //! Single integration-test binary for plum-wasm-codegen (one link instead of one per file).
3
+ mod codegen_tests;
4
+ mod e2e_mem;
5
+ mod e2e_records;
6
+ mod e2e_tail;
7
+ mod e2e_types;
8
+ mod examples_test;
9
+ mod fusion_alloc_test;