plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
b03ab64
— Peter John
2026-11-02T20:32:36+05:30
docs(plans): traits machine plan and run log
docs/plans/2026-11-02-traits.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Traits with fields (embedding)
|
|
2
|
+
|
|
3
|
+
**Review plan:** docs/plans/2026-11-02-traits.review.md
|
|
4
|
+
**Goal:** `trait Name(fields) = methods`; a `record` embeds traits by listing them first in its field list, gains their fields and default methods, and converts to each trait wherever one is expected.
|
|
5
|
+
**Architecture:** Before `resolveMembers`, `lowerTraits` copies trait fields and missing defaults into each embedding record, adds accessors `__get_<f>`/`__set_<f>` per field, and replaces each trait with a union enum (`isTrait: true`). Conversion, dispatch and narrowing reuse unions; trait field reads/writes become accessor dispatches in `union_lower`.
|
|
6
|
+
**Tech stack:** Rust workspace, tree-sitter grammar (regenerate with `tree-sitter generate --disable-optimizations`), Plum std/examples.
|
|
7
|
+
**Spec:** docs/plans/2026-11-02-traits.review.md
|
|
8
|
+
**Detail:** contracts
|
|
9
|
+
**Target-min:** 30
|
|
10
|
+
**Budget-usd:** 40
|
|
11
|
+
|
|
12
|
+
Global constraints:
|
|
13
|
+
- Language Rust; fast tests `cargo test -p <crate> --test <file>`; full suite in `.superlite/test-cmd.json`.
|
|
14
|
+
- `touch` an edited .rs before trusting `cargo build --quiet` (stale-build gotcha).
|
|
15
|
+
- Grammar edits: regenerate with `--disable-optimizations`, then `tree-sitter test` must pass the whole corpus.
|
|
16
|
+
- Identifiers are camelCase in Rust (repo style, `#![allow(non_snake_case)]`).
|
|
17
|
+
- Accessor names are exactly `__get_<field>` and `__set_<field>`; a trait union is an `Enum` with `isTrait: true` and one hidden member variant per embedding record (`Entity__House`), in declaration order.
|
|
18
|
+
- v1 exclusions: generic traits, traits embedding traits, `fun*` in traits, struct embedding, `==` on trait values.
|
|
19
|
+
- `plum format <file>` rewrites in place: run it only on copies or on the new example.
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart LR
|
|
23
|
+
SRC[source] --> P["T1 grammar + parser"]
|
|
24
|
+
P --> L["loader merge"]
|
|
25
|
+
L --> TL["T2 lowerTraits"]
|
|
26
|
+
TL --> RM["resolveMembers (exists)"]
|
|
27
|
+
RM --> CK["T4 checker rules"]
|
|
28
|
+
CK --> FA["T5 trait field plan"]
|
|
29
|
+
FA --> UL["union_lower (exists + T5)"]
|
|
30
|
+
UL --> CG["T6 codegen e2e"]
|
|
31
|
+
CK --> LSP["T7 editor"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
File structure: each task's Owns line. Spec coverage: What you will get (T1-T7), What will be built (T1-T9), How it fits together and How it gets built (diagrams), Decisions you can change (defaults taken), Risks (Review Focus), Out of scope (v1 exclusions), How we will know it works (T6, T8, T9), Rough time and cost (Budget-usd).
|
|
35
|
+
|
|
36
|
+
## Task graph
|
|
37
|
+
```mermaid
|
|
38
|
+
flowchart LR
|
|
39
|
+
T1[grammar parser]
|
|
40
|
+
T2[lowering]
|
|
41
|
+
T3[formatter]
|
|
42
|
+
T4[checker rules]
|
|
43
|
+
T5[field access]
|
|
44
|
+
T6[codegen e2e]
|
|
45
|
+
T7[editor]
|
|
46
|
+
T8[docs example]
|
|
47
|
+
T9[scene actor]
|
|
48
|
+
%% taskgraph
|
|
49
|
+
T1 --> T2
|
|
50
|
+
T1 --> T3
|
|
51
|
+
T2 --> T4
|
|
52
|
+
T2 --> T5
|
|
53
|
+
T4 --> T6
|
|
54
|
+
T5 --> T6
|
|
55
|
+
T2 --> T7
|
|
56
|
+
T6 --> T8
|
|
57
|
+
T8 --> T9
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```mermaid
|
|
61
|
+
%% schedule
|
|
62
|
+
gantt
|
|
63
|
+
title Predicted schedule (mm:ss)
|
|
64
|
+
dateFormat X
|
|
65
|
+
axisFormat %M:%S
|
|
66
|
+
section lane 1
|
|
67
|
+
T1 grammar and parser :t1, 0, 147s
|
|
68
|
+
T2 trait lowering :t2, 147, 153s
|
|
69
|
+
T4 checker trait rules :t4, 300, 142s
|
|
70
|
+
T6 codegen and run tests :t6, 442, 145s
|
|
71
|
+
T8 docs and examples :t8, 588, 144s
|
|
72
|
+
T9 Scene on trait Actor :t9, 731, 202s
|
|
73
|
+
section lane 2
|
|
74
|
+
T3 formatter and highlighting :t3, 147, 100s
|
|
75
|
+
T5 field access through a trait :t5, 300, 140s
|
|
76
|
+
section lane 3
|
|
77
|
+
T7 editor support :t7, 300, 105s
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
critical path: T1 → T2 → T4 → T6 → T8 → T9 (15.6 min) · peak width 3 · predicted build 15.6 min · predicted total ≈ 19.1 min (target ✓)
|
|
81
|
+
|
|
82
|
+
## Package A: syntax
|
|
83
|
+
|
|
84
|
+
**Probe entry:** call=plum_core::parser::parse
|
|
85
|
+
|
|
86
|
+
### T1 grammar and parser · Complexity 6 · Est. context 50k
|
|
87
|
+
**Owns:** plum-tooling/tree-sitter-plum/grammar.js, plum-tooling/tree-sitter-plum/src/, plum-tooling/tree-sitter-plum/test/corpus/trait.txt, plum-core/src/ast.rs, plum-core/src/parser.rs, plum-core/tests/trait_parse_test.rs, plum-core/src/forms.rs, plum-core/src/loader.rs, plum-core/src/builtin_usage.rs, plum-lsp/src/completion.rs, plum-checker/src/unions.rs, plum-core/src/unions.rs
|
|
88
|
+
**Components:** 2
|
|
89
|
+
**LOC owned:** 260
|
|
90
|
+
**Tests:** fast
|
|
91
|
+
**Kind:** design
|
|
92
|
+
**Model:** sonnet
|
|
93
|
+
**Est. time:** 5 min
|
|
94
|
+
**Consumes:** none
|
|
95
|
+
**Produces:** ast::Item::Trait(ast::TraitDecl) · ast::TraitDecl { name: String, line: usize, column: usize, fields: Vec<ast::EnumParam>, methods: Vec<ast::Fn>, required: Vec<ast::Fn> } · ast::Enum.embeds: Vec<String> · ast::Enum.isTrait: bool
|
|
96
|
+
e.g. parse("trait E(z: Int) =\n fun up(self)\n fun g(self) -> Int = self.z") -> Item::Trait(TraitDecl{name:"E", fields:[z: Int], required:[up], methods:[g]})
|
|
97
|
+
e.g. parse("record H(E, k: Int)") -> Enum{name:"H", isRecord:true, embeds:["E"], params/fields:[k]}
|
|
98
|
+
**Review Focus:** `trait_bodiless_method_only_in_trait`: `fun f(self)` with no `=` at top level is still an extern-less error path as today (no new acceptance outside traits); `record_embed_after_field_rejected`: `record H(k: Int, E)` is a syntax error; `struct_embed_parses`: `struct S(E, x: Int)` parses with embeds ["E"] (T2 reports the error); corpus all green.
|
|
99
|
+
**Test code:** named tests: `parses_trait_fields_required_and_defaults` (above e.g.), `parses_record_with_two_embeds` (`record H(E, C, k: Int)` -> embeds ["E","C"]), `parses_record_embeds_only` (`record H(E)` -> embeds ["E"], no own fields), `trait_methods_have_self_and_type_param` (each trait Fn has has_self and type_param = Some("E")), `trait_without_fields` (`trait T =\n fun a(self)` parses, fields empty).
|
|
100
|
+
**Steps:** 1 failing tests, 2 grammar `trait` rule (optional `(fields)`, `=` indent body of `fn`), record/struct field list `commaSep1` with leading `type_identifier` embeds, 3 regenerate `--disable-optimizations`, add corpus, 4 parser + AST (`Enum` gains `embeds`, `isTrait` default false everywhere it is constructed), 5 PASS + `tree-sitter test`.
|
|
101
|
+
**Notes:** required methods are `Fn` with `body: FnBody::Extern` and `is_extern: false`. New `Item::Trait` arms: add to every exhaustive `match` on `Item` across the workspace (`cargo build --workspace` must pass); downstream crates may treat it as unreachable after lowering.
|
|
102
|
+
|
|
103
|
+
### T3 formatter and highlighting · Complexity 3 · Est. context 30k
|
|
104
|
+
**Owns:** plum-tooling/tree-sitter-plum/queries/plum/format.scm, plum-tooling/tree-sitter-plum/queries/plum/highlights.scm, plum-tooling/tree-sitter-plum/queries/plum/tags.scm, plum-core/tests/formatter_test.rs
|
|
105
|
+
**Components:** 1
|
|
106
|
+
**LOC owned:** 80
|
|
107
|
+
**Tests:** fast
|
|
108
|
+
**Kind:** design
|
|
109
|
+
**Model:** sonnet
|
|
110
|
+
**Est. time:** 2.5 min
|
|
111
|
+
**Consumes:** ast::Item::Trait(ast::TraitDecl) (T1)
|
|
112
|
+
**Produces:** none
|
|
113
|
+
**Review Focus:** `format_trait_idempotent`: formatting a trait with fields, a required method, a default with block body and a comment, twice, gives identical output; `format_record_embeds`: `record H(E,C,k:Int)` -> `record H(E, C, k: Int)`.
|
|
114
|
+
**Test code:** named tests `format_trait_idempotent`, `format_record_embeds` as above; `trait` highlighted as `@keyword`, trait name as `@type.definition`, tags.scm emits a definition for traits.
|
|
115
|
+
**Steps:** 1 failing tests, 2 run FAIL, 3 queries, 4 PASS.
|
|
116
|
+
|
|
117
|
+
## Package B: lowering and checking
|
|
118
|
+
|
|
119
|
+
**Probe entry:** call=plum_core::traits::lowerTraits
|
|
120
|
+
|
|
121
|
+
### T2 trait lowering · Complexity 7 · Est. context 55k
|
|
122
|
+
**Owns:** plum-core/src/traits.rs, plum-core/src/lib.rs, plum-core/src/loader.rs, plum-core/tests/traits_test.rs, plum-core/src/ast.rs, plum-core/src/parser.rs, plum-wasm-codegen/src/lib.rs
|
|
123
|
+
**Components:** 2
|
|
124
|
+
**LOC owned:** 300
|
|
125
|
+
**Tests:** fast
|
|
126
|
+
**Kind:** design
|
|
127
|
+
**Model:** sonnet
|
|
128
|
+
**Est. time:** 5.5 min
|
|
129
|
+
**Consumes:** ast::TraitDecl (T1) · ast::Enum.embeds: Vec<String> (T1)
|
|
130
|
+
**Produces:** plum_core::traits::lowerTraits(source: &mut Source) -> Vec<String> · plum_core::traits::getter(field: &str) -> String · plum_core::traits::setter(field: &str) -> String · ast::Source.traits: Vec<ast::TraitDecl>
|
|
131
|
+
e.g. getter("pos") -> "__get_pos"; setter("pos") -> "__set_pos"
|
|
132
|
+
e.g. lowerTraits on `trait E(z: Int) = fun up(self)` + `record H(E, k: Int) = fun up(self) = ()` -> [] errors; items gain `enum E = | H` with isTrait true; H fields become [z, k]; H gains `__get_z(self) -> Int`, `__set_z(self, v: Int)`; no Item::Trait left; source.traits holds E.
|
|
133
|
+
e.g. record H(E) without `up` -> ["record H embeds E but does not define required method up"]
|
|
134
|
+
**Review Focus:** state: idempotent (second call is a no-op, like resolveMembers); errors at the record, never at a copy: `missing_required` (above), `field_clash` (`trait A(x: Int)`, `trait B(x: Int)`, `record R(A, B)` -> "record R gets field x from both A and B"; own field same name -> "record R field x is already declared by trait A"), `default_clash` (A and B both default `f`, R does not define `f` -> "record R gets default method f from both A and B; define R.f"), `embed_not_trait` (`record R(Vec2)` -> "R embeds Vec2, which is not a trait"), `struct_embed` (`struct S(E, x: Int)` -> "struct S cannot embed trait E; only records embed traits"); `self_recursive_field`: `trait A(children: List[A])` copies the field as `List[A]`; a record that defines a default method itself keeps its own; a trait with no embedders still lowers to an empty union enum.
|
|
135
|
+
**Test code:** named tests for every e.g. and Review Focus case, asserting exact strings; `loader_runs_lowerTraits` (a two-file program via loader with the trait in one module and the record in the other lowers).
|
|
136
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 implement; call from loader immediately before `resolveMembers`, 4 PASS.
|
|
137
|
+
**Notes:** copied defaults are deep clones with `type_param = Some(record)`; insert the union enum where the trait item was; accessor Fns carry the record's file and the trait's line.
|
|
138
|
+
|
|
139
|
+
### T4 checker trait rules · Complexity 6 · Est. context 60k
|
|
140
|
+
**Owns:** plum-checker/src/lib.rs, plum-checker/src/traits.rs, plum-checker/tests/traits_tests.rs, plum-checker/tests/trait_fields_tests.rs
|
|
141
|
+
**Components:** 2
|
|
142
|
+
**LOC owned:** 220
|
|
143
|
+
**Tests:** fast
|
|
144
|
+
**Kind:** design
|
|
145
|
+
**Model:** sonnet
|
|
146
|
+
**Est. time:** 5 min
|
|
147
|
+
**Consumes:** plum_core::traits::lowerTraits(source: &mut Source) -> Vec<String> (T2) · plum_checker::unions::traitField(obj: &PlumType, field: &str, exprAddr: usize, write: bool, ctx: &CheckCtx) -> Option<Result<PlumType, String>> (T5)
|
|
148
|
+
**Produces:** plum_checker::traits::isTrait(name: &str, ctx: &CheckCtx) -> bool · plum_checker::traits::traitMethods(name: &str, ctx: &CheckCtx) -> Vec<String>
|
|
149
|
+
e.g. traitMethods("Entity") -> ["update", "draw", "moveBy"] (required then defaults, declaration order)
|
|
150
|
+
**Review Focus:** `only_trait_methods`: `e.onClick()` on `e: Entity` where House defines onClick -> "trait Entity has no method onClick"; `match_trait_needs_wildcard`: `match e | House(k) -> 1` -> "match on trait Entity needs a `_` case"; with `_` it narrows and binds House's fields; plain union enums keep exhaustive matches and shared-method dispatch (regression); a record also listed in a union enum converts to the expected type only (no double wrap).
|
|
151
|
+
**Test code:** named tests for each Review Focus case plus `trait_param_accepts_record` (`fun f(e: Entity)` called with `House(...)` type-checks), `default_self_call_checks` (default `moveBy` calling `self.draw()` checks in each record). Tests call `lowerTraits` then `resolveMembers` after parse; parse helper asserts no syntax error.
|
|
152
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 implement (CheckCtx learns trait names from `Enum.isTrait`; at field-access and field-assignment sites on a trait type, call `unions::traitField`), 4 PASS.
|
|
153
|
+
**Notes:** until T5 commits, `traitField` is the Wave 0 stub returning None; field-access tests live in T5.
|
|
154
|
+
|
|
155
|
+
### T5 field access through a trait · Complexity 6 · Est. context 55k
|
|
156
|
+
**Owns:** plum-checker/src/unions.rs, plum-core/src/unions.rs, plum-core/src/union_lower.rs, plum-checker/tests/trait_fields_tests.rs
|
|
157
|
+
**Components:** 2
|
|
158
|
+
**LOC owned:** 200
|
|
159
|
+
**Tests:** fast
|
|
160
|
+
**Kind:** design
|
|
161
|
+
**Model:** sonnet
|
|
162
|
+
**Est. time:** 4.5 min
|
|
163
|
+
**Consumes:** plum_core::traits::getter(field: &str) -> String (T2) · plum_core::traits::setter(field: &str) -> String (T2)
|
|
164
|
+
**Produces:** plum_checker::unions::traitField(obj: &PlumType, field: &str, exprAddr: usize, write: bool, ctx: &CheckCtx) -> Option<Result<PlumType, String>> · plum_core::unions::recordFieldRead(exprAddr: usize, variants: Vec<String>) · plum_core::unions::recordFieldWrite(stmtAddr: usize, variants: Vec<String>)
|
|
165
|
+
e.g. traitField(TNamed("Entity"), "pos", a, false, ctx) -> Some(Ok(TNamed("Vec2"))) and records a read dispatch of `__get_pos`
|
|
166
|
+
e.g. traitField(TNamed("Entity"), "nope", a, false, ctx) -> Some(Err("trait Entity has no field nope"))
|
|
167
|
+
e.g. traitField(TNamed("Shape"), "radius", a, false, ctx) -> None (not a trait)
|
|
168
|
+
**Review Focus:** `nested_write_rejected`: `e.pos.x = 1.0` on a struct-typed trait field -> "assign the whole field: e.pos = ..."; `write_then_read`: after `e.pos = p` both `e.pos` and the House value see p; UnionPlan gains `fieldReads`/`fieldWrites`, `union_lower` rewrites `Attribute(e, f)` to `e.__get_f()` and `Assign([Field(e,f)], [v])` to `e.__set_f(v)` with the dispatch synthesized like method dispatch; compound assign (`e.z += 1`) lowers to `__set_z(__get_z() + 1)`.
|
|
169
|
+
**Test code:** named tests for each e.g. and Review Focus case (checker-level, through `typedSource` and the lowered copy).
|
|
170
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 implement, 4 PASS.
|
|
171
|
+
|
|
172
|
+
## Package C: integration
|
|
173
|
+
|
|
174
|
+
**Probe entry:** run=./target/debug/plum test plum-examples/traits.plum
|
|
175
|
+
|
|
176
|
+
### T6 codegen and run tests · Complexity 5 · Est. context 60k
|
|
177
|
+
**Owns:** plum-wasm-codegen/src/lib.rs, plum-wasm-codegen/tests/traits_e2e_test.rs
|
|
178
|
+
**Components:** 1
|
|
179
|
+
**LOC owned:** 150
|
|
180
|
+
**Tests:** slow
|
|
181
|
+
**Kind:** design
|
|
182
|
+
**Model:** sonnet
|
|
183
|
+
**Est. time:** 4 min
|
|
184
|
+
**Consumes:** plum_checker::traits::isTrait(name: &str, ctx: &CheckCtx) -> bool (T4) · plum_checker::unions::traitField(obj: &PlumType, field: &str, exprAddr: usize, write: bool, ctx: &CheckCtx) -> Option<Result<PlumType, String>> (T5)
|
|
185
|
+
**Produces:** none
|
|
186
|
+
**Review Focus:** `opening_example_runs` (review plan's example: List[Entity] of House and Unit, update and draw dispatch, default moveBy changes pos seen via House and Entity); `default_calls_override`; `three_level_actor_tree` (`trait A(children: List[A])`, mixed records, depth-first sum); `trait_without_embedders_compiles`; `record_in_trait_and_union_enum`; `match_trait_narrows`.
|
|
187
|
+
**Test code:** each named test compiles a Plum source and asserts its printed output.
|
|
188
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 fix codegen gaps found, 4 PASS, full suite in background.
|
|
189
|
+
|
|
190
|
+
### T7 editor support · Complexity 4 · Est. context 45k
|
|
191
|
+
**Owns:** plum-lsp/src/main.rs, plum-lsp/src/completion.rs
|
|
192
|
+
**Components:** 1
|
|
193
|
+
**LOC owned:** 120
|
|
194
|
+
**Tests:** fast
|
|
195
|
+
**Kind:** design
|
|
196
|
+
**Model:** sonnet
|
|
197
|
+
**Est. time:** 3 min
|
|
198
|
+
**Consumes:** ast::Source.traits: Vec<ast::TraitDecl> (T2) · plum_core::traits::lowerTraits(source: &mut Source) -> Vec<String> (T2)
|
|
199
|
+
**Produces:** none
|
|
200
|
+
**Review Focus:** `hover_embedded_field` (hover `h.pos` -> `pos: Vec2 (from Entity)`), `complete_trait_value` (completion on `e.` with `e: Entity` lists trait fields and methods only, no `__get_`/`__set_` names), `goto_embedded_field` (jumps to the trait's field), `goto_trait_name`.
|
|
201
|
+
**Test code:** the four named tests in plum-lsp's existing test module style.
|
|
202
|
+
**Steps:** 1 failing tests, 2 FAIL, 3 implement, 4 PASS.
|
|
203
|
+
|
|
204
|
+
### T8 docs and examples · Complexity 2 · Est. context 30k
|
|
205
|
+
**Owns:** README.md, plum-examples/traits.plum, plum-examples/trait_scene.plum
|
|
206
|
+
**Components:** 1
|
|
207
|
+
**LOC owned:** 140
|
|
208
|
+
**Tests:** slow
|
|
209
|
+
**Kind:** design
|
|
210
|
+
**Model:** sonnet
|
|
211
|
+
**Est. time:** 2.5 min
|
|
212
|
+
**Consumes:** none
|
|
213
|
+
**Produces:** none
|
|
214
|
+
**Review Focus:** `traits_example_tests` (`plum test plum-examples/traits.plum` green, covers each error-free feature); `trait_scene_example` (a Scene of Entity values updated and drawn, House and Unit); `format_idempotent` (format a copy twice, no diff).
|
|
215
|
+
**Test code:** `test` blocks inside the two example files.
|
|
216
|
+
**Steps:** write examples, run `plum test`, README "Traits" section after union enums.
|
|
217
|
+
|
|
218
|
+
### T9 Scene on trait Actor · Complexity 7 · Est. context 70k
|
|
219
|
+
**Owns:** plum-std/game/Scene.plum, plum-examples/scene.plum, plum-game/web-test/fixtures/, plum-game/web-test/BENCHMARK.md, README.md
|
|
220
|
+
**Components:** 2
|
|
221
|
+
**LOC owned:** 400
|
|
222
|
+
**Tests:** slow
|
|
223
|
+
**Kind:** design
|
|
224
|
+
**Model:** sonnet
|
|
225
|
+
**Est. time:** 6 min
|
|
226
|
+
**Consumes:** none
|
|
227
|
+
**Produces:** none
|
|
228
|
+
**Review Focus:** `scene_tests_green` (Scene's z-index, sortChildren, culling, hit tests in Scene.plum stay green); `walker_record` (`record Walker(Actor, speed: Float)` overriding act/draw works in a Stage); browser check of scene.plum passes; BENCHMARK.md records 5k and 20k culled for closure and trait versions; >10% slower -> report DONE_WITH_CONCERNS.
|
|
229
|
+
**Test code:** existing Scene tests ported plus `walker_record`.
|
|
230
|
+
**Steps:** benchmark closure version first, migrate `Node` to `trait Actor(x, y, width, height, originX, originY, scaleX, scaleY, rotation, color, visible, children: List[Actor], ...)` with default act/draw/hit, port example and fixtures, re-benchmark, README Scene section.
|
|
231
|
+
|
|
232
|
+
### T10 box-free trait layout (wasm-gc subtyping) · Complexity 8 · Est. context 35k
|
|
233
|
+
**Owns:** plum-wasm-codegen/src/lib.rs, plum-wasm-codegen/tests/traits_e2e_test.rs, plum-core/src/union_lower.rs, plum-checker/src/unions.rs, plum-game/web-test/BENCHMARK.md, plum-core/src/traits.rs, plum-core/src/ast.rs, plum-checker/tests/trait_fields_tests.rs, plum-core/src/parser.rs, plum-core/tests/traits_test.rs
|
|
234
|
+
**Components:** 2
|
|
235
|
+
**LOC owned:** 350
|
|
236
|
+
**Tests:** slow
|
|
237
|
+
**Kind:** design
|
|
238
|
+
**Model:** sonnet
|
|
239
|
+
**Est. time:** 6 min
|
|
240
|
+
**Consumes:** none
|
|
241
|
+
**Produces:** none
|
|
242
|
+
**Review Focus:** `subtyped_field_read_direct` (a field read on an Actor value compiles to one `struct.get` on the trait base type, no call); `conversion_is_identity` (pushing a record into List[Actor] allocates nothing; `a == b` on the same record converted twice is True); `second_trait_stays_boxed` (a record's second embedded trait keeps the box and still works); all traits_e2e, Scene and examples tests green; BENCHMARK.md stress_world within 10% of the closure version.
|
|
243
|
+
**Test code:** the three named tests plus re-running T6's six e2e tests.
|
|
244
|
+
**Steps:** a trait is subtyped iff every embedder lists it FIRST; emit a non-final base struct type with its fields in declaration order; each embedder's struct type declares it as supertype (lowering already puts trait fields first); trait value = ref to base; wrap = no-op upcast; trait field read/write = struct.get/set on base (skip accessor dispatch); hidden-variant match/narrowing = ref.test/ref.cast on the record type; method dispatch = cast chain (or a kind tag + br_table). Others keep the union box.
|
|
245
|
+
**Notes:** triggered by T9's benchmark (+92% on stress_world from accessor dispatch in the y-sort); approved by the review plan's "more than 10% slower" rule.
|
|
246
|
+
|
|
247
|
+
## Review Focus
|
|
248
|
+
- Package A (parser): deep (nesting traits in bodies), encoding (grammar ambiguity: bare name vs `name: Type`).
|
|
249
|
+
- Package B (compiler pass): state (idempotent lowering, plan addresses after rewrite), deep (recursive trait field), protocol (union rules must not leak into traits and back).
|
|
250
|
+
- Package C (integration): state (writes through trait visible on record), numeric (benchmark threshold 10%).
|
|
251
|
+
|
|
252
|
+
## Self-review
|
|
253
|
+
Coverage: every review-plan bullet maps to T1-T9. Edges are execution dependencies only; T4 and T5 share no file (T4 owns checker lib.rs, T5 checker unions.rs). README: T8 then T9 (edge).
|
docs/plans/2026-11-02-traits.progress.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- start 6b983a7e16fccc5ac27e50a54b779a462e41e87e
|
|
2
|
+
Ruling: archived .superlite run state of the finished game-host run to .superlite/archive/game-host-run - its T1..T9 ledger collided with this plan - cost if wrong: none, files kept
|
|
3
|
+
Ruling: orchestrator switched to AI by the user (`! sl orchestrator set ai`) - the approval reply hook never logged "approve" - cost if wrong: none
|
|
4
|
+
Ruling: prime referenced by path instead of pasted - same bytes, saves coordinator context - cost if wrong: agent skips reading it
|
|
5
|
+
Ruling: T0 stubs carried by T1's commit - hook blocks coordinator git commit - cost if wrong: none
|
|
6
|
+
Ruling: T1/T2/T4/T10 Owns extended post-dispatch with cross-crate initializer/match-arm edits - commit_task stages only Owns - cost if wrong: none, edits were sequential
|
|
7
|
+
Ruling: fresh agents get prime path + task in one message - saves a READY round trip - cost if wrong: none
|
|
8
|
+
Ruling: review low-tier items (R(A,A) wording, required-vs-default across traits reported missing, non-record wording) left open - errors still fire - cost if wrong: confusing messages
|
|
9
|
+
Ruling: T10 subtyping added - T9 stress_world +92% exceeds the review plan's 10% rule - a trait is subtyped only if every embedder lists it first, others keep the box - cost if wrong: Clickable-style second traits stay slow
|
|
10
|
+
Ruling: boxed-trait == compares wrappers - == on trait values is out of scope in v1; subtyped traits compare equal - cost if wrong: surprising false for Clickable-style traits
|
|
11
|
+
- commits: T1 134a034, T3 5bb1d4c, T2 d83c36b f50415c, T7 818e0d0 670cb84, T5 ed7f16c 4306b97, T4 fe4959d add63b7, T6 808db73, T8 a1704af 7ae62f5, T9 3c9dcee, T10 538b1ee aa923a9, vscode trait ee042db
|
|
12
|
+
- tests full (final): cargo test --workspace --no-fail-fast 396 passed 0 failed; plum test plum-std plum-examples 446 passed (57 files); plum-game 20 passed
|
|
13
|
+
- benchmark (avg ms): stress_scene closure 1.60 / boxed 1.69 / box-free 1.68; stress_world closure 1.09 / boxed 2.09 / box-free 1.10
|